From 6edebcda00053eecf7b3e55eeb3fe5d7fb9e7db7 Mon Sep 17 00:00:00 2001 From: Armano Date: Mon, 27 Dec 2021 07:03:07 +0100 Subject: [PATCH] fix(eslint-plugin): [consistent-indexed-object-style] do not report for circular references (#4347) --- .../rules/consistent-indexed-object-style.ts | 35 +++++++++++- .../consistent-indexed-object-style.test.ts | 55 +++++++++++++++++++ 2 files changed, 88 insertions(+), 2 deletions(-) diff --git a/packages/eslint-plugin/src/rules/consistent-indexed-object-style.ts b/packages/eslint-plugin/src/rules/consistent-indexed-object-style.ts index 8c2081adebf..2c00dfd43eb 100644 --- a/packages/eslint-plugin/src/rules/consistent-indexed-object-style.ts +++ b/packages/eslint-plugin/src/rules/consistent-indexed-object-style.ts @@ -34,7 +34,8 @@ export default createRule({ function checkMembers( members: TSESTree.TypeElement[], - node: TSESTree.Node, + node: TSESTree.TSTypeLiteral | TSESTree.TSInterfaceDeclaration, + parentId: TSESTree.Identifier | undefined, prefix: string, postfix: string, safeFix = true, @@ -67,6 +68,22 @@ export default createRule({ return; } + if (parentId) { + const scope = context.getScope(); + const superVar = scope.set.get(parentId.name); + if (superVar) { + const isCircular = superVar.references.some( + item => + item.isTypeReference && + node.range[0] <= item.identifier.range[0] && + node.range[1] >= item.identifier.range[1], + ); + if (isCircular) { + return; + } + } + } + context.report({ node, messageId: 'preferRecord', @@ -112,7 +129,8 @@ export default createRule({ }), ...(mode === 'record' && { TSTypeLiteral(node): void { - checkMembers(node.members, node, '', ''); + const parent = findParentDeclaration(node); + checkMembers(node.members, node, parent?.id, '', ''); }, TSInterfaceDeclaration(node): void { let genericTypes = ''; @@ -126,6 +144,7 @@ export default createRule({ checkMembers( node.body.body, node, + node.id, `type ${node.id.name}${genericTypes} = `, ';', !node.extends?.length, @@ -135,3 +154,15 @@ export default createRule({ }; }, }); + +function findParentDeclaration( + node: TSESTree.Node, +): TSESTree.TSTypeAliasDeclaration | undefined { + if (node.parent && node.parent.type !== AST_NODE_TYPES.TSTypeAnnotation) { + if (node.parent.type === AST_NODE_TYPES.TSTypeAliasDeclaration) { + return node.parent; + } + return findParentDeclaration(node.parent); + } + return undefined; +} diff --git a/packages/eslint-plugin/tests/rules/consistent-indexed-object-style.test.ts b/packages/eslint-plugin/tests/rules/consistent-indexed-object-style.test.ts index eb7e5cc8d1f..9c92f1a2eac 100644 --- a/packages/eslint-plugin/tests/rules/consistent-indexed-object-style.test.ts +++ b/packages/eslint-plugin/tests/rules/consistent-indexed-object-style.test.ts @@ -30,6 +30,15 @@ interface Foo { bar: string; } `, + // circular + 'type Foo = { [key: string]: string | Foo };', + 'type Foo = { [key: string]: Foo };', + 'type Foo = { [key: string]: Foo } | Foo;', + ` +interface Foo { + [key: string]: Foo; +} + `, // Type literal 'type Foo = {};', @@ -287,6 +296,52 @@ type Foo = Readonly>; errors: [{ messageId: 'preferIndexSignature', line: 1, column: 15 }], }, + // Circular + { + code: 'type Foo = { [k: string]: A.Foo };', + output: 'type Foo = Record;', + errors: [{ messageId: 'preferRecord', line: 1, column: 12 }], + }, + { + code: 'type Foo = { [key: string]: AnotherFoo };', + output: 'type Foo = Record;', + errors: [{ messageId: 'preferRecord', line: 1, column: 12 }], + }, + { + code: 'type Foo = { [key: string]: { [key: string]: Foo } };', + output: 'type Foo = { [key: string]: Record };', + errors: [{ messageId: 'preferRecord', line: 1, column: 29 }], + }, + { + code: 'type Foo = { [key: string]: string } | Foo;', + output: 'type Foo = Record | Foo;', + errors: [{ messageId: 'preferRecord', line: 1, column: 12 }], + }, + { + code: ` +interface Foo { + [k: string]: A.Foo; +} + `, + output: ` +type Foo = Record; + `, + errors: [{ messageId: 'preferRecord', line: 2, column: 1 }], + }, + { + code: ` +interface Foo { + [k: string]: { [key: string]: Foo }; +} + `, + output: ` +interface Foo { + [k: string]: Record; +} + `, + errors: [{ messageId: 'preferRecord', line: 3, column: 16 }], + }, + // Generic { code: 'type Foo = Generic>;',