Skip to content

Commit

Permalink
fix(type-utils): check IndexSignature internals when checking isTypeR…
Browse files Browse the repository at this point in the history
…eadonly (typescript-eslint#4417)

* fix(type-utils): make isTypeReadonly's options param optional

fix typescript-eslint#4410

* test(type-utils): add basic tests for isTypeReadonly

* test(type-utils): add test for IndexSignature internals

* fix(type-utils): check IndexSignature internals when checking isTypeReadonly

fix typescript-eslint#3714

* perf(type-utils): don't test IndexSignature key for readonlyness as it will always be readonly

Co-authored-by: Josh Goldberg <joshuakgoldberg@outlook.com>
  • Loading branch information
2 people authored and lonyele committed Feb 12, 2022
1 parent 1cee51c commit 115d3f2
Show file tree
Hide file tree
Showing 2 changed files with 68 additions and 16 deletions.
56 changes: 40 additions & 16 deletions packages/type-utils/src/isTypeReadonly.ts
Expand Up @@ -107,9 +107,16 @@ function isTypeReadonlyObject(
function checkIndexSignature(kind: ts.IndexKind): Readonlyness {
const indexInfo = checker.getIndexInfoOfType(type, kind);
if (indexInfo) {
return indexInfo.isReadonly
? Readonlyness.Readonly
: Readonlyness.Mutable;
if (!indexInfo.isReadonly) {
return Readonlyness.Mutable;
}

return isTypeReadonlyRecurser(
checker,
indexInfo.type,
options,
seenTypes,
);
}

return Readonlyness.UnknownType;
Expand All @@ -119,20 +126,37 @@ function isTypeReadonlyObject(
if (properties.length) {
// ensure the properties are marked as readonly
for (const property of properties) {
if (
!(
isPropertyReadonlyInType(type, property.getEscapedName(), checker) ||
(options.treatMethodsAsReadonly &&
property.valueDeclaration !== undefined &&
hasSymbol(property.valueDeclaration) &&
isSymbolFlagSet(
property.valueDeclaration.symbol,
ts.SymbolFlags.Method,
))
)
) {
return Readonlyness.Mutable;
if (options.treatMethodsAsReadonly) {
if (
property.valueDeclaration !== undefined &&
hasSymbol(property.valueDeclaration) &&
isSymbolFlagSet(
property.valueDeclaration.symbol,
ts.SymbolFlags.Method,
)
) {
continue;
}

const declarations = property.getDeclarations();
const lastDeclaration =
declarations !== undefined && declarations.length > 0
? declarations[declarations.length - 1]
: undefined;
if (
lastDeclaration !== undefined &&
hasSymbol(lastDeclaration) &&
isSymbolFlagSet(lastDeclaration.symbol, ts.SymbolFlags.Method)
) {
continue;
}
}

if (isPropertyReadonlyInType(type, property.getEscapedName(), checker)) {
continue;
}

return Readonlyness.Mutable;
}

// all properties were readonly
Expand Down
28 changes: 28 additions & 0 deletions packages/type-utils/tests/isTypeReadonly.test.ts
Expand Up @@ -109,6 +109,34 @@ describe('isTypeReadonly', () => {
])('handles non fully readonly sets and maps', runTests);
});
});

describe('IndexSignature', () => {
describe('is readonly', () => {
const runTests = runTestIsReadonly;

it.each([
['type Test = { readonly [key: string]: string };'],
[
'type Test = { readonly [key: string]: { readonly foo: readonly string[]; }; };',
],
])(
'handles readonly PropertySignature inside a readonly IndexSignature',
runTests,
);
});

describe('is not readonly', () => {
const runTests = runTestIsNotReadonly;

it.each([
['type Test = { [key: string]: string };'],
['type Test = { readonly [key: string]: { foo: string[]; }; };'],
])(
'handles mutable PropertySignature inside a readonly IndexSignature',
runTests,
);
});
});
});

describe('treatMethodsAsReadonly', () => {
Expand Down

0 comments on commit 115d3f2

Please sign in to comment.