Skip to content

Commit 7cba2de

Browse files
author
Josh Goldberg
authored
fix(eslint-plugin): added safe getTypeOfPropertyOfType wrapper (typescript-eslint#2567)
1 parent 2b2224b commit 7cba2de

File tree

5 files changed

+75
-4
lines changed

5 files changed

+75
-4
lines changed

packages/eslint-plugin/src/rules/no-unnecessary-condition.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import {
2424
isTypeAnyType,
2525
isTypeUnknownType,
2626
getTypeName,
27+
getTypeOfPropertyOfName,
2728
} from '../util';
2829

2930
// Truthiness utilities
@@ -499,7 +500,8 @@ export default createRule<Options, MessageId>({
499500
);
500501
}
501502
if (propertyType.isNumberLiteral() || propertyType.isStringLiteral()) {
502-
const propType = checker.getTypeOfPropertyOfType(
503+
const propType = getTypeOfPropertyOfName(
504+
checker,
503505
objType,
504506
propertyType.value.toString(),
505507
);
@@ -535,7 +537,11 @@ export default createRule<Options, MessageId>({
535537
const propertyType = getNodeType(node.property);
536538
return isNullablePropertyType(type, propertyType);
537539
}
538-
const propType = checker.getTypeOfPropertyOfType(type, property.name);
540+
const propType = getTypeOfPropertyOfName(
541+
checker,
542+
type,
543+
property.name,
544+
);
539545
return propType && isNullableType(propType, { allowUndefined: true });
540546
});
541547
return (

packages/eslint-plugin/src/util/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export * from './isTypeReadonly';
66
export * from './misc';
77
export * from './nullThrows';
88
export * from './objectIterators';
9+
export * from './propertyTypes';
910
export * from './types';
1011

1112
// this is done for convenience - saves migrating all of the old rules

packages/eslint-plugin/src/util/isTypeReadonly.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66
isPropertyReadonlyInType,
77
} from 'tsutils';
88
import * as ts from 'typescript';
9-
import { nullThrows, NullThrowsReasons } from '.';
9+
import { getTypeOfPropertyOfType, nullThrows, NullThrowsReasons } from '.';
1010

1111
const enum Readonlyness {
1212
/** the type cannot be handled by the function */
@@ -101,7 +101,7 @@ function isTypeReadonlyObject(
101101
// doing this deep, potentially expensive check.
102102
for (const property of properties) {
103103
const propertyType = nullThrows(
104-
checker.getTypeOfPropertyOfType(type, property.getName()),
104+
getTypeOfPropertyOfType(checker, type, property),
105105
NullThrowsReasons.MissingToken(`property "${property.name}"`, 'type'),
106106
);
107107

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import * as ts from 'typescript';
2+
3+
export function getTypeOfPropertyOfName(
4+
checker: ts.TypeChecker,
5+
type: ts.Type,
6+
name: string,
7+
escapedName?: ts.__String,
8+
): ts.Type | undefined {
9+
// Most names are directly usable in the checker and aren't different from escaped names
10+
if (!escapedName || !name.startsWith('__')) {
11+
return checker.getTypeOfPropertyOfType(type, name);
12+
}
13+
14+
// Symbolic names may differ in their escaped name compared to their human-readable name
15+
// https://github.com/typescript-eslint/typescript-eslint/issues/2143
16+
const escapedProperty = type
17+
.getProperties()
18+
.find(property => property.escapedName === escapedName);
19+
20+
return escapedProperty
21+
? checker.getDeclaredTypeOfSymbol(escapedProperty)
22+
: undefined;
23+
}
24+
25+
export function getTypeOfPropertyOfType(
26+
checker: ts.TypeChecker,
27+
type: ts.Type,
28+
property: ts.Symbol,
29+
): ts.Type | undefined {
30+
return getTypeOfPropertyOfName(
31+
checker,
32+
type,
33+
property.getName(),
34+
property.getEscapedName(),
35+
);
36+
}

packages/eslint-plugin/tests/rules/prefer-readonly-parameter-types.test.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,15 @@ ruleTester.run('prefer-readonly-parameter-types', rule, {
238238
}
239239
function foo(arg: Readonly<Foo>) {}
240240
`,
241+
`
242+
const sym = Symbol('sym');
243+
244+
interface WithSymbol {
245+
[sym]: number;
246+
}
247+
248+
const willNotCrash = (foo: Readonly<WithSymbol>) => {};
249+
`,
241250
],
242251
invalid: [
243252
// arrays
@@ -643,5 +652,24 @@ ruleTester.run('prefer-readonly-parameter-types', rule, {
643652
},
644653
],
645654
},
655+
{
656+
code: `
657+
const sym = Symbol('sym');
658+
659+
interface WithSymbol {
660+
[sym]: number;
661+
}
662+
663+
const willNot = (foo: WithSymbol) => {};
664+
`,
665+
errors: [
666+
{
667+
messageId: 'shouldBeReadonly',
668+
line: 8,
669+
column: 26,
670+
endColumn: 41,
671+
},
672+
],
673+
},
646674
],
647675
});

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy