diff --git a/docs/development/CUSTOM_RULES.md b/docs/development/CUSTOM_RULES.md index f04255d00e68..532e7c9ae961 100644 --- a/docs/development/CUSTOM_RULES.md +++ b/docs/development/CUSTOM_RULES.md @@ -222,9 +222,9 @@ By mapping from ESTree nodes to TypeScript nodes and retrieving the TypeScript p This rule bans for-of looping over an enum by using the type-checker via typescript-eslint and TypeScript APIs: ```ts +import { isTypeFlagSet } from '@typescript-eslint/type-utils'; import { ESLintUtils } from '@typescript-eslint/utils'; import * as ts from 'typescript'; -import * as tsutils from 'tsutils'; export const rule = createRule({ create(context) { @@ -241,7 +241,7 @@ export const rule = createRule({ const nodeType = checker.getTypeAtLocation(originalNode); // 3. Check the TS node type using the TypeScript APIs - if (tsutils.isTypeFlagSet(nodeType, ts.TypeFlags.EnumLike)) { + if (isTypeFlagSet(nodeType, ts.TypeFlags.EnumLike)) { context.report({ messageId: 'loopOverEnum', node: node.right, diff --git a/packages/eslint-plugin/src/rules/await-thenable.ts b/packages/eslint-plugin/src/rules/await-thenable.ts index ab9f97da7c27..ef7ec23dc834 100644 --- a/packages/eslint-plugin/src/rules/await-thenable.ts +++ b/packages/eslint-plugin/src/rules/await-thenable.ts @@ -1,4 +1,4 @@ -import * as tsutils from 'tsutils'; +import { isThenableType } from '@typescript-eslint/type-utils'; import * as util from '../util'; @@ -30,7 +30,7 @@ export default util.createRule({ if ( !util.isTypeAnyType(type) && !util.isTypeUnknownType(type) && - !tsutils.isThenableType(checker, originalNode.expression, type) + !isThenableType(checker, originalNode.expression, type) ) { context.report({ messageId: 'await', diff --git a/packages/eslint-plugin/src/rules/dot-notation.ts b/packages/eslint-plugin/src/rules/dot-notation.ts index 9f0b0d1304bf..bbc9d9e2f9a4 100644 --- a/packages/eslint-plugin/src/rules/dot-notation.ts +++ b/packages/eslint-plugin/src/rules/dot-notation.ts @@ -1,5 +1,5 @@ +import { isCompilerOptionEnabled } from '@typescript-eslint/type-utils'; import type { TSESTree } from '@typescript-eslint/utils'; -import * as tsutils from 'tsutils'; import * as ts from 'typescript'; import type { @@ -77,9 +77,8 @@ export default createRule({ options.allowProtectedClassPropertyAccess; const allowIndexSignaturePropertyAccess = (options.allowIndexSignaturePropertyAccess ?? false) || - tsutils.isCompilerOptionEnabled( + isCompilerOptionEnabled( program.getCompilerOptions(), - // @ts-expect-error - TS is refining the type to never for some reason 'noPropertyAccessFromIndexSignature', ); diff --git a/packages/eslint-plugin/src/rules/no-confusing-void-expression.ts b/packages/eslint-plugin/src/rules/no-confusing-void-expression.ts index 01e5b45dbb8b..2b2e8305730d 100644 --- a/packages/eslint-plugin/src/rules/no-confusing-void-expression.ts +++ b/packages/eslint-plugin/src/rules/no-confusing-void-expression.ts @@ -1,6 +1,6 @@ +import { isTypeFlagSet } from '@typescript-eslint/type-utils'; import type { TSESLint, TSESTree } from '@typescript-eslint/utils'; import { AST_NODE_TYPES } from '@typescript-eslint/utils'; -import * as tsutils from 'tsutils'; import * as ts from 'typescript'; import * as util from '../util'; @@ -84,7 +84,7 @@ export default util.createRule({ const checker = parserServices.program.getTypeChecker(); const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node); const type = util.getConstrainedTypeAtLocation(checker, tsNode); - if (!tsutils.isTypeFlagSet(type, ts.TypeFlags.VoidLike)) { + if (!isTypeFlagSet(type, ts.TypeFlags.VoidLike)) { // not a void expression return; } diff --git a/packages/eslint-plugin/src/rules/no-dynamic-delete.ts b/packages/eslint-plugin/src/rules/no-dynamic-delete.ts index 09d5b5420d8a..2a9e24482949 100644 --- a/packages/eslint-plugin/src/rules/no-dynamic-delete.ts +++ b/packages/eslint-plugin/src/rules/no-dynamic-delete.ts @@ -1,6 +1,6 @@ +import { isValidPropertyAccess } from '@typescript-eslint/type-utils'; import type { TSESLint, TSESTree } from '@typescript-eslint/utils'; import { AST_NODE_TYPES } from '@typescript-eslint/utils'; -import * as tsutils from 'tsutils'; import * as util from '../util'; @@ -96,7 +96,6 @@ function isNecessaryDynamicAccess(property: TSESTree.Expression): boolean { } return ( - typeof property.value === 'string' && - !tsutils.isValidPropertyAccess(property.value) + typeof property.value === 'string' && !isValidPropertyAccess(property.value) ); } diff --git a/packages/eslint-plugin/src/rules/no-floating-promises.ts b/packages/eslint-plugin/src/rules/no-floating-promises.ts index 05f0954e305d..b996127cfeb6 100644 --- a/packages/eslint-plugin/src/rules/no-floating-promises.ts +++ b/packages/eslint-plugin/src/rules/no-floating-promises.ts @@ -1,6 +1,6 @@ +import { unionTypeParts } from '@typescript-eslint/type-utils'; import type { TSESLint, TSESTree } from '@typescript-eslint/utils'; import { AST_NODE_TYPES } from '@typescript-eslint/utils'; -import * as tsutils from 'tsutils'; import type * as ts from 'typescript'; import * as util from '../util'; @@ -183,7 +183,7 @@ export default util.createRule({ // https://github.com/ajafff/tsutils/blob/49d0d31050b44b81e918eae4fbaf1dfe7b7286af/util/type.ts#L95-L125 function isPromiseLike(checker: ts.TypeChecker, node: ts.Node): boolean { const type = checker.getTypeAtLocation(node); - for (const ty of tsutils.unionTypeParts(checker.getApparentType(type))) { + for (const ty of unionTypeParts(checker.getApparentType(type))) { const then = ty.getProperty('then'); if (then === undefined) { continue; @@ -209,7 +209,7 @@ function hasMatchingSignature( type: ts.Type, matcher: (signature: ts.Signature) => boolean, ): boolean { - for (const t of tsutils.unionTypeParts(type)) { + for (const t of unionTypeParts(type)) { if (t.getCallSignatures().some(matcher)) { return true; } @@ -226,7 +226,7 @@ function isFunctionParam( const type: ts.Type | undefined = checker.getApparentType( checker.getTypeOfSymbolAtLocation(param, node), ); - for (const t of tsutils.unionTypeParts(type)) { + for (const t of unionTypeParts(type)) { if (t.getCallSignatures().length !== 0) { return true; } diff --git a/packages/eslint-plugin/src/rules/no-implied-eval.ts b/packages/eslint-plugin/src/rules/no-implied-eval.ts index 0ae6698c533c..efec9e5646fa 100644 --- a/packages/eslint-plugin/src/rules/no-implied-eval.ts +++ b/packages/eslint-plugin/src/rules/no-implied-eval.ts @@ -1,6 +1,6 @@ +import { isSymbolFlagSet } from '@typescript-eslint/type-utils'; import type { TSESTree } from '@typescript-eslint/utils'; import { AST_NODE_TYPES } from '@typescript-eslint/utils'; -import * as tsutils from 'tsutils'; import * as ts from 'typescript'; import * as util from '../util'; @@ -71,10 +71,7 @@ export default util.createRule({ if ( symbol && - tsutils.isSymbolFlagSet( - symbol, - ts.SymbolFlags.Function | ts.SymbolFlags.Method, - ) + isSymbolFlagSet(symbol, ts.SymbolFlags.Function | ts.SymbolFlags.Method) ) { return true; } diff --git a/packages/eslint-plugin/src/rules/no-meaningless-void-operator.ts b/packages/eslint-plugin/src/rules/no-meaningless-void-operator.ts index 79d0611e5d05..b2edc3aac804 100644 --- a/packages/eslint-plugin/src/rules/no-meaningless-void-operator.ts +++ b/packages/eslint-plugin/src/rules/no-meaningless-void-operator.ts @@ -1,6 +1,6 @@ +import { unionTypeParts } from '@typescript-eslint/type-utils'; import type { TSESLint, TSESTree } from '@typescript-eslint/utils'; import { ESLintUtils } from '@typescript-eslint/utils'; -import * as tsutils from 'tsutils'; import * as ts from 'typescript'; import * as util from '../util'; @@ -64,7 +64,7 @@ export default util.createRule< node.argument, ); const argType = checker.getTypeAtLocation(argTsNode); - const unionParts = tsutils.unionTypeParts(argType); + const unionParts = unionTypeParts(argType); if ( unionParts.every( part => part.flags & (ts.TypeFlags.Void | ts.TypeFlags.Undefined), diff --git a/packages/eslint-plugin/src/rules/no-misused-promises.ts b/packages/eslint-plugin/src/rules/no-misused-promises.ts index 2c7f1eeb2599..b3cad5936333 100644 --- a/packages/eslint-plugin/src/rules/no-misused-promises.ts +++ b/packages/eslint-plugin/src/rules/no-misused-promises.ts @@ -1,6 +1,11 @@ +import { + isCallExpression, + isThenableType, + isTypeFlagSet, + unionTypeParts, +} from '@typescript-eslint/type-utils'; import type { TSESLint, TSESTree } from '@typescript-eslint/utils'; import { AST_NODE_TYPES } from '@typescript-eslint/utils'; -import * as tsutils from 'tsutils'; import * as ts from 'typescript'; import * as util from '../util'; @@ -410,8 +415,8 @@ export default util.createRule({ function isSometimesThenable(checker: ts.TypeChecker, node: ts.Node): boolean { const type = checker.getTypeAtLocation(node); - for (const subType of tsutils.unionTypeParts(checker.getApparentType(type))) { - if (tsutils.isThenableType(checker, node, subType)) { + for (const subType of unionTypeParts(checker.getApparentType(type))) { + if (isThenableType(checker, node, subType)) { return true; } } @@ -426,7 +431,7 @@ function isSometimesThenable(checker: ts.TypeChecker, node: ts.Node): boolean { function isAlwaysThenable(checker: ts.TypeChecker, node: ts.Node): boolean { const type = checker.getTypeAtLocation(node); - for (const subType of tsutils.unionTypeParts(checker.getApparentType(type))) { + for (const subType of unionTypeParts(checker.getApparentType(type))) { const thenProp = subType.getProperty('then'); // If one of the alternates has no then property, it is not thenable in all @@ -440,7 +445,7 @@ function isAlwaysThenable(checker: ts.TypeChecker, node: ts.Node): boolean { // be of the right form to consider it thenable. const thenType = checker.getTypeOfSymbolAtLocation(thenProp, node); let hasThenableSignature = false; - for (const subType of tsutils.unionTypeParts(thenType)) { + for (const subType of unionTypeParts(thenType)) { for (const signature of subType.getCallSignatures()) { if ( signature.parameters.length !== 0 && @@ -478,7 +483,7 @@ function isFunctionParam( const type: ts.Type | undefined = checker.getApparentType( checker.getTypeOfSymbolAtLocation(param, node), ); - for (const subType of tsutils.unionTypeParts(type)) { + for (const subType of unionTypeParts(type)) { if (subType.getCallSignatures().length !== 0) { return true; } @@ -500,9 +505,9 @@ function voidFunctionParams( // We can't use checker.getResolvedSignature because it prefers an early '() => void' over a later '() => Promise' // See https://github.com/microsoft/TypeScript/issues/48077 - for (const subType of tsutils.unionTypeParts(type)) { + for (const subType of unionTypeParts(type)) { // Standard function calls and `new` have two different types of signatures - const signatures = ts.isCallExpression(node) + const signatures = isCallExpression(node) ? subType.getCallSignatures() : subType.getConstructSignatures(); for (const signature of signatures) { @@ -540,7 +545,7 @@ function anySignatureIsThenableType( ): boolean { for (const signature of type.getCallSignatures()) { const returnType = signature.getReturnType(); - if (tsutils.isThenableType(checker, node, returnType)) { + if (isThenableType(checker, node, returnType)) { return true; } } @@ -556,7 +561,7 @@ function isThenableReturningFunctionType( node: ts.Node, type: ts.Type, ): boolean { - for (const subType of tsutils.unionTypeParts(type)) { + for (const subType of unionTypeParts(type)) { if (anySignatureIsThenableType(checker, node, subType)) { return true; } @@ -575,17 +580,17 @@ function isVoidReturningFunctionType( ): boolean { let hadVoidReturn = false; - for (const subType of tsutils.unionTypeParts(type)) { + for (const subType of unionTypeParts(type)) { for (const signature of subType.getCallSignatures()) { const returnType = signature.getReturnType(); // If a certain positional argument accepts both thenable and void returns, // a promise-returning function is valid - if (tsutils.isThenableType(checker, node, returnType)) { + if (isThenableType(checker, node, returnType)) { return false; } - hadVoidReturn ||= tsutils.isTypeFlagSet(returnType, ts.TypeFlags.Void); + hadVoidReturn ||= isTypeFlagSet(returnType, ts.TypeFlags.Void); } } diff --git a/packages/eslint-plugin/src/rules/no-redundant-type-constituents.ts b/packages/eslint-plugin/src/rules/no-redundant-type-constituents.ts index 33237a8ae4e2..30ecd2657b8f 100644 --- a/packages/eslint-plugin/src/rules/no-redundant-type-constituents.ts +++ b/packages/eslint-plugin/src/rules/no-redundant-type-constituents.ts @@ -1,5 +1,8 @@ +import { + isBooleanLiteralType, + unionTypeParts, +} from '@typescript-eslint/type-utils'; import { AST_NODE_TYPES, TSESTree } from '@typescript-eslint/utils'; -import * as tsutils from 'tsutils'; import * as ts from 'typescript'; import * as util from '../util'; @@ -106,11 +109,11 @@ function describeLiteralType(type: ts.Type): string { return `${type.value.negative ? '-' : ''}${type.value.base10Value}n`; } - if (tsutils.isBooleanLiteralType(type, true)) { + if (isBooleanLiteralType(type, true)) { return 'true'; } - if (tsutils.isBooleanLiteralType(type, false)) { + if (isBooleanLiteralType(type, false)) { return 'false'; } @@ -167,10 +170,10 @@ function isNodeInsideReturnType(node: TSESTree.TSUnionType): boolean { function unionTypePartsUnlessBoolean(type: ts.Type): ts.Type[] { return type.isUnion() && type.types.length === 2 && - tsutils.isBooleanLiteralType(type.types[0], false) && - tsutils.isBooleanLiteralType(type.types[1], true) + isBooleanLiteralType(type.types[0], false) && + isBooleanLiteralType(type.types[1], true) ? [type] - : tsutils.unionTypeParts(type); + : unionTypeParts(type); } export default util.createRule({ diff --git a/packages/eslint-plugin/src/rules/no-unnecessary-boolean-literal-compare.ts b/packages/eslint-plugin/src/rules/no-unnecessary-boolean-literal-compare.ts index f874905b738b..9998e299f23c 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-boolean-literal-compare.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-boolean-literal-compare.ts @@ -1,6 +1,6 @@ import type { TSESTree } from '@typescript-eslint/utils'; import { AST_NODE_TYPES } from '@typescript-eslint/utils'; -import * as tsutils from 'tsutils'; +import { isTypeFlagSet } from 'tsutils'; import * as ts from 'typescript'; import * as util from '../util'; @@ -113,7 +113,7 @@ export default util.createRule({ } function isBooleanType(expressionType: ts.Type): boolean { - return tsutils.isTypeFlagSet( + return isTypeFlagSet( expressionType, ts.TypeFlags.Boolean | ts.TypeFlags.BooleanLiteral, ); @@ -134,10 +134,7 @@ export default util.createRule({ const nonNullishTypes = types.filter( type => - !tsutils.isTypeFlagSet( - type, - ts.TypeFlags.Undefined | ts.TypeFlags.Null, - ), + !isTypeFlagSet(type, ts.TypeFlags.Undefined | ts.TypeFlags.Null), ); const hasNonNullishType = nonNullishTypes.length > 0; diff --git a/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts b/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts index 942048bce0bd..f17990252db7 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts @@ -1,5 +1,3 @@ -import type { TSESTree } from '@typescript-eslint/utils'; -import { AST_NODE_TYPES, AST_TOKEN_TYPES } from '@typescript-eslint/utils'; import { getCallSignaturesOfType, isBooleanLiteralType, @@ -7,7 +5,9 @@ import { isLiteralType, isStrictCompilerOptionEnabled, unionTypeParts, -} from 'tsutils'; +} from '@typescript-eslint/type-utils'; +import type { TSESTree } from '@typescript-eslint/utils'; +import { AST_NODE_TYPES, AST_TOKEN_TYPES } from '@typescript-eslint/utils'; import * as ts from 'typescript'; import { diff --git a/packages/eslint-plugin/src/rules/no-unnecessary-qualifier.ts b/packages/eslint-plugin/src/rules/no-unnecessary-qualifier.ts index fbf3b41e9668..e45baf27fbdc 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-qualifier.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-qualifier.ts @@ -1,6 +1,6 @@ +import { isSymbolFlagSet } from '@typescript-eslint/type-utils'; import type { TSESTree } from '@typescript-eslint/utils'; import { AST_NODE_TYPES } from '@typescript-eslint/utils'; -import * as tsutils from 'tsutils'; import * as ts from 'typescript'; import * as util from '../util'; @@ -35,7 +35,7 @@ export default util.createRule({ symbol: ts.Symbol, checker: ts.TypeChecker, ): ts.Symbol | null { - return tsutils.isSymbolFlagSet(symbol, ts.SymbolFlags.Alias) + return isSymbolFlagSet(symbol, ts.SymbolFlags.Alias) ? checker.getAliasedSymbol(symbol) : null; } diff --git a/packages/eslint-plugin/src/rules/no-unnecessary-type-arguments.ts b/packages/eslint-plugin/src/rules/no-unnecessary-type-arguments.ts index 6d300b36fe12..63a00a913c60 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-type-arguments.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-type-arguments.ts @@ -1,5 +1,8 @@ +import { + isCallExpression, + isSymbolFlagSet, +} from '@typescript-eslint/type-utils'; import type { TSESTree } from '@typescript-eslint/utils'; -import * as tsutils from 'tsutils'; import * as ts from 'typescript'; import * as util from '../util'; @@ -129,7 +132,7 @@ function getTypeParametersFromNode( return getTypeParametersFromType(node.typeName, checker); } - if (ts.isCallExpression(node) || ts.isNewExpression(node)) { + if (isCallExpression(node) || ts.isNewExpression(node)) { return getTypeParametersFromCall(node, checker); } @@ -180,7 +183,7 @@ function getAliasedSymbol( symbol: ts.Symbol, checker: ts.TypeChecker, ): ts.Symbol { - return tsutils.isSymbolFlagSet(symbol, ts.SymbolFlags.Alias) + return isSymbolFlagSet(symbol, ts.SymbolFlags.Alias) ? checker.getAliasedSymbol(symbol) : symbol; } diff --git a/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts b/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts index 38248f311235..4410cab83da8 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts @@ -1,12 +1,12 @@ -import type { TSESTree } from '@typescript-eslint/utils'; -import { AST_NODE_TYPES } from '@typescript-eslint/utils'; import { isObjectFlagSet, isObjectType, isStrictCompilerOptionEnabled, isTypeFlagSet, isVariableDeclaration, -} from 'tsutils'; +} from '@typescript-eslint/type-utils'; +import type { TSESTree } from '@typescript-eslint/utils'; +import { AST_NODE_TYPES } from '@typescript-eslint/utils'; import * as ts from 'typescript'; import * as util from '../util'; diff --git a/packages/eslint-plugin/src/rules/no-unsafe-assignment.ts b/packages/eslint-plugin/src/rules/no-unsafe-assignment.ts index 4833d84a84c7..981d62b36641 100644 --- a/packages/eslint-plugin/src/rules/no-unsafe-assignment.ts +++ b/packages/eslint-plugin/src/rules/no-unsafe-assignment.ts @@ -1,6 +1,6 @@ +import { isStrictCompilerOptionEnabled } from '@typescript-eslint/type-utils'; import type { TSESTree } from '@typescript-eslint/utils'; import { AST_NODE_TYPES } from '@typescript-eslint/utils'; -import * as tsutils from 'tsutils'; import type * as ts from 'typescript'; import * as util from '../util'; @@ -45,7 +45,7 @@ export default util.createRule({ const { program, esTreeNodeToTSNodeMap } = util.getParserServices(context); const checker = program.getTypeChecker(); const compilerOptions = program.getCompilerOptions(); - const isNoImplicitThis = tsutils.isStrictCompilerOptionEnabled( + const isNoImplicitThis = isStrictCompilerOptionEnabled( compilerOptions, 'noImplicitThis', ); diff --git a/packages/eslint-plugin/src/rules/no-unsafe-call.ts b/packages/eslint-plugin/src/rules/no-unsafe-call.ts index dfa6fa2fb4c7..a0359ac2a9eb 100644 --- a/packages/eslint-plugin/src/rules/no-unsafe-call.ts +++ b/packages/eslint-plugin/src/rules/no-unsafe-call.ts @@ -1,5 +1,5 @@ +import { isStrictCompilerOptionEnabled } from '@typescript-eslint/type-utils'; import type { TSESTree } from '@typescript-eslint/utils'; -import * as tsutils from 'tsutils'; import * as util from '../util'; import { getThisExpression } from '../util'; @@ -35,7 +35,7 @@ export default util.createRule<[], MessageIds>({ const { program, esTreeNodeToTSNodeMap } = util.getParserServices(context); const checker = program.getTypeChecker(); const compilerOptions = program.getCompilerOptions(); - const isNoImplicitThis = tsutils.isStrictCompilerOptionEnabled( + const isNoImplicitThis = isStrictCompilerOptionEnabled( compilerOptions, 'noImplicitThis', ); diff --git a/packages/eslint-plugin/src/rules/no-unsafe-member-access.ts b/packages/eslint-plugin/src/rules/no-unsafe-member-access.ts index 410ff78f5458..f25bf3e1d41c 100644 --- a/packages/eslint-plugin/src/rules/no-unsafe-member-access.ts +++ b/packages/eslint-plugin/src/rules/no-unsafe-member-access.ts @@ -1,6 +1,6 @@ +import { isStrictCompilerOptionEnabled } from '@typescript-eslint/type-utils'; import type { TSESTree } from '@typescript-eslint/utils'; import { AST_NODE_TYPES } from '@typescript-eslint/utils'; -import * as tsutils from 'tsutils'; import * as util from '../util'; import { getThisExpression } from '../util'; @@ -36,7 +36,7 @@ export default util.createRule({ const { program, esTreeNodeToTSNodeMap } = util.getParserServices(context); const checker = program.getTypeChecker(); const compilerOptions = program.getCompilerOptions(); - const isNoImplicitThis = tsutils.isStrictCompilerOptionEnabled( + const isNoImplicitThis = isStrictCompilerOptionEnabled( compilerOptions, 'noImplicitThis', ); diff --git a/packages/eslint-plugin/src/rules/no-unsafe-return.ts b/packages/eslint-plugin/src/rules/no-unsafe-return.ts index 63d60ff81f8e..945b8e1538a2 100644 --- a/packages/eslint-plugin/src/rules/no-unsafe-return.ts +++ b/packages/eslint-plugin/src/rules/no-unsafe-return.ts @@ -1,6 +1,9 @@ +import { + isExpression, + isStrictCompilerOptionEnabled, +} from '@typescript-eslint/type-utils'; import type { TSESTree } from '@typescript-eslint/utils'; import { AST_NODE_TYPES } from '@typescript-eslint/utils'; -import * as tsutils from 'tsutils'; import * as util from '../util'; import { getThisExpression } from '../util'; @@ -30,7 +33,7 @@ export default util.createRule({ const { program, esTreeNodeToTSNodeMap } = util.getParserServices(context); const checker = program.getTypeChecker(); const compilerOptions = program.getCompilerOptions(); - const isNoImplicitThis = tsutils.isStrictCompilerOptionEnabled( + const isNoImplicitThis = isStrictCompilerOptionEnabled( compilerOptions, 'noImplicitThis', ); @@ -82,7 +85,7 @@ export default util.createRule({ // so we have to use the contextual typing in these cases, i.e. // const foo1: () => Set = () => new Set(); // the return type of the arrow function is Set even though the variable is typed as Set - let functionType = tsutils.isExpression(functionTSNode) + let functionType = isExpression(functionTSNode) ? util.getContextualType(checker, functionTSNode) : checker.getTypeAtLocation(functionTSNode); if (!functionType) { diff --git a/packages/eslint-plugin/src/rules/non-nullable-type-assertion-style.ts b/packages/eslint-plugin/src/rules/non-nullable-type-assertion-style.ts index 4953cf8041ea..33b1e6a60c87 100644 --- a/packages/eslint-plugin/src/rules/non-nullable-type-assertion-style.ts +++ b/packages/eslint-plugin/src/rules/non-nullable-type-assertion-style.ts @@ -1,6 +1,10 @@ +import { + isTypeFlagSet, + isUnionType, + unionTypeParts, +} from '@typescript-eslint/type-utils'; import type { TSESTree } from '@typescript-eslint/utils'; import { AST_NODE_TYPES } from '@typescript-eslint/utils'; -import * as tsutils from 'tsutils'; import * as ts from 'typescript'; import * as util from '../util'; @@ -33,20 +37,18 @@ export default util.createRule({ parserServices.esTreeNodeToTSNodeMap.get(node), ); - if ( - tsutils.isTypeFlagSet(type, ts.TypeFlags.Any | ts.TypeFlags.Unknown) - ) { + if (isTypeFlagSet(type, ts.TypeFlags.Any | ts.TypeFlags.Unknown)) { return undefined; } - return tsutils.unionTypeParts(type); + return unionTypeParts(type); }; const couldBeNullish = (type: ts.Type): boolean => { if (type.flags & ts.TypeFlags.TypeParameter) { const constraint = type.getConstraint(); return constraint == null || couldBeNullish(constraint); - } else if (tsutils.isUnionType(type)) { + } else if (isUnionType(type)) { for (const part of type.types) { if (couldBeNullish(part)) { return true; diff --git a/packages/eslint-plugin/src/rules/prefer-optional-chain.ts b/packages/eslint-plugin/src/rules/prefer-optional-chain.ts index dc9b514e3531..5007e68cfb95 100644 --- a/packages/eslint-plugin/src/rules/prefer-optional-chain.ts +++ b/packages/eslint-plugin/src/rules/prefer-optional-chain.ts @@ -1,6 +1,6 @@ +import { isBinaryExpression } from '@typescript-eslint/type-utils'; import type { TSESLint, TSESTree } from '@typescript-eslint/utils'; import { AST_NODE_TYPES } from '@typescript-eslint/utils'; -import { isBinaryExpression } from 'tsutils'; import * as ts from 'typescript'; import * as util from '../util'; diff --git a/packages/eslint-plugin/src/rules/prefer-readonly.ts b/packages/eslint-plugin/src/rules/prefer-readonly.ts index 3a1cd3f42f62..0cfe7f075f52 100644 --- a/packages/eslint-plugin/src/rules/prefer-readonly.ts +++ b/packages/eslint-plugin/src/rules/prefer-readonly.ts @@ -1,6 +1,15 @@ +import type { ScopeBoundary } from '@typescript-eslint/type-utils'; +import { + isAssignmentKind, + isBinaryExpression, + isFunctionScopeBoundary, + isIntersectionType, + isModifierFlagSet, + isObjectFlagSet, + isObjectType, +} from '@typescript-eslint/type-utils'; import type { TSESTree } from '@typescript-eslint/utils'; import { AST_NODE_TYPES, ASTUtils } from '@typescript-eslint/utils'; -import * as tsutils from 'tsutils'; import * as ts from 'typescript'; import * as util from '../util'; @@ -58,7 +67,7 @@ export default util.createRule({ parent: ts.Node, classScope: ClassScope, ): void { - if (ts.isBinaryExpression(parent)) { + if (isBinaryExpression(parent)) { handleParentBinaryExpression(node, parent, classScope); return; } @@ -81,10 +90,7 @@ export default util.createRule({ parent: ts.BinaryExpression, classScope: ClassScope, ): void { - if ( - parent.left === node && - tsutils.isAssignmentKind(parent.operatorToken.kind) - ) { + if (parent.left === node && isAssignmentKind(parent.operatorToken.kind)) { classScope.addVariableModification(node); } } @@ -119,7 +125,7 @@ export default util.createRule({ ts.isArrayLiteralExpression(parent.parent)) ) { current = parent; - } else if (ts.isBinaryExpression(parent)) { + } else if (isBinaryExpression(parent)) { return ( parent.left === current && parent.operatorToken.kind === ts.SyntaxKind.EqualsToken @@ -138,7 +144,7 @@ export default util.createRule({ | TSESTree.FunctionDeclaration | TSESTree.FunctionExpression | TSESTree.MethodDefinition, - ): boolean | tsutils.ScopeBoundary { + ): boolean | ScopeBoundary { if (classScopeStack.length === 0) { return false; } @@ -148,7 +154,7 @@ export default util.createRule({ return false; } - return tsutils.isFunctionScopeBoundary(tsNode); + return isFunctionScopeBoundary(tsNode); } function getEsNodesFromViolatingNode( @@ -273,7 +279,7 @@ class ClassScope { private readonly onlyInlineLambdas?: boolean, ) { const classType = checker.getTypeAtLocation(classNode); - if (tsutils.isIntersectionType(classType)) { + if (isIntersectionType(classType)) { this.classType = classType.types[0]; } else { this.classType = classType; @@ -288,8 +294,8 @@ class ClassScope { public addDeclaredVariable(node: ParameterOrPropertyDeclaration): void { if ( - !tsutils.isModifierFlagSet(node, ts.ModifierFlags.Private) || - tsutils.isModifierFlagSet(node, ts.ModifierFlags.Readonly) || + !isModifierFlagSet(node, ts.ModifierFlags.Private) || + isModifierFlagSet(node, ts.ModifierFlags.Readonly) || ts.isComputedPropertyName(node.name) ) { return; @@ -303,7 +309,7 @@ class ClassScope { return; } - (tsutils.isModifierFlagSet(node, ts.ModifierFlags.Static) + (isModifierFlagSet(node, ts.ModifierFlags.Static) ? this.privateModifiableStatics : this.privateModifiableMembers ).set(node.name.getText(), node); @@ -319,8 +325,8 @@ class ClassScope { } const modifyingStatic = - tsutils.isObjectType(modifierType) && - tsutils.isObjectFlagSet(modifierType, ts.ObjectFlags.Anonymous); + isObjectType(modifierType) && + isObjectFlagSet(modifierType, ts.ObjectFlags.Anonymous); if ( !modifyingStatic && this.constructorScopeDepth === DIRECTLY_INSIDE_CONSTRUCTOR @@ -344,7 +350,7 @@ class ClassScope { this.constructorScopeDepth = DIRECTLY_INSIDE_CONSTRUCTOR; for (const parameter of node.parameters) { - if (tsutils.isModifierFlagSet(parameter, ts.ModifierFlags.Private)) { + if (isModifierFlagSet(parameter, ts.ModifierFlags.Private)) { this.addDeclaredVariable(parameter); } } diff --git a/packages/eslint-plugin/src/rules/prefer-regexp-exec.ts b/packages/eslint-plugin/src/rules/prefer-regexp-exec.ts index 13452d48f86f..603c95639079 100644 --- a/packages/eslint-plugin/src/rules/prefer-regexp-exec.ts +++ b/packages/eslint-plugin/src/rules/prefer-regexp-exec.ts @@ -1,6 +1,6 @@ +import { unionTypeParts } from '@typescript-eslint/type-utils'; import type { TSESTree } from '@typescript-eslint/utils'; import { AST_NODE_TYPES } from '@typescript-eslint/utils'; -import * as tsutils from 'tsutils'; import type * as ts from 'typescript'; import { @@ -142,7 +142,7 @@ export default createRule({ parserServices.esTreeNodeToTSNodeMap.get(argumentNode), ); const argumentTypes = collectArgumentTypes( - tsutils.unionTypeParts(argumentType), + unionTypeParts(argumentType), ); switch (argumentTypes) { case ArgumentType.RegExp: diff --git a/packages/eslint-plugin/src/rules/require-await.ts b/packages/eslint-plugin/src/rules/require-await.ts index 076cd6077a0f..51c39905705f 100644 --- a/packages/eslint-plugin/src/rules/require-await.ts +++ b/packages/eslint-plugin/src/rules/require-await.ts @@ -1,6 +1,9 @@ +import { + getWellKnownSymbolPropertyOfType, + isThenableType, +} from '@typescript-eslint/type-utils'; import type { TSESLint, TSESTree } from '@typescript-eslint/utils'; import { AST_NODE_TYPES } from '@typescript-eslint/utils'; -import * as tsutils from 'tsutils'; import type * as ts from 'typescript'; import * as util from '../util'; @@ -82,15 +85,6 @@ export default util.createRule({ scopeInfo = scopeInfo.upper; } - /** - * Checks if the node returns a thenable type - */ - function isThenableType(node: ts.Node): boolean { - const type = checker.getTypeAtLocation(node); - - return tsutils.isThenableType(checker, node, type); - } - /** * Marks the current scope as having an await */ @@ -120,7 +114,7 @@ export default util.createRule({ const type = checker.getTypeAtLocation(tsNode); const typesToCheck = expandUnionOrIntersectionType(type); for (const type of typesToCheck) { - const asyncIterator = tsutils.getWellKnownSymbolPropertyOfType( + const asyncIterator = getWellKnownSymbolPropertyOfType( type, 'asyncIterator', checker, @@ -153,7 +147,14 @@ export default util.createRule({ >, ): void { const expression = parserServices.esTreeNodeToTSNodeMap.get(node); - if (expression && isThenableType(expression)) { + if ( + expression && + isThenableType( + checker, + expression, + checker.getTypeAtLocation(expression), + ) + ) { markAsHasAwait(); } }, @@ -164,7 +165,14 @@ export default util.createRule({ } const { expression } = parserServices.esTreeNodeToTSNodeMap.get(node); - if (expression && isThenableType(expression)) { + if ( + expression && + isThenableType( + checker, + expression, + checker.getTypeAtLocation(expression), + ) + ) { markAsHasAwait(); } }, diff --git a/packages/eslint-plugin/src/rules/return-await.ts b/packages/eslint-plugin/src/rules/return-await.ts index 1797e47e1276..dd0067411752 100644 --- a/packages/eslint-plugin/src/rules/return-await.ts +++ b/packages/eslint-plugin/src/rules/return-await.ts @@ -1,7 +1,9 @@ +import { + isBinaryExpression, + isThenableType, +} from '@typescript-eslint/type-utils'; import type { TSESLint, TSESTree } from '@typescript-eslint/utils'; import { AST_NODE_TYPES } from '@typescript-eslint/utils'; -import * as tsutils from 'tsutils'; -import { isBinaryExpression } from 'tsutils'; import * as ts from 'typescript'; import * as util from '../util'; @@ -189,7 +191,7 @@ export default util.createRule({ } const type = checker.getTypeAtLocation(child); - const isThenable = tsutils.isThenableType(checker, expression, type); + const isThenable = isThenableType(checker, expression, type); if (!isAwait && !isThenable) { return; diff --git a/packages/eslint-plugin/src/rules/strict-boolean-expressions.ts b/packages/eslint-plugin/src/rules/strict-boolean-expressions.ts index 1e327a8a4b83..1fa73a7bb11b 100644 --- a/packages/eslint-plugin/src/rules/strict-boolean-expressions.ts +++ b/packages/eslint-plugin/src/rules/strict-boolean-expressions.ts @@ -1,6 +1,11 @@ +import { + isBooleanLiteralType, + isStrictCompilerOptionEnabled, + isTypeFlagSet, + unionTypeParts, +} from '@typescript-eslint/type-utils'; import type { ParserServices, TSESTree } from '@typescript-eslint/utils'; import { AST_NODE_TYPES } from '@typescript-eslint/utils'; -import * as tsutils from 'tsutils'; import * as ts from 'typescript'; import * as util from '../util'; @@ -146,7 +151,7 @@ export default util.createRule({ const typeChecker = parserServices.program.getTypeChecker(); const compilerOptions = parserServices.program.getCompilerOptions(); const sourceCode = context.getSourceCode(); - const isStrictNullChecks = tsutils.isStrictCompilerOptionEnabled( + const isStrictNullChecks = isStrictCompilerOptionEnabled( compilerOptions, 'strictNullChecks', ); @@ -260,7 +265,7 @@ export default util.createRule({ function checkNode(node: TSESTree.Node): void { const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node); const type = util.getConstrainedTypeAtLocation(typeChecker, tsNode); - const types = inspectVariantTypes(tsutils.unionTypeParts(type)); + const types = inspectVariantTypes(unionTypeParts(type)); const is = (...wantedTypes: readonly VariantType[]): boolean => types.size === wantedTypes.length && @@ -764,7 +769,7 @@ export default util.createRule({ if ( types.some(type => - tsutils.isTypeFlagSet( + isTypeFlagSet( type, ts.TypeFlags.Null | ts.TypeFlags.Undefined | ts.TypeFlags.VoidLike, ), @@ -773,15 +778,15 @@ export default util.createRule({ variantTypes.add('nullish'); } const booleans = types.filter(type => - tsutils.isTypeFlagSet(type, ts.TypeFlags.BooleanLike), + isTypeFlagSet(type, ts.TypeFlags.BooleanLike), ); // If incoming type is either "true" or "false", there will be one type // object with intrinsicName set accordingly // If incoming type is boolean, there will be two type objects with - // intrinsicName set "true" and "false" each because of tsutils.unionTypeParts() + // intrinsicName set "true" and "false" each because of unionTypeParts() if (booleans.length === 1) { - tsutils.isBooleanLiteralType(booleans[0], true) + isBooleanLiteralType(booleans[0], true) ? variantTypes.add('truthy boolean') : variantTypes.add('boolean'); } else if (booleans.length === 2) { @@ -789,7 +794,7 @@ export default util.createRule({ } const strings = types.filter(type => - tsutils.isTypeFlagSet(type, ts.TypeFlags.StringLike), + isTypeFlagSet(type, ts.TypeFlags.StringLike), ); if (strings.length) { @@ -801,10 +806,7 @@ export default util.createRule({ } const numbers = types.filter(type => - tsutils.isTypeFlagSet( - type, - ts.TypeFlags.NumberLike | ts.TypeFlags.BigIntLike, - ), + isTypeFlagSet(type, ts.TypeFlags.NumberLike | ts.TypeFlags.BigIntLike), ); if (numbers.length) { if (numbers.some(type => type.isNumberLiteral() && type.value !== 0)) { @@ -817,7 +819,7 @@ export default util.createRule({ if ( types.some( type => - !tsutils.isTypeFlagSet( + !isTypeFlagSet( type, ts.TypeFlags.Null | ts.TypeFlags.Undefined | @@ -849,7 +851,7 @@ export default util.createRule({ variantTypes.add('any'); } - if (types.some(type => tsutils.isTypeFlagSet(type, ts.TypeFlags.Never))) { + if (types.some(type => isTypeFlagSet(type, ts.TypeFlags.Never))) { variantTypes.add('never'); } diff --git a/packages/eslint-plugin/src/rules/switch-exhaustiveness-check.ts b/packages/eslint-plugin/src/rules/switch-exhaustiveness-check.ts index cff8960dac84..00c2c476b7dd 100644 --- a/packages/eslint-plugin/src/rules/switch-exhaustiveness-check.ts +++ b/packages/eslint-plugin/src/rules/switch-exhaustiveness-check.ts @@ -1,5 +1,5 @@ +import { isTypeFlagSet, unionTypeParts } from '@typescript-eslint/type-utils'; import type { TSESLint, TSESTree } from '@typescript-eslint/utils'; -import { isTypeFlagSet, unionTypeParts } from 'tsutils'; import * as ts from 'typescript'; import { diff --git a/packages/type-utils/src/containsAllTypesByName.ts b/packages/type-utils/src/containsAllTypesByName.ts index 07aa20dac048..c6d5abf7e17a 100644 --- a/packages/type-utils/src/containsAllTypesByName.ts +++ b/packages/type-utils/src/containsAllTypesByName.ts @@ -1,7 +1,8 @@ -import { isTypeReference, isUnionOrIntersectionType } from 'tsutils'; import * as ts from 'typescript'; -import { isTypeFlagSet } from './typeFlagUtils'; +import { isTypeFlagSet } from './isTypeFlagSet'; +import { isTypeReference } from './isTypeReference'; +import { isUnionOrIntersectionType } from './isUnionOrIntersectionType'; /** * @param type Type being checked by name. diff --git a/packages/type-utils/src/getCallSignaturesOfType.ts b/packages/type-utils/src/getCallSignaturesOfType.ts new file mode 100644 index 000000000000..784ceef9f214 --- /dev/null +++ b/packages/type-utils/src/getCallSignaturesOfType.ts @@ -0,0 +1,34 @@ +import type { Signature, Type } from 'typescript'; + +import { isIntersectionType } from './isIntersectionType'; +import { isUnionType } from './isUnionType'; + +export function getCallSignaturesOfType(type: Type): ReadonlyArray { + if (isUnionType(type)) { + const signatures = []; + for (const t of type.types) { + signatures.push(...getCallSignaturesOfType(t)); + } + + return signatures; + } + + if (isIntersectionType(type)) { + let signatures: ReadonlyArray | undefined; + + for (const t of type.types) { + const sig = getCallSignaturesOfType(t); + if (sig.length !== 0) { + if (signatures !== undefined) { + return []; // if more than one type of the intersection has call signatures, none of them is useful for inference + } + + signatures = sig; + } + } + + return signatures === undefined ? [] : signatures; + } + + return type.getCallSignatures(); +} diff --git a/packages/type-utils/src/getContextualType.ts b/packages/type-utils/src/getContextualType.ts index 075156282658..eb622cc7cb00 100644 --- a/packages/type-utils/src/getContextualType.ts +++ b/packages/type-utils/src/getContextualType.ts @@ -1,15 +1,16 @@ +import { isIdentifier } from 'tsutils'; +import * as ts from 'typescript'; + import { isBinaryExpression, isCallExpression, - isIdentifier, isJsxExpression, isNewExpression, isParameterDeclaration, isPropertyAssignment, isPropertyDeclaration, isVariableDeclaration, -} from 'tsutils'; -import * as ts from 'typescript'; +} from './'; /** * Returns the contextual type of a given node. diff --git a/packages/type-utils/src/getPropertyOfType.ts b/packages/type-utils/src/getPropertyOfType.ts new file mode 100644 index 000000000000..6fae0f50e38e --- /dev/null +++ b/packages/type-utils/src/getPropertyOfType.ts @@ -0,0 +1,12 @@ +import type { __String, Symbol as SymbolType, Type } from 'typescript'; + +export function getPropertyOfType( + type: Type, + name: __String, +): SymbolType | undefined { + if (!(name).startsWith('__')) { + return type.getProperty(name); + } + + return type.getProperties().find(s => s.escapedName === name); +} diff --git a/packages/type-utils/src/getTypeFlags.ts b/packages/type-utils/src/getTypeFlags.ts new file mode 100644 index 000000000000..32279b57e0ef --- /dev/null +++ b/packages/type-utils/src/getTypeFlags.ts @@ -0,0 +1,14 @@ +import type { Type, TypeFlags } from 'typescript'; + +import { unionTypeParts } from './unionTypeParts'; + +/** + * Gets all of the type flags in a type, iterating through unions automatically + */ +export function getTypeFlags(type: Type): TypeFlags { + let flags: TypeFlags = 0; + for (const t of unionTypeParts(type)) { + flags |= t.flags; + } + return flags; +} diff --git a/packages/type-utils/src/getWellKnownSymbolPropertyOfType.ts b/packages/type-utils/src/getWellKnownSymbolPropertyOfType.ts new file mode 100644 index 000000000000..c65b7fc8f13d --- /dev/null +++ b/packages/type-utils/src/getWellKnownSymbolPropertyOfType.ts @@ -0,0 +1,78 @@ +import type { + __String, + ComputedPropertyName, + NamedDeclaration, + Symbol as SymbolType, + Type, + TypeChecker, +} from 'typescript'; + +import { isUniqueESSymbolType } from './isUniqueESSymbolType'; + +export function getWellKnownSymbolPropertyOfType( + type: Type, + wellKnownSymbolName: string, + checker: TypeChecker, +): SymbolType | undefined { + const prefix = '__@' + wellKnownSymbolName; + for (const prop of type.getProperties()) { + if (!prop.name.startsWith(prefix)) { + continue; + } + + const globalSymbol = checker + .getApparentType( + checker.getTypeAtLocation( + ((prop.valueDeclaration).name) + .expression, + ), + ) + .getSymbol(); + + if ( + prop.escapedName === + getPropertyNameOfWellKnownSymbol( + checker, + globalSymbol, + wellKnownSymbolName, + ) + ) { + return prop; + } + } + + return; +} + +function getPropertyNameOfWellKnownSymbol( + checker: TypeChecker, + symbolConstructor: SymbolType | undefined, + symbolName: string, +): __String { + const knownSymbol = + symbolConstructor && + checker + .getTypeOfSymbolAtLocation( + symbolConstructor, + // TODO: Investigate type error + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-expect-error + symbolConstructor.valueDeclaration, + ) + .getProperty(symbolName); + const knownSymbolType = + knownSymbol && + checker.getTypeOfSymbolAtLocation( + knownSymbol, + // TODO: Investigate type error + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-expect-error + knownSymbol.valueDeclaration, + ); + + if (knownSymbolType && isUniqueESSymbolType(knownSymbolType)) { + return knownSymbolType.escapedName; + } + + return <__String>('__@' + symbolName); +} diff --git a/packages/type-utils/src/index.ts b/packages/type-utils/src/index.ts index dde032e1770c..2ab09b258323 100644 --- a/packages/type-utils/src/index.ts +++ b/packages/type-utils/src/index.ts @@ -1,17 +1,67 @@ export * from './containsAllTypesByName'; +export * from './getCallSignaturesOfType'; export * from './getConstrainedTypeAtLocation'; export * from './getContextualType'; export * from './getDeclaration'; +export * from './getPropertyOfType'; export * from './getSourceFileOfNode'; export * from './getTokenAtPosition'; export * from './getTypeArguments'; +export * from './getTypeFlags'; export * from './getTypeName'; +export * from './getWellKnownSymbolPropertyOfType'; +export * from './isAssignmentKind'; +export * from './isBinaryExpression'; +export * from './isBindableObjectDefinePropertyCall'; +export * from './isBooleanLiteralType'; +export * from './isCallExpression'; +export * from './isCompilerOptionEnabled'; +export * from './isConditionalType'; +export * from './isConstAssertion'; +export * from './isEntityNameExpression'; +export * from './isEnumMember'; +export * from './isExpression'; +export * from './isFalsyType'; +export * from './isFlagSet'; +export * from './isFunctionScopeBoundary'; +export * from './isInConstContext'; +export * from './isIntersectionType'; +export * from './isJsxExpression'; +export * from './isLiteralType'; +export * from './isModifierFlagSet'; +export * from './isNewExpression'; +export * from './isNodeFlagSet'; +export * from './isNumericOrStringLikeLiteral'; +export * from './isNumericPropertyName'; +export * from './isObjectFlagSet'; +export * from './isObjectType'; +export * from './isParameterDeclaration'; +export * from './isPropertyAccessExpression'; +export * from './isPropertyAssignment'; +export * from './isPropertyDeclaration'; +export * from './isPropertyReadonlyInType'; +export * from './isReadonlyAssignmentDeclaration'; +export * from './isShorthandPropertyAssignment'; +export * from './isStrictCompilerOptionEnabled'; +export * from './isSymbolFlagSet'; +export * from './isThenableType'; +export * from './isTupleType'; +export * from './isTupleTypeReference'; +export * from './isTypeFlagSet'; export * from './isTypeReadonly'; +export * from './isTypeReference'; +export * from './isTypeReferenceNode'; +export * from './isUnionType'; +export * from './isUniqueESSymbolType'; export * from './isUnsafeAssignment'; +export * from './isValidPropertyAccess'; +export * from './isVariableDeclaration'; export * from './predicates'; export * from './propertyTypes'; export * from './requiresQuoting'; -export * from './typeFlagUtils'; +export * from './someTypePart'; +export * from './symbolHasReadOnlyDeclaration'; +export * from './unionTypeParts'; export { getDecorators, getModifiers, diff --git a/packages/type-utils/src/isAssignmentKind.ts b/packages/type-utils/src/isAssignmentKind.ts new file mode 100644 index 000000000000..db60bdab98fd --- /dev/null +++ b/packages/type-utils/src/isAssignmentKind.ts @@ -0,0 +1,7 @@ +import { SyntaxKind } from 'typescript'; + +export function isAssignmentKind(kind: SyntaxKind): boolean { + return ( + kind >= SyntaxKind.FirstAssignment && kind <= SyntaxKind.LastAssignment + ); +} diff --git a/packages/type-utils/src/isBinaryExpression.ts b/packages/type-utils/src/isBinaryExpression.ts new file mode 100644 index 000000000000..e2ef62d4bf14 --- /dev/null +++ b/packages/type-utils/src/isBinaryExpression.ts @@ -0,0 +1,6 @@ +import type { BinaryExpression, Node } from 'typescript'; +import { SyntaxKind } from 'typescript'; + +export function isBinaryExpression(node: Node): node is BinaryExpression { + return node.kind === SyntaxKind.BinaryExpression; +} diff --git a/packages/type-utils/src/isBindableObjectDefinePropertyCall.ts b/packages/type-utils/src/isBindableObjectDefinePropertyCall.ts new file mode 100644 index 000000000000..cc20d170a436 --- /dev/null +++ b/packages/type-utils/src/isBindableObjectDefinePropertyCall.ts @@ -0,0 +1,21 @@ +// TODO: Remove tsutils function +import { isIdentifier } from 'tsutils'; +import type { CallExpression } from 'typescript'; + +import { isEntityNameExpression } from './isEntityNameExpression'; +import { isNumericOrStringLikeLiteral } from './isNumericOrStringLikeLiteral'; +import { isPropertyAccessExpression } from './isPropertyAccessExpression'; + +export function isBindableObjectDefinePropertyCall( + node: CallExpression, +): boolean { + return ( + node.arguments.length === 3 && + isEntityNameExpression(node.arguments[0]) && + isNumericOrStringLikeLiteral(node.arguments[1]) && + isPropertyAccessExpression(node.expression) && + node.expression.name.escapedText === 'defineProperty' && + isIdentifier(node.expression.expression) && + node.expression.expression.escapedText === 'Object' + ); +} diff --git a/packages/type-utils/src/isBooleanLiteralType.ts b/packages/type-utils/src/isBooleanLiteralType.ts new file mode 100644 index 000000000000..b9998bab62fb --- /dev/null +++ b/packages/type-utils/src/isBooleanLiteralType.ts @@ -0,0 +1,12 @@ +import type { Type } from 'typescript'; +import { TypeFlags } from 'typescript'; + +import { isTypeFlagSet } from './typeFlagUtils'; + +export function isBooleanLiteralType(type: Type, literal: boolean): boolean { + return ( + isTypeFlagSet(type, TypeFlags.BooleanLiteral) && + (<{ intrinsicName: string }>(>(type))) + .intrinsicName === (literal ? 'true' : 'false') + ); +} diff --git a/packages/type-utils/src/isCallExpression.ts b/packages/type-utils/src/isCallExpression.ts new file mode 100644 index 000000000000..cbad9b3cf54e --- /dev/null +++ b/packages/type-utils/src/isCallExpression.ts @@ -0,0 +1,6 @@ +import type { CallExpression, Node } from 'typescript'; +import { SyntaxKind } from 'typescript'; + +export function isCallExpression(node: Node): node is CallExpression { + return node.kind === SyntaxKind.CallExpression; +} diff --git a/packages/type-utils/src/isCompilerOptionEnabled.ts b/packages/type-utils/src/isCompilerOptionEnabled.ts new file mode 100644 index 000000000000..f1908ff0d2b0 --- /dev/null +++ b/packages/type-utils/src/isCompilerOptionEnabled.ts @@ -0,0 +1,71 @@ +import * as ts from 'typescript'; + +import type { StrictCompilerOption } from './isStrictCompilerOptionEnabled'; +import { isStrictCompilerOptionEnabled } from './isStrictCompilerOptionEnabled'; + +/** + * Checks if a given compiler option is enabled. + * It handles dependencies of options, e.g. `declaration` is implicitly enabled by `composite` or `strictNullChecks` is enabled by `strict`. + * However, it does not check dependencies that are already checked and reported as errors, e.g. `checkJs` without `allowJs`. + * This function only handles boolean flags. + */ +export function isCompilerOptionEnabled( + options: ts.CompilerOptions, + option: keyof ts.CompilerOptions, +): boolean { + switch (option) { + case 'stripInternal': + case 'declarationMap': + case 'emitDeclarationOnly': + return ( + options[option] === true && + isCompilerOptionEnabled(options, 'declaration') + ); + case 'declaration': + return ( + options.declaration ?? isCompilerOptionEnabled(options, 'composite') + ); + case 'incremental': + return options.incremental === undefined + ? isCompilerOptionEnabled(options, 'composite') + : options.incremental; + case 'skipDefaultLibCheck': + return ( + options.skipDefaultLibCheck ?? + isCompilerOptionEnabled(options, 'skipLibCheck') + ); + case 'suppressImplicitAnyIndexErrors': + return ( + options.suppressImplicitAnyIndexErrors === true && + isCompilerOptionEnabled(options, 'noImplicitAny') + ); + case 'allowSyntheticDefaultImports': + return options.allowSyntheticDefaultImports !== undefined + ? options.allowSyntheticDefaultImports + : isCompilerOptionEnabled(options, 'esModuleInterop') || + options.module === ts.ModuleKind.System; + case 'noUncheckedIndexedAccess': + return ( + options.noUncheckedIndexedAccess === true && + isCompilerOptionEnabled(options, 'strictNullChecks') + ); + case 'allowJs': + return options.allowJs === undefined + ? isCompilerOptionEnabled(options, 'checkJs') + : options.allowJs; + case 'noImplicitAny': + case 'noImplicitThis': + case 'strictNullChecks': + case 'strictFunctionTypes': + case 'strictPropertyInitialization': + case 'alwaysStrict': + case 'strictBindCallApply': + type AssertEqual = U; // make sure all strict options are handled here + return isStrictCompilerOptionEnabled( + options, + >option, + ); + } + + return options[option] === true; +} diff --git a/packages/type-utils/src/isConditionalType.ts b/packages/type-utils/src/isConditionalType.ts new file mode 100644 index 000000000000..bc9aaab4cb30 --- /dev/null +++ b/packages/type-utils/src/isConditionalType.ts @@ -0,0 +1,6 @@ +import type { ConditionalType, Type } from 'typescript'; +import { TypeFlags } from 'typescript'; + +export function isConditionalType(type: Type): type is ConditionalType { + return (type.flags & TypeFlags.Conditional) !== 0; +} diff --git a/packages/type-utils/src/isConstAssertion.ts b/packages/type-utils/src/isConstAssertion.ts new file mode 100644 index 000000000000..4fb0e05b166e --- /dev/null +++ b/packages/type-utils/src/isConstAssertion.ts @@ -0,0 +1,12 @@ +import type { AssertionExpression } from 'typescript'; +import { SyntaxKind } from 'typescript'; + +import { isTypeReferenceNode } from './isTypeReferenceNode'; + +export function isConstAssertion(node: AssertionExpression): boolean { + return ( + isTypeReferenceNode(node.type) && + node.type.typeName.kind === SyntaxKind.Identifier && + node.type.typeName.escapedText === 'const' + ); +} diff --git a/packages/type-utils/src/isEntityNameExpression.ts b/packages/type-utils/src/isEntityNameExpression.ts new file mode 100644 index 000000000000..4d6096556c4f --- /dev/null +++ b/packages/type-utils/src/isEntityNameExpression.ts @@ -0,0 +1,14 @@ +import type { EntityNameExpression, Node } from 'typescript'; +import { SyntaxKind } from 'typescript'; + +import { isPropertyAccessExpression } from './isPropertyAccessExpression'; + +export function isEntityNameExpression( + node: Node, +): node is EntityNameExpression { + return ( + node.kind === SyntaxKind.Identifier || + (isPropertyAccessExpression(node) && + isEntityNameExpression(node.expression)) + ); +} diff --git a/packages/type-utils/src/isEnumMember.ts b/packages/type-utils/src/isEnumMember.ts new file mode 100644 index 000000000000..ad6186f6b0e9 --- /dev/null +++ b/packages/type-utils/src/isEnumMember.ts @@ -0,0 +1,6 @@ +import type { EnumMember, Node } from 'typescript'; +import { SyntaxKind } from 'typescript'; + +export function isEnumMember(node: Node): node is EnumMember { + return node.kind === SyntaxKind.EnumMember; +} diff --git a/packages/type-utils/src/isExpression.ts b/packages/type-utils/src/isExpression.ts new file mode 100644 index 000000000000..afde0c181880 --- /dev/null +++ b/packages/type-utils/src/isExpression.ts @@ -0,0 +1,54 @@ +import type { Expression, Node } from 'typescript'; +import { SyntaxKind } from 'typescript'; + +export function isExpression(node: Node): node is Expression { + switch (node.kind) { + case SyntaxKind.ArrayLiteralExpression: + case SyntaxKind.ArrowFunction: + case SyntaxKind.AsExpression: + case SyntaxKind.AwaitExpression: + case SyntaxKind.BinaryExpression: + case SyntaxKind.CallExpression: + case SyntaxKind.ClassExpression: + case SyntaxKind.CommaListExpression: + case SyntaxKind.ConditionalExpression: + case SyntaxKind.DeleteExpression: + case SyntaxKind.ElementAccessExpression: + case SyntaxKind.FalseKeyword: + case SyntaxKind.FunctionExpression: + case SyntaxKind.Identifier: + case SyntaxKind.JsxElement: + case SyntaxKind.JsxFragment: + case SyntaxKind.JsxExpression: + case SyntaxKind.JsxOpeningElement: + case SyntaxKind.JsxOpeningFragment: + case SyntaxKind.JsxSelfClosingElement: + case SyntaxKind.MetaProperty: + case SyntaxKind.NewExpression: + case SyntaxKind.NonNullExpression: + case SyntaxKind.NoSubstitutionTemplateLiteral: + case SyntaxKind.NullKeyword: + case SyntaxKind.NumericLiteral: + case SyntaxKind.ObjectLiteralExpression: + case SyntaxKind.OmittedExpression: + case SyntaxKind.ParenthesizedExpression: + case SyntaxKind.PostfixUnaryExpression: + case SyntaxKind.PrefixUnaryExpression: + case SyntaxKind.PropertyAccessExpression: + case SyntaxKind.RegularExpressionLiteral: + case SyntaxKind.SpreadElement: + case SyntaxKind.StringLiteral: + case SyntaxKind.SuperKeyword: + case SyntaxKind.TaggedTemplateExpression: + case SyntaxKind.TemplateExpression: + case SyntaxKind.ThisKeyword: + case SyntaxKind.TrueKeyword: + case SyntaxKind.TypeAssertionExpression: + case SyntaxKind.TypeOfExpression: + case SyntaxKind.VoidExpression: + case SyntaxKind.YieldExpression: + return true; + default: + return false; + } +} diff --git a/packages/type-utils/src/isFalsyType.ts b/packages/type-utils/src/isFalsyType.ts new file mode 100644 index 000000000000..47c58471555a --- /dev/null +++ b/packages/type-utils/src/isFalsyType.ts @@ -0,0 +1,18 @@ +import type { Type } from 'typescript'; +import { TypeFlags } from 'typescript'; + +import { isBooleanLiteralType } from './isBooleanLiteralType'; +import { isLiteralType } from './isLiteralType'; + +/** Determine if a type is definitely falsy. This function doesn't unwrap union types. */ +export function isFalsyType(type: Type): boolean { + if (type.flags & (TypeFlags.Undefined | TypeFlags.Null | TypeFlags.Void)) { + return true; + } + + if (isLiteralType(type)) { + return !type.value; + } + + return isBooleanLiteralType(type, false); +} diff --git a/packages/type-utils/src/isFlagSet.ts b/packages/type-utils/src/isFlagSet.ts new file mode 100644 index 000000000000..cd5f0d75ebeb --- /dev/null +++ b/packages/type-utils/src/isFlagSet.ts @@ -0,0 +1,3 @@ +export function isFlagSet(obj: { flags: number }, flag: number): boolean { + return (obj.flags & flag) !== 0; +} diff --git a/packages/type-utils/src/isFunctionScopeBoundary.ts b/packages/type-utils/src/isFunctionScopeBoundary.ts new file mode 100644 index 000000000000..809677ccde43 --- /dev/null +++ b/packages/type-utils/src/isFunctionScopeBoundary.ts @@ -0,0 +1,39 @@ +import type { Node, SourceFile } from 'typescript'; +import { isExternalModule, SyntaxKind } from 'typescript'; + +export const enum ScopeBoundary { + None = 0, + Function = 1, + Block = 2, + Type = 4, + ConditionalType = 8, +} + +export function isFunctionScopeBoundary(node: Node): ScopeBoundary { + switch (node.kind) { + case SyntaxKind.FunctionExpression: + case SyntaxKind.ArrowFunction: + case SyntaxKind.Constructor: + case SyntaxKind.ModuleDeclaration: + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ClassExpression: + case SyntaxKind.EnumDeclaration: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + case SyntaxKind.MethodSignature: + case SyntaxKind.CallSignature: + case SyntaxKind.ConstructSignature: + case SyntaxKind.ConstructorType: + case SyntaxKind.FunctionType: + return ScopeBoundary.Function; + case SyntaxKind.SourceFile: + // if SourceFile is no module, it contributes to the global scope and is therefore no scope boundary + return isExternalModule(node) + ? ScopeBoundary.Function + : ScopeBoundary.None; + default: + return ScopeBoundary.None; + } +} diff --git a/packages/type-utils/src/isInConstContext.ts b/packages/type-utils/src/isInConstContext.ts new file mode 100644 index 000000000000..8e36bdc5e4da --- /dev/null +++ b/packages/type-utils/src/isInConstContext.ts @@ -0,0 +1,56 @@ +import type { + AssertionExpression, + Expression, + Node, + PrefixUnaryExpression, + PropertyAssignment, +} from 'typescript'; +import { SyntaxKind } from 'typescript'; + +import { isConstAssertion } from './isConstAssertion'; + +export function isInConstContext(node: Expression): boolean { + let current: Node = node; + + // eslint-disable-next-line no-constant-condition + while (true) { + const parent = current.parent; + + outer: switch (parent.kind) { + case SyntaxKind.TypeAssertionExpression: + case SyntaxKind.AsExpression: + return isConstAssertion(parent); + case SyntaxKind.PrefixUnaryExpression: + if (current.kind !== SyntaxKind.NumericLiteral) { + return false; + } + + switch ((parent).operator) { + case SyntaxKind.PlusToken: + case SyntaxKind.MinusToken: + current = parent; + break outer; + default: + return false; + } + case SyntaxKind.PropertyAssignment: + if ((parent).initializer !== current) { + return false; + } + + current = parent.parent!; + break; + case SyntaxKind.ShorthandPropertyAssignment: + current = parent.parent!; + break; + case SyntaxKind.ParenthesizedExpression: + case SyntaxKind.ArrayLiteralExpression: + case SyntaxKind.ObjectLiteralExpression: + case SyntaxKind.TemplateExpression: + current = parent; + break; + default: + return false; + } + } +} diff --git a/packages/type-utils/src/isIntersectionType.ts b/packages/type-utils/src/isIntersectionType.ts new file mode 100644 index 000000000000..3e8abb11be13 --- /dev/null +++ b/packages/type-utils/src/isIntersectionType.ts @@ -0,0 +1,6 @@ +import type { IntersectionType, Type } from 'typescript'; +import { TypeFlags } from 'typescript'; + +export function isIntersectionType(type: Type): type is IntersectionType { + return (type.flags & TypeFlags.Intersection) !== 0; +} diff --git a/packages/type-utils/src/isJsxExpression.ts b/packages/type-utils/src/isJsxExpression.ts new file mode 100644 index 000000000000..a7caf6758f93 --- /dev/null +++ b/packages/type-utils/src/isJsxExpression.ts @@ -0,0 +1,6 @@ +import type { JsxExpression, Node } from 'typescript'; +import { SyntaxKind } from 'typescript'; + +export function isJsxExpression(node: Node): node is JsxExpression { + return node.kind === SyntaxKind.JsxExpression; +} diff --git a/packages/type-utils/src/isLiteralType.ts b/packages/type-utils/src/isLiteralType.ts new file mode 100644 index 000000000000..4d0f341f3189 --- /dev/null +++ b/packages/type-utils/src/isLiteralType.ts @@ -0,0 +1,10 @@ +import type { LiteralType, Type } from 'typescript'; +import { TypeFlags } from 'typescript'; + +export function isLiteralType(type: Type): type is LiteralType { + return ( + (type.flags & + (TypeFlags.StringOrNumberLiteral | TypeFlags.BigIntLiteral)) !== + 0 + ); +} diff --git a/packages/type-utils/src/isModifierFlagSet.ts b/packages/type-utils/src/isModifierFlagSet.ts new file mode 100644 index 000000000000..e16f626bd73d --- /dev/null +++ b/packages/type-utils/src/isModifierFlagSet.ts @@ -0,0 +1,6 @@ +import type { Declaration, ModifierFlags, Node } from 'typescript'; +import { getCombinedModifierFlags } from 'typescript'; + +export function isModifierFlagSet(node: Node, flag: ModifierFlags): boolean { + return (getCombinedModifierFlags(node) & flag) !== 0; +} diff --git a/packages/type-utils/src/isNewExpression.ts b/packages/type-utils/src/isNewExpression.ts new file mode 100644 index 000000000000..ca3bbb00e0c2 --- /dev/null +++ b/packages/type-utils/src/isNewExpression.ts @@ -0,0 +1,6 @@ +import type { NewExpression, Node } from 'typescript'; +import { SyntaxKind } from 'typescript'; + +export function isNewExpression(node: Node): node is NewExpression { + return node.kind === SyntaxKind.NewExpression; +} diff --git a/packages/type-utils/src/isNodeFlagSet.ts b/packages/type-utils/src/isNodeFlagSet.ts new file mode 100644 index 000000000000..640b7fddef72 --- /dev/null +++ b/packages/type-utils/src/isNodeFlagSet.ts @@ -0,0 +1,6 @@ +import type { Node, NodeFlags } from 'typescript'; + +import { isFlagSet } from './isFlagSet'; + +export const isNodeFlagSet: (node: Node, flag: NodeFlags) => boolean = + isFlagSet; diff --git a/packages/type-utils/src/isNumericOrStringLikeLiteral.ts b/packages/type-utils/src/isNumericOrStringLikeLiteral.ts new file mode 100644 index 000000000000..5484291ebeca --- /dev/null +++ b/packages/type-utils/src/isNumericOrStringLikeLiteral.ts @@ -0,0 +1,20 @@ +import type { + Node, + NoSubstitutionTemplateLiteral, + NumericLiteral, + StringLiteral, +} from 'typescript'; +import { SyntaxKind } from 'typescript'; + +export function isNumericOrStringLikeLiteral( + node: Node, +): node is NumericLiteral | StringLiteral | NoSubstitutionTemplateLiteral { + switch (node.kind) { + case SyntaxKind.StringLiteral: + case SyntaxKind.NumericLiteral: + case SyntaxKind.NoSubstitutionTemplateLiteral: + return true; + default: + return false; + } +} diff --git a/packages/type-utils/src/isNumericPropertyName.ts b/packages/type-utils/src/isNumericPropertyName.ts new file mode 100644 index 000000000000..9710187d6349 --- /dev/null +++ b/packages/type-utils/src/isNumericPropertyName.ts @@ -0,0 +1,5 @@ +import type { __String } from 'typescript'; + +export function isNumericPropertyName(name: string | __String): boolean { + return String(+name) === name; +} diff --git a/packages/type-utils/src/isObjectFlagSet.ts b/packages/type-utils/src/isObjectFlagSet.ts new file mode 100644 index 000000000000..b43cc79eeb34 --- /dev/null +++ b/packages/type-utils/src/isObjectFlagSet.ts @@ -0,0 +1,8 @@ +import type { ObjectFlags, ObjectType } from 'typescript'; + +export function isObjectFlagSet( + objectType: ObjectType, + flag: ObjectFlags, +): boolean { + return (objectType.objectFlags & flag) !== 0; +} diff --git a/packages/type-utils/src/isObjectType.ts b/packages/type-utils/src/isObjectType.ts new file mode 100644 index 000000000000..3fa9778ea16c --- /dev/null +++ b/packages/type-utils/src/isObjectType.ts @@ -0,0 +1,6 @@ +import type { ObjectType, Type } from 'typescript'; +import { TypeFlags } from 'typescript'; + +export function isObjectType(type: Type): type is ObjectType { + return (type.flags & TypeFlags.Object) !== 0; +} diff --git a/packages/type-utils/src/isParameterDeclaration.ts b/packages/type-utils/src/isParameterDeclaration.ts new file mode 100644 index 000000000000..3f8838aaf0a2 --- /dev/null +++ b/packages/type-utils/src/isParameterDeclaration.ts @@ -0,0 +1,8 @@ +import type { Node, ParameterDeclaration } from 'typescript'; +import { SyntaxKind } from 'typescript'; + +export function isParameterDeclaration( + node: Node, +): node is ParameterDeclaration { + return node.kind === SyntaxKind.Parameter; +} diff --git a/packages/type-utils/src/isPropertyAccessExpression.ts b/packages/type-utils/src/isPropertyAccessExpression.ts new file mode 100644 index 000000000000..ed8bf1ead7f5 --- /dev/null +++ b/packages/type-utils/src/isPropertyAccessExpression.ts @@ -0,0 +1,8 @@ +import type { Node, PropertyAccessExpression } from 'typescript'; +import { SyntaxKind } from 'typescript'; + +export function isPropertyAccessExpression( + node: Node, +): node is PropertyAccessExpression { + return node.kind === SyntaxKind.PropertyAccessExpression; +} diff --git a/packages/type-utils/src/isPropertyAssignment.ts b/packages/type-utils/src/isPropertyAssignment.ts new file mode 100644 index 000000000000..0ea45cff7546 --- /dev/null +++ b/packages/type-utils/src/isPropertyAssignment.ts @@ -0,0 +1,6 @@ +import type { Node, PropertyAssignment } from 'typescript'; +import { SyntaxKind } from 'typescript'; + +export function isPropertyAssignment(node: Node): node is PropertyAssignment { + return node.kind === SyntaxKind.PropertyAssignment; +} diff --git a/packages/type-utils/src/isPropertyDeclaration.ts b/packages/type-utils/src/isPropertyDeclaration.ts new file mode 100644 index 000000000000..cd55be50e9a3 --- /dev/null +++ b/packages/type-utils/src/isPropertyDeclaration.ts @@ -0,0 +1,6 @@ +import type { Node, PropertyDeclaration } from 'typescript'; +import { SyntaxKind } from 'typescript'; + +export function isPropertyDeclaration(node: Node): node is PropertyDeclaration { + return node.kind === SyntaxKind.PropertyDeclaration; +} diff --git a/packages/type-utils/src/isPropertyReadonlyInType.ts b/packages/type-utils/src/isPropertyReadonlyInType.ts new file mode 100644 index 000000000000..a7978f92520d --- /dev/null +++ b/packages/type-utils/src/isPropertyReadonlyInType.ts @@ -0,0 +1,110 @@ +import type { __String, MappedTypeNode, Type, TypeChecker } from 'typescript'; +import { IndexKind, ObjectFlags, SymbolFlags, SyntaxKind } from 'typescript'; + +import { getPropertyOfType } from './getPropertyOfType'; +import { isIntersectionType } from './isIntersectionType'; +import { isNumericPropertyName } from './isNumericPropertyName'; +import { isObjectFlagSet } from './isObjectFlagSet'; +import { isObjectType } from './isObjectType'; +import { isSymbolFlagSet } from './isSymbolFlagSet'; +import { isTupleTypeReference } from './isTupleTypeReference'; +import { someTypePart } from './someTypePart'; +import { symbolHasReadonlyDeclaration } from './symbolHasReadOnlyDeclaration'; +import { unionTypeParts } from './unionTypeParts'; + +export function isPropertyReadonlyInType( + type: Type, + name: __String, + checker: TypeChecker, +): boolean { + let seenProperty = false; + let seenReadonlySignature = false; + + for (const t of unionTypeParts(type)) { + if (getPropertyOfType(t, name) === undefined) { + // property is not present in this part of the union -> check for readonly index signature + const index = + (isNumericPropertyName(name) + ? checker.getIndexInfoOfType(t, IndexKind.Number) + : undefined) ?? checker.getIndexInfoOfType(t, IndexKind.String); + + if (index?.isReadonly) { + if (seenProperty) { + return true; + } + + seenReadonlySignature = true; + } + } else if ( + seenReadonlySignature || + isReadonlyPropertyIntersection(t, name, checker) + ) { + return true; + } else { + seenProperty = true; + } + } + + return false; +} + +function isReadonlyPropertyIntersection( + type: Type, + name: __String, + checker: TypeChecker, +): boolean { + return someTypePart(type, isIntersectionType, t => { + const prop = getPropertyOfType(t, name); + + if (prop === undefined) { + return false; + } + + if (prop.flags & SymbolFlags.Transient) { + if (/^(?:[1-9]\d*|0)$/.test(name) && isTupleTypeReference(t)) { + return t.target.readonly; + } + + switch (isReadonlyPropertyFromMappedType(t, name, checker)) { + case true: + return true; + case false: + return false; + default: + // `undefined` falls through + } + } + + return ( + // members of namespace import + isSymbolFlagSet(prop, SymbolFlags.ValueModule) || + // we unwrapped every mapped type, now we can check the actual declarations + symbolHasReadonlyDeclaration(prop, checker) + ); + }); +} + +function isReadonlyPropertyFromMappedType( + type: Type, + name: __String, + checker: TypeChecker, +): boolean | undefined { + if (!isObjectType(type) || !isObjectFlagSet(type, ObjectFlags.Mapped)) { + return; + } + + const declaration = type.symbol.getDeclarations()![0]; + // well-known symbols are not affected by mapped types + if ( + declaration.readonlyToken !== undefined && + !/^__@[^@]+$/.test(name) + ) { + return declaration.readonlyToken.kind !== SyntaxKind.MinusToken; + } + + return isPropertyReadonlyInType( + (<{ modifiersType: Type }>(type)).modifiersType, + name, + checker, + ); +} diff --git a/packages/type-utils/src/isReadonlyAssignmentDeclaration.ts b/packages/type-utils/src/isReadonlyAssignmentDeclaration.ts new file mode 100644 index 000000000000..8a61a1bd6ce0 --- /dev/null +++ b/packages/type-utils/src/isReadonlyAssignmentDeclaration.ts @@ -0,0 +1,34 @@ +import type { CallExpression, TypeChecker } from 'typescript'; + +import { isBindableObjectDefinePropertyCall } from './isBindableObjectDefinePropertyCall'; +import { isBooleanLiteralType } from './isBooleanLiteralType'; +import { isPropertyAssignment } from './isPropertyAssignment'; + +export function isReadonlyAssignmentDeclaration( + node: CallExpression, + checker: TypeChecker, +): boolean { + if (!isBindableObjectDefinePropertyCall(node)) { + return false; + } + + const descriptorType = checker.getTypeAtLocation(node.arguments[2]); + + if (descriptorType.getProperty('value') === undefined) { + return descriptorType.getProperty('set') === undefined; + } + + const writableProp = descriptorType.getProperty('writable'); + + if (writableProp === undefined) { + return false; + } + + const writableType = + writableProp.valueDeclaration !== undefined && + isPropertyAssignment(writableProp.valueDeclaration) + ? checker.getTypeAtLocation(writableProp.valueDeclaration.initializer) + : checker.getTypeOfSymbolAtLocation(writableProp, node.arguments[2]); + + return isBooleanLiteralType(writableType, false); +} diff --git a/packages/type-utils/src/isShorthandPropertyAssignment.ts b/packages/type-utils/src/isShorthandPropertyAssignment.ts new file mode 100644 index 000000000000..1ed62dd1ff7d --- /dev/null +++ b/packages/type-utils/src/isShorthandPropertyAssignment.ts @@ -0,0 +1,8 @@ +import type { Node, ShorthandPropertyAssignment } from 'typescript'; +import { SyntaxKind } from 'typescript'; + +export function isShorthandPropertyAssignment( + node: Node, +): node is ShorthandPropertyAssignment { + return node.kind === SyntaxKind.ShorthandPropertyAssignment; +} diff --git a/packages/type-utils/src/isStrictCompilerOptionEnabled.ts b/packages/type-utils/src/isStrictCompilerOptionEnabled.ts new file mode 100644 index 000000000000..d6f0c3c6591a --- /dev/null +++ b/packages/type-utils/src/isStrictCompilerOptionEnabled.ts @@ -0,0 +1,21 @@ +import type { CompilerOptions } from 'typescript'; + +export type StrictCompilerOption = + | 'noImplicitAny' + | 'noImplicitThis' + | 'strictNullChecks' + | 'strictFunctionTypes' + | 'strictPropertyInitialization' + | 'alwaysStrict' + | 'strictBindCallApply'; + +export function isStrictCompilerOptionEnabled( + options: CompilerOptions, + option: StrictCompilerOption, +): boolean { + return ( + (options.strict ? options[option] !== false : options[option] === true) && + (option !== 'strictPropertyInitialization' || + isStrictCompilerOptionEnabled(options, 'strictNullChecks')) + ); +} diff --git a/packages/type-utils/src/isSymbolFlagSet.ts b/packages/type-utils/src/isSymbolFlagSet.ts new file mode 100644 index 000000000000..3dd921dc8ce2 --- /dev/null +++ b/packages/type-utils/src/isSymbolFlagSet.ts @@ -0,0 +1,8 @@ +import type { Symbol as SymbolType, SymbolFlags } from 'typescript'; + +import { isFlagSet } from './isFlagSet'; + +export const isSymbolFlagSet: ( + symbol: SymbolType, + flag: SymbolFlags, +) => boolean = isFlagSet; diff --git a/packages/type-utils/src/isThenableType.ts b/packages/type-utils/src/isThenableType.ts new file mode 100644 index 000000000000..62505bfb6975 --- /dev/null +++ b/packages/type-utils/src/isThenableType.ts @@ -0,0 +1,77 @@ +import type { + Expression, + Node, + ParameterDeclaration, + Symbol as SymbolType, + Type, + TypeChecker, +} from 'typescript'; + +import { unionTypeParts } from './unionTypeParts'; + +/** Determines if a type thenable and can be used with `await`. */ +export function isThenableType( + checker: TypeChecker, + node: Node, + type: Type, +): boolean; +/** Determines if a type thenable and can be used with `await`. */ +export function isThenableType( + checker: TypeChecker, + node: Expression, + type?: Type, +): boolean; +export function isThenableType( + checker: TypeChecker, + node: Node, + type = checker.getTypeAtLocation(node)!, +): boolean { + for (const ty of unionTypeParts(checker.getApparentType(type))) { + const then = ty.getProperty('then'); + if (then === undefined) { + continue; + } + + const thenType = checker.getTypeOfSymbolAtLocation(then, node); + + for (const t of unionTypeParts(thenType)) { + for (const signature of t.getCallSignatures()) { + if ( + signature.parameters.length !== 0 && + isCallback(checker, signature.parameters[0], node) + ) { + return true; + } + } + } + } + + return false; +} + +function isCallback( + checker: TypeChecker, + param: SymbolType, + node: Node, +): boolean { + let type: Type | undefined = checker.getApparentType( + checker.getTypeOfSymbolAtLocation(param, node), + ); + + if ((param.valueDeclaration).dotDotDotToken) { + // unwrap array type of rest parameter + type = type.getNumberIndexType(); + + if (type === undefined) { + return false; + } + } + + for (const t of unionTypeParts(type)) { + if (t.getCallSignatures().length !== 0) { + return true; + } + } + + return false; +} diff --git a/packages/type-utils/src/isTupleType.ts b/packages/type-utils/src/isTupleType.ts new file mode 100644 index 000000000000..7e7305f80191 --- /dev/null +++ b/packages/type-utils/src/isTupleType.ts @@ -0,0 +1,9 @@ +import type { ObjectType, TupleType, Type } from 'typescript'; +import { ObjectFlags, TypeFlags } from 'typescript'; + +export function isTupleType(type: Type): type is TupleType { + return ( + (type.flags & TypeFlags.Object && + (type).objectFlags & ObjectFlags.Tuple) !== 0 + ); +} diff --git a/packages/type-utils/src/isTupleTypeReference.ts b/packages/type-utils/src/isTupleTypeReference.ts new file mode 100644 index 000000000000..28bf52a35e44 --- /dev/null +++ b/packages/type-utils/src/isTupleTypeReference.ts @@ -0,0 +1,10 @@ +import type { TupleType, Type, TypeReference } from 'typescript'; + +import { isTupleType } from './isTupleType'; +import { isTypeReference } from './isTypeReference'; + +export function isTupleTypeReference( + type: Type, +): type is TypeReference & { target: TupleType } { + return isTypeReference(type) && isTupleType(type.target); +} diff --git a/packages/type-utils/src/isTypeFlagSet.ts b/packages/type-utils/src/isTypeFlagSet.ts new file mode 100644 index 000000000000..84e6c7228156 --- /dev/null +++ b/packages/type-utils/src/isTypeFlagSet.ts @@ -0,0 +1,22 @@ +import type { Type } from 'typescript'; +import { TypeFlags } from 'typescript'; + +import { getTypeFlags } from './getTypeFlags'; + +/** + * Checks if the given type is (or accepts) the given flags + * @param isReceiver true if the type is a receiving type (i.e. the type of a called function's parameter) + */ +export function isTypeFlagSet( + type: Type, + flagsToCheck: TypeFlags, + isReceiver?: boolean, +): boolean { + const flags = getTypeFlags(type); + + if (isReceiver && flags & (TypeFlags.Any | TypeFlags.Unknown)) { + return true; + } + + return (flags & flagsToCheck) !== 0; +} diff --git a/packages/type-utils/src/isTypeReadonly.ts b/packages/type-utils/src/isTypeReadonly.ts index 42dcc2ce9079..491f55b3ea79 100644 --- a/packages/type-utils/src/isTypeReadonly.ts +++ b/packages/type-utils/src/isTypeReadonly.ts @@ -1,16 +1,14 @@ import { ESLintUtils } from '@typescript-eslint/utils'; -import { - isConditionalType, - isIntersectionType, - isObjectType, - isPropertyReadonlyInType, - isSymbolFlagSet, - isUnionType, - unionTypeParts, -} from 'tsutils'; import * as ts from 'typescript'; +import { isConditionalType } from './isConditionalType'; +import { isIntersectionType } from './isIntersectionType'; +import { isObjectType } from './isObjectType'; +import { isPropertyReadonlyInType } from './isPropertyReadonlyInType'; +import { isSymbolFlagSet } from './isSymbolFlagSet'; +import { isUnionType } from './isUnionType'; import { getTypeOfPropertyOfType } from './propertyTypes'; +import { unionTypeParts } from './unionTypeParts'; const enum Readonlyness { /** the type cannot be handled by the function */ diff --git a/packages/type-utils/src/isTypeReference.ts b/packages/type-utils/src/isTypeReference.ts new file mode 100644 index 000000000000..93b5f4770962 --- /dev/null +++ b/packages/type-utils/src/isTypeReference.ts @@ -0,0 +1,9 @@ +import type { ObjectType, Type, TypeReference } from 'typescript'; +import { ObjectFlags, TypeFlags } from 'typescript'; + +export function isTypeReference(type: Type): type is TypeReference { + return ( + (type.flags & TypeFlags.Object) !== 0 && + ((type).objectFlags & ObjectFlags.Reference) !== 0 + ); +} diff --git a/packages/type-utils/src/isTypeReferenceNode.ts b/packages/type-utils/src/isTypeReferenceNode.ts new file mode 100644 index 000000000000..17435608a791 --- /dev/null +++ b/packages/type-utils/src/isTypeReferenceNode.ts @@ -0,0 +1,6 @@ +import type { Node, TypeReferenceNode } from 'typescript'; +import { SyntaxKind } from 'typescript'; + +export function isTypeReferenceNode(node: Node): node is TypeReferenceNode { + return node.kind === SyntaxKind.TypeReference; +} diff --git a/packages/type-utils/src/isUnionOrIntersectionType.ts b/packages/type-utils/src/isUnionOrIntersectionType.ts new file mode 100644 index 000000000000..349dd681a6ea --- /dev/null +++ b/packages/type-utils/src/isUnionOrIntersectionType.ts @@ -0,0 +1,8 @@ +import type { Type, UnionOrIntersectionType } from 'typescript'; +import { TypeFlags } from 'typescript'; + +export function isUnionOrIntersectionType( + type: Type, +): type is UnionOrIntersectionType { + return (type.flags & TypeFlags.UnionOrIntersection) !== 0; +} diff --git a/packages/type-utils/src/isUnionType.ts b/packages/type-utils/src/isUnionType.ts new file mode 100644 index 000000000000..e3043b0619ca --- /dev/null +++ b/packages/type-utils/src/isUnionType.ts @@ -0,0 +1,6 @@ +import type { Type, UnionType } from 'typescript'; +import { TypeFlags } from 'typescript'; + +export function isUnionType(type: Type): type is UnionType { + return (type.flags & TypeFlags.Union) !== 0; +} diff --git a/packages/type-utils/src/isUniqueESSymbolType.ts b/packages/type-utils/src/isUniqueESSymbolType.ts new file mode 100644 index 000000000000..10a42ea0f93f --- /dev/null +++ b/packages/type-utils/src/isUniqueESSymbolType.ts @@ -0,0 +1,6 @@ +import type { Type, UniqueESSymbolType } from 'typescript'; +import { TypeFlags } from 'typescript'; + +export function isUniqueESSymbolType(type: Type): type is UniqueESSymbolType { + return (type.flags & TypeFlags.UniqueESSymbol) !== 0; +} diff --git a/packages/type-utils/src/isUnsafeAssignment.ts b/packages/type-utils/src/isUnsafeAssignment.ts index f74220249ad9..c69beac9e81a 100644 --- a/packages/type-utils/src/isUnsafeAssignment.ts +++ b/packages/type-utils/src/isUnsafeAssignment.ts @@ -1,8 +1,8 @@ import type { TSESTree } from '@typescript-eslint/utils'; import { AST_NODE_TYPES } from '@typescript-eslint/utils'; -import { isTypeReference } from 'tsutils'; import type * as ts from 'typescript'; +import { isTypeReference } from './isTypeReference'; import { isTypeAnyType, isTypeUnknownType } from './predicates'; /** diff --git a/packages/type-utils/src/isValidPropertyAccess.ts b/packages/type-utils/src/isValidPropertyAccess.ts new file mode 100644 index 000000000000..167dae99c6b4 --- /dev/null +++ b/packages/type-utils/src/isValidPropertyAccess.ts @@ -0,0 +1,33 @@ +import { isIdentifierPart, isIdentifierStart, ScriptTarget } from 'typescript'; + +/** + * Determines whether the given text can be used to access a property with a PropertyAccessExpression while preserving the property's name. + */ +export function isValidPropertyAccess( + text: string, + languageVersion = ScriptTarget.Latest, +): boolean { + if (text.length === 0) { + return false; + } + + let ch = text.codePointAt(0)!; + + if (!isIdentifierStart(ch, languageVersion)) { + return false; + } + + for (let i = charSize(ch); i < text.length; i += charSize(ch)) { + ch = text.codePointAt(i)!; + + if (!isIdentifierPart(ch, languageVersion)) { + return false; + } + } + + return true; +} + +function charSize(ch: number): 2 | 1 { + return ch >= 0x10000 ? 2 : 1; +} diff --git a/packages/type-utils/src/isVariableDeclaration.ts b/packages/type-utils/src/isVariableDeclaration.ts new file mode 100644 index 000000000000..f8a81078acc0 --- /dev/null +++ b/packages/type-utils/src/isVariableDeclaration.ts @@ -0,0 +1,6 @@ +import type { Node, VariableDeclaration } from 'typescript'; +import { SyntaxKind } from 'typescript'; + +export function isVariableDeclaration(node: Node): node is VariableDeclaration { + return node.kind === SyntaxKind.VariableDeclaration; +} diff --git a/packages/type-utils/src/predicates.ts b/packages/type-utils/src/predicates.ts index 72f59e4fc9df..3f6e6a0e9313 100644 --- a/packages/type-utils/src/predicates.ts +++ b/packages/type-utils/src/predicates.ts @@ -1,9 +1,8 @@ import debug from 'debug'; -import { unionTypeParts } from 'tsutils'; import * as ts from 'typescript'; +import { getTypeFlags, isTypeFlagSet, unionTypeParts } from './'; import { getTypeArguments } from './getTypeArguments'; -import { getTypeFlags, isTypeFlagSet } from './typeFlagUtils'; const log = debug('typescript-eslint:eslint-plugin:utils:types'); diff --git a/packages/type-utils/src/someTypePart.ts b/packages/type-utils/src/someTypePart.ts new file mode 100644 index 000000000000..0d98e46441fd --- /dev/null +++ b/packages/type-utils/src/someTypePart.ts @@ -0,0 +1,9 @@ +import type { Type, UnionOrIntersectionType } from 'typescript'; + +export function someTypePart( + type: Type, + predicate: (t: Type) => t is UnionOrIntersectionType, + cb: (t: Type) => boolean, +): boolean { + return predicate(type) ? type.types.some(cb) : cb(type); +} diff --git a/packages/type-utils/src/symbolHasReadOnlyDeclaration.ts b/packages/type-utils/src/symbolHasReadOnlyDeclaration.ts new file mode 100644 index 000000000000..e4a0276dcebf --- /dev/null +++ b/packages/type-utils/src/symbolHasReadOnlyDeclaration.ts @@ -0,0 +1,37 @@ +import type { Symbol as SymbolType, TypeChecker } from 'typescript'; +import { ModifierFlags, NodeFlags, SymbolFlags } from 'typescript'; + +import { isCallExpression } from './isCallExpression'; +import { isEnumMember } from './isEnumMember'; +import { isInConstContext } from './isInConstContext'; +import { isModifierFlagSet } from './isModifierFlagSet'; +import { isNodeFlagSet } from './isNodeFlagSet'; +import { isPropertyAssignment } from './isPropertyAssignment'; +import { isReadonlyAssignmentDeclaration } from './isReadonlyAssignmentDeclaration'; +import { isShorthandPropertyAssignment } from './isShorthandPropertyAssignment'; +import { isVariableDeclaration } from './isVariableDeclaration'; + +export function symbolHasReadonlyDeclaration( + symbol: SymbolType, + checker: TypeChecker, +): boolean { + return ( + (symbol.flags & SymbolFlags.Accessor) === SymbolFlags.GetAccessor || + Boolean( + symbol + .getDeclarations() + ?.some( + node => + isModifierFlagSet(node, ModifierFlags.Readonly) || + (isVariableDeclaration(node) && + isNodeFlagSet(node.parent, NodeFlags.Const)) || + (isCallExpression(node) && + isReadonlyAssignmentDeclaration(node, checker)) || + isEnumMember(node) || + ((isPropertyAssignment(node) || + isShorthandPropertyAssignment(node)) && + isInConstContext(node.parent)), + ), + ) + ); +} diff --git a/packages/type-utils/src/typeFlagUtils.ts b/packages/type-utils/src/typeFlagUtils.ts deleted file mode 100644 index 134fdcf4ece1..000000000000 --- a/packages/type-utils/src/typeFlagUtils.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { unionTypeParts } from 'tsutils'; -import * as ts from 'typescript'; - -/** - * Gets all of the type flags in a type, iterating through unions automatically - */ -export function getTypeFlags(type: ts.Type): ts.TypeFlags { - let flags: ts.TypeFlags = 0; - for (const t of unionTypeParts(type)) { - flags |= t.flags; - } - return flags; -} - -/** - * Checks if the given type is (or accepts) the given flags - * @param isReceiver true if the type is a receiving type (i.e. the type of a called function's parameter) - */ -export function isTypeFlagSet( - type: ts.Type, - flagsToCheck: ts.TypeFlags, - isReceiver?: boolean, -): boolean { - const flags = getTypeFlags(type); - - if (isReceiver && flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) { - return true; - } - - return (flags & flagsToCheck) !== 0; -} diff --git a/packages/type-utils/src/unionTypeParts.ts b/packages/type-utils/src/unionTypeParts.ts new file mode 100644 index 000000000000..603a57ce0e8b --- /dev/null +++ b/packages/type-utils/src/unionTypeParts.ts @@ -0,0 +1,7 @@ +import type { Type } from 'typescript'; + +import { isUnionType } from './isUnionType'; + +export function unionTypeParts(type: Type): Type[] { + return isUnionType(type) ? type.types : [type]; +}