diff --git a/packages/eslint-plugin/docs/rules/no-unnecessary-type-parameters.mdx b/packages/eslint-plugin/docs/rules/no-unnecessary-type-parameters.mdx index 79eb4b5d4936..a11969f58f51 100644 --- a/packages/eslint-plugin/docs/rules/no-unnecessary-type-parameters.mdx +++ b/packages/eslint-plugin/docs/rules/no-unnecessary-type-parameters.mdx @@ -1,5 +1,5 @@ --- -description: 'Disallow type parameters that only appear once.' +description: "Disallow type parameters that aren't used multiple times." --- import Tabs from '@theme/Tabs'; @@ -9,10 +9,10 @@ import TabItem from '@theme/TabItem'; > > See **https://typescript-eslint.io/rules/no-unnecessary-type-parameters** for documentation. -This rule forbids type parameters that only appear once in a function, method, or class definition. +This rule forbids type parameters that aren't used multiple times in a function, method, or class definition. Type parameters relate two types. -If a type parameter only appears once, then it is not relating anything. +If a type parameter is only used once, then it is not relating anything. It can usually be replaced with explicit types such as `unknown`. At best unnecessary type parameters make code harder to read. diff --git a/packages/eslint-plugin/src/rules/no-unnecessary-type-parameters.ts b/packages/eslint-plugin/src/rules/no-unnecessary-type-parameters.ts index d8c1853eca3b..02b51d184533 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-type-parameters.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-type-parameters.ts @@ -16,12 +16,12 @@ export default createRule({ defaultOptions: [], meta: { docs: { - description: 'Disallow type parameters that only appear once', + description: "Disallow type parameters that aren't used multiple times", requiresTypeChecking: true, recommended: 'strict', }, messages: { - sole: 'Type parameter {{name}} is used only once.', + sole: 'Type parameter {{name}} is {{uses}} in the {{descriptor}} signature.', }, schema: [], type: 'problem', @@ -29,11 +29,56 @@ export default createRule({ name: 'no-unnecessary-type-parameters', create(context) { const parserServices = getParserServices(context); + + function checkNode(node: TSESTree.FunctionLike, descriptor: string): void { + const tsNode = parserServices.esTreeNodeToTSNodeMap.get( + node, + ) as NodeWithTypeParameters; + + const checker = parserServices.program.getTypeChecker(); + let counts: Map | undefined; + + for (const typeParameter of tsNode.typeParameters) { + const esTypeParameter = + parserServices.tsNodeToESTreeNodeMap.get( + typeParameter, + ); + const scope = context.sourceCode.getScope(esTypeParameter); + + // Quick path: if the type parameter is used multiple times in the AST, + // we don't need to dip into types to know it's repeated. + if ( + isTypeParameterRepeatedInAST( + esTypeParameter, + scope.references, + node.body?.range[0] ?? node.returnType?.range[1], + ) + ) { + continue; + } + + // For any inferred types, we have to dip into type checking. + counts ??= countTypeParameterUsage(checker, tsNode); + const identifierCounts = counts.get(typeParameter.name); + if (!identifierCounts || identifierCounts > 2) { + continue; + } + + context.report({ + data: { + descriptor, + uses: identifierCounts === 1 ? 'never used' : 'used only once', + name: typeParameter.name.text, + }, + node: esTypeParameter, + messageId: 'sole', + }); + } + } + return { [[ 'ArrowFunctionExpression[typeParameters]', - 'ClassDeclaration[typeParameters]', - 'ClassExpression[typeParameters]', 'FunctionDeclaration[typeParameters]', 'FunctionExpression[typeParameters]', 'TSCallSignatureDeclaration[typeParameters]', @@ -43,47 +88,13 @@ export default createRule({ 'TSFunctionType[typeParameters]', 'TSMethodSignature[typeParameters]', ].join(', ')](node: TSESTree.FunctionLike): void { - const tsNode = parserServices.esTreeNodeToTSNodeMap.get( - node, - ) as NodeWithTypeParameters; - - const checker = parserServices.program.getTypeChecker(); - let counts: Map | undefined; - - for (const typeParameter of tsNode.typeParameters) { - const esTypeParameter = - parserServices.tsNodeToESTreeNodeMap.get( - typeParameter, - ); - const scope = context.sourceCode.getScope(esTypeParameter); - - // Quick path: if the type parameter is used multiple times in the AST, - // we don't need to dip into types to know it's repeated. - if ( - isTypeParameterRepeatedInAST( - esTypeParameter, - scope.references, - node.body?.range[0] ?? node.returnType?.range[1], - ) - ) { - continue; - } - - // For any inferred types, we have to dip into type checking. - counts ??= countTypeParameterUsage(checker, tsNode); - const identifierCounts = counts.get(typeParameter.name); - if (!identifierCounts || identifierCounts > 2) { - continue; - } - - context.report({ - data: { - name: typeParameter.name.text, - }, - node: esTypeParameter, - messageId: 'sole', - }); - } + checkNode(node, 'function'); + }, + [[ + 'ClassDeclaration[typeParameters]', + 'ClassExpression[typeParameters]', + ].join(', ')](node: TSESTree.FunctionLike): void { + checkNode(node, 'class'); }, }; }, diff --git a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-unnecessary-type-parameters.shot b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-unnecessary-type-parameters.shot index ff3a00a42b6e..7e52aaf3324d 100644 --- a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-unnecessary-type-parameters.shot +++ b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-unnecessary-type-parameters.shot @@ -4,17 +4,17 @@ exports[`Validating rule docs no-unnecessary-type-parameters.mdx code examples E "Incorrect function second(a: A, b: B): B { - ~ Type parameter A is used only once. + ~ Type parameter A is used only once in the function signature. return b; } function parseJSON(input: string): T { - ~ Type parameter T is used only once. + ~ Type parameter T is used only once in the function signature. return JSON.parse(input); } function printProperty(obj: T, key: K) { - ~~~~~~~~~~~~~~~~~ Type parameter K is used only once. + ~~~~~~~~~~~~~~~~~ Type parameter K is used only once in the function signature. console.log(obj[key]); } " diff --git a/packages/eslint-plugin/tests/rules/no-unnecessary-type-parameters.test.ts b/packages/eslint-plugin/tests/rules/no-unnecessary-type-parameters.test.ts index 79db12a954a3..1ad76a9210d7 100644 --- a/packages/eslint-plugin/tests/rules/no-unnecessary-type-parameters.test.ts +++ b/packages/eslint-plugin/tests/rules/no-unnecessary-type-parameters.test.ts @@ -353,11 +353,21 @@ ruleTester.run('no-unnecessary-type-parameters', rule, { invalid: [ { code: 'const func = (param: T) => null;', - errors: [{ messageId: 'sole', data: { name: 'T' } }], + errors: [ + { + messageId: 'sole', + data: { descriptor: 'function', name: 'T', uses: 'used only once' }, + }, + ], }, { code: 'const f1 = (): T => {};', - errors: [{ messageId: 'sole', data: { name: 'T' } }], + errors: [ + { + messageId: 'sole', + data: { descriptor: 'function', name: 'T', uses: 'used only once' }, + }, + ], }, { code: ` @@ -365,7 +375,12 @@ ruleTester.run('no-unnecessary-type-parameters', rule, { (value: T): void; } `, - errors: [{ messageId: 'sole', data: { name: 'T' } }], + errors: [ + { + messageId: 'sole', + data: { descriptor: 'function', name: 'T', uses: 'used only once' }, + }, + ], }, { code: ` @@ -383,13 +398,23 @@ ruleTester.run('no-unnecessary-type-parameters', rule, { } } `, - errors: [{ messageId: 'sole', data: { name: 'T' } }], + errors: [ + { + messageId: 'sole', + data: { descriptor: 'class', name: 'T', uses: 'used only once' }, + }, + ], }, { code: ` declare class C {} `, - errors: [{ messageId: 'sole', data: { name: 'V' } }], + errors: [ + { + messageId: 'sole', + data: { descriptor: 'class', name: 'V', uses: 'never used' }, + }, + ], }, { code: ` @@ -398,8 +423,14 @@ ruleTester.run('no-unnecessary-type-parameters', rule, { } `, errors: [ - { messageId: 'sole', data: { name: 'T' } }, - { messageId: 'sole', data: { name: 'U' } }, + { + messageId: 'sole', + data: { descriptor: 'class', name: 'T', uses: 'used only once' }, + }, + { + messageId: 'sole', + data: { descriptor: 'class', name: 'U', uses: 'used only once' }, + }, ], }, { @@ -409,8 +440,14 @@ ruleTester.run('no-unnecessary-type-parameters', rule, { } `, errors: [ - { messageId: 'sole', data: { name: 'T' } }, - { messageId: 'sole', data: { name: 'U' } }, + { + messageId: 'sole', + data: { descriptor: 'function', name: 'T', uses: 'used only once' }, + }, + { + messageId: 'sole', + data: { descriptor: 'function', name: 'U', uses: 'used only once' }, + }, ], }, { @@ -419,7 +456,12 @@ ruleTester.run('no-unnecessary-type-parameters', rule, { prop:

() => P; } `, - errors: [{ messageId: 'sole', data: { name: 'P' } }], + errors: [ + { + messageId: 'sole', + data: { descriptor: 'function', name: 'P', uses: 'used only once' }, + }, + ], }, { code: ` @@ -430,7 +472,7 @@ ruleTester.run('no-unnecessary-type-parameters', rule, { errors: [ { messageId: 'sole', - data: { name: 'T' }, + data: { descriptor: 'function', name: 'T', uses: 'used only once' }, }, ], }, @@ -441,8 +483,14 @@ ruleTester.run('no-unnecessary-type-parameters', rule, { } `, errors: [ - { messageId: 'sole', data: { name: 'A' } }, - { messageId: 'sole', data: { name: 'B' } }, + { + messageId: 'sole', + data: { descriptor: 'function', name: 'A', uses: 'used only once' }, + }, + { + messageId: 'sole', + data: { descriptor: 'function', name: 'B', uses: 'used only once' }, + }, ], }, { @@ -452,7 +500,12 @@ ruleTester.run('no-unnecessary-type-parameters', rule, { const y: T = null!; } `, - errors: [{ messageId: 'sole', data: { name: 'T' } }], + errors: [ + { + messageId: 'sole', + data: { descriptor: 'function', name: 'T', uses: 'used only once' }, + }, + ], }, { code: ` @@ -461,7 +514,12 @@ ruleTester.run('no-unnecessary-type-parameters', rule, { const y: T = null!; } `, - errors: [{ messageId: 'sole', data: { name: 'T' } }], + errors: [ + { + messageId: 'sole', + data: { descriptor: 'function', name: 'T', uses: 'used only once' }, + }, + ], }, { code: ` @@ -470,7 +528,12 @@ ruleTester.run('no-unnecessary-type-parameters', rule, { const y: T = null!; } `, - errors: [{ messageId: 'sole', data: { name: 'T' } }], + errors: [ + { + messageId: 'sole', + data: { descriptor: 'function', name: 'T', uses: 'used only once' }, + }, + ], }, { code: ` @@ -483,7 +546,12 @@ ruleTester.run('no-unnecessary-type-parameters', rule, { } } `, - errors: [{ messageId: 'sole', data: { name: 'T' } }], + errors: [ + { + messageId: 'sole', + data: { descriptor: 'function', name: 'T', uses: 'used only once' }, + }, + ], }, { code: ` @@ -491,7 +559,12 @@ ruleTester.run('no-unnecessary-type-parameters', rule, { return input as any as T; } `, - errors: [{ messageId: 'sole', data: { name: 'T' } }], + errors: [ + { + messageId: 'sole', + data: { descriptor: 'function', name: 'T', uses: 'used only once' }, + }, + ], }, { code: ` @@ -499,7 +572,12 @@ ruleTester.run('no-unnecessary-type-parameters', rule, { console.log(obj[key]); } `, - errors: [{ messageId: 'sole', data: { name: 'K' } }], + errors: [ + { + messageId: 'sole', + data: { descriptor: 'function', name: 'K', uses: 'used only once' }, + }, + ], }, { code: ` @@ -510,7 +588,7 @@ ruleTester.run('no-unnecessary-type-parameters', rule, { `, errors: [ { - data: { name: 'T' }, + data: { descriptor: 'function', name: 'T', uses: 'used only once' }, messageId: 'sole', }, ], @@ -529,8 +607,14 @@ ruleTester.run('no-unnecessary-type-parameters', rule, { } `, errors: [ - { messageId: 'sole', data: { name: 'CB1' } }, - { messageId: 'sole', data: { name: 'CB2' } }, + { + messageId: 'sole', + data: { descriptor: 'function', name: 'CB1', uses: 'used only once' }, + }, + { + messageId: 'sole', + data: { descriptor: 'function', name: 'CB2', uses: 'used only once' }, + }, ], }, { @@ -539,7 +623,12 @@ ruleTester.run('no-unnecessary-type-parameters', rule, { return x.length; } `, - errors: [{ messageId: 'sole' }], + errors: [ + { + data: { descriptor: 'function', name: 'T', uses: 'used only once' }, + messageId: 'sole', + }, + ], }, { code: ` @@ -550,109 +639,241 @@ ruleTester.run('no-unnecessary-type-parameters', rule, { return x.length; } `, - errors: [{ messageId: 'sole' }], + errors: [ + { + data: { descriptor: 'function', name: 'T', uses: 'used only once' }, + messageId: 'sole', + }, + ], + }, + { + code: 'declare function get(): unknown;', + errors: [ + { + messageId: 'sole', + data: { descriptor: 'function', name: 'T', uses: 'never used' }, + }, + ], }, { code: 'declare function get(): T;', - errors: [{ messageId: 'sole', data: { name: 'T' } }], + errors: [ + { + messageId: 'sole', + data: { descriptor: 'function', name: 'T', uses: 'used only once' }, + }, + ], }, { code: 'declare function get(): T;', - errors: [{ messageId: 'sole', data: { name: 'T' } }], + errors: [ + { + messageId: 'sole', + data: { descriptor: 'function', name: 'T', uses: 'used only once' }, + }, + ], }, { code: 'declare function take(param: T): void;', - errors: [{ messageId: 'sole', data: { name: 'T' } }], + errors: [ + { + messageId: 'sole', + data: { descriptor: 'function', name: 'T', uses: 'used only once' }, + }, + ], }, { code: 'declare function take(param: T): void;', - errors: [{ messageId: 'sole', data: { name: 'T' } }], + errors: [ + { + messageId: 'sole', + data: { descriptor: 'function', name: 'T', uses: 'used only once' }, + }, + ], }, { code: 'declare function take(param1: T, param2: U): void;', - errors: [{ messageId: 'sole', data: { name: 'U' } }], + errors: [ + { + messageId: 'sole', + data: { descriptor: 'function', name: 'U', uses: 'used only once' }, + }, + ], }, { code: 'declare function take(param: T): U;', - errors: [{ messageId: 'sole', data: { name: 'U' } }], + errors: [ + { + messageId: 'sole', + data: { descriptor: 'function', name: 'U', uses: 'used only once' }, + }, + ], }, { code: 'declare function take(param: U): U;', - errors: [{ messageId: 'sole', data: { name: 'T' } }], + errors: [ + { + messageId: 'sole', + data: { descriptor: 'function', name: 'T', uses: 'used only once' }, + }, + ], }, { code: 'declare function get(param: U): U;', - errors: [{ messageId: 'sole', data: { name: 'T' } }], + errors: [ + { + messageId: 'sole', + data: { descriptor: 'function', name: 'T', uses: 'used only once' }, + }, + ], }, { code: 'declare function get(param: T): U;', - errors: [{ messageId: 'sole', data: { name: 'U' } }], + errors: [ + { + messageId: 'sole', + data: { descriptor: 'function', name: 'U', uses: 'used only once' }, + }, + ], }, { code: 'declare function compare(param1: T, param2: U): boolean;', - errors: [{ messageId: 'sole', data: { name: 'U' } }], + errors: [ + { + messageId: 'sole', + data: { descriptor: 'function', name: 'U', uses: 'used only once' }, + }, + ], }, { code: 'declare function get(param: (param: U) => V): T;', errors: [ - { messageId: 'sole', data: { name: 'T' } }, - { messageId: 'sole', data: { name: 'U' } }, - { messageId: 'sole', data: { name: 'V' } }, + { + messageId: 'sole', + data: { descriptor: 'function', name: 'T', uses: 'used only once' }, + }, + { + messageId: 'sole', + data: { descriptor: 'function', name: 'U', uses: 'used only once' }, + }, + { + messageId: 'sole', + data: { descriptor: 'function', name: 'V', uses: 'used only once' }, + }, ], }, { code: 'declare function get(param: (param: T) => U): T;', errors: [ - { messageId: 'sole', data: { name: 'T' } }, - { messageId: 'sole', data: { name: 'T' } }, - { messageId: 'sole', data: { name: 'U' } }, + { + messageId: 'sole', + data: { descriptor: 'function', name: 'T', uses: 'used only once' }, + }, + { + messageId: 'sole', + data: { descriptor: 'function', name: 'T', uses: 'used only once' }, + }, + { + messageId: 'sole', + data: { descriptor: 'function', name: 'U', uses: 'used only once' }, + }, ], }, { code: 'type Fn = () => T;', - errors: [{ messageId: 'sole', data: { name: 'T' } }], + errors: [ + { + messageId: 'sole', + data: { descriptor: 'function', name: 'T', uses: 'used only once' }, + }, + ], }, { code: 'type Fn = () => [];', - errors: [{ messageId: 'sole', data: { name: 'T' } }], + errors: [ + { + messageId: 'sole', + data: { descriptor: 'function', name: 'T', uses: 'never used' }, + }, + ], }, { code: ` type Other = 0; type Fn = () => Other; `, - errors: [{ messageId: 'sole', data: { name: 'T' } }], + errors: [ + { + messageId: 'sole', + data: { descriptor: 'function', name: 'T', uses: 'never used' }, + }, + ], }, { code: ` type Other = 0 | 1; type Fn = () => Other; `, - errors: [{ messageId: 'sole', data: { name: 'T' } }], + errors: [ + { + messageId: 'sole', + data: { descriptor: 'function', name: 'T', uses: 'never used' }, + }, + ], }, { code: 'type Fn = (param: U) => void;', - errors: [{ messageId: 'sole', data: { name: 'U' } }], + errors: [ + { + messageId: 'sole', + data: { descriptor: 'function', name: 'U', uses: 'used only once' }, + }, + ], }, { code: 'type Ctr = new () => T;', - errors: [{ messageId: 'sole', data: { name: 'T' } }], + errors: [ + { + messageId: 'sole', + data: { descriptor: 'function', name: 'T', uses: 'used only once' }, + }, + ], }, { code: 'type Fn = () => { [K in keyof T]: K };', - errors: [{ messageId: 'sole', data: { name: 'T' } }], + errors: [ + { + messageId: 'sole', + data: { descriptor: 'function', name: 'T', uses: 'used only once' }, + }, + ], }, { code: "type Fn = () => { [K in 'a']: T };", - errors: [{ messageId: 'sole', data: { name: 'T' } }], + errors: [ + { + messageId: 'sole', + data: { descriptor: 'function', name: 'T', uses: 'used only once' }, + }, + ], }, { code: 'type Fn = (value: unknown) => value is T;', - errors: [{ messageId: 'sole', data: { name: 'T' } }], + errors: [ + { + messageId: 'sole', + data: { descriptor: 'function', name: 'T', uses: 'used only once' }, + }, + ], }, { code: 'type Fn = () => `a${T}b`;', - errors: [{ messageId: 'sole', data: { name: 'T' } }], + errors: [ + { + messageId: 'sole', + data: { descriptor: 'function', name: 'T', uses: 'used only once' }, + }, + ], }, { code: ` @@ -661,7 +882,12 @@ ruleTester.run('no-unnecessary-type-parameters', rule, { fn: (key: K) => number, ): number[]; `, - errors: [{ messageId: 'sole', data: { name: 'V' } }], + errors: [ + { + messageId: 'sole', + data: { descriptor: 'function', name: 'V', uses: 'used only once' }, + }, + ], }, ], }); pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy