From d7bcdc670131d6dd11f8f80ab905cb4eac25f530 Mon Sep 17 00:00:00 2001 From: "typescript-eslint[bot]" <53356952+typescript-eslint[bot]@users.noreply.github.com> Date: Fri, 13 Dec 2024 14:31:03 +1030 Subject: [PATCH 01/15] add getConstraintTypeInfoAtLocation --- packages/type-utils/package.json | 1 + .../src/getConstrainedTypeAtLocation.ts | 66 ++++ .../getConstrainedTypeAtLocation.test.ts | 342 +++++++++++++++--- yarn.lock | 12 + 4 files changed, 376 insertions(+), 45 deletions(-) diff --git a/packages/type-utils/package.json b/packages/type-utils/package.json index c46b5b9421a6..1471e5000dc2 100644 --- a/packages/type-utils/package.json +++ b/packages/type-utils/package.json @@ -58,6 +58,7 @@ "devDependencies": { "@jest/types": "29.6.3", "@typescript-eslint/parser": "8.18.0", + "@typescript/vfs": "*", "ajv": "^6.12.6", "downlevel-dts": "*", "jest": "29.7.0", diff --git a/packages/type-utils/src/getConstrainedTypeAtLocation.ts b/packages/type-utils/src/getConstrainedTypeAtLocation.ts index cbd332f98da3..0c9eb4ed35cc 100644 --- a/packages/type-utils/src/getConstrainedTypeAtLocation.ts +++ b/packages/type-utils/src/getConstrainedTypeAtLocation.ts @@ -4,8 +4,18 @@ import type { } from '@typescript-eslint/typescript-estree'; import type * as ts from 'typescript'; +import * as tsApiUtils from 'ts-api-utils'; + /** * Resolves the given node's type. Will resolve to the type's generic constraint, if it has one. + * + * If the type is generic and does _not_ have a constraint, the type will be + * returned as-is, rather than returning an `unknown` type. This can be checked + * for by checking for the type flag ts.TypeFlags.TypeParameter. + * + * @see https://github.com/typescript-eslint/typescript-eslint/issues/10438 + * + * @deprecated Use getConstraintTypeInfoAtLocation instead. */ export function getConstrainedTypeAtLocation( services: ParserServicesWithTypeInformation, @@ -18,3 +28,59 @@ export function getConstrainedTypeAtLocation( return constrained ?? nodeType; } + +export interface ConstraintTypeInfoBase { + constraintType: ts.Type | undefined; + isTypeParameter: boolean; + type: ts.Type; +} + +export interface ConstraintTypeInfoUnconstrained + extends ConstraintTypeInfoBase { + constraintType: undefined; + isTypeParameter: true; + type: ts.TypeParameter; +} + +export interface ConstraintTypeInfoConstrained extends ConstraintTypeInfoBase { + constraintType: ts.Type; + isTypeParameter: true; + type: ts.TypeParameter; +} + +export interface ConstraintTypeInfoNonGeneric extends ConstraintTypeInfoBase { + constraintType: ts.Type; + isTypeParameter: false; + type: ts.Type; +} + +export type ConstraintTypeInfo = + | ConstraintTypeInfoConstrained + | ConstraintTypeInfoNonGeneric + | ConstraintTypeInfoUnconstrained; + +/** + * Resolves the given node's type, and returns info about whether it is a generic or ordinary type. + * + * Successor to getConstrainedTypeAtLocation due to https://github.com/typescript-eslint/typescript-eslint/issues/10438 + */ +export function getConstraintTypeInfoAtLocation( + services: ParserServicesWithTypeInformation, + node: TSESTree.Node, +): ConstraintTypeInfo { + const type = services.getTypeAtLocation(node); + if (tsApiUtils.isTypeParameter(type)) { + const checker = services.program.getTypeChecker(); + const constraintType = checker.getBaseConstraintOfType(type); + return { + constraintType, + isTypeParameter: true, + type, + }; + } + return { + constraintType: type, + isTypeParameter: false, + type, + }; +} diff --git a/packages/type-utils/tests/getConstrainedTypeAtLocation.test.ts b/packages/type-utils/tests/getConstrainedTypeAtLocation.test.ts index 592c45b1520b..e6135ed897d7 100644 --- a/packages/type-utils/tests/getConstrainedTypeAtLocation.test.ts +++ b/packages/type-utils/tests/getConstrainedTypeAtLocation.test.ts @@ -1,57 +1,309 @@ import type { TSESTree } from '@typescript-eslint/types'; import type { ParserServicesWithTypeInformation } from '@typescript-eslint/typescript-estree'; -import type * as ts from 'typescript'; - -import { getConstrainedTypeAtLocation } from '../src'; - -const node = {} as TSESTree.Node; - -const mockType = (): ts.Type => { - return {} as ts.Type; -}; - -const mockServices = ({ - baseConstraintOfType, - typeAtLocation, -}: { - baseConstraintOfType?: ts.Type; - typeAtLocation: ts.Type; -}): ParserServicesWithTypeInformation => { - const typeChecker = { - getBaseConstraintOfType: (_: ts.Type) => baseConstraintOfType, - } as ts.TypeChecker; - const program = { - getTypeChecker: () => typeChecker, - } as ts.Program; + +import * as tsvfs from '@typescript/vfs'; +import * as tsutils from 'ts-api-utils'; +import * as ts from 'typescript'; + +import { + getConstrainedTypeAtLocation, + getConstraintTypeInfoAtLocation, + isTypeUnknownType, +} from '../src'; + +/** + * Creates a dummy value that the type system will treat as if it's a TSESTree.Node. + */ +function mockEstreeNode(): TSESTree.Node { + // this should never actually be used. + return null!; +} + +/** + * Creates a mock of the mock ParserServicesWithTypeInformation object. + * + * Should be provided with a real type checker. `getTypeAtLocation` will always + * return the type of the provided node. + */ +function mockServices( + checker: ts.TypeChecker, + tsNode: ts.Node, +): ParserServicesWithTypeInformation { + return { + getTypeAtLocation: () => checker.getTypeAtLocation(tsNode), + // @ts-expect-error -- mocking + program: { + getTypeChecker: () => checker, + }, + }; +} + +interface SourceFileAndTypeChecker { + sourceFile: ts.SourceFile; + typeChecker: ts.TypeChecker; +} + +// copied with <3 from https://github.com/JoshuaKGoldberg/ts-api-utils/blob/8c747294f24d0adca8817f4028832b855d907b5b/src/test/utils.ts#L41 +function createSourceFileAndTypeChecker( + sourceText: string, + fileName = 'file.tsx', +): SourceFileAndTypeChecker { + const compilerOptions: ts.CompilerOptions = { + lib: ['ES2018'], + target: ts.ScriptTarget.ES2018, + }; + + const fsMap = tsvfs.createDefaultMapFromNodeModules(compilerOptions, ts); + fsMap.set(fileName, sourceText); + + const system = tsvfs.createSystem(fsMap); + const env = tsvfs.createVirtualTypeScriptEnvironment( + system, + [fileName], + ts, + compilerOptions, + ); + + const program = env.languageService.getProgram(); + if (program == null) { + throw new Error('Failed to get program.'); + } return { - getTypeAtLocation: (_: TSESTree.Node) => typeAtLocation, - program, - } as ParserServicesWithTypeInformation; -}; + sourceFile: program.getSourceFile(fileName)!, + typeChecker: program.getTypeChecker(), + }; +} describe('getConstrainedTypeAtLocation', () => { - describe('when the node has a generic constraint', () => { - it('returns the generic constraint type', () => { - const typeAtLocation = mockType(); - const baseConstraintOfType = mockType(); - const services = mockServices({ - baseConstraintOfType, - typeAtLocation, - }); - - expect(getConstrainedTypeAtLocation(services, node)).toBe( - baseConstraintOfType, + // See https://github.com/typescript-eslint/typescript-eslint/issues/10438 + // eslint-disable-next-line jest/no-disabled-tests -- known issue. + it.skip('returns unknown for unconstrained generic', () => { + const sourceCode = ` +function foo(x: T); + `; + + const { sourceFile, typeChecker } = + createSourceFileAndTypeChecker(sourceCode); + + const functionNode = sourceFile.statements.at(-1) as ts.FunctionDeclaration; + const parameterNode = functionNode.parameters[0]; + + const constraintAtLocation = getConstrainedTypeAtLocation( + mockServices(typeChecker, parameterNode), + mockEstreeNode(), + ); + + expect(tsutils.isTypeParameter(constraintAtLocation)).toBe(false); + // Requires https://github.com/microsoft/TypeScript/issues/60475 to solve. + expect(isTypeUnknownType(constraintAtLocation)).toBe(true); + }); + + it('returns unknown for extends unknown', () => { + const sourceCode = ` +function foo(x: T); + `; + + const { sourceFile, typeChecker } = + createSourceFileAndTypeChecker(sourceCode); + + const functionNode = sourceFile.statements.at(-1) as ts.FunctionDeclaration; + const parameterNode = functionNode.parameters[0]; + + const constraintAtLocation = getConstrainedTypeAtLocation( + mockServices(typeChecker, parameterNode), + mockEstreeNode(), + ); + + expect(tsutils.isTypeParameter(constraintAtLocation)).toBe(false); + expect(tsutils.isIntrinsicUnknownType(constraintAtLocation)).toBe(true); + }); + + it('returns unknown for extends any', () => { + const sourceCode = ` +function foo(x: T); + `; + + const { sourceFile, typeChecker } = + createSourceFileAndTypeChecker(sourceCode); + + const functionNode = sourceFile.statements.at(-1) as ts.FunctionDeclaration; + const parameterNode = functionNode.parameters[0]; + + const constraintAtLocation = getConstrainedTypeAtLocation( + mockServices(typeChecker, parameterNode), + mockEstreeNode(), + ); + + expect(tsutils.isTypeParameter(constraintAtLocation)).toBe(false); + expect(tsutils.isIntrinsicUnknownType(constraintAtLocation)).toBe(true); + }); + + it('returns string for extends string', () => { + const sourceCode = ` +function foo(x: T); + `; + + const { sourceFile, typeChecker } = + createSourceFileAndTypeChecker(sourceCode); + + const functionNode = sourceFile.statements.at(-1) as ts.FunctionDeclaration; + const parameterNode = functionNode.parameters[0]; + + const constraintAtLocation = getConstrainedTypeAtLocation( + mockServices(typeChecker, parameterNode), + mockEstreeNode(), + ); + + expect(tsutils.isTypeParameter(constraintAtLocation)).toBe(false); + expect(tsutils.isIntrinsicStringType(constraintAtLocation)).toBe(true); + }); + + it('returns string for non-generic string', () => { + const sourceCode = ` +function foo(x: string); + `; + + const { sourceFile, typeChecker } = + createSourceFileAndTypeChecker(sourceCode); + + const functionNode = sourceFile.statements.at(-1) as ts.FunctionDeclaration; + const parameterNode = functionNode.parameters[0]; + + const constraintAtLocation = getConstrainedTypeAtLocation( + mockServices(typeChecker, parameterNode), + mockEstreeNode(), + ); + + expect(tsutils.isTypeParameter(constraintAtLocation)).toBe(false); + expect(tsutils.isIntrinsicStringType(constraintAtLocation)).toBe(true); + }); +}); + +describe('getConstraintTypeInfoAtLocation', () => { + it('returns undefined for unconstrained generic', () => { + const sourceCode = ` +function foo(x: T); + `; + + const { sourceFile, typeChecker } = + createSourceFileAndTypeChecker(sourceCode); + + const functionNode = sourceFile.statements.at(-1) as ts.FunctionDeclaration; + const parameterNode = functionNode.parameters[0]; + const parameterType = typeChecker.getTypeAtLocation(parameterNode); + + const { constraintType, isTypeParameter, type } = + getConstraintTypeInfoAtLocation( + mockServices(typeChecker, parameterNode), + mockEstreeNode(), ); - }); + + // ideally one day we'll be able to change this to assert that it be the intrinsic unknown type. + // Requires https://github.com/microsoft/TypeScript/issues/60475 + expect(constraintType).toBeUndefined(); + expect(isTypeParameter).toBe(true); + expect(type).toBe(parameterType); }); - describe('when the node does not have a generic constraint', () => { - it('returns the node type', () => { - const typeAtLocation = mockType(); - const services = mockServices({ typeAtLocation }); + it('returns unknown for extends unknown', () => { + const sourceCode = ` +function foo(x: T); + `; + + const { sourceFile, typeChecker } = + createSourceFileAndTypeChecker(sourceCode); + + const functionNode = sourceFile.statements.at(-1) as ts.FunctionDeclaration; + const parameterNode = functionNode.parameters[0]; + const parameterType = typeChecker.getTypeAtLocation(parameterNode); + + const { constraintType, isTypeParameter, type } = + getConstraintTypeInfoAtLocation( + mockServices(typeChecker, parameterNode), + mockEstreeNode(), + ); + + expect(constraintType).toBeDefined(); + expect(tsutils.isIntrinsicUnknownType(constraintType!)).toBe(true); + expect(tsutils.isTypeParameter(constraintType!)).toBe(false); + expect(isTypeParameter).toBe(true); + expect(type).toBe(parameterType); + }); + + it('returns unknown for extends any', () => { + const sourceCode = ` +function foo(x: T); + `; + + const { sourceFile, typeChecker } = + createSourceFileAndTypeChecker(sourceCode); + + const functionNode = sourceFile.statements.at(-1) as ts.FunctionDeclaration; + const parameterNode = functionNode.parameters[0]; + const parameterType = typeChecker.getTypeAtLocation(parameterNode); + + const { constraintType, isTypeParameter, type } = + getConstraintTypeInfoAtLocation( + mockServices(typeChecker, parameterNode), + mockEstreeNode(), + ); + + expect(constraintType).toBeDefined(); + expect(tsutils.isIntrinsicUnknownType(constraintType!)).toBe(true); + expect(tsutils.isTypeParameter(constraintType!)).toBe(false); + expect(isTypeParameter).toBe(true); + expect(type).toBe(parameterType); + }); + + it('returns string for extends string', () => { + const sourceCode = ` +function foo(x: T); + `; + + const { sourceFile, typeChecker } = + createSourceFileAndTypeChecker(sourceCode); + + const functionNode = sourceFile.statements.at(-1) as ts.FunctionDeclaration; + const parameterNode = functionNode.parameters[0]; + const parameterType = typeChecker.getTypeAtLocation(parameterNode); + + const { constraintType, isTypeParameter, type } = + getConstraintTypeInfoAtLocation( + mockServices(typeChecker, parameterNode), + mockEstreeNode(), + ); + + expect(constraintType).toBeDefined(); + expect(tsutils.isIntrinsicStringType(constraintType!)).toBe(true); + expect(tsutils.isTypeParameter(constraintType!)).toBe(false); + expect(isTypeParameter).toBe(true); + expect(type).toBe(parameterType); + }); + + it('returns string for non-generic string', () => { + const sourceCode = ` +function foo(x: string); + `; + + const { sourceFile, typeChecker } = + createSourceFileAndTypeChecker(sourceCode); + + const functionNode = sourceFile.statements.at(-1) as ts.FunctionDeclaration; + const parameterNode = functionNode.parameters[0]; + const parameterType = typeChecker.getTypeAtLocation(parameterNode); + + const { constraintType, isTypeParameter, type } = + getConstraintTypeInfoAtLocation( + mockServices(typeChecker, parameterNode), + mockEstreeNode(), + ); - expect(getConstrainedTypeAtLocation(services, node)).toBe(typeAtLocation); - }); + expect(constraintType).toBeDefined(); + expect(tsutils.isIntrinsicStringType(constraintType!)).toBe(true); + expect(tsutils.isTypeParameter(constraintType!)).toBe(false); + expect(isTypeParameter).toBe(false); + expect(type).toBe(parameterType); + expect(type).toBe(constraintType); }); }); diff --git a/yarn.lock b/yarn.lock index c2dd353568c1..1e14fcf79a13 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5835,6 +5835,7 @@ __metadata: "@typescript-eslint/parser": 8.18.0 "@typescript-eslint/typescript-estree": 8.18.0 "@typescript-eslint/utils": 8.18.0 + "@typescript/vfs": "*" ajv: ^6.12.6 debug: ^4.3.4 downlevel-dts: "*" @@ -6018,6 +6019,17 @@ __metadata: languageName: unknown linkType: soft +"@typescript/vfs@npm:*": + version: 1.6.0 + resolution: "@typescript/vfs@npm:1.6.0" + dependencies: + debug: ^4.1.1 + peerDependencies: + typescript: "*" + checksum: bedc58a712689b8a130518720c8738d2c555053a5b5f0c4889eaaaaef331c650eb652a99875c62cc25c01dc228205ed0e84f5c8d3a3ff6f25f42aa509e33e38f + languageName: node + linkType: hard + "@uiw/react-shields@npm:2.0.1": version: 2.0.1 resolution: "@uiw/react-shields@npm:2.0.1" From cd976920034ea312c85cc106449e6b89b2727392 Mon Sep 17 00:00:00 2001 From: Kirk Waiblinger Date: Fri, 13 Dec 2024 16:31:57 -0700 Subject: [PATCH 02/15] revisit #10314 --- .../eslint-plugin/src/rules/await-thenable.ts | 12 ++++++++---- packages/eslint-plugin/src/rules/return-await.ts | 10 ++++++++-- .../eslint-plugin/src/util/needsToBeAwaited.ts | 15 ++++++--------- 3 files changed, 22 insertions(+), 15 deletions(-) diff --git a/packages/eslint-plugin/src/rules/await-thenable.ts b/packages/eslint-plugin/src/rules/await-thenable.ts index 6fb49227470a..d25d0224fb24 100644 --- a/packages/eslint-plugin/src/rules/await-thenable.ts +++ b/packages/eslint-plugin/src/rules/await-thenable.ts @@ -5,6 +5,7 @@ import * as tsutils from 'ts-api-utils'; import { Awaitable, createRule, + getConstraintTypeInfoAtLocation, getFixOrSuggest, getParserServices, isAwaitKeyword, @@ -51,10 +52,13 @@ export default createRule<[], MessageId>({ return { AwaitExpression(node): void { - const type = services.getTypeAtLocation(node.argument); - - const originalNode = services.esTreeNodeToTSNodeMap.get(node); - const certainty = needsToBeAwaited(checker, originalNode, type); + const awaitedNode = node.argument; + const awaitedTsNode = services.esTreeNodeToTSNodeMap.get(awaitedNode); + const certainty = needsToBeAwaited( + checker, + awaitedTsNode, + getConstraintTypeInfoAtLocation(services, awaitedNode), + ); if (certainty === Awaitable.Never) { context.report({ diff --git a/packages/eslint-plugin/src/rules/return-await.ts b/packages/eslint-plugin/src/rules/return-await.ts index 18cc6e9e420a..b0e7f9c811d3 100644 --- a/packages/eslint-plugin/src/rules/return-await.ts +++ b/packages/eslint-plugin/src/rules/return-await.ts @@ -6,6 +6,7 @@ import * as ts from 'typescript'; import { Awaitable, createRule, + getConstraintTypeInfoAtLocation, getFixOrSuggest, getParserServices, isAwaitExpression, @@ -302,8 +303,13 @@ export default createRule({ child = expression; } - const type = checker.getTypeAtLocation(child); - const certainty = needsToBeAwaited(checker, expression, type); + const childEsNode = services.tsNodeToESTreeNodeMap.get(child); + + const constraintInfo = getConstraintTypeInfoAtLocation( + services, + childEsNode, + ); + const certainty = needsToBeAwaited(checker, expression, constraintInfo); // handle awaited _non_thenables diff --git a/packages/eslint-plugin/src/util/needsToBeAwaited.ts b/packages/eslint-plugin/src/util/needsToBeAwaited.ts index e6d675cc321e..18a0317dfd65 100644 --- a/packages/eslint-plugin/src/util/needsToBeAwaited.ts +++ b/packages/eslint-plugin/src/util/needsToBeAwaited.ts @@ -1,3 +1,4 @@ +import type { ConstraintTypeInfo } from '@typescript-eslint/type-utils'; import type * as ts from 'typescript'; import { @@ -15,26 +16,22 @@ export enum Awaitable { export function needsToBeAwaited( checker: ts.TypeChecker, node: ts.Node, - type: ts.Type, + constraintTypeInfo: ConstraintTypeInfo, ): Awaitable { - // can't use `getConstrainedTypeAtLocation` directly since it's bugged for - // unconstrained generics. - const constrainedType = !tsutils.isTypeParameter(type) - ? type - : checker.getBaseConstraintOfType(type); + const { constraintType, isTypeParameter } = constraintTypeInfo; // unconstrained generic types should be treated as unknown - if (constrainedType == null) { + if (isTypeParameter && constraintType == null) { return Awaitable.May; } // `any` and `unknown` types may need to be awaited - if (isTypeAnyType(constrainedType) || isTypeUnknownType(constrainedType)) { + if (isTypeAnyType(constraintType) || isTypeUnknownType(constraintType)) { return Awaitable.May; } // 'thenable' values should always be be awaited - if (tsutils.isThenableType(checker, node, constrainedType)) { + if (tsutils.isThenableType(checker, node, constraintType)) { return Awaitable.Always; } From 3626dcbce853db26b6d4e021f00e355a6ffbe427 Mon Sep 17 00:00:00 2001 From: Kirk Waiblinger Date: Fri, 13 Dec 2024 16:51:11 -0700 Subject: [PATCH 03/15] handle #10443 --- .../no-unnecessary-boolean-literal-compare.ts | 18 +++++-- ...nnecessary-boolean-literal-compare.test.ts | 54 +++++++++++++++++++ 2 files changed, 68 insertions(+), 4 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 7a1057c83ec2..775fea070a51 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 @@ -4,7 +4,12 @@ import { AST_NODE_TYPES } from '@typescript-eslint/utils'; import * as tsutils from 'ts-api-utils'; import * as ts from 'typescript'; -import { createRule, getParserServices, isStrongPrecedenceNode } from '../util'; +import { + createRule, + getConstraintTypeInfoAtLocation, + getParserServices, + isStrongPrecedenceNode, +} from '../util'; type MessageIds = | 'comparingNullableToFalse' @@ -89,16 +94,21 @@ export default createRule({ return undefined; } - const expressionType = services.getTypeAtLocation(comparison.expression); + const { constraintType, isTypeParameter } = + getConstraintTypeInfoAtLocation(services, comparison.expression); - if (isBooleanType(expressionType)) { + if (isTypeParameter && constraintType == null) { + return undefined; + } + + if (isBooleanType(constraintType)) { return { ...comparison, expressionIsNullableBoolean: false, }; } - if (isNullableBoolean(expressionType)) { + if (isNullableBoolean(constraintType)) { return { ...comparison, expressionIsNullableBoolean: true, diff --git a/packages/eslint-plugin/tests/rules/no-unnecessary-boolean-literal-compare.test.ts b/packages/eslint-plugin/tests/rules/no-unnecessary-boolean-literal-compare.test.ts index 331212829ca3..7b3be6f395bb 100644 --- a/packages/eslint-plugin/tests/rules/no-unnecessary-boolean-literal-compare.test.ts +++ b/packages/eslint-plugin/tests/rules/no-unnecessary-boolean-literal-compare.test.ts @@ -75,6 +75,20 @@ ruleTester.run('no-unnecessary-boolean-literal-compare', rule, { }, "'false' === true;", "'true' === false;", + ` +const unconstrained: (someCondition: T) => void = someCondition => { + if (someCondition === true) { + } +}; + `, + ` +const extendsUnknown: ( + someCondition: T, +) => void = someCondition => { + if (someCondition === true) { + } +}; + `, ], invalid: [ @@ -481,5 +495,45 @@ ruleTester.run('no-unnecessary-boolean-literal-compare', rule, { } `, }, + { + code: ` +const test1: (someCondition: boolean) => void = someCondition => { + if (someCondition === true) { + } +}; + `, + errors: [ + { + line: 3, + messageId: 'direct', + }, + ], + output: ` +const test1: (someCondition: boolean) => void = someCondition => { + if (someCondition) { + } +}; + `, + }, + { + code: ` +const test2: (someCondition: T) => void = someCondition => { + if (someCondition === true) { + } +}; + `, + errors: [ + { + line: 3, + messageId: 'direct', + }, + ], + output: ` +const test2: (someCondition: T) => void = someCondition => { + if (someCondition) { + } +}; + `, + }, ], }); From bc12e04be86f7c7dd42d4f2a02f1cf51f35bf067 Mon Sep 17 00:00:00 2001 From: Kirk Waiblinger Date: Sat, 14 Dec 2024 00:05:43 -0700 Subject: [PATCH 04/15] tweak tests --- .../getConstrainedTypeAtLocation.test.ts | 132 +++++++++++++++--- 1 file changed, 109 insertions(+), 23 deletions(-) diff --git a/packages/type-utils/tests/getConstrainedTypeAtLocation.test.ts b/packages/type-utils/tests/getConstrainedTypeAtLocation.test.ts index e6135ed897d7..607b9646e02a 100644 --- a/packages/type-utils/tests/getConstrainedTypeAtLocation.test.ts +++ b/packages/type-utils/tests/getConstrainedTypeAtLocation.test.ts @@ -86,7 +86,7 @@ function foo(x: T); const { sourceFile, typeChecker } = createSourceFileAndTypeChecker(sourceCode); - const functionNode = sourceFile.statements.at(-1) as ts.FunctionDeclaration; + const functionNode = sourceFile.statements[0] as ts.FunctionDeclaration; const parameterNode = functionNode.parameters[0]; const constraintAtLocation = getConstrainedTypeAtLocation( @@ -107,7 +107,7 @@ function foo(x: T); const { sourceFile, typeChecker } = createSourceFileAndTypeChecker(sourceCode); - const functionNode = sourceFile.statements.at(-1) as ts.FunctionDeclaration; + const functionNode = sourceFile.statements[0] as ts.FunctionDeclaration; const parameterNode = functionNode.parameters[0]; const constraintAtLocation = getConstrainedTypeAtLocation( @@ -127,7 +127,7 @@ function foo(x: T); const { sourceFile, typeChecker } = createSourceFileAndTypeChecker(sourceCode); - const functionNode = sourceFile.statements.at(-1) as ts.FunctionDeclaration; + const functionNode = sourceFile.statements[0] as ts.FunctionDeclaration; const parameterNode = functionNode.parameters[0]; const constraintAtLocation = getConstrainedTypeAtLocation( @@ -147,7 +147,7 @@ function foo(x: T); const { sourceFile, typeChecker } = createSourceFileAndTypeChecker(sourceCode); - const functionNode = sourceFile.statements.at(-1) as ts.FunctionDeclaration; + const functionNode = sourceFile.statements[0] as ts.FunctionDeclaration; const parameterNode = functionNode.parameters[0]; const constraintAtLocation = getConstrainedTypeAtLocation( @@ -167,7 +167,7 @@ function foo(x: string); const { sourceFile, typeChecker } = createSourceFileAndTypeChecker(sourceCode); - const functionNode = sourceFile.statements.at(-1) as ts.FunctionDeclaration; + const functionNode = sourceFile.statements[0] as ts.FunctionDeclaration; const parameterNode = functionNode.parameters[0]; const constraintAtLocation = getConstrainedTypeAtLocation( @@ -178,6 +178,32 @@ function foo(x: string); expect(tsutils.isTypeParameter(constraintAtLocation)).toBe(false); expect(tsutils.isIntrinsicStringType(constraintAtLocation)).toBe(true); }); + + it('handles type parameter whose constraint is a constrained type parameter', () => { + const sourceCode = ` +function foo() { + function bar(x: V) { + } +} + `; + + const { sourceFile, typeChecker } = + createSourceFileAndTypeChecker(sourceCode); + + const outerFunctionNode = sourceFile + .statements[0] as ts.FunctionDeclaration; + const innerFunctionNode = outerFunctionNode.body! + .statements[0] as ts.FunctionDeclaration; + const parameterNode = innerFunctionNode.parameters[0]; + + const constraintAtLocation = getConstrainedTypeAtLocation( + mockServices(typeChecker, parameterNode), + mockEstreeNode(), + ); + + expect(tsutils.isTypeParameter(constraintAtLocation)).toBe(false); + expect(tsutils.isIntrinsicStringType(constraintAtLocation)).toBe(true); + }); }); describe('getConstraintTypeInfoAtLocation', () => { @@ -189,7 +215,7 @@ function foo(x: T); const { sourceFile, typeChecker } = createSourceFileAndTypeChecker(sourceCode); - const functionNode = sourceFile.statements.at(-1) as ts.FunctionDeclaration; + const functionNode = sourceFile.statements[0] as ts.FunctionDeclaration; const parameterNode = functionNode.parameters[0]; const parameterType = typeChecker.getTypeAtLocation(parameterNode); @@ -199,11 +225,11 @@ function foo(x: T); mockEstreeNode(), ); + expect(type).toBe(parameterType); + expect(isTypeParameter).toBe(true); // ideally one day we'll be able to change this to assert that it be the intrinsic unknown type. // Requires https://github.com/microsoft/TypeScript/issues/60475 expect(constraintType).toBeUndefined(); - expect(isTypeParameter).toBe(true); - expect(type).toBe(parameterType); }); it('returns unknown for extends unknown', () => { @@ -214,7 +240,7 @@ function foo(x: T); const { sourceFile, typeChecker } = createSourceFileAndTypeChecker(sourceCode); - const functionNode = sourceFile.statements.at(-1) as ts.FunctionDeclaration; + const functionNode = sourceFile.statements[0] as ts.FunctionDeclaration; const parameterNode = functionNode.parameters[0]; const parameterType = typeChecker.getTypeAtLocation(parameterNode); @@ -224,11 +250,11 @@ function foo(x: T); mockEstreeNode(), ); + expect(type).toBe(parameterType); + expect(isTypeParameter).toBe(true); expect(constraintType).toBeDefined(); - expect(tsutils.isIntrinsicUnknownType(constraintType!)).toBe(true); expect(tsutils.isTypeParameter(constraintType!)).toBe(false); - expect(isTypeParameter).toBe(true); - expect(type).toBe(parameterType); + expect(tsutils.isIntrinsicUnknownType(constraintType!)).toBe(true); }); it('returns unknown for extends any', () => { @@ -239,7 +265,7 @@ function foo(x: T); const { sourceFile, typeChecker } = createSourceFileAndTypeChecker(sourceCode); - const functionNode = sourceFile.statements.at(-1) as ts.FunctionDeclaration; + const functionNode = sourceFile.statements[0] as ts.FunctionDeclaration; const parameterNode = functionNode.parameters[0]; const parameterType = typeChecker.getTypeAtLocation(parameterNode); @@ -249,11 +275,11 @@ function foo(x: T); mockEstreeNode(), ); + expect(type).toBe(parameterType); + expect(isTypeParameter).toBe(true); expect(constraintType).toBeDefined(); - expect(tsutils.isIntrinsicUnknownType(constraintType!)).toBe(true); expect(tsutils.isTypeParameter(constraintType!)).toBe(false); - expect(isTypeParameter).toBe(true); - expect(type).toBe(parameterType); + expect(tsutils.isIntrinsicUnknownType(constraintType!)).toBe(true); }); it('returns string for extends string', () => { @@ -264,7 +290,7 @@ function foo(x: T); const { sourceFile, typeChecker } = createSourceFileAndTypeChecker(sourceCode); - const functionNode = sourceFile.statements.at(-1) as ts.FunctionDeclaration; + const functionNode = sourceFile.statements[0] as ts.FunctionDeclaration; const parameterNode = functionNode.parameters[0]; const parameterType = typeChecker.getTypeAtLocation(parameterNode); @@ -274,11 +300,11 @@ function foo(x: T); mockEstreeNode(), ); + expect(type).toBe(parameterType); + expect(isTypeParameter).toBe(true); expect(constraintType).toBeDefined(); - expect(tsutils.isIntrinsicStringType(constraintType!)).toBe(true); expect(tsutils.isTypeParameter(constraintType!)).toBe(false); - expect(isTypeParameter).toBe(true); - expect(type).toBe(parameterType); + expect(tsutils.isIntrinsicStringType(constraintType!)).toBe(true); }); it('returns string for non-generic string', () => { @@ -289,7 +315,7 @@ function foo(x: string); const { sourceFile, typeChecker } = createSourceFileAndTypeChecker(sourceCode); - const functionNode = sourceFile.statements.at(-1) as ts.FunctionDeclaration; + const functionNode = sourceFile.statements[0] as ts.FunctionDeclaration; const parameterNode = functionNode.parameters[0]; const parameterType = typeChecker.getTypeAtLocation(parameterNode); @@ -299,11 +325,71 @@ function foo(x: string); mockEstreeNode(), ); + expect(type).toBe(parameterType); + expect(isTypeParameter).toBe(false); expect(constraintType).toBeDefined(); + expect(tsutils.isTypeParameter(constraintType!)).toBe(false); expect(tsutils.isIntrinsicStringType(constraintType!)).toBe(true); + expect(type).toBe(constraintType); + }); + + it('handles type parameter whose constraint is a constrained type parameter', () => { + const sourceCode = ` +function foo() { + function bar(x: V) { + } +} + `; + + const { sourceFile, typeChecker } = + createSourceFileAndTypeChecker(sourceCode); + + const outerFunctionNode = sourceFile + .statements[0] as ts.FunctionDeclaration; + const innerFunctionNode = outerFunctionNode.body! + .statements[0] as ts.FunctionDeclaration; + const parameterNode = innerFunctionNode.parameters[0]; + const parameterType = typeChecker.getTypeAtLocation(parameterNode); + + const { constraintType, isTypeParameter, type } = + getConstraintTypeInfoAtLocation( + mockServices(typeChecker, parameterNode), + mockEstreeNode(), + ); + + expect(type).toBe(parameterType); + expect(constraintType).toBeDefined(); expect(tsutils.isTypeParameter(constraintType!)).toBe(false); - expect(isTypeParameter).toBe(false); + expect(tsutils.isIntrinsicStringType(constraintType!)).toBe(true); + expect(isTypeParameter).toBe(true); + }); + + it('handles type parameter whose constraint is an unconstrained type parameter', () => { + const sourceCode = ` +function foo() { + function bar(x: V) { + } +} + `; + + const { sourceFile, typeChecker } = + createSourceFileAndTypeChecker(sourceCode); + + const outerFunctionNode = sourceFile + .statements[0] as ts.FunctionDeclaration; + const innerFunctionNode = outerFunctionNode.body! + .statements[0] as ts.FunctionDeclaration; + const parameterNode = innerFunctionNode.parameters[0]; + const parameterType = typeChecker.getTypeAtLocation(parameterNode); + + const { constraintType, isTypeParameter, type } = + getConstraintTypeInfoAtLocation( + mockServices(typeChecker, parameterNode), + mockEstreeNode(), + ); + expect(type).toBe(parameterType); - expect(type).toBe(constraintType); + expect(isTypeParameter).toBe(true); + expect(constraintType).toBeUndefined(); }); }); From f0ebffad7b10526fb1936ba7e39e1757ddd2b8cf Mon Sep 17 00:00:00 2001 From: Kirk Waiblinger Date: Sat, 14 Dec 2024 11:00:32 -0700 Subject: [PATCH 05/15] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Josh Goldberg ✨ --- packages/type-utils/src/getConstrainedTypeAtLocation.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/type-utils/src/getConstrainedTypeAtLocation.ts b/packages/type-utils/src/getConstrainedTypeAtLocation.ts index 0c9eb4ed35cc..7ff2aa4e95cb 100644 --- a/packages/type-utils/src/getConstrainedTypeAtLocation.ts +++ b/packages/type-utils/src/getConstrainedTypeAtLocation.ts @@ -15,7 +15,7 @@ import * as tsApiUtils from 'ts-api-utils'; * * @see https://github.com/typescript-eslint/typescript-eslint/issues/10438 * - * @deprecated Use getConstraintTypeInfoAtLocation instead. + * @deprecated Use {@link getConstraintTypeInfoAtLocation} instead. */ export function getConstrainedTypeAtLocation( services: ParserServicesWithTypeInformation, From 2fa6426aaed2c2179aa3c865a84ce5389f0b4695 Mon Sep 17 00:00:00 2001 From: Kirk Waiblinger Date: Sat, 14 Dec 2024 11:04:21 -0700 Subject: [PATCH 06/15] code review --- .../src/getConstrainedTypeAtLocation.ts | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/packages/type-utils/src/getConstrainedTypeAtLocation.ts b/packages/type-utils/src/getConstrainedTypeAtLocation.ts index 7ff2aa4e95cb..560b8003c1e6 100644 --- a/packages/type-utils/src/getConstrainedTypeAtLocation.ts +++ b/packages/type-utils/src/getConstrainedTypeAtLocation.ts @@ -4,7 +4,7 @@ import type { } from '@typescript-eslint/typescript-estree'; import type * as ts from 'typescript'; -import * as tsApiUtils from 'ts-api-utils'; +import * as tsutils from 'ts-api-utils'; /** * Resolves the given node's type. Will resolve to the type's generic constraint, if it has one. @@ -29,26 +29,19 @@ export function getConstrainedTypeAtLocation( return constrained ?? nodeType; } -export interface ConstraintTypeInfoBase { - constraintType: ts.Type | undefined; - isTypeParameter: boolean; - type: ts.Type; -} - -export interface ConstraintTypeInfoUnconstrained - extends ConstraintTypeInfoBase { +export interface ConstraintTypeInfoUnconstrained { constraintType: undefined; isTypeParameter: true; type: ts.TypeParameter; } -export interface ConstraintTypeInfoConstrained extends ConstraintTypeInfoBase { +export interface ConstraintTypeInfoConstrained { constraintType: ts.Type; isTypeParameter: true; type: ts.TypeParameter; } -export interface ConstraintTypeInfoNonGeneric extends ConstraintTypeInfoBase { +export interface ConstraintTypeInfoNonGeneric { constraintType: ts.Type; isTypeParameter: false; type: ts.Type; @@ -62,14 +55,14 @@ export type ConstraintTypeInfo = /** * Resolves the given node's type, and returns info about whether it is a generic or ordinary type. * - * Successor to getConstrainedTypeAtLocation due to https://github.com/typescript-eslint/typescript-eslint/issues/10438 + * Successor to {@link getConstrainedTypeAtLocation} due to https://github.com/typescript-eslint/typescript-eslint/issues/10438 */ export function getConstraintTypeInfoAtLocation( services: ParserServicesWithTypeInformation, node: TSESTree.Node, ): ConstraintTypeInfo { const type = services.getTypeAtLocation(node); - if (tsApiUtils.isTypeParameter(type)) { + if (tsutils.isTypeParameter(type)) { const checker = services.program.getTypeChecker(); const constraintType = checker.getBaseConstraintOfType(type); return { From e753c02c7cd6669ad78528bf7a1a63a4d6a82619 Mon Sep 17 00:00:00 2001 From: Kirk Waiblinger Date: Sat, 14 Dec 2024 11:55:17 -0700 Subject: [PATCH 07/15] deal with deprecated API lint failures --- .../src/rules/no-array-delete.ts | 7 +++- .../src/rules/no-base-to-string.ts | 7 +++- .../src/rules/no-confusing-void-expression.ts | 9 +++-- .../src/rules/no-for-in-array.ts | 7 +++- .../src/rules/no-unnecessary-condition.ts | 40 ++++++++++++------- .../no-unnecessary-template-expression.ts | 7 +++- .../rules/no-unnecessary-type-assertion.ts | 9 +++-- .../src/rules/no-unsafe-assignment.ts | 4 +- .../eslint-plugin/src/rules/no-unsafe-call.ts | 6 +-- .../src/rules/no-unsafe-member-access.ts | 4 +- .../src/rules/no-unsafe-return.ts | 9 +++-- .../src/rules/no-unsafe-type-assertion.ts | 6 +-- .../src/rules/no-unsafe-unary-minus.ts | 2 +- .../eslint-plugin/src/rules/prefer-find.ts | 4 +- .../src/rules/prefer-includes.ts | 7 +++- .../src/rules/prefer-reduce-type-parameter.ts | 4 +- .../src/rules/require-array-sort-compare.ts | 4 +- .../src/rules/restrict-plus-operands.ts | 4 +- .../rules/restrict-template-expressions.ts | 4 +- .../src/rules/strict-boolean-expressions.ts | 9 +++-- .../src/rules/switch-exhaustiveness-check.ts | 6 +-- packages/eslint-plugin/src/util/index.ts | 21 ++++++++++ .../util/isArrayMethodCallWithPredicate.ts | 7 +++- .../src/getConstrainedTypeAtLocation.ts | 2 +- .../getConstrainedTypeAtLocation.test.ts | 2 + 25 files changed, 128 insertions(+), 63 deletions(-) diff --git a/packages/eslint-plugin/src/rules/no-array-delete.ts b/packages/eslint-plugin/src/rules/no-array-delete.ts index a179418a7b01..c448279c82f0 100644 --- a/packages/eslint-plugin/src/rules/no-array-delete.ts +++ b/packages/eslint-plugin/src/rules/no-array-delete.ts @@ -5,7 +5,7 @@ import { AST_NODE_TYPES, AST_TOKEN_TYPES } from '@typescript-eslint/utils'; import { createRule, - getConstrainedTypeAtLocation, + DEPRECATED_getConstrainedTypeAtLocation, getParserServices, } from '../util'; @@ -58,7 +58,10 @@ export default createRule<[], MessageId>({ return; } - const type = getConstrainedTypeAtLocation(services, argument.object); + const type = DEPRECATED_getConstrainedTypeAtLocation( + services, + argument.object, + ); if (!isUnderlyingTypeArray(type)) { return; diff --git a/packages/eslint-plugin/src/rules/no-base-to-string.ts b/packages/eslint-plugin/src/rules/no-base-to-string.ts index b681f820a3b0..fc449270a0b2 100644 --- a/packages/eslint-plugin/src/rules/no-base-to-string.ts +++ b/packages/eslint-plugin/src/rules/no-base-to-string.ts @@ -6,7 +6,7 @@ import * as ts from 'typescript'; import { createRule, - getConstrainedTypeAtLocation, + DEPRECATED_getConstrainedTypeAtLocation, getParserServices, getTypeName, nullThrows, @@ -274,7 +274,10 @@ export default createRule({ node: TSESTree.Expression, ): void { const memberExpr = node.parent as TSESTree.MemberExpression; - const type = getConstrainedTypeAtLocation(services, memberExpr.object); + const type = DEPRECATED_getConstrainedTypeAtLocation( + services, + memberExpr.object, + ); checkExpressionForArrayJoin(memberExpr.object, type); }, 'CallExpression > MemberExpression.callee > Identifier[name = /^(toLocaleString|toString)$/].property'( 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 e638afc8b944..7f612248c31e 100644 --- a/packages/eslint-plugin/src/rules/no-confusing-void-expression.ts +++ b/packages/eslint-plugin/src/rules/no-confusing-void-expression.ts @@ -8,7 +8,7 @@ import type { MakeRequired } from '../util'; import { createRule, - getConstrainedTypeAtLocation, + DEPRECATED_getConstrainedTypeAtLocation, getParserServices, isClosingParenToken, isOpeningParenToken, @@ -116,7 +116,7 @@ export default createRule({ | TSESTree.CallExpression | TSESTree.TaggedTemplateExpression, ): void { - const type = getConstrainedTypeAtLocation(services, node); + const type = DEPRECATED_getConstrainedTypeAtLocation(services, node); if (!tsutils.isTypeFlagSet(type, ts.TypeFlags.VoidLike)) { // not a void expression return; @@ -420,7 +420,10 @@ export default createRule({ ? node.argument : node.body; - const type = getConstrainedTypeAtLocation(services, targetNode); + const type = DEPRECATED_getConstrainedTypeAtLocation( + services, + targetNode, + ); return tsutils.isTypeFlagSet(type, ts.TypeFlags.VoidLike); } diff --git a/packages/eslint-plugin/src/rules/no-for-in-array.ts b/packages/eslint-plugin/src/rules/no-for-in-array.ts index ec8ebe9125fb..620821fcc9df 100644 --- a/packages/eslint-plugin/src/rules/no-for-in-array.ts +++ b/packages/eslint-plugin/src/rules/no-for-in-array.ts @@ -2,7 +2,7 @@ import * as ts from 'typescript'; import { createRule, - getConstrainedTypeAtLocation, + DEPRECATED_getConstrainedTypeAtLocation, getParserServices, isTypeArrayTypeOrUnionOfArrayTypes, } from '../util'; @@ -30,7 +30,10 @@ export default createRule({ const services = getParserServices(context); const checker = services.program.getTypeChecker(); - const type = getConstrainedTypeAtLocation(services, node.right); + const type = DEPRECATED_getConstrainedTypeAtLocation( + services, + node.right, + ); if ( isTypeArrayTypeOrUnionOfArrayTypes(type, checker) || diff --git a/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts b/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts index e6eaf77190f6..b9b1fed35c84 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts @@ -6,7 +6,7 @@ import * as ts from 'typescript'; import { createRule, - getConstrainedTypeAtLocation, + DEPRECATED_getConstrainedTypeAtLocation, getParserServices, getTypeName, getTypeOfPropertyOfName, @@ -288,14 +288,14 @@ export default createRule({ } function nodeIsArrayType(node: TSESTree.Expression): boolean { - const nodeType = getConstrainedTypeAtLocation(services, node); + const nodeType = DEPRECATED_getConstrainedTypeAtLocation(services, node); return tsutils .unionTypeParts(nodeType) .some(part => checker.isArrayType(part)); } function nodeIsTupleType(node: TSESTree.Expression): boolean { - const nodeType = getConstrainedTypeAtLocation(services, node); + const nodeType = DEPRECATED_getConstrainedTypeAtLocation(services, node); return tsutils .unionTypeParts(nodeType) .some(part => checker.isTupleType(part)); @@ -379,7 +379,10 @@ export default createRule({ return checkNode(expression.right); } - const type = getConstrainedTypeAtLocation(services, expression); + const type = DEPRECATED_getConstrainedTypeAtLocation( + services, + expression, + ); // Conditional is always necessary if it involves: // `any` or `unknown` or a naked type variable @@ -411,7 +414,7 @@ export default createRule({ } function checkNodeForNullish(node: TSESTree.Expression): void { - const type = getConstrainedTypeAtLocation(services, node); + const type = DEPRECATED_getConstrainedTypeAtLocation(services, node); // Conditional is always necessary if it involves `any`, `unknown` or a naked type parameter if ( @@ -474,8 +477,11 @@ export default createRule({ right: TSESTree.Node, operator: BoolOperator, ): void { - const leftType = getConstrainedTypeAtLocation(services, left); - const rightType = getConstrainedTypeAtLocation(services, right); + const leftType = DEPRECATED_getConstrainedTypeAtLocation(services, left); + const rightType = DEPRECATED_getConstrainedTypeAtLocation( + services, + right, + ); const leftStaticValue = toStaticValue(leftType); const rightStaticValue = toStaticValue(rightType); @@ -570,7 +576,7 @@ export default createRule({ if ( allowConstantLoopConditions && tsutils.isTrueLiteralType( - getConstrainedTypeAtLocation(services, node.test), + DEPRECATED_getConstrainedTypeAtLocation(services, node.test), ) ) { return; @@ -594,7 +600,7 @@ export default createRule({ node, ); if (typeGuardAssertedArgument != null) { - const typeOfArgument = getConstrainedTypeAtLocation( + const typeOfArgument = DEPRECATED_getConstrainedTypeAtLocation( services, typeGuardAssertedArgument.argument, ); @@ -644,7 +650,7 @@ export default createRule({ // Otherwise just do type analysis on the function as a whole. const returnTypes = tsutils .getCallSignaturesOfType( - getConstrainedTypeAtLocation(services, callback), + DEPRECATED_getConstrainedTypeAtLocation(services, callback), ) .map(sig => sig.getReturnType()); /* istanbul ignore if */ if (returnTypes.length === 0) { @@ -731,12 +737,15 @@ export default createRule({ function isMemberExpressionNullableOriginFromObject( node: TSESTree.MemberExpression, ): boolean { - const prevType = getConstrainedTypeAtLocation(services, node.object); + const prevType = DEPRECATED_getConstrainedTypeAtLocation( + services, + node.object, + ); const property = node.property; if (prevType.isUnion() && isIdentifier(property)) { const isOwnNullable = prevType.types.some(type => { if (node.computed) { - const propertyType = getConstrainedTypeAtLocation( + const propertyType = DEPRECATED_getConstrainedTypeAtLocation( services, node.property, ); @@ -767,7 +776,10 @@ export default createRule({ function isCallExpressionNullableOriginFromCallee( node: TSESTree.CallExpression, ): boolean { - const prevType = getConstrainedTypeAtLocation(services, node.callee); + const prevType = DEPRECATED_getConstrainedTypeAtLocation( + services, + node.callee, + ); if (prevType.isUnion()) { const isOwnNullable = prevType.types.some(type => { @@ -781,7 +793,7 @@ export default createRule({ } function isOptionableExpression(node: TSESTree.Expression): boolean { - const type = getConstrainedTypeAtLocation(services, node); + const type = DEPRECATED_getConstrainedTypeAtLocation(services, node); const isOwnNullable = node.type === AST_NODE_TYPES.MemberExpression ? !isMemberExpressionNullableOriginFromObject(node) diff --git a/packages/eslint-plugin/src/rules/no-unnecessary-template-expression.ts b/packages/eslint-plugin/src/rules/no-unnecessary-template-expression.ts index 90ad42fb0169..0515c8dc3ef4 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-template-expression.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-template-expression.ts @@ -5,7 +5,7 @@ import * as ts from 'typescript'; import { createRule, - getConstrainedTypeAtLocation, + DEPRECATED_getConstrainedTypeAtLocation, getMovedNodeCode, getParserServices, isTypeFlagSet, @@ -51,7 +51,10 @@ export default createRule<[], MessageId>({ function isUnderlyingTypeString( expression: TSESTree.Expression, ): expression is TSESTree.Identifier | TSESTree.StringLiteral { - const type = getConstrainedTypeAtLocation(services, expression); + const type = DEPRECATED_getConstrainedTypeAtLocation( + services, + expression, + ); const isString = (t: ts.Type): boolean => { return isTypeFlagSet(t, ts.TypeFlags.StringLike); 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 c73d8717c3e1..9558313dbbf0 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts @@ -8,7 +8,7 @@ import * as ts from 'typescript'; import { createRule, - getConstrainedTypeAtLocation, + DEPRECATED_getConstrainedTypeAtLocation, getContextualType, getDeclaration, getModifiers, @@ -123,7 +123,7 @@ export default createRule({ ) { // check if the defined variable type has changed since assignment const declarationType = checker.getTypeFromTypeNode(declaration.type); - const type = getConstrainedTypeAtLocation(services, node); + const type = DEPRECATED_getConstrainedTypeAtLocation(services, node); if ( declarationType === type && // `declare`s are never narrowed, so never skip them @@ -319,7 +319,10 @@ export default createRule({ const originalNode = services.esTreeNodeToTSNodeMap.get(node); - const type = getConstrainedTypeAtLocation(services, node.expression); + const type = DEPRECATED_getConstrainedTypeAtLocation( + services, + node.expression, + ); if (!isNullableType(type)) { if ( diff --git a/packages/eslint-plugin/src/rules/no-unsafe-assignment.ts b/packages/eslint-plugin/src/rules/no-unsafe-assignment.ts index a9f1358c0c6d..06361e043bfb 100644 --- a/packages/eslint-plugin/src/rules/no-unsafe-assignment.ts +++ b/packages/eslint-plugin/src/rules/no-unsafe-assignment.ts @@ -6,7 +6,7 @@ import * as tsutils from 'ts-api-utils'; import { createRule, - getConstrainedTypeAtLocation, + DEPRECATED_getConstrainedTypeAtLocation, getContextualType, getParserServices, getThisExpression, @@ -270,7 +270,7 @@ export default createRule({ if ( thisExpression && isTypeAnyType( - getConstrainedTypeAtLocation(services, thisExpression), + DEPRECATED_getConstrainedTypeAtLocation(services, thisExpression), ) ) { messageId = 'anyAssignmentThis'; diff --git a/packages/eslint-plugin/src/rules/no-unsafe-call.ts b/packages/eslint-plugin/src/rules/no-unsafe-call.ts index 2c29caa2c9e1..0c3f228dc25b 100644 --- a/packages/eslint-plugin/src/rules/no-unsafe-call.ts +++ b/packages/eslint-plugin/src/rules/no-unsafe-call.ts @@ -4,7 +4,7 @@ import * as tsutils from 'ts-api-utils'; import { createRule, - getConstrainedTypeAtLocation, + DEPRECATED_getConstrainedTypeAtLocation, getParserServices, getThisExpression, isBuiltinSymbolLike, @@ -51,7 +51,7 @@ export default createRule<[], MessageIds>({ reportingNode: TSESTree.Node, messageId: MessageIds, ): void { - const type = getConstrainedTypeAtLocation(services, node); + const type = DEPRECATED_getConstrainedTypeAtLocation(services, node); if (isTypeAnyType(type)) { if (!isNoImplicitThis) { @@ -60,7 +60,7 @@ export default createRule<[], MessageIds>({ if ( thisExpression && isTypeAnyType( - getConstrainedTypeAtLocation(services, thisExpression), + DEPRECATED_getConstrainedTypeAtLocation(services, thisExpression), ) ) { messageId = 'unsafeCallThis'; 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 6cf16d5211e4..c6a93bb088a3 100644 --- a/packages/eslint-plugin/src/rules/no-unsafe-member-access.ts +++ b/packages/eslint-plugin/src/rules/no-unsafe-member-access.ts @@ -6,7 +6,7 @@ import * as tsutils from 'ts-api-utils'; import { createRule, - getConstrainedTypeAtLocation, + DEPRECATED_getConstrainedTypeAtLocation, getParserServices, getThisExpression, isTypeAnyType, @@ -87,7 +87,7 @@ export default createRule({ if ( thisExpression && isTypeAnyType( - getConstrainedTypeAtLocation(services, thisExpression), + DEPRECATED_getConstrainedTypeAtLocation(services, thisExpression), ) ) { messageId = 'unsafeThisMemberExpression'; diff --git a/packages/eslint-plugin/src/rules/no-unsafe-return.ts b/packages/eslint-plugin/src/rules/no-unsafe-return.ts index 838f84eff6af..e2ff9e5416b3 100644 --- a/packages/eslint-plugin/src/rules/no-unsafe-return.ts +++ b/packages/eslint-plugin/src/rules/no-unsafe-return.ts @@ -7,7 +7,7 @@ import { AnyType, createRule, discriminateAnyType, - getConstrainedTypeAtLocation, + DEPRECATED_getConstrainedTypeAtLocation, getContextualType, getParserServices, getThisExpression, @@ -68,7 +68,10 @@ export default createRule({ } // function has an explicit return type, so ensure it's a safe return - const returnNodeType = getConstrainedTypeAtLocation(services, returnNode); + const returnNodeType = DEPRECATED_getConstrainedTypeAtLocation( + services, + returnNode, + ); const functionTSNode = services.esTreeNodeToTSNodeMap.get(functionNode); // function expressions will not have their return type modified based on receiver typing @@ -159,7 +162,7 @@ export default createRule({ if ( thisExpression && isTypeAnyType( - getConstrainedTypeAtLocation(services, thisExpression), + DEPRECATED_getConstrainedTypeAtLocation(services, thisExpression), ) ) { messageId = 'unsafeReturnThis'; diff --git a/packages/eslint-plugin/src/rules/no-unsafe-type-assertion.ts b/packages/eslint-plugin/src/rules/no-unsafe-type-assertion.ts index 06c3dd44615a..417417934842 100644 --- a/packages/eslint-plugin/src/rules/no-unsafe-type-assertion.ts +++ b/packages/eslint-plugin/src/rules/no-unsafe-type-assertion.ts @@ -5,7 +5,7 @@ import * as ts from 'typescript'; import { createRule, - getConstrainedTypeAtLocation, + DEPRECATED_getConstrainedTypeAtLocation, getParserServices, isTypeAnyType, isTypeUnknownType, @@ -49,11 +49,11 @@ export default createRule({ function checkExpression( node: TSESTree.TSAsExpression | TSESTree.TSTypeAssertion, ): void { - const expressionType = getConstrainedTypeAtLocation( + const expressionType = DEPRECATED_getConstrainedTypeAtLocation( services, node.expression, ); - const assertedType = getConstrainedTypeAtLocation( + const assertedType = DEPRECATED_getConstrainedTypeAtLocation( services, node.typeAnnotation, ); diff --git a/packages/eslint-plugin/src/rules/no-unsafe-unary-minus.ts b/packages/eslint-plugin/src/rules/no-unsafe-unary-minus.ts index fcfca3145db6..9f54f784ce38 100644 --- a/packages/eslint-plugin/src/rules/no-unsafe-unary-minus.ts +++ b/packages/eslint-plugin/src/rules/no-unsafe-unary-minus.ts @@ -29,7 +29,7 @@ export default util.createRule({ return; } const services = util.getParserServices(context); - const argType = util.getConstrainedTypeAtLocation( + const argType = util.DEPRECATED_getConstrainedTypeAtLocation( services, node.argument, ); diff --git a/packages/eslint-plugin/src/rules/prefer-find.ts b/packages/eslint-plugin/src/rules/prefer-find.ts index 0a7c4d099542..862ab6c0b17c 100644 --- a/packages/eslint-plugin/src/rules/prefer-find.ts +++ b/packages/eslint-plugin/src/rules/prefer-find.ts @@ -7,7 +7,7 @@ import * as tsutils from 'ts-api-utils'; import { createRule, - getConstrainedTypeAtLocation, + DEPRECATED_getConstrainedTypeAtLocation, getParserServices, getStaticValue, isStaticMemberAccessOfValue, @@ -94,7 +94,7 @@ export default createRule({ if (isStaticMemberAccessOfValue(callee, context, 'filter')) { const filterNode = callee.property; - const filteredObjectType = getConstrainedTypeAtLocation( + const filteredObjectType = DEPRECATED_getConstrainedTypeAtLocation( services, callee.object, ); diff --git a/packages/eslint-plugin/src/rules/prefer-includes.ts b/packages/eslint-plugin/src/rules/prefer-includes.ts index 6825fc042a6d..4975853cbdbe 100644 --- a/packages/eslint-plugin/src/rules/prefer-includes.ts +++ b/packages/eslint-plugin/src/rules/prefer-includes.ts @@ -6,7 +6,7 @@ import * as ts from 'typescript'; import { createRule, - getConstrainedTypeAtLocation, + DEPRECATED_getConstrainedTypeAtLocation, getParserServices, getStaticValue, isStaticMemberAccessOfValue, @@ -234,7 +234,10 @@ export default createRule({ //check the argument type of test methods const argument = callNode.arguments[0]; - const type = getConstrainedTypeAtLocation(services, argument); + const type = DEPRECATED_getConstrainedTypeAtLocation( + services, + argument, + ); const includesMethodDecl = type .getProperty('includes') diff --git a/packages/eslint-plugin/src/rules/prefer-reduce-type-parameter.ts b/packages/eslint-plugin/src/rules/prefer-reduce-type-parameter.ts index e3ff336f2b70..927dbb2da791 100644 --- a/packages/eslint-plugin/src/rules/prefer-reduce-type-parameter.ts +++ b/packages/eslint-plugin/src/rules/prefer-reduce-type-parameter.ts @@ -5,7 +5,7 @@ import * as tsutils from 'ts-api-utils'; import { createRule, - getConstrainedTypeAtLocation, + DEPRECATED_getConstrainedTypeAtLocation, getParserServices, isStaticMemberAccessOfValue, isTypeAssertion, @@ -62,7 +62,7 @@ export default createRule({ } // Get the symbol of the `reduce` method. - const calleeObjType = getConstrainedTypeAtLocation( + const calleeObjType = DEPRECATED_getConstrainedTypeAtLocation( services, callee.object, ); diff --git a/packages/eslint-plugin/src/rules/require-array-sort-compare.ts b/packages/eslint-plugin/src/rules/require-array-sort-compare.ts index 2f19e56f4d63..f7004e61f3a6 100644 --- a/packages/eslint-plugin/src/rules/require-array-sort-compare.ts +++ b/packages/eslint-plugin/src/rules/require-array-sort-compare.ts @@ -2,7 +2,7 @@ import type { TSESTree } from '@typescript-eslint/utils'; import { createRule, - getConstrainedTypeAtLocation, + DEPRECATED_getConstrainedTypeAtLocation, getParserServices, getTypeName, isStaticMemberAccessOfValue, @@ -70,7 +70,7 @@ export default createRule({ if (!isStaticMemberAccessOfValue(callee, context, 'sort', 'toSorted')) { return; } - const calleeObjType = getConstrainedTypeAtLocation( + const calleeObjType = DEPRECATED_getConstrainedTypeAtLocation( services, callee.object, ); diff --git a/packages/eslint-plugin/src/rules/restrict-plus-operands.ts b/packages/eslint-plugin/src/rules/restrict-plus-operands.ts index 306342754018..caf186b033ae 100644 --- a/packages/eslint-plugin/src/rules/restrict-plus-operands.ts +++ b/packages/eslint-plugin/src/rules/restrict-plus-operands.ts @@ -5,7 +5,7 @@ import * as ts from 'typescript'; import { createRule, - getConstrainedTypeAtLocation, + DEPRECATED_getConstrainedTypeAtLocation, getParserServices, getTypeName, isTypeAnyType, @@ -130,7 +130,7 @@ export default createRule({ function getTypeConstrained(node: TSESTree.Node): ts.Type { return typeChecker.getBaseTypeOfLiteralType( - getConstrainedTypeAtLocation(services, node), + DEPRECATED_getConstrainedTypeAtLocation(services, node), ); } diff --git a/packages/eslint-plugin/src/rules/restrict-template-expressions.ts b/packages/eslint-plugin/src/rules/restrict-template-expressions.ts index 3049c1fe3fcf..c9c42689dac4 100644 --- a/packages/eslint-plugin/src/rules/restrict-template-expressions.ts +++ b/packages/eslint-plugin/src/rules/restrict-template-expressions.ts @@ -12,7 +12,7 @@ import type { TypeOrValueSpecifier } from '../util'; import { createRule, - getConstrainedTypeAtLocation, + DEPRECATED_getConstrainedTypeAtLocation, getParserServices, getTypeName, isTypeAnyType, @@ -137,7 +137,7 @@ export default createRule({ } for (const expression of node.expressions) { - const expressionType = getConstrainedTypeAtLocation( + const expressionType = DEPRECATED_getConstrainedTypeAtLocation( services, expression, ); diff --git a/packages/eslint-plugin/src/rules/strict-boolean-expressions.ts b/packages/eslint-plugin/src/rules/strict-boolean-expressions.ts index eb127d434d78..dcd67dba6322 100644 --- a/packages/eslint-plugin/src/rules/strict-boolean-expressions.ts +++ b/packages/eslint-plugin/src/rules/strict-boolean-expressions.ts @@ -9,7 +9,7 @@ import * as ts from 'typescript'; import { createRule, - getConstrainedTypeAtLocation, + DEPRECATED_getConstrainedTypeAtLocation, getParserServices, getWrappingFixer, isTypeArrayTypeOrUnionOfArrayTypes, @@ -317,7 +317,7 @@ export default createRule({ * It analyzes the type of a node and checks if it is allowed in a boolean context. */ function checkNode(node: TSESTree.Expression): void { - const type = getConstrainedTypeAtLocation(services, node); + const type = DEPRECATED_getConstrainedTypeAtLocation(services, node); const types = inspectVariantTypes(tsutils.unionTypeParts(type)); const is = (...wantedTypes: readonly VariantType[]): boolean => @@ -995,7 +995,10 @@ function isArrayLengthExpression( if (node.property.name !== 'length') { return false; } - const objectType = getConstrainedTypeAtLocation(services, node.object); + const objectType = DEPRECATED_getConstrainedTypeAtLocation( + services, + node.object, + ); return isTypeArrayTypeOrUnionOfArrayTypes(objectType, typeChecker); } diff --git a/packages/eslint-plugin/src/rules/switch-exhaustiveness-check.ts b/packages/eslint-plugin/src/rules/switch-exhaustiveness-check.ts index 1323344605c8..c5dcd4e25b5d 100644 --- a/packages/eslint-plugin/src/rules/switch-exhaustiveness-check.ts +++ b/packages/eslint-plugin/src/rules/switch-exhaustiveness-check.ts @@ -5,7 +5,7 @@ import * as ts from 'typescript'; import { createRule, - getConstrainedTypeAtLocation, + DEPRECATED_getConstrainedTypeAtLocation, getParserServices, isClosingBraceToken, isOpeningBraceToken, @@ -147,7 +147,7 @@ export default createRule({ switchCase => switchCase.test == null, ); - const discriminantType = getConstrainedTypeAtLocation( + const discriminantType = DEPRECATED_getConstrainedTypeAtLocation( services, node.discriminant, ); @@ -167,7 +167,7 @@ export default createRule({ continue; } - const caseType = getConstrainedTypeAtLocation( + const caseType = DEPRECATED_getConstrainedTypeAtLocation( services, switchCase.test, ); diff --git a/packages/eslint-plugin/src/util/index.ts b/packages/eslint-plugin/src/util/index.ts index 3aa935a51a95..4cc7a139dc86 100644 --- a/packages/eslint-plugin/src/util/index.ts +++ b/packages/eslint-plugin/src/util/index.ts @@ -1,3 +1,10 @@ +import type { + ParserServicesWithTypeInformation, + TSESTree, +} from '@typescript-eslint/utils'; +import type * as ts from 'typescript'; + +import { getConstrainedTypeAtLocation } from '@typescript-eslint/type-utils'; import { ESLintUtils } from '@typescript-eslint/utils'; export * from './astUtils'; @@ -26,6 +33,19 @@ export * from './types'; // this is done for convenience - saves migrating all of the old rules export * from '@typescript-eslint/type-utils'; + +/** + * This is a version of {@link getConstrainedTypeAtLocation} not marked with the + * `@deprecated` JSDoc tag, in order to allow gradual migration away from it. + * This is a workaround for https://github.com/typescript-eslint/typescript-eslint/issues/9899 + */ +const DEPRECATED_getConstrainedTypeAtLocation: ( + services: ParserServicesWithTypeInformation, + node: TSESTree.Node, +) => ts.Type = + // eslint-disable-next-line @typescript-eslint/no-deprecated + getConstrainedTypeAtLocation; + const { applyDefault, deepMerge, @@ -39,6 +59,7 @@ type InferMessageIdsTypeFromRule = type InferOptionsTypeFromRule = ESLintUtils.InferOptionsTypeFromRule; export { + DEPRECATED_getConstrainedTypeAtLocation, applyDefault, deepMerge, getParserServices, diff --git a/packages/eslint-plugin/src/util/isArrayMethodCallWithPredicate.ts b/packages/eslint-plugin/src/util/isArrayMethodCallWithPredicate.ts index c48e1a13636d..1c607236a7b8 100644 --- a/packages/eslint-plugin/src/util/isArrayMethodCallWithPredicate.ts +++ b/packages/eslint-plugin/src/util/isArrayMethodCallWithPredicate.ts @@ -4,10 +4,10 @@ import type { } from '@typescript-eslint/utils'; import type { RuleContext } from '@typescript-eslint/utils/ts-eslint'; -import { getConstrainedTypeAtLocation } from '@typescript-eslint/type-utils'; import { AST_NODE_TYPES } from '@typescript-eslint/utils'; import * as tsutils from 'ts-api-utils'; +import { DEPRECATED_getConstrainedTypeAtLocation } from './index'; import { getStaticMemberAccessValue } from './misc'; const ARRAY_PREDICATE_FUNCTIONS = new Set([ @@ -36,7 +36,10 @@ export function isArrayMethodCallWithPredicate( } const checker = services.program.getTypeChecker(); - const type = getConstrainedTypeAtLocation(services, node.callee.object); + const type = DEPRECATED_getConstrainedTypeAtLocation( + services, + node.callee.object, + ); return tsutils .unionTypeParts(type) .flatMap(part => tsutils.intersectionTypeParts(part)) diff --git a/packages/type-utils/src/getConstrainedTypeAtLocation.ts b/packages/type-utils/src/getConstrainedTypeAtLocation.ts index 560b8003c1e6..279224f113bb 100644 --- a/packages/type-utils/src/getConstrainedTypeAtLocation.ts +++ b/packages/type-utils/src/getConstrainedTypeAtLocation.ts @@ -55,7 +55,7 @@ export type ConstraintTypeInfo = /** * Resolves the given node's type, and returns info about whether it is a generic or ordinary type. * - * Successor to {@link getConstrainedTypeAtLocation} due to https://github.com/typescript-eslint/typescript-eslint/issues/10438 + * Successor to {@link getConstrainedTypeAtLocationDeprecated} due to https://github.com/typescript-eslint/typescript-eslint/issues/10438 */ export function getConstraintTypeInfoAtLocation( services: ParserServicesWithTypeInformation, diff --git a/packages/type-utils/tests/getConstrainedTypeAtLocation.test.ts b/packages/type-utils/tests/getConstrainedTypeAtLocation.test.ts index 607b9646e02a..b918f2e0c3bc 100644 --- a/packages/type-utils/tests/getConstrainedTypeAtLocation.test.ts +++ b/packages/type-utils/tests/getConstrainedTypeAtLocation.test.ts @@ -75,6 +75,7 @@ function createSourceFileAndTypeChecker( }; } +/* eslint-disable @typescript-eslint/no-deprecated -- testing a deprecated function */ describe('getConstrainedTypeAtLocation', () => { // See https://github.com/typescript-eslint/typescript-eslint/issues/10438 // eslint-disable-next-line jest/no-disabled-tests -- known issue. @@ -205,6 +206,7 @@ function foo() { expect(tsutils.isIntrinsicStringType(constraintAtLocation)).toBe(true); }); }); +/* eslint-enable @typescript-eslint/no-deprecated*/ describe('getConstraintTypeInfoAtLocation', () => { it('returns undefined for unconstrained generic', () => { From 11d3a0aaa719cdc0081bde0c0b943d203322d245 Mon Sep 17 00:00:00 2001 From: Kirk Waiblinger Date: Sat, 14 Dec 2024 12:00:45 -0700 Subject: [PATCH 08/15] space --- packages/type-utils/tests/getConstrainedTypeAtLocation.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/type-utils/tests/getConstrainedTypeAtLocation.test.ts b/packages/type-utils/tests/getConstrainedTypeAtLocation.test.ts index b918f2e0c3bc..d9de382538ce 100644 --- a/packages/type-utils/tests/getConstrainedTypeAtLocation.test.ts +++ b/packages/type-utils/tests/getConstrainedTypeAtLocation.test.ts @@ -206,7 +206,7 @@ function foo() { expect(tsutils.isIntrinsicStringType(constraintAtLocation)).toBe(true); }); }); -/* eslint-enable @typescript-eslint/no-deprecated*/ +/* eslint-enable @typescript-eslint/no-deprecated */ describe('getConstraintTypeInfoAtLocation', () => { it('returns undefined for unconstrained generic', () => { From 5d7cab6feaca99330716143587f6e43f0351bc64 Mon Sep 17 00:00:00 2001 From: Kirk Waiblinger Date: Sun, 15 Dec 2024 21:16:51 -0700 Subject: [PATCH 09/15] remove vfs, derp --- packages/type-utils/package.json | 1 - .../getConstrainedTypeAtLocation.test.ts | 257 ++++++------------ yarn.lock | 12 - 3 files changed, 85 insertions(+), 185 deletions(-) diff --git a/packages/type-utils/package.json b/packages/type-utils/package.json index 1471e5000dc2..c46b5b9421a6 100644 --- a/packages/type-utils/package.json +++ b/packages/type-utils/package.json @@ -58,7 +58,6 @@ "devDependencies": { "@jest/types": "29.6.3", "@typescript-eslint/parser": "8.18.0", - "@typescript/vfs": "*", "ajv": "^6.12.6", "downlevel-dts": "*", "jest": "29.7.0", diff --git a/packages/type-utils/tests/getConstrainedTypeAtLocation.test.ts b/packages/type-utils/tests/getConstrainedTypeAtLocation.test.ts index d9de382538ce..20c6f627af4f 100644 --- a/packages/type-utils/tests/getConstrainedTypeAtLocation.test.ts +++ b/packages/type-utils/tests/getConstrainedTypeAtLocation.test.ts @@ -1,9 +1,9 @@ import type { TSESTree } from '@typescript-eslint/types'; import type { ParserServicesWithTypeInformation } from '@typescript-eslint/typescript-estree'; -import * as tsvfs from '@typescript/vfs'; +import { parseForESLint } from '@typescript-eslint/parser'; +import path from 'node:path'; import * as tsutils from 'ts-api-utils'; -import * as ts from 'typescript'; import { getConstrainedTypeAtLocation, @@ -11,68 +11,18 @@ import { isTypeUnknownType, } from '../src'; -/** - * Creates a dummy value that the type system will treat as if it's a TSESTree.Node. - */ -function mockEstreeNode(): TSESTree.Node { - // this should never actually be used. - return null!; -} - -/** - * Creates a mock of the mock ParserServicesWithTypeInformation object. - * - * Should be provided with a real type checker. `getTypeAtLocation` will always - * return the type of the provided node. - */ -function mockServices( - checker: ts.TypeChecker, - tsNode: ts.Node, -): ParserServicesWithTypeInformation { - return { - getTypeAtLocation: () => checker.getTypeAtLocation(tsNode), - // @ts-expect-error -- mocking - program: { - getTypeChecker: () => checker, - }, - }; -} - -interface SourceFileAndTypeChecker { - sourceFile: ts.SourceFile; - typeChecker: ts.TypeChecker; -} - -// copied with <3 from https://github.com/JoshuaKGoldberg/ts-api-utils/blob/8c747294f24d0adca8817f4028832b855d907b5b/src/test/utils.ts#L41 -function createSourceFileAndTypeChecker( - sourceText: string, - fileName = 'file.tsx', -): SourceFileAndTypeChecker { - const compilerOptions: ts.CompilerOptions = { - lib: ['ES2018'], - target: ts.ScriptTarget.ES2018, - }; - - const fsMap = tsvfs.createDefaultMapFromNodeModules(compilerOptions, ts); - fsMap.set(fileName, sourceText); - - const system = tsvfs.createSystem(fsMap); - const env = tsvfs.createVirtualTypeScriptEnvironment( - system, - [fileName], - ts, - compilerOptions, - ); - - const program = env.languageService.getProgram(); - if (program == null) { - throw new Error('Failed to get program.'); - } - - return { - sourceFile: program.getSourceFile(fileName)!, - typeChecker: program.getTypeChecker(), - }; +function parseCodeForEslint(code: string): ReturnType & { + services: ParserServicesWithTypeInformation; +} { + const rootDir = path.join(__dirname, 'fixtures'); + + // @ts-expect-error -- services will have type information. + return parseForESLint(code, { + disallowAutomaticSingleRunInference: true, + filePath: path.join(rootDir, 'file.ts'), + project: './tsconfig.json', + tsconfigRootDir: rootDir, + }); } /* eslint-disable @typescript-eslint/no-deprecated -- testing a deprecated function */ @@ -84,15 +34,14 @@ describe('getConstrainedTypeAtLocation', () => { function foo(x: T); `; - const { sourceFile, typeChecker } = - createSourceFileAndTypeChecker(sourceCode); + const { ast, services } = parseCodeForEslint(sourceCode); - const functionNode = sourceFile.statements[0] as ts.FunctionDeclaration; - const parameterNode = functionNode.parameters[0]; + const functionNode = ast.body[0] as TSESTree.FunctionDeclaration; + const parameterNode = functionNode.params[0]; const constraintAtLocation = getConstrainedTypeAtLocation( - mockServices(typeChecker, parameterNode), - mockEstreeNode(), + services, + parameterNode, ); expect(tsutils.isTypeParameter(constraintAtLocation)).toBe(false); @@ -105,15 +54,14 @@ function foo(x: T); function foo(x: T); `; - const { sourceFile, typeChecker } = - createSourceFileAndTypeChecker(sourceCode); + const { ast, services } = parseCodeForEslint(sourceCode); - const functionNode = sourceFile.statements[0] as ts.FunctionDeclaration; - const parameterNode = functionNode.parameters[0]; + const functionNode = ast.body[0] as TSESTree.FunctionDeclaration; + const parameterNode = functionNode.params[0]; const constraintAtLocation = getConstrainedTypeAtLocation( - mockServices(typeChecker, parameterNode), - mockEstreeNode(), + services, + parameterNode, ); expect(tsutils.isTypeParameter(constraintAtLocation)).toBe(false); @@ -125,15 +73,14 @@ function foo(x: T); function foo(x: T); `; - const { sourceFile, typeChecker } = - createSourceFileAndTypeChecker(sourceCode); + const { ast, services } = parseCodeForEslint(sourceCode); - const functionNode = sourceFile.statements[0] as ts.FunctionDeclaration; - const parameterNode = functionNode.parameters[0]; + const functionNode = ast.body[0] as TSESTree.FunctionDeclaration; + const parameterNode = functionNode.params[0]; const constraintAtLocation = getConstrainedTypeAtLocation( - mockServices(typeChecker, parameterNode), - mockEstreeNode(), + services, + parameterNode, ); expect(tsutils.isTypeParameter(constraintAtLocation)).toBe(false); @@ -145,15 +92,14 @@ function foo(x: T); function foo(x: T); `; - const { sourceFile, typeChecker } = - createSourceFileAndTypeChecker(sourceCode); + const { ast, services } = parseCodeForEslint(sourceCode); - const functionNode = sourceFile.statements[0] as ts.FunctionDeclaration; - const parameterNode = functionNode.parameters[0]; + const functionNode = ast.body[0] as TSESTree.FunctionDeclaration; + const parameterNode = functionNode.params[0]; const constraintAtLocation = getConstrainedTypeAtLocation( - mockServices(typeChecker, parameterNode), - mockEstreeNode(), + services, + parameterNode, ); expect(tsutils.isTypeParameter(constraintAtLocation)).toBe(false); @@ -165,15 +111,14 @@ function foo(x: T); function foo(x: string); `; - const { sourceFile, typeChecker } = - createSourceFileAndTypeChecker(sourceCode); + const { ast, services } = parseCodeForEslint(sourceCode); - const functionNode = sourceFile.statements[0] as ts.FunctionDeclaration; - const parameterNode = functionNode.parameters[0]; + const functionNode = ast.body[0] as TSESTree.FunctionDeclaration; + const parameterNode = functionNode.params[0]; const constraintAtLocation = getConstrainedTypeAtLocation( - mockServices(typeChecker, parameterNode), - mockEstreeNode(), + services, + parameterNode, ); expect(tsutils.isTypeParameter(constraintAtLocation)).toBe(false); @@ -188,18 +133,16 @@ function foo() { } `; - const { sourceFile, typeChecker } = - createSourceFileAndTypeChecker(sourceCode); + const { ast, services } = parseCodeForEslint(sourceCode); - const outerFunctionNode = sourceFile - .statements[0] as ts.FunctionDeclaration; - const innerFunctionNode = outerFunctionNode.body! - .statements[0] as ts.FunctionDeclaration; - const parameterNode = innerFunctionNode.parameters[0]; + const outerFunctionNode = ast.body[0] as TSESTree.FunctionDeclaration; + const innerFunctionNode = outerFunctionNode.body + .body[0] as TSESTree.FunctionDeclaration; + const parameterNode = innerFunctionNode.params[0]; const constraintAtLocation = getConstrainedTypeAtLocation( - mockServices(typeChecker, parameterNode), - mockEstreeNode(), + services, + parameterNode, ); expect(tsutils.isTypeParameter(constraintAtLocation)).toBe(false); @@ -214,18 +157,14 @@ describe('getConstraintTypeInfoAtLocation', () => { function foo(x: T); `; - const { sourceFile, typeChecker } = - createSourceFileAndTypeChecker(sourceCode); + const { ast, services } = parseCodeForEslint(sourceCode); - const functionNode = sourceFile.statements[0] as ts.FunctionDeclaration; - const parameterNode = functionNode.parameters[0]; - const parameterType = typeChecker.getTypeAtLocation(parameterNode); + const functionNode = ast.body[0] as TSESTree.FunctionDeclaration; + const parameterNode = functionNode.params[0]; + const parameterType = services.getTypeAtLocation(parameterNode); const { constraintType, isTypeParameter, type } = - getConstraintTypeInfoAtLocation( - mockServices(typeChecker, parameterNode), - mockEstreeNode(), - ); + getConstraintTypeInfoAtLocation(services, parameterNode); expect(type).toBe(parameterType); expect(isTypeParameter).toBe(true); @@ -239,18 +178,14 @@ function foo(x: T); function foo(x: T); `; - const { sourceFile, typeChecker } = - createSourceFileAndTypeChecker(sourceCode); + const { ast, services } = parseCodeForEslint(sourceCode); - const functionNode = sourceFile.statements[0] as ts.FunctionDeclaration; - const parameterNode = functionNode.parameters[0]; - const parameterType = typeChecker.getTypeAtLocation(parameterNode); + const functionNode = ast.body[0] as TSESTree.FunctionDeclaration; + const parameterNode = functionNode.params[0]; + const parameterType = services.getTypeAtLocation(parameterNode); const { constraintType, isTypeParameter, type } = - getConstraintTypeInfoAtLocation( - mockServices(typeChecker, parameterNode), - mockEstreeNode(), - ); + getConstraintTypeInfoAtLocation(services, parameterNode); expect(type).toBe(parameterType); expect(isTypeParameter).toBe(true); @@ -264,18 +199,14 @@ function foo(x: T); function foo(x: T); `; - const { sourceFile, typeChecker } = - createSourceFileAndTypeChecker(sourceCode); + const { ast, services } = parseCodeForEslint(sourceCode); - const functionNode = sourceFile.statements[0] as ts.FunctionDeclaration; - const parameterNode = functionNode.parameters[0]; - const parameterType = typeChecker.getTypeAtLocation(parameterNode); + const functionNode = ast.body[0] as TSESTree.FunctionDeclaration; + const parameterNode = functionNode.params[0]; + const parameterType = services.getTypeAtLocation(parameterNode); const { constraintType, isTypeParameter, type } = - getConstraintTypeInfoAtLocation( - mockServices(typeChecker, parameterNode), - mockEstreeNode(), - ); + getConstraintTypeInfoAtLocation(services, parameterNode); expect(type).toBe(parameterType); expect(isTypeParameter).toBe(true); @@ -289,18 +220,14 @@ function foo(x: T); function foo(x: T); `; - const { sourceFile, typeChecker } = - createSourceFileAndTypeChecker(sourceCode); + const { ast, services } = parseCodeForEslint(sourceCode); - const functionNode = sourceFile.statements[0] as ts.FunctionDeclaration; - const parameterNode = functionNode.parameters[0]; - const parameterType = typeChecker.getTypeAtLocation(parameterNode); + const functionNode = ast.body[0] as TSESTree.FunctionDeclaration; + const parameterNode = functionNode.params[0]; + const parameterType = services.getTypeAtLocation(parameterNode); const { constraintType, isTypeParameter, type } = - getConstraintTypeInfoAtLocation( - mockServices(typeChecker, parameterNode), - mockEstreeNode(), - ); + getConstraintTypeInfoAtLocation(services, parameterNode); expect(type).toBe(parameterType); expect(isTypeParameter).toBe(true); @@ -314,18 +241,14 @@ function foo(x: T); function foo(x: string); `; - const { sourceFile, typeChecker } = - createSourceFileAndTypeChecker(sourceCode); + const { ast, services } = parseCodeForEslint(sourceCode); - const functionNode = sourceFile.statements[0] as ts.FunctionDeclaration; - const parameterNode = functionNode.parameters[0]; - const parameterType = typeChecker.getTypeAtLocation(parameterNode); + const functionNode = ast.body[0] as TSESTree.FunctionDeclaration; + const parameterNode = functionNode.params[0]; + const parameterType = services.getTypeAtLocation(parameterNode); const { constraintType, isTypeParameter, type } = - getConstraintTypeInfoAtLocation( - mockServices(typeChecker, parameterNode), - mockEstreeNode(), - ); + getConstraintTypeInfoAtLocation(services, parameterNode); expect(type).toBe(parameterType); expect(isTypeParameter).toBe(false); @@ -343,21 +266,16 @@ function foo() { } `; - const { sourceFile, typeChecker } = - createSourceFileAndTypeChecker(sourceCode); + const { ast, services } = parseCodeForEslint(sourceCode); - const outerFunctionNode = sourceFile - .statements[0] as ts.FunctionDeclaration; - const innerFunctionNode = outerFunctionNode.body! - .statements[0] as ts.FunctionDeclaration; - const parameterNode = innerFunctionNode.parameters[0]; - const parameterType = typeChecker.getTypeAtLocation(parameterNode); + const outerFunctionNode = ast.body[0] as TSESTree.FunctionDeclaration; + const innerFunctionNode = outerFunctionNode.body + .body[0] as TSESTree.FunctionDeclaration; + const parameterNode = innerFunctionNode.params[0]; + const parameterType = services.getTypeAtLocation(parameterNode); const { constraintType, isTypeParameter, type } = - getConstraintTypeInfoAtLocation( - mockServices(typeChecker, parameterNode), - mockEstreeNode(), - ); + getConstraintTypeInfoAtLocation(services, parameterNode); expect(type).toBe(parameterType); expect(constraintType).toBeDefined(); @@ -374,21 +292,16 @@ function foo() { } `; - const { sourceFile, typeChecker } = - createSourceFileAndTypeChecker(sourceCode); + const { ast, services } = parseCodeForEslint(sourceCode); - const outerFunctionNode = sourceFile - .statements[0] as ts.FunctionDeclaration; - const innerFunctionNode = outerFunctionNode.body! - .statements[0] as ts.FunctionDeclaration; - const parameterNode = innerFunctionNode.parameters[0]; - const parameterType = typeChecker.getTypeAtLocation(parameterNode); + const outerFunctionNode = ast.body[0] as TSESTree.FunctionDeclaration; + const innerFunctionNode = outerFunctionNode.body + .body[0] as TSESTree.FunctionDeclaration; + const parameterNode = innerFunctionNode.params[0]; + const parameterType = services.getTypeAtLocation(parameterNode); const { constraintType, isTypeParameter, type } = - getConstraintTypeInfoAtLocation( - mockServices(typeChecker, parameterNode), - mockEstreeNode(), - ); + getConstraintTypeInfoAtLocation(services, parameterNode); expect(type).toBe(parameterType); expect(isTypeParameter).toBe(true); diff --git a/yarn.lock b/yarn.lock index 873fa55551b2..f21e35f6b373 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5835,7 +5835,6 @@ __metadata: "@typescript-eslint/parser": 8.18.0 "@typescript-eslint/typescript-estree": 8.18.0 "@typescript-eslint/utils": 8.18.0 - "@typescript/vfs": "*" ajv: ^6.12.6 debug: ^4.3.4 downlevel-dts: "*" @@ -6019,17 +6018,6 @@ __metadata: languageName: unknown linkType: soft -"@typescript/vfs@npm:*": - version: 1.6.0 - resolution: "@typescript/vfs@npm:1.6.0" - dependencies: - debug: ^4.1.1 - peerDependencies: - typescript: "*" - checksum: bedc58a712689b8a130518720c8738d2c555053a5b5f0c4889eaaaaef331c650eb652a99875c62cc25c01dc228205ed0e84f5c8d3a3ff6f25f42aa509e33e38f - languageName: node - linkType: hard - "@uiw/react-shields@npm:2.0.1": version: 2.0.1 resolution: "@uiw/react-shields@npm:2.0.1" From f38328044c86044c42b132a5c5f77201e9814856 Mon Sep 17 00:00:00 2001 From: Kirk Waiblinger Date: Sun, 15 Dec 2024 21:21:31 -0700 Subject: [PATCH 10/15] typo --- packages/type-utils/src/getConstrainedTypeAtLocation.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/type-utils/src/getConstrainedTypeAtLocation.ts b/packages/type-utils/src/getConstrainedTypeAtLocation.ts index 279224f113bb..560b8003c1e6 100644 --- a/packages/type-utils/src/getConstrainedTypeAtLocation.ts +++ b/packages/type-utils/src/getConstrainedTypeAtLocation.ts @@ -55,7 +55,7 @@ export type ConstraintTypeInfo = /** * Resolves the given node's type, and returns info about whether it is a generic or ordinary type. * - * Successor to {@link getConstrainedTypeAtLocationDeprecated} due to https://github.com/typescript-eslint/typescript-eslint/issues/10438 + * Successor to {@link getConstrainedTypeAtLocation} due to https://github.com/typescript-eslint/typescript-eslint/issues/10438 */ export function getConstraintTypeInfoAtLocation( services: ParserServicesWithTypeInformation, From 2aecb982d11c58ffab1484e5f227b891e264525a Mon Sep 17 00:00:00 2001 From: Kirk Waiblinger Date: Sat, 28 Dec 2024 16:39:52 -0700 Subject: [PATCH 11/15] simplify --- .../eslint-plugin/src/rules/await-thenable.ts | 7 +- .../src/rules/no-array-delete.ts | 7 +- .../src/rules/no-base-to-string.ts | 7 +- .../src/rules/no-confusing-void-expression.ts | 9 +- .../src/rules/no-for-in-array.ts | 7 +- .../no-unnecessary-boolean-literal-compare.ts | 9 +- .../src/rules/no-unnecessary-condition.ts | 40 ++-- .../no-unnecessary-template-expression.ts | 7 +- .../rules/no-unnecessary-type-assertion.ts | 9 +- .../src/rules/no-unsafe-assignment.ts | 4 +- .../eslint-plugin/src/rules/no-unsafe-call.ts | 6 +- .../src/rules/no-unsafe-member-access.ts | 4 +- .../src/rules/no-unsafe-return.ts | 9 +- .../src/rules/no-unsafe-unary-minus.ts | 2 +- .../eslint-plugin/src/rules/prefer-find.ts | 4 +- .../src/rules/prefer-includes.ts | 7 +- .../src/rules/prefer-reduce-type-parameter.ts | 4 +- .../src/rules/require-array-sort-compare.ts | 4 +- .../src/rules/restrict-plus-operands.ts | 4 +- .../rules/restrict-template-expressions.ts | 4 +- .../eslint-plugin/src/rules/return-await.ts | 12 +- .../src/rules/strict-boolean-expressions.ts | 9 +- .../src/rules/switch-exhaustiveness-check.ts | 6 +- .../src/util/getConstraintTypeInfo.ts | 51 +++++ packages/eslint-plugin/src/util/index.ts | 21 +- .../util/isArrayMethodCallWithPredicate.ts | 7 +- .../src/util/needsToBeAwaited.ts | 3 +- .../tests/util/getConstraintTypeInfo.test.ts | 196 ++++++++++++++++++ .../src/getConstrainedTypeAtLocation.ts | 57 +---- .../getConstrainedTypeAtLocation.test.ts | 166 +-------------- 30 files changed, 328 insertions(+), 354 deletions(-) create mode 100644 packages/eslint-plugin/src/util/getConstraintTypeInfo.ts create mode 100644 packages/eslint-plugin/tests/util/getConstraintTypeInfo.test.ts diff --git a/packages/eslint-plugin/src/rules/await-thenable.ts b/packages/eslint-plugin/src/rules/await-thenable.ts index d25d0224fb24..b4466b47549d 100644 --- a/packages/eslint-plugin/src/rules/await-thenable.ts +++ b/packages/eslint-plugin/src/rules/await-thenable.ts @@ -5,7 +5,7 @@ import * as tsutils from 'ts-api-utils'; import { Awaitable, createRule, - getConstraintTypeInfoAtLocation, + getConstraintTypeInfo, getFixOrSuggest, getParserServices, isAwaitKeyword, @@ -57,7 +57,10 @@ export default createRule<[], MessageId>({ const certainty = needsToBeAwaited( checker, awaitedTsNode, - getConstraintTypeInfoAtLocation(services, awaitedNode), + getConstraintTypeInfo( + checker, + checker.getTypeAtLocation(awaitedTsNode), + ), ); if (certainty === Awaitable.Never) { diff --git a/packages/eslint-plugin/src/rules/no-array-delete.ts b/packages/eslint-plugin/src/rules/no-array-delete.ts index c448279c82f0..a179418a7b01 100644 --- a/packages/eslint-plugin/src/rules/no-array-delete.ts +++ b/packages/eslint-plugin/src/rules/no-array-delete.ts @@ -5,7 +5,7 @@ import { AST_NODE_TYPES, AST_TOKEN_TYPES } from '@typescript-eslint/utils'; import { createRule, - DEPRECATED_getConstrainedTypeAtLocation, + getConstrainedTypeAtLocation, getParserServices, } from '../util'; @@ -58,10 +58,7 @@ export default createRule<[], MessageId>({ return; } - const type = DEPRECATED_getConstrainedTypeAtLocation( - services, - argument.object, - ); + const type = getConstrainedTypeAtLocation(services, argument.object); if (!isUnderlyingTypeArray(type)) { return; diff --git a/packages/eslint-plugin/src/rules/no-base-to-string.ts b/packages/eslint-plugin/src/rules/no-base-to-string.ts index fc449270a0b2..b681f820a3b0 100644 --- a/packages/eslint-plugin/src/rules/no-base-to-string.ts +++ b/packages/eslint-plugin/src/rules/no-base-to-string.ts @@ -6,7 +6,7 @@ import * as ts from 'typescript'; import { createRule, - DEPRECATED_getConstrainedTypeAtLocation, + getConstrainedTypeAtLocation, getParserServices, getTypeName, nullThrows, @@ -274,10 +274,7 @@ export default createRule({ node: TSESTree.Expression, ): void { const memberExpr = node.parent as TSESTree.MemberExpression; - const type = DEPRECATED_getConstrainedTypeAtLocation( - services, - memberExpr.object, - ); + const type = getConstrainedTypeAtLocation(services, memberExpr.object); checkExpressionForArrayJoin(memberExpr.object, type); }, 'CallExpression > MemberExpression.callee > Identifier[name = /^(toLocaleString|toString)$/].property'( 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 7f612248c31e..e638afc8b944 100644 --- a/packages/eslint-plugin/src/rules/no-confusing-void-expression.ts +++ b/packages/eslint-plugin/src/rules/no-confusing-void-expression.ts @@ -8,7 +8,7 @@ import type { MakeRequired } from '../util'; import { createRule, - DEPRECATED_getConstrainedTypeAtLocation, + getConstrainedTypeAtLocation, getParserServices, isClosingParenToken, isOpeningParenToken, @@ -116,7 +116,7 @@ export default createRule({ | TSESTree.CallExpression | TSESTree.TaggedTemplateExpression, ): void { - const type = DEPRECATED_getConstrainedTypeAtLocation(services, node); + const type = getConstrainedTypeAtLocation(services, node); if (!tsutils.isTypeFlagSet(type, ts.TypeFlags.VoidLike)) { // not a void expression return; @@ -420,10 +420,7 @@ export default createRule({ ? node.argument : node.body; - const type = DEPRECATED_getConstrainedTypeAtLocation( - services, - targetNode, - ); + const type = getConstrainedTypeAtLocation(services, targetNode); return tsutils.isTypeFlagSet(type, ts.TypeFlags.VoidLike); } diff --git a/packages/eslint-plugin/src/rules/no-for-in-array.ts b/packages/eslint-plugin/src/rules/no-for-in-array.ts index 620821fcc9df..ec8ebe9125fb 100644 --- a/packages/eslint-plugin/src/rules/no-for-in-array.ts +++ b/packages/eslint-plugin/src/rules/no-for-in-array.ts @@ -2,7 +2,7 @@ import * as ts from 'typescript'; import { createRule, - DEPRECATED_getConstrainedTypeAtLocation, + getConstrainedTypeAtLocation, getParserServices, isTypeArrayTypeOrUnionOfArrayTypes, } from '../util'; @@ -30,10 +30,7 @@ export default createRule({ const services = getParserServices(context); const checker = services.program.getTypeChecker(); - const type = DEPRECATED_getConstrainedTypeAtLocation( - services, - node.right, - ); + const type = getConstrainedTypeAtLocation(services, node.right); if ( isTypeArrayTypeOrUnionOfArrayTypes(type, checker) || 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 775fea070a51..b352e20824ec 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 @@ -6,7 +6,7 @@ import * as ts from 'typescript'; import { createRule, - getConstraintTypeInfoAtLocation, + getConstraintTypeInfo, getParserServices, isStrongPrecedenceNode, } from '../util'; @@ -85,6 +85,7 @@ export default createRule({ ], create(context, [options]) { const services = getParserServices(context); + const checker = services.program.getTypeChecker(); function getBooleanComparison( node: TSESTree.BinaryExpression, @@ -94,8 +95,10 @@ export default createRule({ return undefined; } - const { constraintType, isTypeParameter } = - getConstraintTypeInfoAtLocation(services, comparison.expression); + const { constraintType, isTypeParameter } = getConstraintTypeInfo( + checker, + services.getTypeAtLocation(comparison.expression), + ); if (isTypeParameter && constraintType == null) { return undefined; diff --git a/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts b/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts index 49632045ad3f..1d821efbb6f3 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts @@ -6,7 +6,7 @@ import * as ts from 'typescript'; import { createRule, - DEPRECATED_getConstrainedTypeAtLocation, + getConstrainedTypeAtLocation, getParserServices, getTypeName, getTypeOfPropertyOfName, @@ -292,14 +292,14 @@ export default createRule({ } function nodeIsArrayType(node: TSESTree.Expression): boolean { - const nodeType = DEPRECATED_getConstrainedTypeAtLocation(services, node); + const nodeType = getConstrainedTypeAtLocation(services, node); return tsutils .unionTypeParts(nodeType) .some(part => checker.isArrayType(part)); } function nodeIsTupleType(node: TSESTree.Expression): boolean { - const nodeType = DEPRECATED_getConstrainedTypeAtLocation(services, node); + const nodeType = getConstrainedTypeAtLocation(services, node); return tsutils .unionTypeParts(nodeType) .some(part => checker.isTupleType(part)); @@ -383,10 +383,7 @@ export default createRule({ return checkNode(expression.right); } - const type = DEPRECATED_getConstrainedTypeAtLocation( - services, - expression, - ); + const type = getConstrainedTypeAtLocation(services, expression); // Conditional is always necessary if it involves: // `any` or `unknown` or a naked type variable @@ -418,7 +415,7 @@ export default createRule({ } function checkNodeForNullish(node: TSESTree.Expression): void { - const type = DEPRECATED_getConstrainedTypeAtLocation(services, node); + const type = getConstrainedTypeAtLocation(services, node); // Conditional is always necessary if it involves `any`, `unknown` or a naked type parameter if ( @@ -481,11 +478,8 @@ export default createRule({ right: TSESTree.Node, operator: BoolOperator, ): void { - const leftType = DEPRECATED_getConstrainedTypeAtLocation(services, left); - const rightType = DEPRECATED_getConstrainedTypeAtLocation( - services, - right, - ); + const leftType = getConstrainedTypeAtLocation(services, left); + const rightType = getConstrainedTypeAtLocation(services, right); const leftStaticValue = toStaticValue(leftType); const rightStaticValue = toStaticValue(rightType); @@ -583,7 +577,7 @@ export default createRule({ if ( allowConstantLoopConditions && tsutils.isTrueLiteralType( - DEPRECATED_getConstrainedTypeAtLocation(services, node.test), + getConstrainedTypeAtLocation(services, node.test), ) ) { return; @@ -607,7 +601,7 @@ export default createRule({ node, ); if (typeGuardAssertedArgument != null) { - const typeOfArgument = DEPRECATED_getConstrainedTypeAtLocation( + const typeOfArgument = getConstrainedTypeAtLocation( services, typeGuardAssertedArgument.argument, ); @@ -657,7 +651,7 @@ export default createRule({ // Otherwise just do type analysis on the function as a whole. const returnTypes = tsutils .getCallSignaturesOfType( - DEPRECATED_getConstrainedTypeAtLocation(services, callback), + getConstrainedTypeAtLocation(services, callback), ) .map(sig => sig.getReturnType()) .map(t => { @@ -775,15 +769,12 @@ export default createRule({ function isMemberExpressionNullableOriginFromObject( node: TSESTree.MemberExpression, ): boolean { - const prevType = DEPRECATED_getConstrainedTypeAtLocation( - services, - node.object, - ); + const prevType = getConstrainedTypeAtLocation(services, node.object); const property = node.property; if (prevType.isUnion() && isIdentifier(property)) { const isOwnNullable = prevType.types.some(type => { if (node.computed) { - const propertyType = DEPRECATED_getConstrainedTypeAtLocation( + const propertyType = getConstrainedTypeAtLocation( services, node.property, ); @@ -818,10 +809,7 @@ export default createRule({ function isCallExpressionNullableOriginFromCallee( node: TSESTree.CallExpression, ): boolean { - const prevType = DEPRECATED_getConstrainedTypeAtLocation( - services, - node.callee, - ); + const prevType = getConstrainedTypeAtLocation(services, node.callee); if (prevType.isUnion()) { const isOwnNullable = prevType.types.some(type => { @@ -835,7 +823,7 @@ export default createRule({ } function isOptionableExpression(node: TSESTree.Expression): boolean { - const type = DEPRECATED_getConstrainedTypeAtLocation(services, node); + const type = getConstrainedTypeAtLocation(services, node); const isOwnNullable = node.type === AST_NODE_TYPES.MemberExpression ? !isMemberExpressionNullableOriginFromObject(node) diff --git a/packages/eslint-plugin/src/rules/no-unnecessary-template-expression.ts b/packages/eslint-plugin/src/rules/no-unnecessary-template-expression.ts index 0515c8dc3ef4..90ad42fb0169 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-template-expression.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-template-expression.ts @@ -5,7 +5,7 @@ import * as ts from 'typescript'; import { createRule, - DEPRECATED_getConstrainedTypeAtLocation, + getConstrainedTypeAtLocation, getMovedNodeCode, getParserServices, isTypeFlagSet, @@ -51,10 +51,7 @@ export default createRule<[], MessageId>({ function isUnderlyingTypeString( expression: TSESTree.Expression, ): expression is TSESTree.Identifier | TSESTree.StringLiteral { - const type = DEPRECATED_getConstrainedTypeAtLocation( - services, - expression, - ); + const type = getConstrainedTypeAtLocation(services, expression); const isString = (t: ts.Type): boolean => { return isTypeFlagSet(t, ts.TypeFlags.StringLike); 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 9558313dbbf0..c73d8717c3e1 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts @@ -8,7 +8,7 @@ import * as ts from 'typescript'; import { createRule, - DEPRECATED_getConstrainedTypeAtLocation, + getConstrainedTypeAtLocation, getContextualType, getDeclaration, getModifiers, @@ -123,7 +123,7 @@ export default createRule({ ) { // check if the defined variable type has changed since assignment const declarationType = checker.getTypeFromTypeNode(declaration.type); - const type = DEPRECATED_getConstrainedTypeAtLocation(services, node); + const type = getConstrainedTypeAtLocation(services, node); if ( declarationType === type && // `declare`s are never narrowed, so never skip them @@ -319,10 +319,7 @@ export default createRule({ const originalNode = services.esTreeNodeToTSNodeMap.get(node); - const type = DEPRECATED_getConstrainedTypeAtLocation( - services, - node.expression, - ); + const type = getConstrainedTypeAtLocation(services, node.expression); if (!isNullableType(type)) { if ( diff --git a/packages/eslint-plugin/src/rules/no-unsafe-assignment.ts b/packages/eslint-plugin/src/rules/no-unsafe-assignment.ts index d9789c70b44c..d32812f6edfa 100644 --- a/packages/eslint-plugin/src/rules/no-unsafe-assignment.ts +++ b/packages/eslint-plugin/src/rules/no-unsafe-assignment.ts @@ -6,7 +6,7 @@ import * as tsutils from 'ts-api-utils'; import { createRule, - DEPRECATED_getConstrainedTypeAtLocation, + getConstrainedTypeAtLocation, getContextualType, getParserServices, getThisExpression, @@ -270,7 +270,7 @@ export default createRule({ if ( thisExpression && isTypeAnyType( - DEPRECATED_getConstrainedTypeAtLocation(services, thisExpression), + getConstrainedTypeAtLocation(services, thisExpression), ) ) { messageId = 'anyAssignmentThis'; diff --git a/packages/eslint-plugin/src/rules/no-unsafe-call.ts b/packages/eslint-plugin/src/rules/no-unsafe-call.ts index 0c3f228dc25b..2c29caa2c9e1 100644 --- a/packages/eslint-plugin/src/rules/no-unsafe-call.ts +++ b/packages/eslint-plugin/src/rules/no-unsafe-call.ts @@ -4,7 +4,7 @@ import * as tsutils from 'ts-api-utils'; import { createRule, - DEPRECATED_getConstrainedTypeAtLocation, + getConstrainedTypeAtLocation, getParserServices, getThisExpression, isBuiltinSymbolLike, @@ -51,7 +51,7 @@ export default createRule<[], MessageIds>({ reportingNode: TSESTree.Node, messageId: MessageIds, ): void { - const type = DEPRECATED_getConstrainedTypeAtLocation(services, node); + const type = getConstrainedTypeAtLocation(services, node); if (isTypeAnyType(type)) { if (!isNoImplicitThis) { @@ -60,7 +60,7 @@ export default createRule<[], MessageIds>({ if ( thisExpression && isTypeAnyType( - DEPRECATED_getConstrainedTypeAtLocation(services, thisExpression), + getConstrainedTypeAtLocation(services, thisExpression), ) ) { messageId = 'unsafeCallThis'; 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 c6a93bb088a3..6cf16d5211e4 100644 --- a/packages/eslint-plugin/src/rules/no-unsafe-member-access.ts +++ b/packages/eslint-plugin/src/rules/no-unsafe-member-access.ts @@ -6,7 +6,7 @@ import * as tsutils from 'ts-api-utils'; import { createRule, - DEPRECATED_getConstrainedTypeAtLocation, + getConstrainedTypeAtLocation, getParserServices, getThisExpression, isTypeAnyType, @@ -87,7 +87,7 @@ export default createRule({ if ( thisExpression && isTypeAnyType( - DEPRECATED_getConstrainedTypeAtLocation(services, thisExpression), + getConstrainedTypeAtLocation(services, thisExpression), ) ) { messageId = 'unsafeThisMemberExpression'; diff --git a/packages/eslint-plugin/src/rules/no-unsafe-return.ts b/packages/eslint-plugin/src/rules/no-unsafe-return.ts index e2ff9e5416b3..838f84eff6af 100644 --- a/packages/eslint-plugin/src/rules/no-unsafe-return.ts +++ b/packages/eslint-plugin/src/rules/no-unsafe-return.ts @@ -7,7 +7,7 @@ import { AnyType, createRule, discriminateAnyType, - DEPRECATED_getConstrainedTypeAtLocation, + getConstrainedTypeAtLocation, getContextualType, getParserServices, getThisExpression, @@ -68,10 +68,7 @@ export default createRule({ } // function has an explicit return type, so ensure it's a safe return - const returnNodeType = DEPRECATED_getConstrainedTypeAtLocation( - services, - returnNode, - ); + const returnNodeType = getConstrainedTypeAtLocation(services, returnNode); const functionTSNode = services.esTreeNodeToTSNodeMap.get(functionNode); // function expressions will not have their return type modified based on receiver typing @@ -162,7 +159,7 @@ export default createRule({ if ( thisExpression && isTypeAnyType( - DEPRECATED_getConstrainedTypeAtLocation(services, thisExpression), + getConstrainedTypeAtLocation(services, thisExpression), ) ) { messageId = 'unsafeReturnThis'; diff --git a/packages/eslint-plugin/src/rules/no-unsafe-unary-minus.ts b/packages/eslint-plugin/src/rules/no-unsafe-unary-minus.ts index 9f54f784ce38..fcfca3145db6 100644 --- a/packages/eslint-plugin/src/rules/no-unsafe-unary-minus.ts +++ b/packages/eslint-plugin/src/rules/no-unsafe-unary-minus.ts @@ -29,7 +29,7 @@ export default util.createRule({ return; } const services = util.getParserServices(context); - const argType = util.DEPRECATED_getConstrainedTypeAtLocation( + const argType = util.getConstrainedTypeAtLocation( services, node.argument, ); diff --git a/packages/eslint-plugin/src/rules/prefer-find.ts b/packages/eslint-plugin/src/rules/prefer-find.ts index 862ab6c0b17c..0a7c4d099542 100644 --- a/packages/eslint-plugin/src/rules/prefer-find.ts +++ b/packages/eslint-plugin/src/rules/prefer-find.ts @@ -7,7 +7,7 @@ import * as tsutils from 'ts-api-utils'; import { createRule, - DEPRECATED_getConstrainedTypeAtLocation, + getConstrainedTypeAtLocation, getParserServices, getStaticValue, isStaticMemberAccessOfValue, @@ -94,7 +94,7 @@ export default createRule({ if (isStaticMemberAccessOfValue(callee, context, 'filter')) { const filterNode = callee.property; - const filteredObjectType = DEPRECATED_getConstrainedTypeAtLocation( + const filteredObjectType = getConstrainedTypeAtLocation( services, callee.object, ); diff --git a/packages/eslint-plugin/src/rules/prefer-includes.ts b/packages/eslint-plugin/src/rules/prefer-includes.ts index 4975853cbdbe..6825fc042a6d 100644 --- a/packages/eslint-plugin/src/rules/prefer-includes.ts +++ b/packages/eslint-plugin/src/rules/prefer-includes.ts @@ -6,7 +6,7 @@ import * as ts from 'typescript'; import { createRule, - DEPRECATED_getConstrainedTypeAtLocation, + getConstrainedTypeAtLocation, getParserServices, getStaticValue, isStaticMemberAccessOfValue, @@ -234,10 +234,7 @@ export default createRule({ //check the argument type of test methods const argument = callNode.arguments[0]; - const type = DEPRECATED_getConstrainedTypeAtLocation( - services, - argument, - ); + const type = getConstrainedTypeAtLocation(services, argument); const includesMethodDecl = type .getProperty('includes') diff --git a/packages/eslint-plugin/src/rules/prefer-reduce-type-parameter.ts b/packages/eslint-plugin/src/rules/prefer-reduce-type-parameter.ts index d097d76d4f04..997974fe93f9 100644 --- a/packages/eslint-plugin/src/rules/prefer-reduce-type-parameter.ts +++ b/packages/eslint-plugin/src/rules/prefer-reduce-type-parameter.ts @@ -5,7 +5,7 @@ import * as tsutils from 'ts-api-utils'; import { createRule, - DEPRECATED_getConstrainedTypeAtLocation, + getConstrainedTypeAtLocation, getParserServices, isStaticMemberAccessOfValue, isTypeAssertion, @@ -84,7 +84,7 @@ export default createRule({ } // Get the symbol of the `reduce` method. - const calleeObjType = DEPRECATED_getConstrainedTypeAtLocation( + const calleeObjType = getConstrainedTypeAtLocation( services, callee.object, ); diff --git a/packages/eslint-plugin/src/rules/require-array-sort-compare.ts b/packages/eslint-plugin/src/rules/require-array-sort-compare.ts index f7004e61f3a6..2f19e56f4d63 100644 --- a/packages/eslint-plugin/src/rules/require-array-sort-compare.ts +++ b/packages/eslint-plugin/src/rules/require-array-sort-compare.ts @@ -2,7 +2,7 @@ import type { TSESTree } from '@typescript-eslint/utils'; import { createRule, - DEPRECATED_getConstrainedTypeAtLocation, + getConstrainedTypeAtLocation, getParserServices, getTypeName, isStaticMemberAccessOfValue, @@ -70,7 +70,7 @@ export default createRule({ if (!isStaticMemberAccessOfValue(callee, context, 'sort', 'toSorted')) { return; } - const calleeObjType = DEPRECATED_getConstrainedTypeAtLocation( + const calleeObjType = getConstrainedTypeAtLocation( services, callee.object, ); diff --git a/packages/eslint-plugin/src/rules/restrict-plus-operands.ts b/packages/eslint-plugin/src/rules/restrict-plus-operands.ts index caf186b033ae..306342754018 100644 --- a/packages/eslint-plugin/src/rules/restrict-plus-operands.ts +++ b/packages/eslint-plugin/src/rules/restrict-plus-operands.ts @@ -5,7 +5,7 @@ import * as ts from 'typescript'; import { createRule, - DEPRECATED_getConstrainedTypeAtLocation, + getConstrainedTypeAtLocation, getParserServices, getTypeName, isTypeAnyType, @@ -130,7 +130,7 @@ export default createRule({ function getTypeConstrained(node: TSESTree.Node): ts.Type { return typeChecker.getBaseTypeOfLiteralType( - DEPRECATED_getConstrainedTypeAtLocation(services, node), + getConstrainedTypeAtLocation(services, node), ); } diff --git a/packages/eslint-plugin/src/rules/restrict-template-expressions.ts b/packages/eslint-plugin/src/rules/restrict-template-expressions.ts index c9c42689dac4..3049c1fe3fcf 100644 --- a/packages/eslint-plugin/src/rules/restrict-template-expressions.ts +++ b/packages/eslint-plugin/src/rules/restrict-template-expressions.ts @@ -12,7 +12,7 @@ import type { TypeOrValueSpecifier } from '../util'; import { createRule, - DEPRECATED_getConstrainedTypeAtLocation, + getConstrainedTypeAtLocation, getParserServices, getTypeName, isTypeAnyType, @@ -137,7 +137,7 @@ export default createRule({ } for (const expression of node.expressions) { - const expressionType = DEPRECATED_getConstrainedTypeAtLocation( + const expressionType = getConstrainedTypeAtLocation( services, expression, ); diff --git a/packages/eslint-plugin/src/rules/return-await.ts b/packages/eslint-plugin/src/rules/return-await.ts index b0e7f9c811d3..3b8cd990c1ad 100644 --- a/packages/eslint-plugin/src/rules/return-await.ts +++ b/packages/eslint-plugin/src/rules/return-await.ts @@ -6,7 +6,7 @@ import * as ts from 'typescript'; import { Awaitable, createRule, - getConstraintTypeInfoAtLocation, + getConstraintTypeInfo, getFixOrSuggest, getParserServices, isAwaitExpression, @@ -303,13 +303,11 @@ export default createRule({ child = expression; } - const childEsNode = services.tsNodeToESTreeNodeMap.get(child); - - const constraintInfo = getConstraintTypeInfoAtLocation( - services, - childEsNode, + const certainty = needsToBeAwaited( + checker, + expression, + getConstraintTypeInfo(checker, checker.getTypeAtLocation(child)), ); - const certainty = needsToBeAwaited(checker, expression, constraintInfo); // handle awaited _non_thenables diff --git a/packages/eslint-plugin/src/rules/strict-boolean-expressions.ts b/packages/eslint-plugin/src/rules/strict-boolean-expressions.ts index ceecf1e76d80..216929d62630 100644 --- a/packages/eslint-plugin/src/rules/strict-boolean-expressions.ts +++ b/packages/eslint-plugin/src/rules/strict-boolean-expressions.ts @@ -9,7 +9,7 @@ import * as ts from 'typescript'; import { createRule, - DEPRECATED_getConstrainedTypeAtLocation, + getConstrainedTypeAtLocation, getParserServices, getWrappingFixer, isArrayMethodCallWithPredicate, @@ -426,7 +426,7 @@ export default createRule({ * It analyzes the type of a node and checks if it is allowed in a boolean context. */ function checkNode(node: TSESTree.Expression): void { - const type = DEPRECATED_getConstrainedTypeAtLocation(services, node); + const type = getConstrainedTypeAtLocation(services, node); const types = inspectVariantTypes(tsutils.unionTypeParts(type)); const is = (...wantedTypes: readonly VariantType[]): boolean => @@ -1104,10 +1104,7 @@ function isArrayLengthExpression( if (node.property.name !== 'length') { return false; } - const objectType = DEPRECATED_getConstrainedTypeAtLocation( - services, - node.object, - ); + const objectType = getConstrainedTypeAtLocation(services, node.object); return isTypeArrayTypeOrUnionOfArrayTypes(objectType, typeChecker); } diff --git a/packages/eslint-plugin/src/rules/switch-exhaustiveness-check.ts b/packages/eslint-plugin/src/rules/switch-exhaustiveness-check.ts index c5dcd4e25b5d..1323344605c8 100644 --- a/packages/eslint-plugin/src/rules/switch-exhaustiveness-check.ts +++ b/packages/eslint-plugin/src/rules/switch-exhaustiveness-check.ts @@ -5,7 +5,7 @@ import * as ts from 'typescript'; import { createRule, - DEPRECATED_getConstrainedTypeAtLocation, + getConstrainedTypeAtLocation, getParserServices, isClosingBraceToken, isOpeningBraceToken, @@ -147,7 +147,7 @@ export default createRule({ switchCase => switchCase.test == null, ); - const discriminantType = DEPRECATED_getConstrainedTypeAtLocation( + const discriminantType = getConstrainedTypeAtLocation( services, node.discriminant, ); @@ -167,7 +167,7 @@ export default createRule({ continue; } - const caseType = DEPRECATED_getConstrainedTypeAtLocation( + const caseType = getConstrainedTypeAtLocation( services, switchCase.test, ); diff --git a/packages/eslint-plugin/src/util/getConstraintTypeInfo.ts b/packages/eslint-plugin/src/util/getConstraintTypeInfo.ts new file mode 100644 index 000000000000..d9c2cf0def37 --- /dev/null +++ b/packages/eslint-plugin/src/util/getConstraintTypeInfo.ts @@ -0,0 +1,51 @@ +import type * as ts from 'typescript'; + +import * as tsutils from 'ts-api-utils'; + +export interface ConstraintTypeInfoUnconstrained { + constraintType: undefined; + isTypeParameter: true; +} + +export interface ConstraintTypeInfoConstrained { + constraintType: ts.Type; + isTypeParameter: true; +} + +export interface ConstraintTypeInfoNonGeneric { + constraintType: ts.Type; + isTypeParameter: false; +} + +export type ConstraintTypeInfo = + | ConstraintTypeInfoConstrained + | ConstraintTypeInfoNonGeneric + | ConstraintTypeInfoUnconstrained; + +/** + * Resolves the given node's type, and returns info about whether it is a generic or ordinary type. + * + * Successor to {@link getConstrainedTypeAtLocation} due to https://github.com/typescript-eslint/typescript-eslint/issues/10438 + * + * This is considered internal since it is unstable for now and may have breaking changes at any time. + * Use at your own risk. + * + * @internal + * + */ +export function getConstraintTypeInfo( + checker: ts.TypeChecker, + type: ts.Type, +): ConstraintTypeInfo { + if (tsutils.isTypeParameter(type)) { + const constraintType = checker.getBaseConstraintOfType(type); + return { + constraintType, + isTypeParameter: true, + }; + } + return { + constraintType: type, + isTypeParameter: false, + }; +} diff --git a/packages/eslint-plugin/src/util/index.ts b/packages/eslint-plugin/src/util/index.ts index 4cc7a139dc86..40a48f999b8d 100644 --- a/packages/eslint-plugin/src/util/index.ts +++ b/packages/eslint-plugin/src/util/index.ts @@ -1,10 +1,3 @@ -import type { - ParserServicesWithTypeInformation, - TSESTree, -} from '@typescript-eslint/utils'; -import type * as ts from 'typescript'; - -import { getConstrainedTypeAtLocation } from '@typescript-eslint/type-utils'; import { ESLintUtils } from '@typescript-eslint/utils'; export * from './astUtils'; @@ -30,22 +23,11 @@ export * from './objectIterators'; export * from './needsToBeAwaited'; export * from './scopeUtils'; export * from './types'; +export * from './getConstraintTypeInfo'; // this is done for convenience - saves migrating all of the old rules export * from '@typescript-eslint/type-utils'; -/** - * This is a version of {@link getConstrainedTypeAtLocation} not marked with the - * `@deprecated` JSDoc tag, in order to allow gradual migration away from it. - * This is a workaround for https://github.com/typescript-eslint/typescript-eslint/issues/9899 - */ -const DEPRECATED_getConstrainedTypeAtLocation: ( - services: ParserServicesWithTypeInformation, - node: TSESTree.Node, -) => ts.Type = - // eslint-disable-next-line @typescript-eslint/no-deprecated - getConstrainedTypeAtLocation; - const { applyDefault, deepMerge, @@ -59,7 +41,6 @@ type InferMessageIdsTypeFromRule = type InferOptionsTypeFromRule = ESLintUtils.InferOptionsTypeFromRule; export { - DEPRECATED_getConstrainedTypeAtLocation, applyDefault, deepMerge, getParserServices, diff --git a/packages/eslint-plugin/src/util/isArrayMethodCallWithPredicate.ts b/packages/eslint-plugin/src/util/isArrayMethodCallWithPredicate.ts index 1c607236a7b8..33f5b6ea517c 100644 --- a/packages/eslint-plugin/src/util/isArrayMethodCallWithPredicate.ts +++ b/packages/eslint-plugin/src/util/isArrayMethodCallWithPredicate.ts @@ -7,7 +7,7 @@ import type { RuleContext } from '@typescript-eslint/utils/ts-eslint'; import { AST_NODE_TYPES } from '@typescript-eslint/utils'; import * as tsutils from 'ts-api-utils'; -import { DEPRECATED_getConstrainedTypeAtLocation } from './index'; +import { getConstrainedTypeAtLocation } from './index'; import { getStaticMemberAccessValue } from './misc'; const ARRAY_PREDICATE_FUNCTIONS = new Set([ @@ -36,10 +36,7 @@ export function isArrayMethodCallWithPredicate( } const checker = services.program.getTypeChecker(); - const type = DEPRECATED_getConstrainedTypeAtLocation( - services, - node.callee.object, - ); + const type = getConstrainedTypeAtLocation(services, node.callee.object); return tsutils .unionTypeParts(type) .flatMap(part => tsutils.intersectionTypeParts(part)) diff --git a/packages/eslint-plugin/src/util/needsToBeAwaited.ts b/packages/eslint-plugin/src/util/needsToBeAwaited.ts index 18a0317dfd65..2117c3da3cf7 100644 --- a/packages/eslint-plugin/src/util/needsToBeAwaited.ts +++ b/packages/eslint-plugin/src/util/needsToBeAwaited.ts @@ -1,4 +1,3 @@ -import type { ConstraintTypeInfo } from '@typescript-eslint/type-utils'; import type * as ts from 'typescript'; import { @@ -7,6 +6,8 @@ import { } from '@typescript-eslint/type-utils'; import * as tsutils from 'ts-api-utils'; +import type { ConstraintTypeInfo } from './getConstraintTypeInfo'; + export enum Awaitable { Always, Never, diff --git a/packages/eslint-plugin/tests/util/getConstraintTypeInfo.test.ts b/packages/eslint-plugin/tests/util/getConstraintTypeInfo.test.ts new file mode 100644 index 000000000000..348bd6be803e --- /dev/null +++ b/packages/eslint-plugin/tests/util/getConstraintTypeInfo.test.ts @@ -0,0 +1,196 @@ +import type { + TSESTree, + ParserServicesWithTypeInformation, +} from '@typescript-eslint/utils'; + +import { parseForESLint } from '@typescript-eslint/parser'; +import path from 'node:path'; +import * as tsutils from 'ts-api-utils'; + +import { getConstraintTypeInfo } from '../../src/util/getConstraintTypeInfo'; + +function parseCodeForEslint(code: string): ReturnType & { + services: ParserServicesWithTypeInformation; +} { + const fixturesDir = path.join(__dirname, '../fixtures/'); + + // @ts-expect-error -- services will have type information. + return parseForESLint(code, { + disallowAutomaticSingleRunInference: true, + filePath: path.join(fixturesDir, 'file.ts'), + project: './tsconfig.json', + tsconfigRootDir: fixturesDir, + }); +} + +describe('getConstraintTypeInfoAtLocation', () => { + it('returns undefined for unconstrained generic', () => { + const sourceCode = ` +function foo(x: T); + `; + + const { ast, services } = parseCodeForEslint(sourceCode); + const checker = services.program.getTypeChecker(); + + const functionNode = ast.body[0] as TSESTree.FunctionDeclaration; + const parameterNode = functionNode.params[0]; + const parameterType = services.getTypeAtLocation(parameterNode); + + const { constraintType, isTypeParameter } = getConstraintTypeInfo( + checker, + parameterType, + ); + + expect(isTypeParameter).toBe(true); + // ideally one day we'll be able to change this to assert that it be the intrinsic unknown type. + // Requires https://github.com/microsoft/TypeScript/issues/60475 + expect(constraintType).toBeUndefined(); + }); + + it('returns unknown for extends unknown', () => { + const sourceCode = ` +function foo(x: T); + `; + + const { ast, services } = parseCodeForEslint(sourceCode); + const checker = services.program.getTypeChecker(); + + const functionNode = ast.body[0] as TSESTree.FunctionDeclaration; + const parameterNode = functionNode.params[0]; + const parameterType = services.getTypeAtLocation(parameterNode); + + const { constraintType, isTypeParameter } = getConstraintTypeInfo( + checker, + parameterType, + ); + + expect(isTypeParameter).toBe(true); + expect(constraintType).toBeDefined(); + expect(tsutils.isTypeParameter(constraintType!)).toBe(false); + expect(tsutils.isIntrinsicUnknownType(constraintType!)).toBe(true); + }); + + it('returns unknown for extends any', () => { + const sourceCode = ` +function foo(x: T); + `; + + const { ast, services } = parseCodeForEslint(sourceCode); + const checker = services.program.getTypeChecker(); + + const functionNode = ast.body[0] as TSESTree.FunctionDeclaration; + const parameterNode = functionNode.params[0]; + const parameterType = services.getTypeAtLocation(parameterNode); + + const { constraintType, isTypeParameter } = getConstraintTypeInfo( + checker, + parameterType, + ); + + expect(isTypeParameter).toBe(true); + expect(constraintType).toBeDefined(); + expect(tsutils.isTypeParameter(constraintType!)).toBe(false); + expect(tsutils.isIntrinsicUnknownType(constraintType!)).toBe(true); + }); + + it('returns string for extends string', () => { + const sourceCode = ` +function foo(x: T); + `; + + const { ast, services } = parseCodeForEslint(sourceCode); + const checker = services.program.getTypeChecker(); + + const functionNode = ast.body[0] as TSESTree.FunctionDeclaration; + const parameterNode = functionNode.params[0]; + const parameterType = services.getTypeAtLocation(parameterNode); + + const { constraintType, isTypeParameter } = getConstraintTypeInfo( + checker, + parameterType, + ); + + expect(isTypeParameter).toBe(true); + expect(constraintType).toBeDefined(); + expect(tsutils.isTypeParameter(constraintType!)).toBe(false); + expect(tsutils.isIntrinsicStringType(constraintType!)).toBe(true); + }); + + it('returns string for non-generic string', () => { + const sourceCode = ` +function foo(x: string); + `; + + const { ast, services } = parseCodeForEslint(sourceCode); + const checker = services.program.getTypeChecker(); + + const functionNode = ast.body[0] as TSESTree.FunctionDeclaration; + const parameterNode = functionNode.params[0]; + const parameterType = services.getTypeAtLocation(parameterNode); + + const { constraintType, isTypeParameter } = getConstraintTypeInfo( + checker, + parameterType, + ); + + expect(isTypeParameter).toBe(false); + expect(constraintType).toBeDefined(); + expect(tsutils.isTypeParameter(constraintType!)).toBe(false); + expect(tsutils.isIntrinsicStringType(constraintType!)).toBe(true); + expect(constraintType).toBe(parameterType); + }); + + it('handles type parameter whose constraint is a constrained type parameter', () => { + const sourceCode = ` +function foo() { + function bar(x: V) { + } +} + `; + + const { ast, services } = parseCodeForEslint(sourceCode); + const checker = services.program.getTypeChecker(); + + const outerFunctionNode = ast.body[0] as TSESTree.FunctionDeclaration; + const innerFunctionNode = outerFunctionNode.body + .body[0] as TSESTree.FunctionDeclaration; + const parameterNode = innerFunctionNode.params[0]; + const parameterType = services.getTypeAtLocation(parameterNode); + + const { constraintType, isTypeParameter } = getConstraintTypeInfo( + checker, + parameterType, + ); + + expect(constraintType).toBeDefined(); + expect(tsutils.isTypeParameter(constraintType!)).toBe(false); + expect(tsutils.isIntrinsicStringType(constraintType!)).toBe(true); + expect(isTypeParameter).toBe(true); + }); + + it('handles type parameter whose constraint is an unconstrained type parameter', () => { + const sourceCode = ` +function foo() { + function bar(x: V) { + } +} + `; + + const { ast, services } = parseCodeForEslint(sourceCode); + const checker = services.program.getTypeChecker(); + + const outerFunctionNode = ast.body[0] as TSESTree.FunctionDeclaration; + const innerFunctionNode = outerFunctionNode.body + .body[0] as TSESTree.FunctionDeclaration; + const parameterNode = innerFunctionNode.params[0]; + const parameterType = services.getTypeAtLocation(parameterNode); + + const { constraintType, isTypeParameter } = getConstraintTypeInfo( + checker, + parameterType, + ); + + expect(isTypeParameter).toBe(true); + expect(constraintType).toBeUndefined(); + }); +}); diff --git a/packages/type-utils/src/getConstrainedTypeAtLocation.ts b/packages/type-utils/src/getConstrainedTypeAtLocation.ts index 560b8003c1e6..d31df29ab5b1 100644 --- a/packages/type-utils/src/getConstrainedTypeAtLocation.ts +++ b/packages/type-utils/src/getConstrainedTypeAtLocation.ts @@ -4,18 +4,14 @@ import type { } from '@typescript-eslint/typescript-estree'; import type * as ts from 'typescript'; -import * as tsutils from 'ts-api-utils'; - /** - * Resolves the given node's type. Will resolve to the type's generic constraint, if it has one. + * Resolves the given node's type. Will return the type's generic constraint, if it has one. * - * If the type is generic and does _not_ have a constraint, the type will be + * Warning - if the type is generic and does _not_ have a constraint, the type will be * returned as-is, rather than returning an `unknown` type. This can be checked * for by checking for the type flag ts.TypeFlags.TypeParameter. * * @see https://github.com/typescript-eslint/typescript-eslint/issues/10438 - * - * @deprecated Use {@link getConstraintTypeInfoAtLocation} instead. */ export function getConstrainedTypeAtLocation( services: ParserServicesWithTypeInformation, @@ -28,52 +24,3 @@ export function getConstrainedTypeAtLocation( return constrained ?? nodeType; } - -export interface ConstraintTypeInfoUnconstrained { - constraintType: undefined; - isTypeParameter: true; - type: ts.TypeParameter; -} - -export interface ConstraintTypeInfoConstrained { - constraintType: ts.Type; - isTypeParameter: true; - type: ts.TypeParameter; -} - -export interface ConstraintTypeInfoNonGeneric { - constraintType: ts.Type; - isTypeParameter: false; - type: ts.Type; -} - -export type ConstraintTypeInfo = - | ConstraintTypeInfoConstrained - | ConstraintTypeInfoNonGeneric - | ConstraintTypeInfoUnconstrained; - -/** - * Resolves the given node's type, and returns info about whether it is a generic or ordinary type. - * - * Successor to {@link getConstrainedTypeAtLocation} due to https://github.com/typescript-eslint/typescript-eslint/issues/10438 - */ -export function getConstraintTypeInfoAtLocation( - services: ParserServicesWithTypeInformation, - node: TSESTree.Node, -): ConstraintTypeInfo { - const type = services.getTypeAtLocation(node); - if (tsutils.isTypeParameter(type)) { - const checker = services.program.getTypeChecker(); - const constraintType = checker.getBaseConstraintOfType(type); - return { - constraintType, - isTypeParameter: true, - type, - }; - } - return { - constraintType: type, - isTypeParameter: false, - type, - }; -} diff --git a/packages/type-utils/tests/getConstrainedTypeAtLocation.test.ts b/packages/type-utils/tests/getConstrainedTypeAtLocation.test.ts index 20c6f627af4f..598e78500afe 100644 --- a/packages/type-utils/tests/getConstrainedTypeAtLocation.test.ts +++ b/packages/type-utils/tests/getConstrainedTypeAtLocation.test.ts @@ -5,11 +5,7 @@ import { parseForESLint } from '@typescript-eslint/parser'; import path from 'node:path'; import * as tsutils from 'ts-api-utils'; -import { - getConstrainedTypeAtLocation, - getConstraintTypeInfoAtLocation, - isTypeUnknownType, -} from '../src'; +import { getConstrainedTypeAtLocation, isTypeUnknownType } from '../src'; function parseCodeForEslint(code: string): ReturnType & { services: ParserServicesWithTypeInformation; @@ -25,7 +21,6 @@ function parseCodeForEslint(code: string): ReturnType & { }); } -/* eslint-disable @typescript-eslint/no-deprecated -- testing a deprecated function */ describe('getConstrainedTypeAtLocation', () => { // See https://github.com/typescript-eslint/typescript-eslint/issues/10438 // eslint-disable-next-line jest/no-disabled-tests -- known issue. @@ -149,162 +144,3 @@ function foo() { expect(tsutils.isIntrinsicStringType(constraintAtLocation)).toBe(true); }); }); -/* eslint-enable @typescript-eslint/no-deprecated */ - -describe('getConstraintTypeInfoAtLocation', () => { - it('returns undefined for unconstrained generic', () => { - const sourceCode = ` -function foo(x: T); - `; - - const { ast, services } = parseCodeForEslint(sourceCode); - - const functionNode = ast.body[0] as TSESTree.FunctionDeclaration; - const parameterNode = functionNode.params[0]; - const parameterType = services.getTypeAtLocation(parameterNode); - - const { constraintType, isTypeParameter, type } = - getConstraintTypeInfoAtLocation(services, parameterNode); - - expect(type).toBe(parameterType); - expect(isTypeParameter).toBe(true); - // ideally one day we'll be able to change this to assert that it be the intrinsic unknown type. - // Requires https://github.com/microsoft/TypeScript/issues/60475 - expect(constraintType).toBeUndefined(); - }); - - it('returns unknown for extends unknown', () => { - const sourceCode = ` -function foo(x: T); - `; - - const { ast, services } = parseCodeForEslint(sourceCode); - - const functionNode = ast.body[0] as TSESTree.FunctionDeclaration; - const parameterNode = functionNode.params[0]; - const parameterType = services.getTypeAtLocation(parameterNode); - - const { constraintType, isTypeParameter, type } = - getConstraintTypeInfoAtLocation(services, parameterNode); - - expect(type).toBe(parameterType); - expect(isTypeParameter).toBe(true); - expect(constraintType).toBeDefined(); - expect(tsutils.isTypeParameter(constraintType!)).toBe(false); - expect(tsutils.isIntrinsicUnknownType(constraintType!)).toBe(true); - }); - - it('returns unknown for extends any', () => { - const sourceCode = ` -function foo(x: T); - `; - - const { ast, services } = parseCodeForEslint(sourceCode); - - const functionNode = ast.body[0] as TSESTree.FunctionDeclaration; - const parameterNode = functionNode.params[0]; - const parameterType = services.getTypeAtLocation(parameterNode); - - const { constraintType, isTypeParameter, type } = - getConstraintTypeInfoAtLocation(services, parameterNode); - - expect(type).toBe(parameterType); - expect(isTypeParameter).toBe(true); - expect(constraintType).toBeDefined(); - expect(tsutils.isTypeParameter(constraintType!)).toBe(false); - expect(tsutils.isIntrinsicUnknownType(constraintType!)).toBe(true); - }); - - it('returns string for extends string', () => { - const sourceCode = ` -function foo(x: T); - `; - - const { ast, services } = parseCodeForEslint(sourceCode); - - const functionNode = ast.body[0] as TSESTree.FunctionDeclaration; - const parameterNode = functionNode.params[0]; - const parameterType = services.getTypeAtLocation(parameterNode); - - const { constraintType, isTypeParameter, type } = - getConstraintTypeInfoAtLocation(services, parameterNode); - - expect(type).toBe(parameterType); - expect(isTypeParameter).toBe(true); - expect(constraintType).toBeDefined(); - expect(tsutils.isTypeParameter(constraintType!)).toBe(false); - expect(tsutils.isIntrinsicStringType(constraintType!)).toBe(true); - }); - - it('returns string for non-generic string', () => { - const sourceCode = ` -function foo(x: string); - `; - - const { ast, services } = parseCodeForEslint(sourceCode); - - const functionNode = ast.body[0] as TSESTree.FunctionDeclaration; - const parameterNode = functionNode.params[0]; - const parameterType = services.getTypeAtLocation(parameterNode); - - const { constraintType, isTypeParameter, type } = - getConstraintTypeInfoAtLocation(services, parameterNode); - - expect(type).toBe(parameterType); - expect(isTypeParameter).toBe(false); - expect(constraintType).toBeDefined(); - expect(tsutils.isTypeParameter(constraintType!)).toBe(false); - expect(tsutils.isIntrinsicStringType(constraintType!)).toBe(true); - expect(type).toBe(constraintType); - }); - - it('handles type parameter whose constraint is a constrained type parameter', () => { - const sourceCode = ` -function foo() { - function bar(x: V) { - } -} - `; - - const { ast, services } = parseCodeForEslint(sourceCode); - - const outerFunctionNode = ast.body[0] as TSESTree.FunctionDeclaration; - const innerFunctionNode = outerFunctionNode.body - .body[0] as TSESTree.FunctionDeclaration; - const parameterNode = innerFunctionNode.params[0]; - const parameterType = services.getTypeAtLocation(parameterNode); - - const { constraintType, isTypeParameter, type } = - getConstraintTypeInfoAtLocation(services, parameterNode); - - expect(type).toBe(parameterType); - expect(constraintType).toBeDefined(); - expect(tsutils.isTypeParameter(constraintType!)).toBe(false); - expect(tsutils.isIntrinsicStringType(constraintType!)).toBe(true); - expect(isTypeParameter).toBe(true); - }); - - it('handles type parameter whose constraint is an unconstrained type parameter', () => { - const sourceCode = ` -function foo() { - function bar(x: V) { - } -} - `; - - const { ast, services } = parseCodeForEslint(sourceCode); - - const outerFunctionNode = ast.body[0] as TSESTree.FunctionDeclaration; - const innerFunctionNode = outerFunctionNode.body - .body[0] as TSESTree.FunctionDeclaration; - const parameterNode = innerFunctionNode.params[0]; - const parameterType = services.getTypeAtLocation(parameterNode); - - const { constraintType, isTypeParameter, type } = - getConstraintTypeInfoAtLocation(services, parameterNode); - - expect(type).toBe(parameterType); - expect(isTypeParameter).toBe(true); - expect(constraintType).toBeUndefined(); - }); -}); From 2a5c248e28360dc615260d094b410200bd74a837 Mon Sep 17 00:00:00 2001 From: Kirk Waiblinger Date: Sat, 28 Dec 2024 16:49:19 -0700 Subject: [PATCH 12/15] more simplify --- .../eslint-plugin/src/rules/await-thenable.ts | 15 +++++---------- packages/eslint-plugin/src/rules/return-await.ts | 7 ++----- .../src/util/isArrayMethodCallWithPredicate.ts | 2 +- .../eslint-plugin/src/util/needsToBeAwaited.ts | 9 ++++++--- 4 files changed, 14 insertions(+), 19 deletions(-) diff --git a/packages/eslint-plugin/src/rules/await-thenable.ts b/packages/eslint-plugin/src/rules/await-thenable.ts index b4466b47549d..84c847bb56b9 100644 --- a/packages/eslint-plugin/src/rules/await-thenable.ts +++ b/packages/eslint-plugin/src/rules/await-thenable.ts @@ -52,16 +52,11 @@ export default createRule<[], MessageId>({ return { AwaitExpression(node): void { - const awaitedNode = node.argument; - const awaitedTsNode = services.esTreeNodeToTSNodeMap.get(awaitedNode); - const certainty = needsToBeAwaited( - checker, - awaitedTsNode, - getConstraintTypeInfo( - checker, - checker.getTypeAtLocation(awaitedTsNode), - ), - ); + const awaitedEsNode = node.argument; + const type = services.getTypeAtLocation(awaitedEsNode); + const awaitedTsNode = services.esTreeNodeToTSNodeMap.get(awaitedEsNode); + + const certainty = needsToBeAwaited(checker, awaitedTsNode, type); if (certainty === Awaitable.Never) { context.report({ diff --git a/packages/eslint-plugin/src/rules/return-await.ts b/packages/eslint-plugin/src/rules/return-await.ts index 3b8cd990c1ad..85ad30e65178 100644 --- a/packages/eslint-plugin/src/rules/return-await.ts +++ b/packages/eslint-plugin/src/rules/return-await.ts @@ -303,11 +303,8 @@ export default createRule({ child = expression; } - const certainty = needsToBeAwaited( - checker, - expression, - getConstraintTypeInfo(checker, checker.getTypeAtLocation(child)), - ); + const type = checker.getTypeAtLocation(child); + const certainty = needsToBeAwaited(checker, expression, type); // handle awaited _non_thenables diff --git a/packages/eslint-plugin/src/util/isArrayMethodCallWithPredicate.ts b/packages/eslint-plugin/src/util/isArrayMethodCallWithPredicate.ts index 33f5b6ea517c..c48e1a13636d 100644 --- a/packages/eslint-plugin/src/util/isArrayMethodCallWithPredicate.ts +++ b/packages/eslint-plugin/src/util/isArrayMethodCallWithPredicate.ts @@ -4,10 +4,10 @@ import type { } from '@typescript-eslint/utils'; import type { RuleContext } from '@typescript-eslint/utils/ts-eslint'; +import { getConstrainedTypeAtLocation } from '@typescript-eslint/type-utils'; import { AST_NODE_TYPES } from '@typescript-eslint/utils'; import * as tsutils from 'ts-api-utils'; -import { getConstrainedTypeAtLocation } from './index'; import { getStaticMemberAccessValue } from './misc'; const ARRAY_PREDICATE_FUNCTIONS = new Set([ diff --git a/packages/eslint-plugin/src/util/needsToBeAwaited.ts b/packages/eslint-plugin/src/util/needsToBeAwaited.ts index 2117c3da3cf7..4e2369e4c45f 100644 --- a/packages/eslint-plugin/src/util/needsToBeAwaited.ts +++ b/packages/eslint-plugin/src/util/needsToBeAwaited.ts @@ -6,7 +6,7 @@ import { } from '@typescript-eslint/type-utils'; import * as tsutils from 'ts-api-utils'; -import type { ConstraintTypeInfo } from './getConstraintTypeInfo'; +import { getConstraintTypeInfo } from './getConstraintTypeInfo'; export enum Awaitable { Always, @@ -17,9 +17,12 @@ export enum Awaitable { export function needsToBeAwaited( checker: ts.TypeChecker, node: ts.Node, - constraintTypeInfo: ConstraintTypeInfo, + type: ts.Type, ): Awaitable { - const { constraintType, isTypeParameter } = constraintTypeInfo; + const { constraintType, isTypeParameter } = getConstraintTypeInfo( + checker, + type, + ); // unconstrained generic types should be treated as unknown if (isTypeParameter && constraintType == null) { From c646ffbf0aa7ea656e48a21952b015f09f37de84 Mon Sep 17 00:00:00 2001 From: Kirk Waiblinger Date: Sat, 28 Dec 2024 16:54:28 -0700 Subject: [PATCH 13/15] jsdoc --- .../eslint-plugin/src/util/getConstraintTypeInfo.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/eslint-plugin/src/util/getConstraintTypeInfo.ts b/packages/eslint-plugin/src/util/getConstraintTypeInfo.ts index d9c2cf0def37..358f1acf8505 100644 --- a/packages/eslint-plugin/src/util/getConstraintTypeInfo.ts +++ b/packages/eslint-plugin/src/util/getConstraintTypeInfo.ts @@ -23,7 +23,16 @@ export type ConstraintTypeInfo = | ConstraintTypeInfoUnconstrained; /** - * Resolves the given node's type, and returns info about whether it is a generic or ordinary type. + * Returns whether the type is a generic and what its constraint is. + * + * If the type is not a generic, `isTypeParameter` will be `false`, and + * `constraintType` will be the same as the input type. + * + * If the type is a generic, and it is constrained, `isTypeParameter` will be + * `true`, and `constraintType` will be the constraint type. + * + * If the type is a generic, but it is not constrained, `constraintType` will be + * `undefined` (rather than an `unknown` type), due to https://github.com/microsoft/TypeScript/issues/60475 * * Successor to {@link getConstrainedTypeAtLocation} due to https://github.com/typescript-eslint/typescript-eslint/issues/10438 * From 943dc7575748399608da371b8847abdc48c8cd1b Mon Sep 17 00:00:00 2001 From: Kirk Waiblinger Date: Sat, 28 Dec 2024 17:02:53 -0700 Subject: [PATCH 14/15] more better --- .../eslint-plugin/src/rules/await-thenable.ts | 17 +++++++++++------ .../no-unnecessary-boolean-literal-compare.ts | 4 ++-- .../src/rules/no-unnecessary-condition.ts | 11 ++--------- .../eslint-plugin/src/rules/return-await.ts | 1 - ...straintTypeInfo.ts => getConstraintInfo.ts} | 2 +- packages/eslint-plugin/src/util/index.ts | 2 +- .../eslint-plugin/src/util/needsToBeAwaited.ts | 7 ++----- ...eInfo.test.ts => getConstraintInfo.test.ts} | 18 +++++++++--------- 8 files changed, 28 insertions(+), 34 deletions(-) rename packages/eslint-plugin/src/util/{getConstraintTypeInfo.ts => getConstraintInfo.ts} (97%) rename packages/eslint-plugin/tests/util/{getConstraintTypeInfo.test.ts => getConstraintInfo.test.ts} (90%) diff --git a/packages/eslint-plugin/src/rules/await-thenable.ts b/packages/eslint-plugin/src/rules/await-thenable.ts index 84c847bb56b9..5f63a8dc0f31 100644 --- a/packages/eslint-plugin/src/rules/await-thenable.ts +++ b/packages/eslint-plugin/src/rules/await-thenable.ts @@ -5,7 +5,6 @@ import * as tsutils from 'ts-api-utils'; import { Awaitable, createRule, - getConstraintTypeInfo, getFixOrSuggest, getParserServices, isAwaitKeyword, @@ -52,11 +51,17 @@ export default createRule<[], MessageId>({ return { AwaitExpression(node): void { - const awaitedEsNode = node.argument; - const type = services.getTypeAtLocation(awaitedEsNode); - const awaitedTsNode = services.esTreeNodeToTSNodeMap.get(awaitedEsNode); - - const certainty = needsToBeAwaited(checker, awaitedTsNode, type); + const awaitArgumentEsNode = node.argument; + const awaitArgumentType = + services.getTypeAtLocation(awaitArgumentEsNode); + const awaitArgumentTsNode = + services.esTreeNodeToTSNodeMap.get(awaitArgumentEsNode); + + const certainty = needsToBeAwaited( + checker, + awaitArgumentTsNode, + awaitArgumentType, + ); if (certainty === Awaitable.Never) { context.report({ 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 b352e20824ec..fa8a49a03f85 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 @@ -6,7 +6,7 @@ import * as ts from 'typescript'; import { createRule, - getConstraintTypeInfo, + getConstraintInfo, getParserServices, isStrongPrecedenceNode, } from '../util'; @@ -95,7 +95,7 @@ export default createRule({ return undefined; } - const { constraintType, isTypeParameter } = getConstraintTypeInfo( + const { constraintType, isTypeParameter } = getConstraintInfo( checker, services.getTypeAtLocation(comparison.expression), ); diff --git a/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts b/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts index 1d821efbb6f3..6da2418f696a 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts @@ -7,6 +7,7 @@ import * as ts from 'typescript'; import { createRule, getConstrainedTypeAtLocation, + getConstraintInfo, getParserServices, getTypeName, getTypeOfPropertyOfName, @@ -654,15 +655,7 @@ export default createRule({ getConstrainedTypeAtLocation(services, callback), ) .map(sig => sig.getReturnType()) - .map(t => { - // TODO: use `getConstraintTypeInfoAtLocation` once it's merged - // https://github.com/typescript-eslint/typescript-eslint/pull/10496 - if (tsutils.isTypeParameter(t)) { - return checker.getBaseConstraintOfType(t); - } - - return t; - }); + .map(t => getConstraintInfo(checker, t).constraintType); if (returnTypes.length === 0) { // Not a callable function, e.g. `any` diff --git a/packages/eslint-plugin/src/rules/return-await.ts b/packages/eslint-plugin/src/rules/return-await.ts index 85ad30e65178..18cc6e9e420a 100644 --- a/packages/eslint-plugin/src/rules/return-await.ts +++ b/packages/eslint-plugin/src/rules/return-await.ts @@ -6,7 +6,6 @@ import * as ts from 'typescript'; import { Awaitable, createRule, - getConstraintTypeInfo, getFixOrSuggest, getParserServices, isAwaitExpression, diff --git a/packages/eslint-plugin/src/util/getConstraintTypeInfo.ts b/packages/eslint-plugin/src/util/getConstraintInfo.ts similarity index 97% rename from packages/eslint-plugin/src/util/getConstraintTypeInfo.ts rename to packages/eslint-plugin/src/util/getConstraintInfo.ts index 358f1acf8505..80eaeb72e24c 100644 --- a/packages/eslint-plugin/src/util/getConstraintTypeInfo.ts +++ b/packages/eslint-plugin/src/util/getConstraintInfo.ts @@ -42,7 +42,7 @@ export type ConstraintTypeInfo = * @internal * */ -export function getConstraintTypeInfo( +export function getConstraintInfo( checker: ts.TypeChecker, type: ts.Type, ): ConstraintTypeInfo { diff --git a/packages/eslint-plugin/src/util/index.ts b/packages/eslint-plugin/src/util/index.ts index 40a48f999b8d..934956e91ad0 100644 --- a/packages/eslint-plugin/src/util/index.ts +++ b/packages/eslint-plugin/src/util/index.ts @@ -23,7 +23,7 @@ export * from './objectIterators'; export * from './needsToBeAwaited'; export * from './scopeUtils'; export * from './types'; -export * from './getConstraintTypeInfo'; +export * from './getConstraintInfo'; // this is done for convenience - saves migrating all of the old rules export * from '@typescript-eslint/type-utils'; diff --git a/packages/eslint-plugin/src/util/needsToBeAwaited.ts b/packages/eslint-plugin/src/util/needsToBeAwaited.ts index 4e2369e4c45f..c9affe4d797e 100644 --- a/packages/eslint-plugin/src/util/needsToBeAwaited.ts +++ b/packages/eslint-plugin/src/util/needsToBeAwaited.ts @@ -6,7 +6,7 @@ import { } from '@typescript-eslint/type-utils'; import * as tsutils from 'ts-api-utils'; -import { getConstraintTypeInfo } from './getConstraintTypeInfo'; +import { getConstraintInfo } from './getConstraintInfo'; export enum Awaitable { Always, @@ -19,10 +19,7 @@ export function needsToBeAwaited( node: ts.Node, type: ts.Type, ): Awaitable { - const { constraintType, isTypeParameter } = getConstraintTypeInfo( - checker, - type, - ); + const { constraintType, isTypeParameter } = getConstraintInfo(checker, type); // unconstrained generic types should be treated as unknown if (isTypeParameter && constraintType == null) { diff --git a/packages/eslint-plugin/tests/util/getConstraintTypeInfo.test.ts b/packages/eslint-plugin/tests/util/getConstraintInfo.test.ts similarity index 90% rename from packages/eslint-plugin/tests/util/getConstraintTypeInfo.test.ts rename to packages/eslint-plugin/tests/util/getConstraintInfo.test.ts index 348bd6be803e..4e92270db32e 100644 --- a/packages/eslint-plugin/tests/util/getConstraintTypeInfo.test.ts +++ b/packages/eslint-plugin/tests/util/getConstraintInfo.test.ts @@ -7,7 +7,7 @@ import { parseForESLint } from '@typescript-eslint/parser'; import path from 'node:path'; import * as tsutils from 'ts-api-utils'; -import { getConstraintTypeInfo } from '../../src/util/getConstraintTypeInfo'; +import { getConstraintInfo } from '../../src/util/getConstraintInfo'; function parseCodeForEslint(code: string): ReturnType & { services: ParserServicesWithTypeInformation; @@ -23,7 +23,7 @@ function parseCodeForEslint(code: string): ReturnType & { }); } -describe('getConstraintTypeInfoAtLocation', () => { +describe('getConstraintInfo', () => { it('returns undefined for unconstrained generic', () => { const sourceCode = ` function foo(x: T); @@ -36,7 +36,7 @@ function foo(x: T); const parameterNode = functionNode.params[0]; const parameterType = services.getTypeAtLocation(parameterNode); - const { constraintType, isTypeParameter } = getConstraintTypeInfo( + const { constraintType, isTypeParameter } = getConstraintInfo( checker, parameterType, ); @@ -59,7 +59,7 @@ function foo(x: T); const parameterNode = functionNode.params[0]; const parameterType = services.getTypeAtLocation(parameterNode); - const { constraintType, isTypeParameter } = getConstraintTypeInfo( + const { constraintType, isTypeParameter } = getConstraintInfo( checker, parameterType, ); @@ -82,7 +82,7 @@ function foo(x: T); const parameterNode = functionNode.params[0]; const parameterType = services.getTypeAtLocation(parameterNode); - const { constraintType, isTypeParameter } = getConstraintTypeInfo( + const { constraintType, isTypeParameter } = getConstraintInfo( checker, parameterType, ); @@ -105,7 +105,7 @@ function foo(x: T); const parameterNode = functionNode.params[0]; const parameterType = services.getTypeAtLocation(parameterNode); - const { constraintType, isTypeParameter } = getConstraintTypeInfo( + const { constraintType, isTypeParameter } = getConstraintInfo( checker, parameterType, ); @@ -128,7 +128,7 @@ function foo(x: string); const parameterNode = functionNode.params[0]; const parameterType = services.getTypeAtLocation(parameterNode); - const { constraintType, isTypeParameter } = getConstraintTypeInfo( + const { constraintType, isTypeParameter } = getConstraintInfo( checker, parameterType, ); @@ -157,7 +157,7 @@ function foo() { const parameterNode = innerFunctionNode.params[0]; const parameterType = services.getTypeAtLocation(parameterNode); - const { constraintType, isTypeParameter } = getConstraintTypeInfo( + const { constraintType, isTypeParameter } = getConstraintInfo( checker, parameterType, ); @@ -185,7 +185,7 @@ function foo() { const parameterNode = innerFunctionNode.params[0]; const parameterType = services.getTypeAtLocation(parameterNode); - const { constraintType, isTypeParameter } = getConstraintTypeInfo( + const { constraintType, isTypeParameter } = getConstraintInfo( checker, parameterType, ); From 4c9da080b9c4180722e1c627c228f05c6981165a Mon Sep 17 00:00:00 2001 From: Kirk Waiblinger Date: Sat, 28 Dec 2024 17:05:25 -0700 Subject: [PATCH 15/15] better --- .../src/rules/no-unnecessary-condition.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts b/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts index 6da2418f696a..08249fe19e19 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts @@ -654,8 +654,7 @@ export default createRule({ .getCallSignaturesOfType( getConstrainedTypeAtLocation(services, callback), ) - .map(sig => sig.getReturnType()) - .map(t => getConstraintInfo(checker, t).constraintType); + .map(sig => sig.getReturnType()); if (returnTypes.length === 0) { // Not a callable function, e.g. `any` @@ -666,16 +665,21 @@ export default createRule({ let hasTruthyReturnTypes = false; for (const type of returnTypes) { + const { constraintType } = getConstraintInfo(checker, type); // Predicate is always necessary if it involves `any` or `unknown` - if (!type || isTypeAnyType(type) || isTypeUnknownType(type)) { + if ( + !constraintType || + isTypeAnyType(constraintType) || + isTypeUnknownType(constraintType) + ) { return; } - if (isPossiblyFalsy(type)) { + if (isPossiblyFalsy(constraintType)) { hasFalsyReturnTypes = true; } - if (isPossiblyTruthy(type)) { + if (isPossiblyTruthy(constraintType)) { hasTruthyReturnTypes = true; }