Skip to content

Commit 7eba36e

Browse files
authored
fix(eslint-plugin): [consistent-indexed-object-style] don't report on indirect circular references (#10537)
* initial implementation (squashed) * revert unrelated changes * remove now redundant eslint-disable comment * slight formatting change * remove unrelated changes * add a test for missing coverage * add indirect union recursive test * remove additional unnecessary comments
1 parent 638c2e0 commit 7eba36e

File tree

3 files changed

+377
-13
lines changed

3 files changed

+377
-13
lines changed

‎packages/eslint-plugin/src/rules/consistent-indexed-object-style.ts

Lines changed: 94 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { ScopeVariable } from '@typescript-eslint/scope-manager';
12
import type { TSESLint, TSESTree } from '@typescript-eslint/utils';
23
import type { ReportFixFunction } from '@typescript-eslint/utils/ts-eslint';
34

@@ -6,6 +7,7 @@ import { AST_NODE_TYPES, ASTUtils } from '@typescript-eslint/utils';
67
import {
78
createRule,
89
getFixOrSuggest,
10+
isNodeEqual,
911
isParenthesized,
1012
nullThrows,
1113
} from '../util';
@@ -78,16 +80,12 @@ export default createRule<Options, MessageIds>({
7880
if (parentId) {
7981
const scope = context.sourceCode.getScope(parentId);
8082
const superVar = ASTUtils.findVariable(scope, parentId.name);
81-
if (superVar) {
82-
const isCircular = superVar.references.some(
83-
item =>
84-
item.isTypeReference &&
85-
node.range[0] <= item.identifier.range[0] &&
86-
node.range[1] >= item.identifier.range[1],
87-
);
88-
if (isCircular) {
89-
return;
90-
}
83+
84+
if (
85+
superVar &&
86+
isDeeplyReferencingType(node, superVar, new Set([parentId]))
87+
) {
88+
return;
9189
}
9290
}
9391

@@ -269,3 +267,89 @@ function findParentDeclaration(
269267
}
270268
return undefined;
271269
}
270+
271+
function isDeeplyReferencingType(
272+
node: TSESTree.Node,
273+
superVar: ScopeVariable,
274+
visited: Set<TSESTree.Node>,
275+
): boolean {
276+
if (visited.has(node)) {
277+
// something on the chain is circular but it's not the reference being checked
278+
return false;
279+
}
280+
281+
visited.add(node);
282+
283+
switch (node.type) {
284+
case AST_NODE_TYPES.TSTypeLiteral:
285+
return node.members.some(member =>
286+
isDeeplyReferencingType(member, superVar, visited),
287+
);
288+
case AST_NODE_TYPES.TSTypeAliasDeclaration:
289+
return isDeeplyReferencingType(node.typeAnnotation, superVar, visited);
290+
case AST_NODE_TYPES.TSIndexedAccessType:
291+
return [node.indexType, node.objectType].some(type =>
292+
isDeeplyReferencingType(type, superVar, visited),
293+
);
294+
case AST_NODE_TYPES.TSConditionalType:
295+
return [
296+
node.checkType,
297+
node.extendsType,
298+
node.falseType,
299+
node.trueType,
300+
].some(type => isDeeplyReferencingType(type, superVar, visited));
301+
case AST_NODE_TYPES.TSUnionType:
302+
case AST_NODE_TYPES.TSIntersectionType:
303+
return node.types.some(type =>
304+
isDeeplyReferencingType(type, superVar, visited),
305+
);
306+
case AST_NODE_TYPES.TSInterfaceDeclaration:
307+
return node.body.body.some(type =>
308+
isDeeplyReferencingType(type, superVar, visited),
309+
);
310+
case AST_NODE_TYPES.TSTypeAnnotation:
311+
return isDeeplyReferencingType(node.typeAnnotation, superVar, visited);
312+
case AST_NODE_TYPES.TSIndexSignature: {
313+
if (node.typeAnnotation) {
314+
return isDeeplyReferencingType(node.typeAnnotation, superVar, visited);
315+
}
316+
break;
317+
}
318+
case AST_NODE_TYPES.TSTypeParameterInstantiation: {
319+
return node.params.some(param =>
320+
isDeeplyReferencingType(param, superVar, visited),
321+
);
322+
}
323+
case AST_NODE_TYPES.TSTypeReference: {
324+
if (isDeeplyReferencingType(node.typeName, superVar, visited)) {
325+
return true;
326+
}
327+
328+
if (
329+
node.typeArguments &&
330+
isDeeplyReferencingType(node.typeArguments, superVar, visited)
331+
) {
332+
return true;
333+
}
334+
335+
break;
336+
}
337+
case AST_NODE_TYPES.Identifier: {
338+
// check if the identifier is a reference of the type being checked
339+
if (superVar.references.some(ref => isNodeEqual(ref.identifier, node))) {
340+
return true;
341+
}
342+
343+
// otherwise, follow its definition(s)
344+
const refVar = ASTUtils.findVariable(superVar.scope, node.name);
345+
346+
if (refVar) {
347+
return refVar.defs.some(def =>
348+
isDeeplyReferencingType(def.node, superVar, visited),
349+
);
350+
}
351+
}
352+
}
353+
354+
return false;
355+
}

0 commit comments

Comments
 (0)