diff --git a/packages/type-utils/src/isTypeReadonly.ts b/packages/type-utils/src/isTypeReadonly.ts index 7f689b5b7df1..f1ea845e6e09 100644 --- a/packages/type-utils/src/isTypeReadonly.ts +++ b/packages/type-utils/src/isTypeReadonly.ts @@ -107,9 +107,18 @@ 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.keyType, + options, + seenTypes, + ) && isTypeReadonlyRecurser(checker, indexInfo.type, options, seenTypes) + ); } return Readonlyness.UnknownType; @@ -119,20 +128,33 @@ 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 lastDeclaration = property.getDeclarations()?.at(-1); + 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 diff --git a/packages/type-utils/tests/isTypeReadonly.test.ts b/packages/type-utils/tests/isTypeReadonly.test.ts index 94e6add83372..49789f1e8c8a 100644 --- a/packages/type-utils/tests/isTypeReadonly.test.ts +++ b/packages/type-utils/tests/isTypeReadonly.test.ts @@ -264,15 +264,13 @@ describe('isTypeReadonly', () => { }); describe('is not readonly', () => { - describe('default options', () => { - it('fails with a mutable PropertySignature inside a readonly IndexSignature', () => { - const { type, checker } = getType( - `type Test = { readonly [key: string]: { foo: string[]; }; };`, - ); + it('fails with a mutable PropertySignature inside a readonly IndexSignature', () => { + const { type, checker } = getType( + `type Test = { readonly [key: string]: { foo: string[]; }; };`, + ); - const result = isTypeReadonly(checker, type); - expect(result).toBe(false); - }); + const result = isTypeReadonly(checker, type); + expect(result).toBe(false); }); }); });