forked from typescript-eslint/typescript-eslint
-
Notifications
You must be signed in to change notification settings - Fork 0
/
no-unnecessary-type-constraint.ts
141 lines (127 loc) · 4.01 KB
/
no-unnecessary-type-constraint.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
import type { TSESLint, TSESTree } from '@typescript-eslint/utils';
import { AST_NODE_TYPES } from '@typescript-eslint/utils';
import { extname } from 'path';
import * as semver from 'semver';
import * as ts from 'typescript';
import * as util from '../util';
type MakeRequired<Base, Key extends keyof Base> = Omit<Base, Key> &
Required<Pick<Base, Key>>;
type TypeParameterWithConstraint = MakeRequired<
TSESTree.TSTypeParameter,
'constraint'
>;
const is3dot5 = semver.satisfies(
ts.version,
`>= 3.5.0 || >= 3.5.1-rc || >= 3.5.0-beta`,
{
includePrerelease: true,
},
);
const is3dot9 =
is3dot5 &&
semver.satisfies(ts.version, `>= 3.9.0 || >= 3.9.1-rc || >= 3.9.0-beta`, {
includePrerelease: true,
});
export default util.createRule({
name: 'no-unnecessary-type-constraint',
meta: {
docs: {
description: 'Disallow unnecessary constraints on generic types',
recommended: 'error',
},
hasSuggestions: true,
messages: {
unnecessaryConstraint:
'Constraining the generic type `{{name}}` to `{{constraint}}` does nothing and is unnecessary.',
removeUnnecessaryConstraint:
'Remove the unnecessary `{{constraint}}` constraint.',
},
schema: [],
type: 'suggestion',
},
defaultOptions: [],
create(context) {
if (!is3dot5) {
return {};
}
// In theory, we could use the type checker for more advanced constraint types...
// ...but in practice, these types are rare, and likely not worth requiring type info.
// https://github.com/typescript-eslint/typescript-eslint/pull/2516#discussion_r495731858
const unnecessaryConstraints = is3dot9
? new Map([
[AST_NODE_TYPES.TSAnyKeyword, 'any'],
[AST_NODE_TYPES.TSUnknownKeyword, 'unknown'],
])
: new Map([[AST_NODE_TYPES.TSUnknownKeyword, 'unknown']]);
function checkRequiresGenericDeclarationDisambiguation(
filename: string,
): boolean {
const pathExt = extname(filename).toLocaleLowerCase();
switch (pathExt) {
case ts.Extension.Cts:
case ts.Extension.Mts:
case ts.Extension.Tsx:
return true;
default:
return false;
}
}
const requiresGenericDeclarationDisambiguation =
checkRequiresGenericDeclarationDisambiguation(context.getFilename());
const source = context.getSourceCode();
const checkNode = (
node: TypeParameterWithConstraint,
inArrowFunction: boolean,
): void => {
const constraint = unnecessaryConstraints.get(node.constraint.type);
function shouldAddTrailingComma(): boolean {
if (!inArrowFunction || !requiresGenericDeclarationDisambiguation) {
return false;
}
// Only <T>() => {} would need trailing comma
return (
(node.parent as TSESTree.TSTypeParameterDeclaration).params.length ===
1 &&
source.getTokensAfter(node)[0].value !== ',' &&
!node.default
);
}
if (constraint) {
context.report({
data: {
constraint,
name: node.name.name,
},
suggest: [
{
messageId: 'removeUnnecessaryConstraint',
data: {
constraint,
},
fix(fixer): TSESLint.RuleFix | null {
return fixer.replaceTextRange(
[node.name.range[1], node.constraint.range[1]],
shouldAddTrailingComma() ? ',' : '',
);
},
},
],
messageId: 'unnecessaryConstraint',
node,
});
}
};
return {
':not(ArrowFunctionExpression) > TSTypeParameterDeclaration > TSTypeParameter[constraint]'(
node: TypeParameterWithConstraint,
): void {
checkNode(node, false);
},
'ArrowFunctionExpression > TSTypeParameterDeclaration > TSTypeParameter[constraint]'(
node: TypeParameterWithConstraint,
): void {
checkNode(node, true);
},
};
},
});