diff --git a/packages/eslint-plugin/tests/rules/prefer-readonly-parameter-types.test.ts b/packages/eslint-plugin/tests/rules/prefer-readonly-parameter-types.test.ts index b6b499b0efd..7cd0527b2cf 100644 --- a/packages/eslint-plugin/tests/rules/prefer-readonly-parameter-types.test.ts +++ b/packages/eslint-plugin/tests/rules/prefer-readonly-parameter-types.test.ts @@ -380,6 +380,27 @@ ruleTester.run('prefer-readonly-parameter-types', rule, { }, ], }, + { + name: 'circular readonly types (Bug: #5875)', + code: ` + interface Obj1 { + readonly [K: string]: Obj2; + } + + interface Obj2 { + readonly [K: string]: Obj1; + } + + function foo(event: Obj1): void {} + `, + options: [ + { + checkParameterProperties: true, + ignoreInferredTypes: false, + ...readonlynessOptionsDefaults, + }, + ], + }, ], invalid: [ // arrays diff --git a/packages/type-utils/src/isTypeReadonly.ts b/packages/type-utils/src/isTypeReadonly.ts index cac7690ea3a..7ba2b300089 100644 --- a/packages/type-utils/src/isTypeReadonly.ts +++ b/packages/type-utils/src/isTypeReadonly.ts @@ -113,7 +113,7 @@ function isTypeReadonlyObject( return Readonlyness.Mutable; } - if (indexInfo.type === type) { + if (indexInfo.type === type || seenTypes.has(indexInfo.type)) { return Readonlyness.Readonly; } diff --git a/packages/type-utils/tests/isTypeReadonly.test.ts b/packages/type-utils/tests/isTypeReadonly.test.ts index 77d9f65b2f9..382091a2375 100644 --- a/packages/type-utils/tests/isTypeReadonly.test.ts +++ b/packages/type-utils/tests/isTypeReadonly.test.ts @@ -139,6 +139,11 @@ describe('isTypeReadonly', () => { it('handles circular readonly PropertySignature inside a readonly IndexSignature', () => runTests('interface Test { readonly [key: string]: Test };')); + + it('handles circular readonly PropertySignature inside interdependent objects', () => + runTests( + 'interface Test1 { readonly [key: string]: Test } interface Test { readonly [key: string]: Test1 }', + )); }); describe('is not readonly', () => { @@ -156,8 +161,23 @@ describe('isTypeReadonly', () => { describe('is not readonly circular', () => { const runTests = runTestIsNotReadonly; - it('handles circular mutable PropertySignature inside a readonly IndexSignature', () => + it('handles circular mutable PropertySignature', () => runTests('interface Test { [key: string]: Test };')); + + it.each([ + [ + 'interface Test1 { [key: string]: Test } interface Test { readonly [key: string]: Test1 }', + ], + [ + 'interface Test1 { readonly [key: string]: Test } interface Test { [key: string]: Test1 }', + ], + [ + 'interface Test1 { [key: string]: Test } interface Test { [key: string]: Test1 }', + ], + ])( + 'handles circular mutable PropertySignature inside interdependent objects', + runTests, + ); }); });