Skip to content
Permalink
Browse files

fix(eslint-plugin): added safe getTypeOfPropertyOfType wrapper (#2567)

  • Loading branch information
JoshuaKGoldberg committed Sep 27, 2020
1 parent 2b2224b commit 7cba2de138542563d678fbfc738cd1b3ebf01e07
@@ -24,6 +24,7 @@ import {
isTypeAnyType,
isTypeUnknownType,
getTypeName,
getTypeOfPropertyOfName,
} from '../util';

// Truthiness utilities
@@ -499,7 +500,8 @@ export default createRule<Options, MessageId>({
);
}
if (propertyType.isNumberLiteral() || propertyType.isStringLiteral()) {
const propType = checker.getTypeOfPropertyOfType(
const propType = getTypeOfPropertyOfName(
checker,
objType,
propertyType.value.toString(),
);
@@ -535,7 +537,11 @@ export default createRule<Options, MessageId>({
const propertyType = getNodeType(node.property);
return isNullablePropertyType(type, propertyType);
}
const propType = checker.getTypeOfPropertyOfType(type, property.name);
const propType = getTypeOfPropertyOfName(
checker,
type,
property.name,
);
return propType && isNullableType(propType, { allowUndefined: true });
});
return (
@@ -6,6 +6,7 @@ export * from './isTypeReadonly';
export * from './misc';
export * from './nullThrows';
export * from './objectIterators';
export * from './propertyTypes';
export * from './types';

// this is done for convenience - saves migrating all of the old rules
@@ -6,7 +6,7 @@ import {
isPropertyReadonlyInType,
} from 'tsutils';
import * as ts from 'typescript';
import { nullThrows, NullThrowsReasons } from '.';
import { getTypeOfPropertyOfType, nullThrows, NullThrowsReasons } from '.';

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

@@ -0,0 +1,36 @@
import * as ts from 'typescript';

export function getTypeOfPropertyOfName(
checker: ts.TypeChecker,
type: ts.Type,
name: string,
escapedName?: ts.__String,
): ts.Type | undefined {
// Most names are directly usable in the checker and aren't different from escaped names
if (!escapedName || !name.startsWith('__')) {
return checker.getTypeOfPropertyOfType(type, name);
}

// Symbolic names may differ in their escaped name compared to their human-readable name
// https://github.com/typescript-eslint/typescript-eslint/issues/2143
const escapedProperty = type
.getProperties()
.find(property => property.escapedName === escapedName);

return escapedProperty
? checker.getDeclaredTypeOfSymbol(escapedProperty)
: undefined;
}

export function getTypeOfPropertyOfType(
checker: ts.TypeChecker,
type: ts.Type,
property: ts.Symbol,
): ts.Type | undefined {
return getTypeOfPropertyOfName(
checker,
type,
property.getName(),
property.getEscapedName(),
);
}
@@ -238,6 +238,15 @@ ruleTester.run('prefer-readonly-parameter-types', rule, {
}
function foo(arg: Readonly<Foo>) {}
`,
`
const sym = Symbol('sym');
interface WithSymbol {
[sym]: number;
}
const willNotCrash = (foo: Readonly<WithSymbol>) => {};
`,
],
invalid: [
// arrays
@@ -643,5 +652,24 @@ ruleTester.run('prefer-readonly-parameter-types', rule, {
},
],
},
{
code: `
const sym = Symbol('sym');
interface WithSymbol {
[sym]: number;
}
const willNot = (foo: WithSymbol) => {};
`,
errors: [
{
messageId: 'shouldBeReadonly',
line: 8,
column: 26,
endColumn: 41,
},
],
},
],
});

0 comments on commit 7cba2de

Please sign in to comment.