From bdde6345c5d20babe6c436e660d5e1fbb41db5c1 Mon Sep 17 00:00:00 2001 From: varwasabi Date: Sun, 25 Sep 2022 17:26:15 +1000 Subject: [PATCH 01/25] WIP Integrate used tsutils functions --- .../src/rules/no-misused-promises.ts | 3 +- .../rules/no-unnecessary-type-arguments.ts | 3 +- .../src/rules/prefer-optional-chain.ts | 2 +- .../src/rules/prefer-readonly.ts | 16 +++++----- .../eslint-plugin/src/rules/return-await.ts | 2 +- .../type-utils/src/containsAllTypesByName.ts | 5 +-- packages/type-utils/src/getContextualType.ts | 4 +-- packages/type-utils/src/getTypeFlags.ts | 16 ++++++++++ packages/type-utils/src/index.ts | 11 ++++++- packages/type-utils/src/isAssignmentKind.ts | 7 +++++ packages/type-utils/src/isBinaryExpression.ts | 6 ++++ .../type-utils/src/isBooleanLiteralType.ts | 12 +++++++ packages/type-utils/src/isCallExpression.ts | 6 ++++ packages/type-utils/src/isConditionalType.ts | 6 ++++ packages/type-utils/src/isIntersectionType.ts | 6 ++++ packages/type-utils/src/isTypeFlagSet.ts | 22 +++++++++++++ packages/type-utils/src/isTypeReadonly.ts | 4 +-- packages/type-utils/src/isTypeReference.ts | 9 ++++++ packages/type-utils/src/isUnionType.ts | 6 ++++ packages/type-utils/src/isUnsafeAssignment.ts | 2 +- packages/type-utils/src/predicates.ts | 3 +- packages/type-utils/src/typeFlagUtils.ts | 31 ------------------- packages/type-utils/src/unionTypeParts.ts | 7 +++++ 23 files changed, 137 insertions(+), 52 deletions(-) create mode 100644 packages/type-utils/src/getTypeFlags.ts create mode 100644 packages/type-utils/src/isAssignmentKind.ts create mode 100644 packages/type-utils/src/isBinaryExpression.ts create mode 100644 packages/type-utils/src/isBooleanLiteralType.ts create mode 100644 packages/type-utils/src/isCallExpression.ts create mode 100644 packages/type-utils/src/isConditionalType.ts create mode 100644 packages/type-utils/src/isIntersectionType.ts create mode 100644 packages/type-utils/src/isTypeFlagSet.ts create mode 100644 packages/type-utils/src/isTypeReference.ts create mode 100644 packages/type-utils/src/isUnionType.ts delete mode 100644 packages/type-utils/src/typeFlagUtils.ts create mode 100644 packages/type-utils/src/unionTypeParts.ts diff --git a/packages/eslint-plugin/src/rules/no-misused-promises.ts b/packages/eslint-plugin/src/rules/no-misused-promises.ts index 2c7f1eeb2599..9522db45fcc9 100644 --- a/packages/eslint-plugin/src/rules/no-misused-promises.ts +++ b/packages/eslint-plugin/src/rules/no-misused-promises.ts @@ -1,3 +1,4 @@ +import { isCallExpression } 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'; @@ -502,7 +503,7 @@ function voidFunctionParams( for (const subType of tsutils.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) { 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..952876c2ccc1 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-type-arguments.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-type-arguments.ts @@ -1,3 +1,4 @@ +import { isCallExpression } from '@typescript-eslint/type-utils'; import type { TSESTree } from '@typescript-eslint/utils'; import * as tsutils from 'tsutils'; import * as ts from 'typescript'; @@ -129,7 +130,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); } 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..b348f8d1a42e 100644 --- a/packages/eslint-plugin/src/rules/prefer-readonly.ts +++ b/packages/eslint-plugin/src/rules/prefer-readonly.ts @@ -1,3 +1,8 @@ +import { + isAssignmentKind, + isBinaryExpression, + isIntersectionType, +} 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'; @@ -58,7 +63,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 +86,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 +121,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 @@ -273,7 +275,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; diff --git a/packages/eslint-plugin/src/rules/return-await.ts b/packages/eslint-plugin/src/rules/return-await.ts index 1797e47e1276..36f2c8c2f9a8 100644 --- a/packages/eslint-plugin/src/rules/return-await.ts +++ b/packages/eslint-plugin/src/rules/return-await.ts @@ -1,7 +1,7 @@ +import { isBinaryExpression } 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'; diff --git a/packages/type-utils/src/containsAllTypesByName.ts b/packages/type-utils/src/containsAllTypesByName.ts index 07aa20dac048..7c01933f68bb 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 { isUnionOrIntersectionType } from 'tsutils'; import * as ts from 'typescript'; -import { isTypeFlagSet } from './typeFlagUtils'; +import { isTypeFlagSet } from './isTypeFlagSet'; +import { isTypeReference } from './isTypeReference'; /** * @param type Type being checked by name. diff --git a/packages/type-utils/src/getContextualType.ts b/packages/type-utils/src/getContextualType.ts index 075156282658..af38c0a22655 100644 --- a/packages/type-utils/src/getContextualType.ts +++ b/packages/type-utils/src/getContextualType.ts @@ -1,6 +1,4 @@ import { - isBinaryExpression, - isCallExpression, isIdentifier, isJsxExpression, isNewExpression, @@ -11,6 +9,8 @@ import { } from 'tsutils'; import * as ts from 'typescript'; +import { isBinaryExpression, isCallExpression } from './'; + /** * Returns the contextual type of a given node. * Contextual type is the type of the target the node is going into. diff --git a/packages/type-utils/src/getTypeFlags.ts b/packages/type-utils/src/getTypeFlags.ts new file mode 100644 index 000000000000..76d4c37d202f --- /dev/null +++ b/packages/type-utils/src/getTypeFlags.ts @@ -0,0 +1,16 @@ +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/index.ts b/packages/type-utils/src/index.ts index dde032e1770c..fc56760dc3e2 100644 --- a/packages/type-utils/src/index.ts +++ b/packages/type-utils/src/index.ts @@ -5,13 +5,22 @@ export * from './getDeclaration'; export * from './getSourceFileOfNode'; export * from './getTokenAtPosition'; export * from './getTypeArguments'; +export * from './getTypeFlags'; export * from './getTypeName'; +export * from './isAssignmentKind'; +export * from './isBinaryExpression'; +export * from './isCallExpression'; +export * from './isConditionalType'; +export * from './isIntersectionType'; +export * from './isTypeFlagSet'; export * from './isTypeReadonly'; +export * from './isTypeReference'; +export * from './isUnionType'; export * from './isUnsafeAssignment'; export * from './predicates'; export * from './propertyTypes'; export * from './requiresQuoting'; -export * from './typeFlagUtils'; +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/isBooleanLiteralType.ts b/packages/type-utils/src/isBooleanLiteralType.ts new file mode 100644 index 000000000000..294b5438d486 --- /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 './isTypeFlagSet'; + +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/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/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/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..3bc7f904e0a0 100644 --- a/packages/type-utils/src/isTypeReadonly.ts +++ b/packages/type-utils/src/isTypeReadonly.ts @@ -1,7 +1,5 @@ import { ESLintUtils } from '@typescript-eslint/utils'; import { - isConditionalType, - isIntersectionType, isObjectType, isPropertyReadonlyInType, isSymbolFlagSet, @@ -10,6 +8,8 @@ import { } from 'tsutils'; import * as ts from 'typescript'; +import { isConditionalType } from './isConditionalType'; +import { isIntersectionType } from './isIntersectionType'; import { getTypeOfPropertyOfType } from './propertyTypes'; const enum Readonlyness { 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/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/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/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/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]; +} From ffb2dd1e8b13a2d56e9256d1eeb6383ee646dde1 Mon Sep 17 00:00:00 2001 From: varwasabi Date: Sun, 25 Sep 2022 17:36:10 +1000 Subject: [PATCH 02/25] Add `isJsxExpression` --- packages/type-utils/src/getContextualType.ts | 3 +-- packages/type-utils/src/index.ts | 1 + packages/type-utils/src/isJsxExpression.ts | 6 ++++++ 3 files changed, 8 insertions(+), 2 deletions(-) create mode 100644 packages/type-utils/src/isJsxExpression.ts diff --git a/packages/type-utils/src/getContextualType.ts b/packages/type-utils/src/getContextualType.ts index af38c0a22655..75b074283caf 100644 --- a/packages/type-utils/src/getContextualType.ts +++ b/packages/type-utils/src/getContextualType.ts @@ -1,6 +1,5 @@ import { isIdentifier, - isJsxExpression, isNewExpression, isParameterDeclaration, isPropertyAssignment, @@ -9,7 +8,7 @@ import { } from 'tsutils'; import * as ts from 'typescript'; -import { isBinaryExpression, isCallExpression } from './'; +import { isBinaryExpression, isCallExpression, isJsxExpression } from './'; /** * Returns the contextual type of a given node. diff --git a/packages/type-utils/src/index.ts b/packages/type-utils/src/index.ts index fc56760dc3e2..18e746d7c8af 100644 --- a/packages/type-utils/src/index.ts +++ b/packages/type-utils/src/index.ts @@ -12,6 +12,7 @@ export * from './isBinaryExpression'; export * from './isCallExpression'; export * from './isConditionalType'; export * from './isIntersectionType'; +export * from './isJsxExpression'; export * from './isTypeFlagSet'; export * from './isTypeReadonly'; export * from './isTypeReference'; 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; +} From 13422539a07a64a834753bbdd1d595afea1131d6 Mon Sep 17 00:00:00 2001 From: varwasabi Date: Sun, 25 Sep 2022 17:39:21 +1000 Subject: [PATCH 03/25] Add `isNewExpression` --- packages/type-utils/src/getContextualType.ts | 8 ++++++-- packages/type-utils/src/index.ts | 1 + packages/type-utils/src/isNewExpression.ts | 6 ++++++ 3 files changed, 13 insertions(+), 2 deletions(-) create mode 100644 packages/type-utils/src/isNewExpression.ts diff --git a/packages/type-utils/src/getContextualType.ts b/packages/type-utils/src/getContextualType.ts index 75b074283caf..20e2a69082b5 100644 --- a/packages/type-utils/src/getContextualType.ts +++ b/packages/type-utils/src/getContextualType.ts @@ -1,6 +1,5 @@ import { isIdentifier, - isNewExpression, isParameterDeclaration, isPropertyAssignment, isPropertyDeclaration, @@ -8,7 +7,12 @@ import { } from 'tsutils'; import * as ts from 'typescript'; -import { isBinaryExpression, isCallExpression, isJsxExpression } from './'; +import { + isBinaryExpression, + isCallExpression, + isJsxExpression, + isNewExpression, +} from './'; /** * Returns the contextual type of a given node. diff --git a/packages/type-utils/src/index.ts b/packages/type-utils/src/index.ts index 18e746d7c8af..5569b396cc2e 100644 --- a/packages/type-utils/src/index.ts +++ b/packages/type-utils/src/index.ts @@ -13,6 +13,7 @@ export * from './isCallExpression'; export * from './isConditionalType'; export * from './isIntersectionType'; export * from './isJsxExpression'; +export * from './isNewExpression'; export * from './isTypeFlagSet'; export * from './isTypeReadonly'; export * from './isTypeReference'; 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; +} From 84b2283d34282d3e756c711d171994534168ac62 Mon Sep 17 00:00:00 2001 From: varwasabi Date: Sun, 25 Sep 2022 17:51:20 +1000 Subject: [PATCH 04/25] Add `isPropertyDeclaration` --- packages/type-utils/src/getContextualType.ts | 2 +- packages/type-utils/src/index.ts | 1 + packages/type-utils/src/isPropertyDeclaration.ts | 6 ++++++ 3 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 packages/type-utils/src/isPropertyDeclaration.ts diff --git a/packages/type-utils/src/getContextualType.ts b/packages/type-utils/src/getContextualType.ts index 20e2a69082b5..340726fa28a1 100644 --- a/packages/type-utils/src/getContextualType.ts +++ b/packages/type-utils/src/getContextualType.ts @@ -2,7 +2,6 @@ import { isIdentifier, isParameterDeclaration, isPropertyAssignment, - isPropertyDeclaration, isVariableDeclaration, } from 'tsutils'; import * as ts from 'typescript'; @@ -12,6 +11,7 @@ import { isCallExpression, isJsxExpression, isNewExpression, + isPropertyDeclaration, } from './'; /** diff --git a/packages/type-utils/src/index.ts b/packages/type-utils/src/index.ts index 5569b396cc2e..ef01acfe5b4f 100644 --- a/packages/type-utils/src/index.ts +++ b/packages/type-utils/src/index.ts @@ -14,6 +14,7 @@ export * from './isConditionalType'; export * from './isIntersectionType'; export * from './isJsxExpression'; export * from './isNewExpression'; +export * from './isPropertyDeclaration'; export * from './isTypeFlagSet'; export * from './isTypeReadonly'; export * from './isTypeReference'; 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; +} From daf96d368774f3e8e653a22b880878503e3eb126 Mon Sep 17 00:00:00 2001 From: varwasabi Date: Sun, 25 Sep 2022 17:52:57 +1000 Subject: [PATCH 05/25] Add `isPropertyAssignment` --- packages/type-utils/src/getContextualType.ts | 2 +- packages/type-utils/src/index.ts | 1 + packages/type-utils/src/isPropertyAssignment.ts | 6 ++++++ 3 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 packages/type-utils/src/isPropertyAssignment.ts diff --git a/packages/type-utils/src/getContextualType.ts b/packages/type-utils/src/getContextualType.ts index 340726fa28a1..3db3c2195fdd 100644 --- a/packages/type-utils/src/getContextualType.ts +++ b/packages/type-utils/src/getContextualType.ts @@ -1,7 +1,6 @@ import { isIdentifier, isParameterDeclaration, - isPropertyAssignment, isVariableDeclaration, } from 'tsutils'; import * as ts from 'typescript'; @@ -11,6 +10,7 @@ import { isCallExpression, isJsxExpression, isNewExpression, + isPropertyAssignment, isPropertyDeclaration, } from './'; diff --git a/packages/type-utils/src/index.ts b/packages/type-utils/src/index.ts index ef01acfe5b4f..9da0a5a78a6a 100644 --- a/packages/type-utils/src/index.ts +++ b/packages/type-utils/src/index.ts @@ -14,6 +14,7 @@ export * from './isConditionalType'; export * from './isIntersectionType'; export * from './isJsxExpression'; export * from './isNewExpression'; +export * from './isPropertyAssignment'; export * from './isPropertyDeclaration'; export * from './isTypeFlagSet'; export * from './isTypeReadonly'; 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; +} From 03f3d0026b51bb69bff5cedb8493f0d7e0804790 Mon Sep 17 00:00:00 2001 From: varwasabi Date: Sun, 25 Sep 2022 17:55:11 +1000 Subject: [PATCH 06/25] Add `isUnionOrIntersectionType` --- packages/type-utils/src/containsAllTypesByName.ts | 2 +- packages/type-utils/src/isUnionOrIntersectionType.ts | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 packages/type-utils/src/isUnionOrIntersectionType.ts diff --git a/packages/type-utils/src/containsAllTypesByName.ts b/packages/type-utils/src/containsAllTypesByName.ts index 7c01933f68bb..c6d5abf7e17a 100644 --- a/packages/type-utils/src/containsAllTypesByName.ts +++ b/packages/type-utils/src/containsAllTypesByName.ts @@ -1,8 +1,8 @@ -import { isUnionOrIntersectionType } from 'tsutils'; import * as ts from 'typescript'; 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/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; +} From 371b6b1ec478703fbb39bc5eefa31574a038b2dc Mon Sep 17 00:00:00 2001 From: varwasabi Date: Sun, 25 Sep 2022 17:59:18 +1000 Subject: [PATCH 07/25] Add `isValidPropertyAccess` --- .../src/rules/no-dynamic-delete.ts | 5 ++- packages/type-utils/src/index.ts | 1 + .../type-utils/src/isValidPropertyAccess.ts | 33 +++++++++++++++++++ 3 files changed, 36 insertions(+), 3 deletions(-) create mode 100644 packages/type-utils/src/isValidPropertyAccess.ts 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/type-utils/src/index.ts b/packages/type-utils/src/index.ts index 9da0a5a78a6a..2b124d3cad3b 100644 --- a/packages/type-utils/src/index.ts +++ b/packages/type-utils/src/index.ts @@ -21,6 +21,7 @@ export * from './isTypeReadonly'; export * from './isTypeReference'; export * from './isUnionType'; export * from './isUnsafeAssignment'; +export * from './isValidPropertyAccess'; export * from './predicates'; export * from './propertyTypes'; export * from './requiresQuoting'; 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; +} From b74a090cec1260cbd49eb041fe95d6a973bc196d Mon Sep 17 00:00:00 2001 From: varwasabi Date: Sun, 25 Sep 2022 18:01:05 +1000 Subject: [PATCH 08/25] Add `isParameterDeclaration` --- packages/type-utils/src/getContextualType.ts | 7 ++----- packages/type-utils/src/index.ts | 1 + packages/type-utils/src/isParameterDeclaration.ts | 8 ++++++++ 3 files changed, 11 insertions(+), 5 deletions(-) create mode 100644 packages/type-utils/src/isParameterDeclaration.ts diff --git a/packages/type-utils/src/getContextualType.ts b/packages/type-utils/src/getContextualType.ts index 3db3c2195fdd..0e44a3c534f4 100644 --- a/packages/type-utils/src/getContextualType.ts +++ b/packages/type-utils/src/getContextualType.ts @@ -1,8 +1,4 @@ -import { - isIdentifier, - isParameterDeclaration, - isVariableDeclaration, -} from 'tsutils'; +import { isIdentifier, isVariableDeclaration } from 'tsutils'; import * as ts from 'typescript'; import { @@ -10,6 +6,7 @@ import { isCallExpression, isJsxExpression, isNewExpression, + isParameterDeclaration, isPropertyAssignment, isPropertyDeclaration, } from './'; diff --git a/packages/type-utils/src/index.ts b/packages/type-utils/src/index.ts index 2b124d3cad3b..61ab00079842 100644 --- a/packages/type-utils/src/index.ts +++ b/packages/type-utils/src/index.ts @@ -14,6 +14,7 @@ export * from './isConditionalType'; export * from './isIntersectionType'; export * from './isJsxExpression'; export * from './isNewExpression'; +export * from './isParameterDeclaration'; export * from './isPropertyAssignment'; export * from './isPropertyDeclaration'; export * from './isTypeFlagSet'; 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; +} From 066efc059d6fa9dc7de910b2a2542932c6999a7c Mon Sep 17 00:00:00 2001 From: varwasabi Date: Sun, 25 Sep 2022 18:03:42 +1000 Subject: [PATCH 09/25] Add `isObjectType` --- .../src/rules/no-unnecessary-type-assertion.ts | 2 +- packages/eslint-plugin/src/rules/prefer-readonly.ts | 3 ++- packages/type-utils/src/index.ts | 1 + packages/type-utils/src/isObjectType.ts | 6 ++++++ packages/type-utils/src/isTypeReadonly.ts | 2 +- 5 files changed, 11 insertions(+), 3 deletions(-) create mode 100644 packages/type-utils/src/isObjectType.ts 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..333c9321ff1d 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts @@ -1,8 +1,8 @@ +import { isObjectType } from '@typescript-eslint/type-utils'; import type { TSESTree } from '@typescript-eslint/utils'; import { AST_NODE_TYPES } from '@typescript-eslint/utils'; import { isObjectFlagSet, - isObjectType, isStrictCompilerOptionEnabled, isTypeFlagSet, isVariableDeclaration, diff --git a/packages/eslint-plugin/src/rules/prefer-readonly.ts b/packages/eslint-plugin/src/rules/prefer-readonly.ts index b348f8d1a42e..a6abb90bcae7 100644 --- a/packages/eslint-plugin/src/rules/prefer-readonly.ts +++ b/packages/eslint-plugin/src/rules/prefer-readonly.ts @@ -2,6 +2,7 @@ import { isAssignmentKind, isBinaryExpression, isIntersectionType, + isObjectType, } from '@typescript-eslint/type-utils'; import type { TSESTree } from '@typescript-eslint/utils'; import { AST_NODE_TYPES, ASTUtils } from '@typescript-eslint/utils'; @@ -321,7 +322,7 @@ class ClassScope { } const modifyingStatic = - tsutils.isObjectType(modifierType) && + isObjectType(modifierType) && tsutils.isObjectFlagSet(modifierType, ts.ObjectFlags.Anonymous); if ( !modifyingStatic && diff --git a/packages/type-utils/src/index.ts b/packages/type-utils/src/index.ts index 61ab00079842..164653c304c8 100644 --- a/packages/type-utils/src/index.ts +++ b/packages/type-utils/src/index.ts @@ -14,6 +14,7 @@ export * from './isConditionalType'; export * from './isIntersectionType'; export * from './isJsxExpression'; export * from './isNewExpression'; +export * from './isObjectType'; export * from './isParameterDeclaration'; export * from './isPropertyAssignment'; export * from './isPropertyDeclaration'; 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/isTypeReadonly.ts b/packages/type-utils/src/isTypeReadonly.ts index 3bc7f904e0a0..f4e2c783d8fe 100644 --- a/packages/type-utils/src/isTypeReadonly.ts +++ b/packages/type-utils/src/isTypeReadonly.ts @@ -1,6 +1,5 @@ import { ESLintUtils } from '@typescript-eslint/utils'; import { - isObjectType, isPropertyReadonlyInType, isSymbolFlagSet, isUnionType, @@ -10,6 +9,7 @@ import * as ts from 'typescript'; import { isConditionalType } from './isConditionalType'; import { isIntersectionType } from './isIntersectionType'; +import { isObjectType } from './isObjectType'; import { getTypeOfPropertyOfType } from './propertyTypes'; const enum Readonlyness { From 844cb1077862472ca003c0b6cb694b5c5cf40eed Mon Sep 17 00:00:00 2001 From: varwasabi Date: Sun, 25 Sep 2022 18:06:42 +1000 Subject: [PATCH 10/25] Add `isObjectFlagSet` --- .../src/rules/no-unnecessary-type-assertion.ts | 3 +-- packages/eslint-plugin/src/rules/prefer-readonly.ts | 3 ++- packages/type-utils/src/index.ts | 1 + packages/type-utils/src/isObjectFlagSet.ts | 8 ++++++++ 4 files changed, 12 insertions(+), 3 deletions(-) create mode 100644 packages/type-utils/src/isObjectFlagSet.ts 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 333c9321ff1d..a41d560bb87d 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts @@ -1,8 +1,7 @@ -import { isObjectType } from '@typescript-eslint/type-utils'; +import { isObjectFlagSet, isObjectType } from '@typescript-eslint/type-utils'; import type { TSESTree } from '@typescript-eslint/utils'; import { AST_NODE_TYPES } from '@typescript-eslint/utils'; import { - isObjectFlagSet, isStrictCompilerOptionEnabled, isTypeFlagSet, isVariableDeclaration, diff --git a/packages/eslint-plugin/src/rules/prefer-readonly.ts b/packages/eslint-plugin/src/rules/prefer-readonly.ts index a6abb90bcae7..307fb37a9807 100644 --- a/packages/eslint-plugin/src/rules/prefer-readonly.ts +++ b/packages/eslint-plugin/src/rules/prefer-readonly.ts @@ -2,6 +2,7 @@ import { isAssignmentKind, isBinaryExpression, isIntersectionType, + isObjectFlagSet, isObjectType, } from '@typescript-eslint/type-utils'; import type { TSESTree } from '@typescript-eslint/utils'; @@ -323,7 +324,7 @@ class ClassScope { const modifyingStatic = isObjectType(modifierType) && - tsutils.isObjectFlagSet(modifierType, ts.ObjectFlags.Anonymous); + isObjectFlagSet(modifierType, ts.ObjectFlags.Anonymous); if ( !modifyingStatic && this.constructorScopeDepth === DIRECTLY_INSIDE_CONSTRUCTOR diff --git a/packages/type-utils/src/index.ts b/packages/type-utils/src/index.ts index 164653c304c8..941d37b663f4 100644 --- a/packages/type-utils/src/index.ts +++ b/packages/type-utils/src/index.ts @@ -14,6 +14,7 @@ export * from './isConditionalType'; export * from './isIntersectionType'; export * from './isJsxExpression'; export * from './isNewExpression'; +export * from './isObjectFlagSet'; export * from './isObjectType'; export * from './isParameterDeclaration'; export * from './isPropertyAssignment'; 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; +} From 4bd0bd00643d20d56172c118cd9740381529693e Mon Sep 17 00:00:00 2001 From: varwasabi Date: Sun, 25 Sep 2022 18:40:28 +1000 Subject: [PATCH 11/25] Add `isModifierFlagSet` --- packages/eslint-plugin/src/rules/prefer-readonly.ts | 9 +++++---- packages/type-utils/src/index.ts | 1 + packages/type-utils/src/isModifierFlagSet.ts | 6 ++++++ 3 files changed, 12 insertions(+), 4 deletions(-) create mode 100644 packages/type-utils/src/isModifierFlagSet.ts diff --git a/packages/eslint-plugin/src/rules/prefer-readonly.ts b/packages/eslint-plugin/src/rules/prefer-readonly.ts index 307fb37a9807..c977f6edcabc 100644 --- a/packages/eslint-plugin/src/rules/prefer-readonly.ts +++ b/packages/eslint-plugin/src/rules/prefer-readonly.ts @@ -2,6 +2,7 @@ import { isAssignmentKind, isBinaryExpression, isIntersectionType, + isModifierFlagSet, isObjectFlagSet, isObjectType, } from '@typescript-eslint/type-utils'; @@ -292,8 +293,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; @@ -307,7 +308,7 @@ class ClassScope { return; } - (tsutils.isModifierFlagSet(node, ts.ModifierFlags.Static) + (isModifierFlagSet(node, ts.ModifierFlags.Static) ? this.privateModifiableStatics : this.privateModifiableMembers ).set(node.name.getText(), node); @@ -348,7 +349,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/type-utils/src/index.ts b/packages/type-utils/src/index.ts index 941d37b663f4..fd3ed795b92d 100644 --- a/packages/type-utils/src/index.ts +++ b/packages/type-utils/src/index.ts @@ -13,6 +13,7 @@ export * from './isCallExpression'; export * from './isConditionalType'; export * from './isIntersectionType'; export * from './isJsxExpression'; +export * from './isModifierFlagSet'; export * from './isNewExpression'; export * from './isObjectFlagSet'; export * from './isObjectType'; 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; +} From 64ea998ed000e95f32062f2235aa249e92b7af90 Mon Sep 17 00:00:00 2001 From: varwasabi Date: Sun, 25 Sep 2022 18:42:53 +1000 Subject: [PATCH 12/25] Add `isVariableDeclaration` --- .../src/rules/no-unnecessary-type-assertion.ts | 12 ++++++------ packages/type-utils/src/getContextualType.ts | 3 ++- packages/type-utils/src/index.ts | 1 + packages/type-utils/src/isVariableDeclaration.ts | 6 ++++++ 4 files changed, 15 insertions(+), 7 deletions(-) create mode 100644 packages/type-utils/src/isVariableDeclaration.ts 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 a41d560bb87d..2bb4e1a842bc 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts @@ -1,11 +1,11 @@ -import { isObjectFlagSet, isObjectType } from '@typescript-eslint/type-utils'; -import type { TSESTree } from '@typescript-eslint/utils'; -import { AST_NODE_TYPES } from '@typescript-eslint/utils'; import { - isStrictCompilerOptionEnabled, - isTypeFlagSet, + isObjectFlagSet, + isObjectType, isVariableDeclaration, -} from 'tsutils'; +} from '@typescript-eslint/type-utils'; +import type { TSESTree } from '@typescript-eslint/utils'; +import { AST_NODE_TYPES } from '@typescript-eslint/utils'; +import { isStrictCompilerOptionEnabled, isTypeFlagSet } from 'tsutils'; import * as ts from 'typescript'; import * as util from '../util'; diff --git a/packages/type-utils/src/getContextualType.ts b/packages/type-utils/src/getContextualType.ts index 0e44a3c534f4..eb622cc7cb00 100644 --- a/packages/type-utils/src/getContextualType.ts +++ b/packages/type-utils/src/getContextualType.ts @@ -1,4 +1,4 @@ -import { isIdentifier, isVariableDeclaration } from 'tsutils'; +import { isIdentifier } from 'tsutils'; import * as ts from 'typescript'; import { @@ -9,6 +9,7 @@ import { isParameterDeclaration, isPropertyAssignment, isPropertyDeclaration, + isVariableDeclaration, } from './'; /** diff --git a/packages/type-utils/src/index.ts b/packages/type-utils/src/index.ts index fd3ed795b92d..fac480a9f077 100644 --- a/packages/type-utils/src/index.ts +++ b/packages/type-utils/src/index.ts @@ -26,6 +26,7 @@ export * from './isTypeReference'; export * from './isUnionType'; export * from './isUnsafeAssignment'; export * from './isValidPropertyAccess'; +export * from './isVariableDeclaration'; export * from './predicates'; export * from './propertyTypes'; export * from './requiresQuoting'; 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; +} From 2090adeb60c9d74166d858becbecca0b8e8643f1 Mon Sep 17 00:00:00 2001 From: varwasabi Date: Sun, 25 Sep 2022 18:50:25 +1000 Subject: [PATCH 13/25] Add `isStrictCompilerOptionEnabled` --- .../src/rules/no-unnecessary-condition.ts | 2 +- .../rules/no-unnecessary-type-assertion.ts | 3 ++- .../src/rules/no-unsafe-assignment.ts | 4 ++-- .../eslint-plugin/src/rules/no-unsafe-call.ts | 4 ++-- .../src/rules/no-unsafe-member-access.ts | 4 ++-- .../src/rules/no-unsafe-return.ts | 3 ++- .../src/rules/strict-boolean-expressions.ts | 3 ++- packages/type-utils/src/index.ts | 1 + .../src/isStrictCompilerOptionEnabled.ts | 21 +++++++++++++++++++ 9 files changed, 35 insertions(+), 10 deletions(-) create mode 100644 packages/type-utils/src/isStrictCompilerOptionEnabled.ts diff --git a/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts b/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts index 942048bce0bd..25b310fe7a82 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts @@ -1,3 +1,4 @@ +import { isStrictCompilerOptionEnabled } from '@typescript-eslint/type-utils'; import type { TSESTree } from '@typescript-eslint/utils'; import { AST_NODE_TYPES, AST_TOKEN_TYPES } from '@typescript-eslint/utils'; import { @@ -5,7 +6,6 @@ import { isBooleanLiteralType, isFalsyType, isLiteralType, - isStrictCompilerOptionEnabled, unionTypeParts, } from 'tsutils'; import * as ts from 'typescript'; 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 2bb4e1a842bc..86ab439629dd 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts @@ -1,11 +1,12 @@ import { isObjectFlagSet, isObjectType, + isStrictCompilerOptionEnabled, isVariableDeclaration, } from '@typescript-eslint/type-utils'; import type { TSESTree } from '@typescript-eslint/utils'; import { AST_NODE_TYPES } from '@typescript-eslint/utils'; -import { isStrictCompilerOptionEnabled, isTypeFlagSet } from 'tsutils'; +import { isTypeFlagSet } from 'tsutils'; 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..93a240649151 100644 --- a/packages/eslint-plugin/src/rules/no-unsafe-return.ts +++ b/packages/eslint-plugin/src/rules/no-unsafe-return.ts @@ -1,3 +1,4 @@ +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'; @@ -30,7 +31,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/strict-boolean-expressions.ts b/packages/eslint-plugin/src/rules/strict-boolean-expressions.ts index 1e327a8a4b83..4935180b5256 100644 --- a/packages/eslint-plugin/src/rules/strict-boolean-expressions.ts +++ b/packages/eslint-plugin/src/rules/strict-boolean-expressions.ts @@ -1,3 +1,4 @@ +import { isStrictCompilerOptionEnabled } 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'; @@ -146,7 +147,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', ); diff --git a/packages/type-utils/src/index.ts b/packages/type-utils/src/index.ts index fac480a9f077..9f69ebd77580 100644 --- a/packages/type-utils/src/index.ts +++ b/packages/type-utils/src/index.ts @@ -20,6 +20,7 @@ export * from './isObjectType'; export * from './isParameterDeclaration'; export * from './isPropertyAssignment'; export * from './isPropertyDeclaration'; +export * from './isStrictCompilerOptionEnabled'; export * from './isTypeFlagSet'; export * from './isTypeReadonly'; export * from './isTypeReference'; 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')) + ); +} From 2f11a141fbcef291bea245912371cf36b403f625 Mon Sep 17 00:00:00 2001 From: varwasabi Date: Sun, 25 Sep 2022 18:55:49 +1000 Subject: [PATCH 14/25] Add `isSymbolFlagSet` --- packages/eslint-plugin/src/rules/no-implied-eval.ts | 7 ++----- .../src/rules/no-unnecessary-qualifier.ts | 4 ++-- .../src/rules/no-unnecessary-type-arguments.ts | 8 +++++--- packages/type-utils/src/index.ts | 1 + packages/type-utils/src/isSymbolFlagSet.ts | 10 ++++++++++ packages/type-utils/src/isTypeReadonly.ts | 8 ++------ 6 files changed, 22 insertions(+), 16 deletions(-) create mode 100644 packages/type-utils/src/isSymbolFlagSet.ts 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-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 952876c2ccc1..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,6 +1,8 @@ -import { isCallExpression } from '@typescript-eslint/type-utils'; +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'; @@ -181,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/type-utils/src/index.ts b/packages/type-utils/src/index.ts index 9f69ebd77580..08d5088ee6b5 100644 --- a/packages/type-utils/src/index.ts +++ b/packages/type-utils/src/index.ts @@ -21,6 +21,7 @@ export * from './isParameterDeclaration'; export * from './isPropertyAssignment'; export * from './isPropertyDeclaration'; export * from './isStrictCompilerOptionEnabled'; +export * from './isSymbolFlagSet'; export * from './isTypeFlagSet'; export * from './isTypeReadonly'; export * from './isTypeReference'; diff --git a/packages/type-utils/src/isSymbolFlagSet.ts b/packages/type-utils/src/isSymbolFlagSet.ts new file mode 100644 index 000000000000..aba6cbba934b --- /dev/null +++ b/packages/type-utils/src/isSymbolFlagSet.ts @@ -0,0 +1,10 @@ +import type * as ts from 'typescript'; + +function isFlagSet(obj: { flags: number }, flag: number): boolean { + return (obj.flags & flag) !== 0; +} + +export const isSymbolFlagSet: ( + symbol: ts.Symbol, + flag: ts.SymbolFlags, +) => boolean = isFlagSet; diff --git a/packages/type-utils/src/isTypeReadonly.ts b/packages/type-utils/src/isTypeReadonly.ts index f4e2c783d8fe..4fb716cd522e 100644 --- a/packages/type-utils/src/isTypeReadonly.ts +++ b/packages/type-utils/src/isTypeReadonly.ts @@ -1,15 +1,11 @@ import { ESLintUtils } from '@typescript-eslint/utils'; -import { - isPropertyReadonlyInType, - isSymbolFlagSet, - isUnionType, - unionTypeParts, -} from 'tsutils'; +import { isPropertyReadonlyInType, isUnionType, unionTypeParts } from 'tsutils'; import * as ts from 'typescript'; import { isConditionalType } from './isConditionalType'; import { isIntersectionType } from './isIntersectionType'; import { isObjectType } from './isObjectType'; +import { isSymbolFlagSet } from './isSymbolFlagSet'; import { getTypeOfPropertyOfType } from './propertyTypes'; const enum Readonlyness { From bc8f8cc366c5255be44a76ac2f15671eeef186c5 Mon Sep 17 00:00:00 2001 From: varwasabi Date: Sun, 25 Sep 2022 19:56:28 +1000 Subject: [PATCH 15/25] Add `isThenableType` --- .../eslint-plugin/src/rules/await-thenable.ts | 4 +- .../src/rules/no-misused-promises.ts | 11 +- .../eslint-plugin/src/rules/require-await.ts | 28 +++-- .../eslint-plugin/src/rules/return-await.ts | 8 +- packages/type-utils/src/getPropertyOfType.ts | 12 ++ packages/type-utils/src/index.ts | 20 ++++ .../src/isBindableObjectDefinePropertyCall.ts | 21 ++++ packages/type-utils/src/isConstAssertion.ts | 12 ++ .../type-utils/src/isEntityNameExpression.ts | 14 +++ packages/type-utils/src/isEnumMember.ts | 6 + packages/type-utils/src/isFlagSet.ts | 3 + packages/type-utils/src/isInConstContext.ts | 56 +++++++++ packages/type-utils/src/isNodeFlagSet.ts | 6 + .../src/isNumericOrStringLikeLiteral.ts | 20 ++++ .../type-utils/src/isNumericPropertyName.ts | 5 + .../src/isPropertyAccessExpression.ts | 8 ++ .../src/isPropertyReadonlyInType.ts | 110 ++++++++++++++++++ .../src/isReadonlyAssignmentDeclaration.ts | 34 ++++++ .../src/isShorthandPropertyAssignment.ts | 8 ++ packages/type-utils/src/isSymbolFlagSet.ts | 10 +- packages/type-utils/src/isThenableType.ts | 77 ++++++++++++ packages/type-utils/src/isTupleType.ts | 9 ++ .../type-utils/src/isTupleTypeReference.ts | 10 ++ packages/type-utils/src/isTypeReadonly.ts | 3 +- .../type-utils/src/isTypeReferenceNode.ts | 6 + packages/type-utils/src/someTypePart.ts | 9 ++ .../src/symbolHasReadOnlyDeclaration.ts | 37 ++++++ 27 files changed, 520 insertions(+), 27 deletions(-) create mode 100644 packages/type-utils/src/getPropertyOfType.ts create mode 100644 packages/type-utils/src/isBindableObjectDefinePropertyCall.ts create mode 100644 packages/type-utils/src/isConstAssertion.ts create mode 100644 packages/type-utils/src/isEntityNameExpression.ts create mode 100644 packages/type-utils/src/isEnumMember.ts create mode 100644 packages/type-utils/src/isFlagSet.ts create mode 100644 packages/type-utils/src/isInConstContext.ts create mode 100644 packages/type-utils/src/isNodeFlagSet.ts create mode 100644 packages/type-utils/src/isNumericOrStringLikeLiteral.ts create mode 100644 packages/type-utils/src/isNumericPropertyName.ts create mode 100644 packages/type-utils/src/isPropertyAccessExpression.ts create mode 100644 packages/type-utils/src/isPropertyReadonlyInType.ts create mode 100644 packages/type-utils/src/isReadonlyAssignmentDeclaration.ts create mode 100644 packages/type-utils/src/isShorthandPropertyAssignment.ts create mode 100644 packages/type-utils/src/isThenableType.ts create mode 100644 packages/type-utils/src/isTupleType.ts create mode 100644 packages/type-utils/src/isTupleTypeReference.ts create mode 100644 packages/type-utils/src/isTypeReferenceNode.ts create mode 100644 packages/type-utils/src/someTypePart.ts create mode 100644 packages/type-utils/src/symbolHasReadOnlyDeclaration.ts 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/no-misused-promises.ts b/packages/eslint-plugin/src/rules/no-misused-promises.ts index 9522db45fcc9..59033fd970f6 100644 --- a/packages/eslint-plugin/src/rules/no-misused-promises.ts +++ b/packages/eslint-plugin/src/rules/no-misused-promises.ts @@ -1,4 +1,7 @@ -import { isCallExpression } from '@typescript-eslint/type-utils'; +import { + isCallExpression, + 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'; @@ -412,7 +415,7 @@ 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)) { + if (isThenableType(checker, node, subType)) { return true; } } @@ -541,7 +544,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; } } @@ -582,7 +585,7 @@ function isVoidReturningFunctionType( // 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; } diff --git a/packages/eslint-plugin/src/rules/require-await.ts b/packages/eslint-plugin/src/rules/require-await.ts index 076cd6077a0f..304aa443230e 100644 --- a/packages/eslint-plugin/src/rules/require-await.ts +++ b/packages/eslint-plugin/src/rules/require-await.ts @@ -1,3 +1,4 @@ +import { 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'; @@ -82,15 +83,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 */ @@ -153,7 +145,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 +163,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 36f2c8c2f9a8..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 } from '@typescript-eslint/type-utils'; +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 * 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/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/index.ts b/packages/type-utils/src/index.ts index 08d5088ee6b5..8245f1269a28 100644 --- a/packages/type-utils/src/index.ts +++ b/packages/type-utils/src/index.ts @@ -2,6 +2,7 @@ export * from './containsAllTypesByName'; export * from './getConstrainedTypeAtLocation'; export * from './getContextualType'; export * from './getDeclaration'; +export * from './getPropertyOfType'; export * from './getSourceFileOfNode'; export * from './getTokenAtPosition'; export * from './getTypeArguments'; @@ -9,22 +10,39 @@ export * from './getTypeFlags'; export * from './getTypeName'; export * from './isAssignmentKind'; export * from './isBinaryExpression'; +export * from './isBindableObjectDefinePropertyCall'; export * from './isCallExpression'; export * from './isConditionalType'; +export * from './isConstAssertion'; +export * from './isEntityNameExpression'; +export * from './isEnumMember'; +export * from './isFlagSet'; +export * from './isInConstContext'; export * from './isIntersectionType'; export * from './isJsxExpression'; 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 './isUnsafeAssignment'; export * from './isValidPropertyAccess'; @@ -32,6 +50,8 @@ export * from './isVariableDeclaration'; export * from './predicates'; export * from './propertyTypes'; export * from './requiresQuoting'; +export * from './someTypePart'; +export * from './symbolHasReadOnlyDeclaration'; export * from './unionTypeParts'; export { getDecorators, 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/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/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/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/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/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/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/isSymbolFlagSet.ts b/packages/type-utils/src/isSymbolFlagSet.ts index aba6cbba934b..3dd921dc8ce2 100644 --- a/packages/type-utils/src/isSymbolFlagSet.ts +++ b/packages/type-utils/src/isSymbolFlagSet.ts @@ -1,10 +1,8 @@ -import type * as ts from 'typescript'; +import type { Symbol as SymbolType, SymbolFlags } from 'typescript'; -function isFlagSet(obj: { flags: number }, flag: number): boolean { - return (obj.flags & flag) !== 0; -} +import { isFlagSet } from './isFlagSet'; export const isSymbolFlagSet: ( - symbol: ts.Symbol, - flag: ts.SymbolFlags, + 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/isTypeReadonly.ts b/packages/type-utils/src/isTypeReadonly.ts index 4fb716cd522e..f96eea9f58f5 100644 --- a/packages/type-utils/src/isTypeReadonly.ts +++ b/packages/type-utils/src/isTypeReadonly.ts @@ -1,10 +1,11 @@ import { ESLintUtils } from '@typescript-eslint/utils'; -import { isPropertyReadonlyInType, isUnionType, unionTypeParts } from 'tsutils'; +import { 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 { getTypeOfPropertyOfType } from './propertyTypes'; 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/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)), + ), + ) + ); +} From fe48d852347b3cc021227a64cd861ceb70c36f0d Mon Sep 17 00:00:00 2001 From: varwasabi Date: Sun, 25 Sep 2022 20:06:15 +1000 Subject: [PATCH 16/25] Add `getWellKnownSymbolPropertyOfType` --- .../eslint-plugin/src/rules/require-await.ts | 8 +- .../src/getWellKnownSymbolPropertyOfType.ts | 78 +++++++++++++++++++ packages/type-utils/src/index.ts | 2 + .../type-utils/src/isUniqueESSymbolType.ts | 6 ++ 4 files changed, 91 insertions(+), 3 deletions(-) create mode 100644 packages/type-utils/src/getWellKnownSymbolPropertyOfType.ts create mode 100644 packages/type-utils/src/isUniqueESSymbolType.ts diff --git a/packages/eslint-plugin/src/rules/require-await.ts b/packages/eslint-plugin/src/rules/require-await.ts index 304aa443230e..51c39905705f 100644 --- a/packages/eslint-plugin/src/rules/require-await.ts +++ b/packages/eslint-plugin/src/rules/require-await.ts @@ -1,7 +1,9 @@ -import { isThenableType } from '@typescript-eslint/type-utils'; +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'; @@ -112,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, 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 8245f1269a28..1b37132aeab2 100644 --- a/packages/type-utils/src/index.ts +++ b/packages/type-utils/src/index.ts @@ -8,6 +8,7 @@ export * from './getTokenAtPosition'; export * from './getTypeArguments'; export * from './getTypeFlags'; export * from './getTypeName'; +export * from './getWellKnownSymbolPropertyOfType'; export * from './isAssignmentKind'; export * from './isBinaryExpression'; export * from './isBindableObjectDefinePropertyCall'; @@ -44,6 +45,7 @@ export * from './isTypeReadonly'; export * from './isTypeReference'; export * from './isTypeReferenceNode'; export * from './isUnionType'; +export * from './isUniqueESSymbolType'; export * from './isUnsafeAssignment'; export * from './isValidPropertyAccess'; export * from './isVariableDeclaration'; 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; +} From cffe5d39996490fb40cbcada5e6c6de7885035f0 Mon Sep 17 00:00:00 2001 From: varwasabi Date: Sun, 25 Sep 2022 20:16:40 +1000 Subject: [PATCH 17/25] Add `isCompilerOptionEnabled` --- .../eslint-plugin/src/rules/dot-notation.ts | 5 +- packages/type-utils/src/index.ts | 1 + .../type-utils/src/isCompilerOptionEnabled.ts | 71 +++++++++++++++++++ 3 files changed, 74 insertions(+), 3 deletions(-) create mode 100644 packages/type-utils/src/isCompilerOptionEnabled.ts 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/type-utils/src/index.ts b/packages/type-utils/src/index.ts index 1b37132aeab2..d31a20d775ee 100644 --- a/packages/type-utils/src/index.ts +++ b/packages/type-utils/src/index.ts @@ -13,6 +13,7 @@ export * from './isAssignmentKind'; export * from './isBinaryExpression'; export * from './isBindableObjectDefinePropertyCall'; export * from './isCallExpression'; +export * from './isCompilerOptionEnabled'; export * from './isConditionalType'; export * from './isConstAssertion'; export * from './isEntityNameExpression'; 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; +} From cb5f1396ec1e47ae02432d53125e91944c9e8426 Mon Sep 17 00:00:00 2001 From: varwasabi Date: Sun, 25 Sep 2022 20:54:48 +1000 Subject: [PATCH 18/25] Remove various usages of tsutils --- .../src/rules/no-confusing-void-expression.ts | 4 +-- .../src/rules/no-floating-promises.ts | 8 ++--- .../src/rules/no-meaningless-void-operator.ts | 4 +-- .../src/rules/no-misused-promises.ts | 19 ++++++------ .../rules/no-redundant-type-constituents.ts | 15 ++++++---- .../no-unnecessary-boolean-literal-compare.ts | 9 ++---- .../src/rules/no-unnecessary-condition.ts | 12 ++++---- .../rules/no-unnecessary-type-assertion.ts | 2 +- .../non-nullable-type-assertion-style.ts | 14 +++++---- .../src/rules/prefer-regexp-exec.ts | 4 +-- .../src/rules/strict-boolean-expressions.ts | 29 ++++++++++--------- .../src/rules/switch-exhaustiveness-check.ts | 2 +- packages/type-utils/src/index.ts | 1 + .../type-utils/src/isBooleanLiteralType.ts | 4 +-- packages/type-utils/src/isTypeReadonly.ts | 3 +- 15 files changed, 67 insertions(+), 63 deletions(-) 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-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-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 59033fd970f6..b3cad5936333 100644 --- a/packages/eslint-plugin/src/rules/no-misused-promises.ts +++ b/packages/eslint-plugin/src/rules/no-misused-promises.ts @@ -1,10 +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'; @@ -414,7 +415,7 @@ 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))) { + for (const subType of unionTypeParts(checker.getApparentType(type))) { if (isThenableType(checker, node, subType)) { return true; } @@ -430,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 @@ -444,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 && @@ -482,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; } @@ -504,7 +505,7 @@ 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 = isCallExpression(node) ? subType.getCallSignatures() @@ -560,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; } @@ -579,7 +580,7 @@ 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(); @@ -589,7 +590,7 @@ function isVoidReturningFunctionType( 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..e75a3b78f567 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 { isTypeFlagSet } 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'; @@ -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 25b310fe7a82..fa400cea75e7 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts @@ -1,13 +1,11 @@ -import { isStrictCompilerOptionEnabled } from '@typescript-eslint/type-utils'; -import type { TSESTree } from '@typescript-eslint/utils'; -import { AST_NODE_TYPES, AST_TOKEN_TYPES } from '@typescript-eslint/utils'; import { - getCallSignaturesOfType, isBooleanLiteralType, - isFalsyType, - 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 { getCallSignaturesOfType, isFalsyType, isLiteralType } from 'tsutils'; import * as ts from 'typescript'; import { 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 86ab439629dd..4410cab83da8 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts @@ -2,11 +2,11 @@ import { isObjectFlagSet, isObjectType, isStrictCompilerOptionEnabled, + isTypeFlagSet, isVariableDeclaration, } from '@typescript-eslint/type-utils'; import type { TSESTree } from '@typescript-eslint/utils'; import { AST_NODE_TYPES } from '@typescript-eslint/utils'; -import { isTypeFlagSet } from 'tsutils'; import * as ts from 'typescript'; import * as util from '../util'; 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-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/strict-boolean-expressions.ts b/packages/eslint-plugin/src/rules/strict-boolean-expressions.ts index 4935180b5256..1fa73a7bb11b 100644 --- a/packages/eslint-plugin/src/rules/strict-boolean-expressions.ts +++ b/packages/eslint-plugin/src/rules/strict-boolean-expressions.ts @@ -1,7 +1,11 @@ -import { isStrictCompilerOptionEnabled } from '@typescript-eslint/type-utils'; +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'; @@ -261,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 && @@ -765,7 +769,7 @@ export default util.createRule({ if ( types.some(type => - tsutils.isTypeFlagSet( + isTypeFlagSet( type, ts.TypeFlags.Null | ts.TypeFlags.Undefined | ts.TypeFlags.VoidLike, ), @@ -774,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) { @@ -790,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) { @@ -802,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)) { @@ -818,7 +819,7 @@ export default util.createRule({ if ( types.some( type => - !tsutils.isTypeFlagSet( + !isTypeFlagSet( type, ts.TypeFlags.Null | ts.TypeFlags.Undefined | @@ -850,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/index.ts b/packages/type-utils/src/index.ts index d31a20d775ee..b3cae85cc3b7 100644 --- a/packages/type-utils/src/index.ts +++ b/packages/type-utils/src/index.ts @@ -12,6 +12,7 @@ export * from './getWellKnownSymbolPropertyOfType'; export * from './isAssignmentKind'; export * from './isBinaryExpression'; export * from './isBindableObjectDefinePropertyCall'; +export * from './isBooleanLiteralType'; export * from './isCallExpression'; export * from './isCompilerOptionEnabled'; export * from './isConditionalType'; diff --git a/packages/type-utils/src/isBooleanLiteralType.ts b/packages/type-utils/src/isBooleanLiteralType.ts index 294b5438d486..a633a5896708 100644 --- a/packages/type-utils/src/isBooleanLiteralType.ts +++ b/packages/type-utils/src/isBooleanLiteralType.ts @@ -6,7 +6,7 @@ import { isTypeFlagSet } from './isTypeFlagSet'; export function isBooleanLiteralType(type: Type, literal: boolean): boolean { return ( isTypeFlagSet(type, TypeFlags.BooleanLiteral) && - (<{ intrinsicName: string }>(<{}>type)).intrinsicName === - (literal ? 'true' : 'false') + (<{ intrinsicName: string }>(>(type))) + .intrinsicName === (literal ? 'true' : 'false') ); } diff --git a/packages/type-utils/src/isTypeReadonly.ts b/packages/type-utils/src/isTypeReadonly.ts index f96eea9f58f5..491f55b3ea79 100644 --- a/packages/type-utils/src/isTypeReadonly.ts +++ b/packages/type-utils/src/isTypeReadonly.ts @@ -1,5 +1,4 @@ import { ESLintUtils } from '@typescript-eslint/utils'; -import { isUnionType, unionTypeParts } from 'tsutils'; import * as ts from 'typescript'; import { isConditionalType } from './isConditionalType'; @@ -7,7 +6,9 @@ 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 */ From b6f1705e8472d0f4b37d703d6d43b4f4ee8d92bd Mon Sep 17 00:00:00 2001 From: varwasabi Date: Sun, 25 Sep 2022 20:57:29 +1000 Subject: [PATCH 19/25] Add `isExpression` --- .../src/rules/no-unsafe-return.ts | 8 +-- packages/type-utils/src/index.ts | 1 + packages/type-utils/src/isExpression.ts | 54 +++++++++++++++++++ 3 files changed, 60 insertions(+), 3 deletions(-) create mode 100644 packages/type-utils/src/isExpression.ts diff --git a/packages/eslint-plugin/src/rules/no-unsafe-return.ts b/packages/eslint-plugin/src/rules/no-unsafe-return.ts index 93a240649151..945b8e1538a2 100644 --- a/packages/eslint-plugin/src/rules/no-unsafe-return.ts +++ b/packages/eslint-plugin/src/rules/no-unsafe-return.ts @@ -1,7 +1,9 @@ -import { isStrictCompilerOptionEnabled } from '@typescript-eslint/type-utils'; +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'; @@ -83,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/type-utils/src/index.ts b/packages/type-utils/src/index.ts index b3cae85cc3b7..296723abd33d 100644 --- a/packages/type-utils/src/index.ts +++ b/packages/type-utils/src/index.ts @@ -19,6 +19,7 @@ export * from './isConditionalType'; export * from './isConstAssertion'; export * from './isEntityNameExpression'; export * from './isEnumMember'; +export * from './isExpression'; export * from './isFlagSet'; export * from './isInConstContext'; export * from './isIntersectionType'; 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; + } +} From e11c028313a1ce9b2ec1e73cab17909988441cd5 Mon Sep 17 00:00:00 2001 From: varwasabi Date: Sun, 25 Sep 2022 21:00:10 +1000 Subject: [PATCH 20/25] Remove tsutils import from `CUSTOM_RULES.md` --- docs/development/CUSTOM_RULES.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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, From e324b3f5e9b96b951ebfaef1c55784da5548274c Mon Sep 17 00:00:00 2001 From: varwasabi Date: Sun, 25 Sep 2022 21:01:52 +1000 Subject: [PATCH 21/25] Add `isLiteralType` --- .../src/rules/no-unnecessary-condition.ts | 3 ++- packages/type-utils/src/index.ts | 1 + packages/type-utils/src/isLiteralType.ts | 10 ++++++++++ 3 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 packages/type-utils/src/isLiteralType.ts diff --git a/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts b/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts index fa400cea75e7..976b44036709 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts @@ -1,11 +1,12 @@ import { isBooleanLiteralType, + isLiteralType, isStrictCompilerOptionEnabled, unionTypeParts, } from '@typescript-eslint/type-utils'; import type { TSESTree } from '@typescript-eslint/utils'; import { AST_NODE_TYPES, AST_TOKEN_TYPES } from '@typescript-eslint/utils'; -import { getCallSignaturesOfType, isFalsyType, isLiteralType } from 'tsutils'; +import { getCallSignaturesOfType, isFalsyType } from 'tsutils'; import * as ts from 'typescript'; import { diff --git a/packages/type-utils/src/index.ts b/packages/type-utils/src/index.ts index 296723abd33d..6fdfce233c39 100644 --- a/packages/type-utils/src/index.ts +++ b/packages/type-utils/src/index.ts @@ -24,6 +24,7 @@ export * from './isFlagSet'; export * from './isInConstContext'; export * from './isIntersectionType'; export * from './isJsxExpression'; +export * from './isLiteralType'; export * from './isModifierFlagSet'; export * from './isNewExpression'; export * from './isNodeFlagSet'; 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 + ); +} From 5661313b35cd329f657b0de557ef746da3418aa3 Mon Sep 17 00:00:00 2001 From: varwasabi Date: Sun, 25 Sep 2022 21:03:18 +1000 Subject: [PATCH 22/25] Add `isFalsyType` --- .../src/rules/no-unnecessary-condition.ts | 3 ++- packages/type-utils/src/index.ts | 1 + packages/type-utils/src/isFalsyType.ts | 18 ++++++++++++++++++ 3 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 packages/type-utils/src/isFalsyType.ts diff --git a/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts b/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts index 976b44036709..f5f072dc013c 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts @@ -1,12 +1,13 @@ import { isBooleanLiteralType, + isFalsyType, isLiteralType, isStrictCompilerOptionEnabled, unionTypeParts, } from '@typescript-eslint/type-utils'; import type { TSESTree } from '@typescript-eslint/utils'; import { AST_NODE_TYPES, AST_TOKEN_TYPES } from '@typescript-eslint/utils'; -import { getCallSignaturesOfType, isFalsyType } from 'tsutils'; +import { getCallSignaturesOfType } from 'tsutils'; import * as ts from 'typescript'; import { diff --git a/packages/type-utils/src/index.ts b/packages/type-utils/src/index.ts index 6fdfce233c39..4e9b6e3aba50 100644 --- a/packages/type-utils/src/index.ts +++ b/packages/type-utils/src/index.ts @@ -20,6 +20,7 @@ export * from './isConstAssertion'; export * from './isEntityNameExpression'; export * from './isEnumMember'; export * from './isExpression'; +export * from './isFalsyType'; export * from './isFlagSet'; export * from './isInConstContext'; export * from './isIntersectionType'; 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); +} From 81cdc8889ef11a9f80af93bed40b7e5e7969c588 Mon Sep 17 00:00:00 2001 From: varwasabi Date: Sun, 25 Sep 2022 21:05:28 +1000 Subject: [PATCH 23/25] Add `getCallSignaturesOfType` --- .../src/rules/no-unnecessary-condition.ts | 2 +- .../type-utils/src/getCallSignaturesOfType.ts | 34 +++++++++++++++++++ packages/type-utils/src/index.ts | 1 + 3 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 packages/type-utils/src/getCallSignaturesOfType.ts diff --git a/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts b/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts index f5f072dc013c..f17990252db7 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts @@ -1,4 +1,5 @@ import { + getCallSignaturesOfType, isBooleanLiteralType, isFalsyType, isLiteralType, @@ -7,7 +8,6 @@ import { } from '@typescript-eslint/type-utils'; import type { TSESTree } from '@typescript-eslint/utils'; import { AST_NODE_TYPES, AST_TOKEN_TYPES } from '@typescript-eslint/utils'; -import { getCallSignaturesOfType } from 'tsutils'; import * as ts from 'typescript'; import { 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/index.ts b/packages/type-utils/src/index.ts index 4e9b6e3aba50..804cc73a9006 100644 --- a/packages/type-utils/src/index.ts +++ b/packages/type-utils/src/index.ts @@ -1,4 +1,5 @@ export * from './containsAllTypesByName'; +export * from './getCallSignaturesOfType'; export * from './getConstrainedTypeAtLocation'; export * from './getContextualType'; export * from './getDeclaration'; From 8457217dfe968719ad4fa5bae0f8567c2843897a Mon Sep 17 00:00:00 2001 From: varwasabi Date: Sun, 25 Sep 2022 21:46:16 +1000 Subject: [PATCH 24/25] Add `isFunctionScopeBoundary` --- .../src/rules/prefer-readonly.ts | 7 ++-- packages/type-utils/src/index.ts | 1 + .../type-utils/src/isBooleanLiteralType.ts | 4 +- .../type-utils/src/isFunctionScopeBoundary.ts | 39 +++++++++++++++++++ packages/type-utils/src/isTypeFlagSet.ts | 24 ++---------- 5 files changed, 50 insertions(+), 25 deletions(-) create mode 100644 packages/type-utils/src/isFunctionScopeBoundary.ts diff --git a/packages/eslint-plugin/src/rules/prefer-readonly.ts b/packages/eslint-plugin/src/rules/prefer-readonly.ts index c977f6edcabc..0cfe7f075f52 100644 --- a/packages/eslint-plugin/src/rules/prefer-readonly.ts +++ b/packages/eslint-plugin/src/rules/prefer-readonly.ts @@ -1,6 +1,8 @@ +import type { ScopeBoundary } from '@typescript-eslint/type-utils'; import { isAssignmentKind, isBinaryExpression, + isFunctionScopeBoundary, isIntersectionType, isModifierFlagSet, isObjectFlagSet, @@ -8,7 +10,6 @@ import { } 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'; @@ -143,7 +144,7 @@ export default util.createRule({ | TSESTree.FunctionDeclaration | TSESTree.FunctionExpression | TSESTree.MethodDefinition, - ): boolean | tsutils.ScopeBoundary { + ): boolean | ScopeBoundary { if (classScopeStack.length === 0) { return false; } @@ -153,7 +154,7 @@ export default util.createRule({ return false; } - return tsutils.isFunctionScopeBoundary(tsNode); + return isFunctionScopeBoundary(tsNode); } function getEsNodesFromViolatingNode( diff --git a/packages/type-utils/src/index.ts b/packages/type-utils/src/index.ts index 804cc73a9006..2ab09b258323 100644 --- a/packages/type-utils/src/index.ts +++ b/packages/type-utils/src/index.ts @@ -23,6 +23,7 @@ export * from './isEnumMember'; export * from './isExpression'; export * from './isFalsyType'; export * from './isFlagSet'; +export * from './isFunctionScopeBoundary'; export * from './isInConstContext'; export * from './isIntersectionType'; export * from './isJsxExpression'; diff --git a/packages/type-utils/src/isBooleanLiteralType.ts b/packages/type-utils/src/isBooleanLiteralType.ts index a633a5896708..294b5438d486 100644 --- a/packages/type-utils/src/isBooleanLiteralType.ts +++ b/packages/type-utils/src/isBooleanLiteralType.ts @@ -6,7 +6,7 @@ import { isTypeFlagSet } from './isTypeFlagSet'; export function isBooleanLiteralType(type: Type, literal: boolean): boolean { return ( isTypeFlagSet(type, TypeFlags.BooleanLiteral) && - (<{ intrinsicName: string }>(>(type))) - .intrinsicName === (literal ? 'true' : 'false') + (<{ intrinsicName: string }>(<{}>type)).intrinsicName === + (literal ? 'true' : 'false') ); } 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/isTypeFlagSet.ts b/packages/type-utils/src/isTypeFlagSet.ts index 84e6c7228156..39aa22ac01e6 100644 --- a/packages/type-utils/src/isTypeFlagSet.ts +++ b/packages/type-utils/src/isTypeFlagSet.ts @@ -1,22 +1,6 @@ -import type { Type } from 'typescript'; -import { TypeFlags } from 'typescript'; +import type { Type, TypeFlags } from 'typescript'; -import { getTypeFlags } from './getTypeFlags'; +import { isFlagSet } from './isFlagSet'; -/** - * 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; -} +export const isTypeFlagSet: (type: Type, flag: TypeFlags) => boolean = + isFlagSet; From eb35908557508aaa6c994bf5d59ab8baf6a09152 Mon Sep 17 00:00:00 2001 From: varwasabi Date: Sun, 25 Sep 2022 22:20:52 +1000 Subject: [PATCH 25/25] Use original isTypeFlagSet --- .../no-unnecessary-boolean-literal-compare.ts | 2 +- packages/type-utils/src/getTypeFlags.ts | 2 -- .../type-utils/src/isBooleanLiteralType.ts | 6 ++--- packages/type-utils/src/isTypeFlagSet.ts | 24 +++++++++++++++---- 4 files changed, 24 insertions(+), 10 deletions(-) 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 e75a3b78f567..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 { isTypeFlagSet } from '@typescript-eslint/type-utils'; import type { TSESTree } from '@typescript-eslint/utils'; import { AST_NODE_TYPES } from '@typescript-eslint/utils'; +import { isTypeFlagSet } from 'tsutils'; import * as ts from 'typescript'; import * as util from '../util'; diff --git a/packages/type-utils/src/getTypeFlags.ts b/packages/type-utils/src/getTypeFlags.ts index 76d4c37d202f..32279b57e0ef 100644 --- a/packages/type-utils/src/getTypeFlags.ts +++ b/packages/type-utils/src/getTypeFlags.ts @@ -7,10 +7,8 @@ import { unionTypeParts } from './unionTypeParts'; */ 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/isBooleanLiteralType.ts b/packages/type-utils/src/isBooleanLiteralType.ts index 294b5438d486..b9998bab62fb 100644 --- a/packages/type-utils/src/isBooleanLiteralType.ts +++ b/packages/type-utils/src/isBooleanLiteralType.ts @@ -1,12 +1,12 @@ import type { Type } from 'typescript'; import { TypeFlags } from 'typescript'; -import { isTypeFlagSet } from './isTypeFlagSet'; +import { isTypeFlagSet } from './typeFlagUtils'; export function isBooleanLiteralType(type: Type, literal: boolean): boolean { return ( isTypeFlagSet(type, TypeFlags.BooleanLiteral) && - (<{ intrinsicName: string }>(<{}>type)).intrinsicName === - (literal ? 'true' : 'false') + (<{ intrinsicName: string }>(>(type))) + .intrinsicName === (literal ? 'true' : 'false') ); } diff --git a/packages/type-utils/src/isTypeFlagSet.ts b/packages/type-utils/src/isTypeFlagSet.ts index 39aa22ac01e6..84e6c7228156 100644 --- a/packages/type-utils/src/isTypeFlagSet.ts +++ b/packages/type-utils/src/isTypeFlagSet.ts @@ -1,6 +1,22 @@ -import type { Type, TypeFlags } from 'typescript'; +import type { Type } from 'typescript'; +import { TypeFlags } from 'typescript'; -import { isFlagSet } from './isFlagSet'; +import { getTypeFlags } from './getTypeFlags'; -export const isTypeFlagSet: (type: Type, flag: TypeFlags) => boolean = - isFlagSet; +/** + * 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; +}