Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
10 changes: 8 additions & 2 deletions packages/eslint-plugin/src/rules/no-unnecessary-condition.ts
Expand Up @@ -24,6 +24,7 @@ import {
isTypeAnyType,
isTypeUnknownType,
getTypeName,
getTypeOfPropertyOfType,
} from '../util';

// Truthiness utilities
Expand Down Expand Up @@ -499,7 +500,8 @@ export default createRule<Options, MessageId>({
);
}
if (propertyType.isNumberLiteral() || propertyType.isStringLiteral()) {
const propType = checker.getTypeOfPropertyOfType(
const propType = getTypeOfPropertyOfType(
checker,
objType,
propertyType.value.toString(),
);
Expand Down Expand Up @@ -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 = getTypeOfPropertyOfType(
checker,
type,
property.name,
);
return propType && isNullableType(propType, { allowUndefined: true });
});
return (
Expand Down
1 change: 1 addition & 0 deletions packages/eslint-plugin/src/util/index.ts
Expand Up @@ -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
Expand Down
9 changes: 5 additions & 4 deletions packages/eslint-plugin/src/util/isTypeReadonly.ts
Expand Up @@ -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 */
Expand Down Expand Up @@ -100,9 +100,10 @@ function isTypeReadonlyObject(
// as we might be able to bail out early due to a mutable property before
// doing this deep, potentially expensive check.
for (const property of properties) {
const propertyType = nullThrows(
checker.getTypeOfPropertyOfType(type, property.getName()),
NullThrowsReasons.MissingToken(`property "${property.name}"`, 'type'),
const propertyType = getTypeOfPropertyOfType(
checker,
type,
property.name,
);

// handle recursive types.
Expand Down
25 changes: 25 additions & 0 deletions packages/eslint-plugin/src/util/propertyTypes.ts
@@ -0,0 +1,25 @@
import * as ts from 'typescript';

import { nullThrows, NullThrowsReasons } from './nullThrows';

export function getTypeOfPropertyOfType(
checker: ts.TypeChecker,
type: ts.Type,
propertyName: string,
): ts.Type {
const reason = NullThrowsReasons.MissingToken(
`property "${propertyName}"`,
'type',
);

return propertyName.startsWith('__')
? checker.getDeclaredTypeOfSymbol(
nullThrows(
type
.getProperties()
.find(property => property.escapedName === propertyName),
JoshuaKGoldberg marked this conversation as resolved.
Show resolved Hide resolved
reason,
),
)
: nullThrows(checker.getTypeOfPropertyOfType(type, propertyName), reason);
}
Expand Up @@ -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
Expand Down Expand Up @@ -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,
},
],
},
],
});