Skip to content

Commit

Permalink
fix: checking of types from ts's lib are now more strict (#862)
Browse files Browse the repository at this point in the history
  • Loading branch information
RebeccaStevens committed Aug 5, 2024
1 parent c2c589c commit 39beb25
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 41 deletions.
16 changes: 11 additions & 5 deletions src/rules/immutable-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand All @@ -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;
}
Expand All @@ -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;
}
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion src/rules/prefer-readonly-type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
? [
{
Expand Down
2 changes: 1 addition & 1 deletion src/utils/tree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ export function isInPromiseHandlerFunction<
}

const objectType = getTypeOfNode(functionNode.parent.callee.object, context);
return isPromiseType(objectType);
return isPromiseType(context, objectType);
}

/**
Expand Down
98 changes: 64 additions & 34 deletions src/utils/type-guards.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,18 @@
*/

import { AST_NODE_TYPES, type TSESTree } from "@typescript-eslint/utils";
import 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, UnionType } from "typescript";

import typescript from "#/conditional-imports/typescript";

const libSpecifier = {
from: "lib",
} satisfies TypeDeclarationSpecifier;

/*
* TS Types.
*/
Expand Down Expand Up @@ -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<string, ReadonlyArray<unknown>>,
type: Type | null,
): boolean {
return typeMatches(context, "Array", type);
}

export function isArrayConstructorType(
context: RuleContext<string, ReadonlyArray<unknown>>,
type: Type | null,
): boolean {
return typeMatches(context, "ArrayConstructor", type);
}

export function isObjectConstructorType(
context: RuleContext<string, ReadonlyArray<unknown>>,
type: Type | null,
): boolean {
return typeMatches(context, "ObjectConstructor", type);
}

export function isPromiseType(
context: RuleContext<string, ReadonlyArray<unknown>>,
type: Type | null,
): boolean {
return typeMatches(context, "Promise", type);
}

function typeMatches(
context: RuleContext<string, ReadonlyArray<unknown>>,
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))
);
};
}

0 comments on commit 39beb25

Please sign in to comment.