Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 2b942ba

Browse files
Josh GoldbergJamesHenry
Josh Goldberg
authored andcommitted
feat(eslint-plugin): added new rule use-default-type-parameter (typescript-eslint#562)
1 parent 55e788c commit 2b942ba

File tree

9 files changed

+361
-4
lines changed

9 files changed

+361
-4
lines changed

packages/eslint-plugin/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@ Then you should add `airbnb` (or `airbnb-base`) to your `extends` section of `.e
160160
| [`@typescript-eslint/no-this-alias`](./docs/rules/no-this-alias.md) | Disallow aliasing `this` | | | |
161161
| [`@typescript-eslint/no-type-alias`](./docs/rules/no-type-alias.md) | Disallow the use of type aliases | | | |
162162
| [`@typescript-eslint/no-unnecessary-qualifier`](./docs/rules/no-unnecessary-qualifier.md) | Warns when a namespace qualifier is unnecessary | | :wrench: | :thought_balloon: |
163+
| [`@typescript-eslint/no-unnecessary-type-arguments`](./docs/rules/no-unnecessary-type-arguments.md) | Warns if an explicitly specified type argument is the default for that type parameter | | :wrench: | :thought_balloon: |
163164
| [`@typescript-eslint/no-unnecessary-type-assertion`](./docs/rules/no-unnecessary-type-assertion.md) | Warns if a type assertion does not change the type of an expression | | :wrench: | :thought_balloon: |
164165
| [`@typescript-eslint/no-unused-vars`](./docs/rules/no-unused-vars.md) | Disallow unused variables | :heavy_check_mark: | | |
165166
| [`@typescript-eslint/no-use-before-define`](./docs/rules/no-use-before-define.md) | Disallow the use of variables before they are defined | :heavy_check_mark: | | |

packages/eslint-plugin/ROADMAP.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@
9999
| [`triple-equals`] | 🌟 | [`eqeqeq`][eqeqeq] |
100100
| [`typeof-compare`] | 🌟 | [`valid-typeof`][valid-typeof] |
101101
| [`unnecessary-constructor`] | 🌟 | [`no-useless-constructor`][no-useless-constructor] |
102-
| [`use-default-type-parameter`] | 🛑 | N/A |
102+
| [`use-default-type-parameter`] | | [`@typescript-eslint/no-unnecessary-type-arguments`] |
103103
| [`use-isnan`] | 🌟 | [`use-isnan`][use-isnan] |
104104

105105
<sup>[1]</sup> The ESLint rule also supports silencing with an extra set of parens (`if ((foo = bar)) {}`)<br>
@@ -617,6 +617,7 @@ Relevant plugins: [`chai-expect-keywords`](https://github.com/gavinaiken/eslint-
617617
[`@typescript-eslint/require-await`]: https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/require-await.md
618618
[`@typescript-eslint/no-for-in-array`]: https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-for-in-array.md
619619
[`@typescript-eslint/no-unnecessary-qualifier`]: https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-unnecessary-qualifier.md
620+
[`@typescript-eslint/no-unnecessary-type-arguments`]: https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-unnecessary-type-arguments.md
620621
[`@typescript-eslint/semi`]: https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/semi.md
621622
[`@typescript-eslint/no-floating-promises`]: https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-floating-promises.md
622623

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# Enforces that types will not to be used (no-unnecessary-type-arguments)
2+
3+
Warns if an explicitly specified type argument is the default for that type parameter.
4+
5+
## Rule Details
6+
7+
Type parameters in TypeScript may specify a default value.
8+
For example:
9+
10+
```ts
11+
function f<T = number>() {}
12+
```
13+
14+
It is redundant to provide an explicit type parameter equal to that default.
15+
16+
Examples of **incorrect** code for this rule:
17+
18+
```ts
19+
function f<T = number>() {}
20+
f<number>();
21+
22+
function g<T = number, U = string>() {}
23+
g<string, string>();
24+
25+
class C<T = number> {}
26+
function h(c: C<number>) {}
27+
new C<number>();
28+
class D extends C<number> {}
29+
30+
interface I<T = number> {}
31+
class Impl implements I<number> {}
32+
```
33+
34+
Examples of **correct** code for this rule:
35+
36+
```ts
37+
function f<T = number>() {}
38+
f<string>();
39+
40+
function g<T = number, U = string>() {}
41+
g<number, number>();
42+
43+
class C<T = number> {}
44+
new C<string>();
45+
class D extends C<string> {}
46+
47+
interface I<T = number> {}
48+
class Impl implements I<string> {}
49+
```
50+
51+
## Related to
52+
53+
- TSLint: [use-default-type-parameter](https://palantir.github.io/tslint/rules/use-default-type-parameter)

packages/eslint-plugin/src/configs/all.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
"@typescript-eslint/no-this-alias": "error",
4545
"@typescript-eslint/no-type-alias": "error",
4646
"@typescript-eslint/no-unnecessary-qualifier": "error",
47+
"@typescript-eslint/no-unnecessary-type-arguments": "error",
4748
"@typescript-eslint/no-unnecessary-type-assertion": "error",
4849
"no-unused-vars": "off",
4950
"@typescript-eslint/no-unused-vars": "error",

packages/eslint-plugin/src/rules/indent-new-do-not-use/BinarySearchTree.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export interface TreeValue {
1717
* can easily be swapped out.
1818
*/
1919
export class BinarySearchTree {
20-
private rbTree = createTree<TreeValue, number>();
20+
private rbTree = createTree<TreeValue>();
2121

2222
/**
2323
* Inserts an entry into the tree.

packages/eslint-plugin/src/rules/index.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ import typeAnnotationSpacing from './type-annotation-spacing';
5858
import typedef from './typedef';
5959
import unboundMethod from './unbound-method';
6060
import unifiedSignatures from './unified-signatures';
61+
import useDefaultTypeParameter from './no-unnecessary-type-arguments';
6162

6263
export default {
6364
'adjacent-overload-signatures': adjacentOverloadSignatures,
@@ -97,6 +98,7 @@ export default {
9798
'no-this-alias': noThisAlias,
9899
'no-type-alias': noTypeAlias,
99100
'no-unnecessary-qualifier': noUnnecessaryQualifier,
101+
'no-unnecessary-type-arguments': useDefaultTypeParameter,
100102
'no-unnecessary-type-assertion': noUnnecessaryTypeAssertion,
101103
'no-unused-vars': noUnusedVars,
102104
'no-use-before-define': noUseBeforeDefine,
@@ -113,11 +115,11 @@ export default {
113115
'require-array-sort-compare': requireArraySortCompare,
114116
'require-await': requireAwait,
115117
'restrict-plus-operands': restrictPlusOperands,
118+
semi: semi,
116119
'strict-boolean-expressions': strictBooleanExpressions,
117120
'triple-slash-reference': tripleSlashReference,
118121
'type-annotation-spacing': typeAnnotationSpacing,
122+
typedef: typedef,
119123
'unbound-method': unboundMethod,
120124
'unified-signatures': unifiedSignatures,
121-
semi: semi,
122-
typedef: typedef,
123125
};
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
import { TSESTree } from '@typescript-eslint/experimental-utils';
2+
import * as tsutils from 'tsutils';
3+
import ts from 'typescript';
4+
import * as util from '../util';
5+
import { findFirstResult } from '../util';
6+
7+
interface ArgsAndParams {
8+
typeArguments: ts.NodeArray<ts.TypeNode>;
9+
typeParameters: readonly ts.TypeParameterDeclaration[];
10+
}
11+
12+
type ExtendingClassLikeDeclaration = ts.ClassLikeDeclaration & {
13+
heritageClauses: ts.NodeArray<ts.HeritageClause>;
14+
};
15+
16+
type ParameterCapableTSNode =
17+
| ts.CallExpression
18+
| ts.NewExpression
19+
| ts.TypeReferenceNode
20+
| ts.ExpressionWithTypeArguments;
21+
22+
type MessageIds = 'unnecessaryTypeParameter';
23+
24+
export default util.createRule<[], MessageIds>({
25+
name: 'no-unnecessary-type-arguments',
26+
meta: {
27+
docs: {
28+
description:
29+
'Warns if an explicitly specified type argument is the default for that type parameter',
30+
category: 'Best Practices',
31+
recommended: false,
32+
},
33+
fixable: 'code',
34+
messages: {
35+
unnecessaryTypeParameter:
36+
'This is the default value for this type parameter, so it can be omitted.',
37+
},
38+
schema: [],
39+
type: 'suggestion',
40+
},
41+
defaultOptions: [],
42+
create(context) {
43+
const parserServices = util.getParserServices(context);
44+
const checker = parserServices.program.getTypeChecker();
45+
46+
function checkTSArgsAndParameters(
47+
esParameters: TSESTree.TSTypeParameterInstantiation,
48+
{ typeArguments, typeParameters }: ArgsAndParams,
49+
): void {
50+
// Just check the last one. Must specify previous type parameters if the last one is specified.
51+
const i = typeArguments.length - 1;
52+
const arg = typeArguments[i];
53+
const param = typeParameters[i];
54+
55+
// TODO: would like checker.areTypesEquivalent. https://github.com/Microsoft/TypeScript/issues/13502
56+
if (
57+
param.default === undefined ||
58+
param.default.getText() !== arg.getText()
59+
) {
60+
return;
61+
}
62+
63+
context.report({
64+
fix: fixer =>
65+
fixer.removeRange(
66+
i === 0
67+
? [typeArguments.pos - 1, typeArguments.end + 1]
68+
: [typeArguments[i - 1].end, arg.end],
69+
),
70+
messageId: 'unnecessaryTypeParameter',
71+
node: esParameters!.params[i],
72+
});
73+
}
74+
75+
return {
76+
TSTypeParameterInstantiation(node) {
77+
const parentDeclaration = parserServices.esTreeNodeToTSNodeMap.get(
78+
node.parent!,
79+
) as ExtendingClassLikeDeclaration | ParameterCapableTSNode;
80+
81+
const expression = tsutils.isClassLikeDeclaration(parentDeclaration)
82+
? parentDeclaration.heritageClauses[0].types[0]
83+
: parentDeclaration;
84+
85+
const argsAndParams = getArgsAndParameters(expression, checker);
86+
if (argsAndParams !== undefined) {
87+
checkTSArgsAndParameters(node, argsAndParams);
88+
}
89+
},
90+
};
91+
},
92+
});
93+
94+
function getArgsAndParameters(
95+
node: ParameterCapableTSNode,
96+
checker: ts.TypeChecker,
97+
): ArgsAndParams | undefined {
98+
const typeParameters = getTypeParametersFromNode(node, checker);
99+
return typeParameters === undefined
100+
? undefined
101+
: { typeArguments: node.typeArguments!, typeParameters };
102+
}
103+
104+
function getTypeParametersFromNode(
105+
node: ParameterCapableTSNode,
106+
checker: ts.TypeChecker,
107+
) {
108+
if (ts.isExpressionWithTypeArguments(node)) {
109+
return getTypeParametersFromType(node.expression, checker);
110+
}
111+
112+
if (ts.isTypeReferenceNode(node)) {
113+
return getTypeParametersFromType(node.typeName, checker);
114+
}
115+
116+
return getTypeParametersFromCall(node, checker);
117+
}
118+
119+
function getTypeParametersFromType(
120+
type: ts.EntityName | ts.Expression | ts.ClassDeclaration,
121+
checker: ts.TypeChecker,
122+
): readonly ts.TypeParameterDeclaration[] | undefined {
123+
const sym = getAliasedSymbol(checker.getSymbolAtLocation(type)!, checker);
124+
if (sym === undefined || sym.declarations === undefined) {
125+
return undefined;
126+
}
127+
128+
return findFirstResult(sym.declarations, decl =>
129+
tsutils.isClassLikeDeclaration(decl) ||
130+
ts.isTypeAliasDeclaration(decl) ||
131+
ts.isInterfaceDeclaration(decl)
132+
? decl.typeParameters
133+
: undefined,
134+
);
135+
}
136+
137+
function getTypeParametersFromCall(
138+
node: ts.CallExpression | ts.NewExpression,
139+
checker: ts.TypeChecker,
140+
): readonly ts.TypeParameterDeclaration[] | undefined {
141+
const sig = checker.getResolvedSignature(node);
142+
const sigDecl = sig === undefined ? undefined : sig.getDeclaration();
143+
if (sigDecl === undefined) {
144+
return ts.isNewExpression(node)
145+
? getTypeParametersFromType(node.expression, checker)
146+
: undefined;
147+
}
148+
149+
return sigDecl.typeParameters;
150+
}
151+
152+
function getAliasedSymbol(
153+
symbol: ts.Symbol,
154+
checker: ts.TypeChecker,
155+
): ts.Symbol | undefined {
156+
return tsutils.isSymbolFlagSet(symbol, ts.SymbolFlags.Alias)
157+
? checker.getAliasedSymbol(symbol)
158+
: symbol;
159+
}

packages/eslint-plugin/src/util/misc.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,20 @@ export function arraysAreEqual<T>(
7777
);
7878
}
7979

80+
/** Returns the first non-`undefined` result. */
81+
export function findFirstResult<T, U>(
82+
inputs: T[],
83+
getResult: (t: T) => U | undefined,
84+
): U | undefined {
85+
for (const element of inputs) {
86+
const result = getResult(element);
87+
if (result !== undefined) {
88+
return result;
89+
}
90+
}
91+
return undefined;
92+
}
93+
8094
/**
8195
* Gets a string name representation of the name of the given MethodDefinition
8296
* or ClassProperty node, with handling for computed property names.

0 commit comments

Comments
 (0)