Skip to content

Commit 4747299

Browse files
authored
fix(eslint-plugin): [no-unnecessary-type-arguments] handle type/value context (typescript-eslint#10503)
* fix(eslint-plugin): [no-unnecessary-type-arguments] handle type/value context * add tests * refactor
1 parent 4c91ed5 commit 4747299

File tree

2 files changed

+186
-20
lines changed

2 files changed

+186
-20
lines changed

packages/eslint-plugin/src/rules/no-unnecessary-type-arguments.ts

Lines changed: 78 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { TSESTree } from '@typescript-eslint/utils';
22

3+
import { AST_NODE_TYPES } from '@typescript-eslint/utils';
34
import * as tsutils from 'ts-api-utils';
45
import * as ts from 'typescript';
56

@@ -111,8 +112,12 @@ export default createRule<[], MessageIds>({
111112
return {
112113
TSTypeParameterInstantiation(node): void {
113114
const expression = services.esTreeNodeToTSNodeMap.get(node);
115+
const typeParameters = getTypeParametersFromNode(
116+
node,
117+
expression,
118+
checker,
119+
);
114120

115-
const typeParameters = getTypeParametersFromNode(expression, checker);
116121
if (typeParameters) {
117122
checkTSArgsAndParameters(node, typeParameters);
118123
}
@@ -122,29 +127,31 @@ export default createRule<[], MessageIds>({
122127
});
123128

124129
function getTypeParametersFromNode(
125-
node: ParameterCapableTSNode,
130+
node: TSESTree.TSTypeParameterInstantiation,
131+
tsNode: ParameterCapableTSNode,
126132
checker: ts.TypeChecker,
127133
): readonly ts.TypeParameterDeclaration[] | undefined {
128-
if (ts.isExpressionWithTypeArguments(node)) {
129-
return getTypeParametersFromType(node.expression, checker);
134+
if (ts.isExpressionWithTypeArguments(tsNode)) {
135+
return getTypeParametersFromType(node, tsNode.expression, checker);
130136
}
131137

132-
if (ts.isTypeReferenceNode(node)) {
133-
return getTypeParametersFromType(node.typeName, checker);
138+
if (ts.isTypeReferenceNode(tsNode)) {
139+
return getTypeParametersFromType(node, tsNode.typeName, checker);
134140
}
135141

136142
if (
137-
ts.isCallExpression(node) ||
138-
ts.isNewExpression(node) ||
139-
ts.isTaggedTemplateExpression(node)
143+
ts.isCallExpression(tsNode) ||
144+
ts.isNewExpression(tsNode) ||
145+
ts.isTaggedTemplateExpression(tsNode)
140146
) {
141-
return getTypeParametersFromCall(node, checker);
147+
return getTypeParametersFromCall(node, tsNode, checker);
142148
}
143149

144150
return undefined;
145151
}
146152

147153
function getTypeParametersFromType(
154+
node: TSESTree.TSTypeParameterInstantiation,
148155
type: ts.ClassDeclaration | ts.EntityName | ts.Expression,
149156
checker: ts.TypeChecker,
150157
): readonly ts.TypeParameterDeclaration[] | undefined {
@@ -160,24 +167,36 @@ function getTypeParametersFromType(
160167
return undefined;
161168
}
162169

163-
return findFirstResult(declarations, decl =>
164-
ts.isClassLike(decl) ||
165-
ts.isTypeAliasDeclaration(decl) ||
166-
ts.isInterfaceDeclaration(decl)
167-
? decl.typeParameters
168-
: undefined,
170+
const sortedDeclaraions = sortDeclarationsByTypeValueContext(
171+
node,
172+
declarations,
169173
);
174+
return findFirstResult(sortedDeclaraions, decl => {
175+
if (
176+
ts.isTypeAliasDeclaration(decl) ||
177+
ts.isInterfaceDeclaration(decl) ||
178+
ts.isClassLike(decl)
179+
) {
180+
return decl.typeParameters;
181+
}
182+
if (ts.isVariableDeclaration(decl)) {
183+
return getConstructSignatureDeclaration(symAtLocation, checker)
184+
?.typeParameters;
185+
}
186+
return undefined;
187+
});
170188
}
171189

172190
function getTypeParametersFromCall(
173-
node: ts.CallExpression | ts.NewExpression | ts.TaggedTemplateExpression,
191+
node: TSESTree.TSTypeParameterInstantiation,
192+
tsNode: ts.CallExpression | ts.NewExpression | ts.TaggedTemplateExpression,
174193
checker: ts.TypeChecker,
175194
): readonly ts.TypeParameterDeclaration[] | undefined {
176-
const sig = checker.getResolvedSignature(node);
195+
const sig = checker.getResolvedSignature(tsNode);
177196
const sigDecl = sig?.getDeclaration();
178197
if (!sigDecl) {
179-
return ts.isNewExpression(node)
180-
? getTypeParametersFromType(node.expression, checker)
198+
return ts.isNewExpression(tsNode)
199+
? getTypeParametersFromType(node, tsNode.expression, checker)
181200
: undefined;
182201
}
183202

@@ -192,3 +211,42 @@ function getAliasedSymbol(
192211
? checker.getAliasedSymbol(symbol)
193212
: symbol;
194213
}
214+
215+
function isInTypeContext(node: TSESTree.TSTypeParameterInstantiation) {
216+
return (
217+
node.parent.type === AST_NODE_TYPES.TSInterfaceHeritage ||
218+
node.parent.type === AST_NODE_TYPES.TSTypeReference ||
219+
node.parent.type === AST_NODE_TYPES.TSClassImplements
220+
);
221+
}
222+
223+
function isTypeContextDeclaration(decl: ts.Declaration) {
224+
return ts.isTypeAliasDeclaration(decl) || ts.isInterfaceDeclaration(decl);
225+
}
226+
227+
function typeFirstCompare(declA: ts.Declaration, declB: ts.Declaration) {
228+
const aIsType = isTypeContextDeclaration(declA);
229+
const bIsType = isTypeContextDeclaration(declB);
230+
231+
return Number(bIsType) - Number(aIsType);
232+
}
233+
234+
function sortDeclarationsByTypeValueContext(
235+
node: TSESTree.TSTypeParameterInstantiation,
236+
declarations: ts.Declaration[],
237+
) {
238+
const sorted = [...declarations].sort(typeFirstCompare);
239+
if (isInTypeContext(node)) {
240+
return sorted;
241+
}
242+
return sorted.reverse();
243+
}
244+
245+
function getConstructSignatureDeclaration(
246+
symbol: ts.Symbol,
247+
checker: ts.TypeChecker,
248+
): ts.SignatureDeclaration | undefined {
249+
const type = checker.getTypeOfSymbol(symbol);
250+
const sig = type.getConstructSignatures();
251+
return sig.at(0)?.getDeclaration();
252+
}

packages/eslint-plugin/tests/rules/no-unnecessary-type-arguments.test.ts

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,30 @@ type A = Map<string, string>;
155155
type B<T = A> = T;
156156
type C2 = B<Map<string, number>>;
157157
`,
158+
`
159+
interface Foo<T = string> {}
160+
declare var Foo: {
161+
new <T>(type: T): any;
162+
};
163+
class Bar extends Foo<string> {}
164+
`,
165+
`
166+
interface Foo<T = string> {}
167+
class Foo<T> {}
168+
class Bar extends Foo<string> {}
169+
`,
170+
`
171+
class Foo<T = string> {}
172+
interface Foo<T> {}
173+
class Bar implements Foo<string> {}
174+
`,
175+
`
176+
class Foo<T> {}
177+
namespace Foo {
178+
export class Bar {}
179+
}
180+
class Bar extends Foo<string> {}
181+
`,
158182
],
159183
invalid: [
160184
{
@@ -452,5 +476,89 @@ type E<T = B> = T;
452476
type F = E;
453477
`,
454478
},
479+
{
480+
code: `
481+
interface Foo {}
482+
declare var Foo: {
483+
new <T = string>(type: T): any;
484+
};
485+
class Bar extends Foo<string> {}
486+
`,
487+
errors: [
488+
{
489+
line: 6,
490+
messageId: 'unnecessaryTypeParameter',
491+
},
492+
],
493+
output: `
494+
interface Foo {}
495+
declare var Foo: {
496+
new <T = string>(type: T): any;
497+
};
498+
class Bar extends Foo {}
499+
`,
500+
},
501+
{
502+
code: `
503+
declare var Foo: {
504+
new <T = string>(type: T): any;
505+
};
506+
interface Foo {}
507+
class Bar extends Foo<string> {}
508+
`,
509+
errors: [
510+
{
511+
line: 6,
512+
messageId: 'unnecessaryTypeParameter',
513+
},
514+
],
515+
output: `
516+
declare var Foo: {
517+
new <T = string>(type: T): any;
518+
};
519+
interface Foo {}
520+
class Bar extends Foo {}
521+
`,
522+
},
523+
{
524+
code: `
525+
class Foo<T> {}
526+
interface Foo<T = string> {}
527+
class Bar implements Foo<string> {}
528+
`,
529+
errors: [
530+
{
531+
line: 4,
532+
messageId: 'unnecessaryTypeParameter',
533+
},
534+
],
535+
output: `
536+
class Foo<T> {}
537+
interface Foo<T = string> {}
538+
class Bar implements Foo {}
539+
`,
540+
},
541+
{
542+
code: `
543+
class Foo<T = string> {}
544+
namespace Foo {
545+
export class Bar {}
546+
}
547+
class Bar extends Foo<string> {}
548+
`,
549+
errors: [
550+
{
551+
line: 6,
552+
messageId: 'unnecessaryTypeParameter',
553+
},
554+
],
555+
output: `
556+
class Foo<T = string> {}
557+
namespace Foo {
558+
export class Bar {}
559+
}
560+
class Bar extends Foo {}
561+
`,
562+
},
455563
],
456564
});

0 commit comments

Comments
 (0)