From 9d3d940d699e40938103a4fdaac0e08e44a9d1e7 Mon Sep 17 00:00:00 2001 From: Rebecca Stevens Date: Sat, 3 Aug 2024 02:24:04 +1200 Subject: [PATCH] fix: checking of types from ts's lib are now more strict similarly named types from outside of ts's lib should no longer match --- src/rules/immutable-data.ts | 16 +++-- src/rules/prefer-readonly-type.ts | 2 +- src/utils/tree.ts | 2 +- src/utils/type-guards.ts | 98 ++++++++++++++++++++----------- 4 files changed, 77 insertions(+), 41 deletions(-) diff --git a/src/rules/immutable-data.ts b/src/rules/immutable-data.ts index 7b742b3ea..28b27d6d2 100644 --- a/src/rules/immutable-data.ts +++ b/src/rules/immutable-data.ts @@ -478,7 +478,7 @@ function isInChainCallAndFollowsNew( // Check for: new Array() if ( isNewExpression(node) && - isArrayConstructorType(getTypeOfNode(node.callee, context)) + isArrayConstructorType(context, getTypeOfNode(node.callee, context)) ) { return true; } @@ -491,7 +491,10 @@ function isInChainCallAndFollowsNew( // Check for: Array.from(iterable) if ( arrayConstructorFunctions.some(isExpected(node.callee.property.name)) && - isArrayConstructorType(getTypeOfNode(node.callee.object, context)) + isArrayConstructorType( + context, + getTypeOfNode(node.callee.object, context), + ) ) { return true; } @@ -508,7 +511,10 @@ function isInChainCallAndFollowsNew( objectConstructorNewObjectReturningMethods.some( isExpected(node.callee.property.name), ) && - isObjectConstructorType(getTypeOfNode(node.callee.object, context)) + isObjectConstructorType( + context, + getTypeOfNode(node.callee.object, context), + ) ) { return true; } @@ -582,7 +588,7 @@ function checkCallExpression( arrayMutatorMethods.has(node.callee.property.name) && (!ignoreImmediateMutation || !isInChainCallAndFollowsNew(node.callee, context)) && - isArrayType(getTypeOfNode(node.callee.object, context)) + isArrayType(context, getTypeOfNode(node.callee.object, context)) ) { if (ignoreNonConstDeclarations === false) { return { @@ -627,7 +633,7 @@ function checkCallExpression( ignoreIdentifierPattern, ignoreAccessorPattern, ) && - isObjectConstructorType(getTypeOfNode(node.callee.object, context)) + isObjectConstructorType(context, getTypeOfNode(node.callee.object, context)) ) { if (ignoreNonConstDeclarations === false) { return { diff --git a/src/rules/prefer-readonly-type.ts b/src/rules/prefer-readonly-type.ts index 10e9bd6f6..89df74999 100644 --- a/src/rules/prefer-readonly-type.ts +++ b/src/rules/prefer-readonly-type.ts @@ -495,7 +495,7 @@ function checkImplicitType( isIdentifier(declarator.id) && declarator.id.typeAnnotation === undefined && declarator.init !== null && - isArrayType(getTypeOfNode(declarator.init, context)) && + isArrayType(context, getTypeOfNode(declarator.init, context)) && !ignoreCollections ? [ { diff --git a/src/utils/tree.ts b/src/utils/tree.ts index 8b4faf261..43f1489f0 100644 --- a/src/utils/tree.ts +++ b/src/utils/tree.ts @@ -149,7 +149,7 @@ export function isInPromiseHandlerFunction< } const objectType = getTypeOfNode(functionNode.parent.callee.object, context); - return isPromiseType(objectType); + return isPromiseType(context, objectType); } /** diff --git a/src/utils/type-guards.ts b/src/utils/type-guards.ts index 4338fb36e..8c81e59d8 100644 --- a/src/utils/type-guards.ts +++ b/src/utils/type-guards.ts @@ -3,10 +3,18 @@ */ import { AST_NODE_TYPES, type TSESTree } from "@typescript-eslint/utils"; -import { type Type, type UnionType } from "typescript"; +import { type RuleContext } from "@typescript-eslint/utils/ts-eslint"; +import typeMatchesSpecifier, { + type TypeDeclarationSpecifier, +} from "ts-declaration-location"; +import { type Program, type Type, type UnionType } from "typescript"; import typescript from "#/conditional-imports/typescript"; +const libSpecifier = { + from: "lib", +} satisfies TypeDeclarationSpecifier; + /* * TS Types. */ @@ -429,41 +437,63 @@ export function isUnionType(type: Type): type is UnionType { return typescript !== undefined && type.flags === typescript.TypeFlags.Union; } -export function isArrayType(type: Type | null): boolean { - return ( - type !== null && - (((type.symbol as unknown) !== undefined && type.symbol.name === "Array") || - (isUnionType(type) && type.types.some(isArrayType))) - ); -} - -export function isArrayConstructorType(type: Type | null): boolean { - return ( - type !== null && - (((type.symbol as unknown) !== undefined && - type.symbol.name === "ArrayConstructor") || - (isUnionType(type) && type.types.some(isArrayConstructorType))) - ); -} - -export function isObjectConstructorType(type: Type | null): boolean { - return ( - type !== null && - (((type.symbol as unknown) !== undefined && - type.symbol.name === "ObjectConstructor") || - (isUnionType(type) && type.types.some(isObjectConstructorType))) - ); -} - export function isFunctionLikeType(type: Type | null): boolean { return type !== null && type.getCallSignatures().length > 0; } -export function isPromiseType(type: Type | null): boolean { - return ( - type !== null && - (((type.symbol as unknown) !== undefined && - type.symbol.name === "Promise") || - (isUnionType(type) && type.types.some(isPromiseType))) - ); +export function isArrayType( + context: RuleContext>, + type: Type | null, +): boolean { + return typeMatches(context, "Array", type); +} + +export function isArrayConstructorType( + context: RuleContext>, + type: Type | null, +): boolean { + return typeMatches(context, "ArrayConstructor", type); +} + +export function isObjectConstructorType( + context: RuleContext>, + type: Type | null, +): boolean { + return typeMatches(context, "ObjectConstructor", type); +} + +export function isPromiseType( + context: RuleContext>, + type: Type | null, +): boolean { + return typeMatches(context, "Promise", type); +} + +function typeMatches( + context: RuleContext>, + typeName: string, + type: Type | null, +): boolean { + if (type === null) { + return false; + } + const program = context.sourceCode.parserServices?.program ?? undefined; + if (program === undefined) { + return false; + } + return typeMatchesHelper(program, typeName)(type); +} + +function typeMatchesHelper( + program: Program, + typeName: string, +): (type: Type) => boolean { + return function test(type: Type) { + return ( + ((type.symbol as unknown) !== undefined && + type.symbol.name === typeName && + typeMatchesSpecifier(program, libSpecifier, type)) || + (isUnionType(type) && type.types.some(test)) + ); + }; }