@flint.fyi/ts 0.14.3 → 0.14.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/lib/createTypeScriptFileFromProgram.js +1 -0
- package/lib/language.d.ts +1 -0
- package/lib/plugin.d.ts +44 -0
- package/lib/plugin.js +22 -0
- package/lib/rules/anyReturns.d.ts +6 -0
- package/lib/rules/anyReturns.js +187 -0
- package/lib/rules/anyReturns.test.d.ts +1 -0
- package/lib/rules/anyReturns.test.js +647 -0
- package/lib/rules/caseDuplicates.d.ts +6 -0
- package/lib/rules/caseDuplicates.js +49 -0
- package/lib/rules/caseDuplicates.test.d.ts +1 -0
- package/lib/rules/caseDuplicates.test.js +307 -0
- package/lib/rules/elseIfDuplicates.d.ts +6 -0
- package/lib/rules/elseIfDuplicates.js +53 -0
- package/lib/rules/elseIfDuplicates.test.d.ts +1 -0
- package/lib/rules/elseIfDuplicates.test.js +176 -0
- package/lib/rules/forDirections.d.ts +6 -0
- package/lib/rules/forDirections.js +124 -0
- package/lib/rules/forDirections.test.d.ts +1 -0
- package/lib/rules/forDirections.test.js +99 -0
- package/lib/rules/functionNewCalls.d.ts +6 -0
- package/lib/rules/functionNewCalls.js +60 -0
- package/lib/rules/functionNewCalls.test.d.ts +1 -0
- package/lib/rules/functionNewCalls.test.js +115 -0
- package/lib/rules/negativeZeroComparisons.js +4 -22
- package/lib/rules/nonOctalDecimalEscapes.d.ts +6 -0
- package/lib/rules/nonOctalDecimalEscapes.js +57 -0
- package/lib/rules/nonOctalDecimalEscapes.test.d.ts +1 -0
- package/lib/rules/nonOctalDecimalEscapes.test.js +69 -0
- package/lib/rules/numericLiteralParsing.d.ts +6 -0
- package/lib/rules/numericLiteralParsing.js +97 -0
- package/lib/rules/numericLiteralParsing.test.d.ts +1 -0
- package/lib/rules/numericLiteralParsing.test.js +99 -0
- package/lib/rules/selfAssignments.d.ts +6 -0
- package/lib/rules/selfAssignments.js +44 -0
- package/lib/rules/selfAssignments.test.d.ts +1 -0
- package/lib/rules/selfAssignments.test.js +66 -0
- package/lib/rules/selfComparisons.d.ts +6 -0
- package/lib/rules/selfComparisons.js +41 -0
- package/lib/rules/selfComparisons.test.d.ts +1 -0
- package/lib/rules/selfComparisons.test.js +297 -0
- package/lib/rules/sequences.d.ts +6 -0
- package/lib/rules/sequences.js +34 -0
- package/lib/rules/sequences.test.d.ts +1 -0
- package/lib/rules/sequences.test.js +48 -0
- package/lib/rules/typeofComparisons.d.ts +6 -0
- package/lib/rules/typeofComparisons.js +78 -0
- package/lib/rules/typeofComparisons.test.d.ts +1 -0
- package/lib/rules/typeofComparisons.test.js +85 -0
- package/lib/rules/utils/discriminateAnyType.d.ts +12 -0
- package/lib/rules/utils/discriminateAnyType.js +41 -0
- package/lib/rules/utils/getThisExpression.d.ts +2 -0
- package/lib/rules/utils/getThisExpression.js +23 -0
- package/lib/rules/utils/isUnsafeAssignment.d.ts +13 -0
- package/lib/rules/utils/isUnsafeAssignment.js +76 -0
- package/lib/rules/utils/operators.d.ts +4 -0
- package/lib/rules/utils/operators.js +36 -0
- package/lib/utils/declarationIncludesGlobal.d.ts +2 -0
- package/lib/utils/declarationIncludesGlobal.js +5 -0
- package/lib/utils/declarationsIncludeGlobal.js +2 -5
- package/lib/utils/hasSameTokens.d.ts +2 -0
- package/lib/utils/hasSameTokens.js +30 -0
- package/lib/utils/isGlobalDeclarationOfName.d.ts +5 -0
- package/lib/utils/isGlobalDeclarationOfName.js +31 -0
- package/package.json +1 -1
- package/src/createTypeScriptFileFromProgram.ts +1 -0
- package/src/language.ts +1 -0
- package/src/plugin.ts +22 -0
- package/src/rules/anyReturns.test.ts +649 -0
- package/src/rules/anyReturns.ts +263 -0
- package/src/rules/caseDuplicates.test.ts +308 -0
- package/src/rules/caseDuplicates.ts +57 -0
- package/src/rules/elseIfDuplicates.test.ts +177 -0
- package/src/rules/elseIfDuplicates.ts +69 -0
- package/src/rules/forDirections.test.ts +100 -0
- package/src/rules/forDirections.ts +163 -0
- package/src/rules/functionNewCalls.test.ts +116 -0
- package/src/rules/functionNewCalls.ts +75 -0
- package/src/rules/negativeZeroComparisons.ts +8 -27
- package/src/rules/nonOctalDecimalEscapes.test.ts +70 -0
- package/src/rules/nonOctalDecimalEscapes.ts +70 -0
- package/src/rules/numericLiteralParsing.test.ts +100 -0
- package/src/rules/numericLiteralParsing.ts +116 -0
- package/src/rules/selfAssignments.test.ts +67 -0
- package/src/rules/selfAssignments.ts +50 -0
- package/src/rules/selfComparisons.test.ts +298 -0
- package/src/rules/selfComparisons.ts +45 -0
- package/src/rules/sequences.test.ts +49 -0
- package/src/rules/sequences.ts +37 -0
- package/src/rules/typeofComparisons.test.ts +86 -0
- package/src/rules/typeofComparisons.ts +88 -0
- package/src/rules/utils/discriminateAnyType.ts +66 -0
- package/src/rules/utils/getThisExpression.ts +24 -0
- package/src/rules/utils/isUnsafeAssignment.ts +113 -0
- package/src/rules/utils/operators.ts +39 -0
- package/src/utils/declarationIncludesGlobal.ts +9 -0
- package/src/utils/declarationsIncludeGlobal.ts +3 -7
- package/src/utils/hasSameTokens.ts +45 -0
- package/src/utils/isGlobalDeclarationOfName.ts +51 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
import { ruleTester } from "./ruleTester.js";
|
|
2
|
+
import rule from "./selfComparisons.js";
|
|
3
|
+
|
|
4
|
+
ruleTester.describe(rule, {
|
|
5
|
+
invalid: [
|
|
6
|
+
{
|
|
7
|
+
code: `
|
|
8
|
+
if (value === value) {
|
|
9
|
+
console.log("always true");
|
|
10
|
+
}
|
|
11
|
+
`,
|
|
12
|
+
snapshot: `
|
|
13
|
+
if (value === value) {
|
|
14
|
+
~~~~~~~~~~~~~~~
|
|
15
|
+
Comparing a value to itself is unnecessary and likely indicates a logic error.
|
|
16
|
+
console.log("always true");
|
|
17
|
+
}
|
|
18
|
+
`,
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
code: `
|
|
22
|
+
if (value == value) {
|
|
23
|
+
console.log("always true");
|
|
24
|
+
}
|
|
25
|
+
`,
|
|
26
|
+
snapshot: `
|
|
27
|
+
if (value == value) {
|
|
28
|
+
~~~~~~~~~~~~~~
|
|
29
|
+
Comparing a value to itself is unnecessary and likely indicates a logic error.
|
|
30
|
+
console.log("always true");
|
|
31
|
+
}
|
|
32
|
+
`,
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
code: `
|
|
36
|
+
if (value !== value) {
|
|
37
|
+
console.log("always false");
|
|
38
|
+
}
|
|
39
|
+
`,
|
|
40
|
+
snapshot: `
|
|
41
|
+
if (value !== value) {
|
|
42
|
+
~~~~~~~~~~~~~~~
|
|
43
|
+
Comparing a value to itself is unnecessary and likely indicates a logic error.
|
|
44
|
+
console.log("always false");
|
|
45
|
+
}
|
|
46
|
+
`,
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
code: `
|
|
50
|
+
if (value != value) {
|
|
51
|
+
console.log("always false");
|
|
52
|
+
}
|
|
53
|
+
`,
|
|
54
|
+
snapshot: `
|
|
55
|
+
if (value != value) {
|
|
56
|
+
~~~~~~~~~~~~~~
|
|
57
|
+
Comparing a value to itself is unnecessary and likely indicates a logic error.
|
|
58
|
+
console.log("always false");
|
|
59
|
+
}
|
|
60
|
+
`,
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
code: `
|
|
64
|
+
if (value < value) {
|
|
65
|
+
console.log("always false");
|
|
66
|
+
}
|
|
67
|
+
`,
|
|
68
|
+
snapshot: `
|
|
69
|
+
if (value < value) {
|
|
70
|
+
~~~~~~~~~~~~~
|
|
71
|
+
Comparing a value to itself is unnecessary and likely indicates a logic error.
|
|
72
|
+
console.log("always false");
|
|
73
|
+
}
|
|
74
|
+
`,
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
code: `
|
|
78
|
+
if (value <= value) {
|
|
79
|
+
console.log("always true");
|
|
80
|
+
}
|
|
81
|
+
`,
|
|
82
|
+
snapshot: `
|
|
83
|
+
if (value <= value) {
|
|
84
|
+
~~~~~~~~~~~~~~
|
|
85
|
+
Comparing a value to itself is unnecessary and likely indicates a logic error.
|
|
86
|
+
console.log("always true");
|
|
87
|
+
}
|
|
88
|
+
`,
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
code: `
|
|
92
|
+
if (value > value) {
|
|
93
|
+
console.log("always false");
|
|
94
|
+
}
|
|
95
|
+
`,
|
|
96
|
+
snapshot: `
|
|
97
|
+
if (value > value) {
|
|
98
|
+
~~~~~~~~~~~~~
|
|
99
|
+
Comparing a value to itself is unnecessary and likely indicates a logic error.
|
|
100
|
+
console.log("always false");
|
|
101
|
+
}
|
|
102
|
+
`,
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
code: `
|
|
106
|
+
if (value >= value) {
|
|
107
|
+
console.log("always true");
|
|
108
|
+
}
|
|
109
|
+
`,
|
|
110
|
+
snapshot: `
|
|
111
|
+
if (value >= value) {
|
|
112
|
+
~~~~~~~~~~~~~~
|
|
113
|
+
Comparing a value to itself is unnecessary and likely indicates a logic error.
|
|
114
|
+
console.log("always true");
|
|
115
|
+
}
|
|
116
|
+
`,
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
code: `
|
|
120
|
+
const result = object.property === object.property;
|
|
121
|
+
`,
|
|
122
|
+
snapshot: `
|
|
123
|
+
const result = object.property === object.property;
|
|
124
|
+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
125
|
+
Comparing a value to itself is unnecessary and likely indicates a logic error.
|
|
126
|
+
`,
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
code: `
|
|
130
|
+
const result = array[0] === array[0];
|
|
131
|
+
`,
|
|
132
|
+
snapshot: `
|
|
133
|
+
const result = array[0] === array[0];
|
|
134
|
+
~~~~~~~~~~~~~~~~~~~~~
|
|
135
|
+
Comparing a value to itself is unnecessary and likely indicates a logic error.
|
|
136
|
+
`,
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
code: `
|
|
140
|
+
if (calculate() === calculate()) {
|
|
141
|
+
console.log("functions");
|
|
142
|
+
}
|
|
143
|
+
`,
|
|
144
|
+
snapshot: `
|
|
145
|
+
if (calculate() === calculate()) {
|
|
146
|
+
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
147
|
+
Comparing a value to itself is unnecessary and likely indicates a logic error.
|
|
148
|
+
console.log("functions");
|
|
149
|
+
}
|
|
150
|
+
`,
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
code: `
|
|
154
|
+
if (value /* comment */ === value) {
|
|
155
|
+
console.log("with comment");
|
|
156
|
+
}
|
|
157
|
+
`,
|
|
158
|
+
snapshot: `
|
|
159
|
+
if (value /* comment */ === value) {
|
|
160
|
+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
161
|
+
Comparing a value to itself is unnecessary and likely indicates a logic error.
|
|
162
|
+
console.log("with comment");
|
|
163
|
+
}
|
|
164
|
+
`,
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
code: `
|
|
168
|
+
if (object.property /* comment */ === object.property) {
|
|
169
|
+
console.log("with comment in property access");
|
|
170
|
+
}
|
|
171
|
+
`,
|
|
172
|
+
snapshot: `
|
|
173
|
+
if (object.property /* comment */ === object.property) {
|
|
174
|
+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
175
|
+
Comparing a value to itself is unnecessary and likely indicates a logic error.
|
|
176
|
+
console.log("with comment in property access");
|
|
177
|
+
}
|
|
178
|
+
`,
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
code: `
|
|
182
|
+
if (array /* comment */ [0] === array[0]) {
|
|
183
|
+
console.log("with comment in array access");
|
|
184
|
+
}
|
|
185
|
+
`,
|
|
186
|
+
snapshot: `
|
|
187
|
+
if (array /* comment */ [0] === array[0]) {
|
|
188
|
+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
189
|
+
Comparing a value to itself is unnecessary and likely indicates a logic error.
|
|
190
|
+
console.log("with comment in array access");
|
|
191
|
+
}
|
|
192
|
+
`,
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
code: `
|
|
196
|
+
if (value === value /* comment */) {
|
|
197
|
+
console.log("comment on right side");
|
|
198
|
+
}
|
|
199
|
+
`,
|
|
200
|
+
snapshot: `
|
|
201
|
+
if (value === value /* comment */) {
|
|
202
|
+
~~~~~~~~~~~~~~~
|
|
203
|
+
Comparing a value to itself is unnecessary and likely indicates a logic error.
|
|
204
|
+
console.log("comment on right side");
|
|
205
|
+
}
|
|
206
|
+
`,
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
code: `
|
|
210
|
+
if (value /* left */ === value /* right */) {
|
|
211
|
+
console.log("comments on both sides");
|
|
212
|
+
}
|
|
213
|
+
`,
|
|
214
|
+
snapshot: `
|
|
215
|
+
if (value /* left */ === value /* right */) {
|
|
216
|
+
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
217
|
+
Comparing a value to itself is unnecessary and likely indicates a logic error.
|
|
218
|
+
console.log("comments on both sides");
|
|
219
|
+
}
|
|
220
|
+
`,
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
code: `
|
|
224
|
+
if (object.property === object /* comment */ .property) {
|
|
225
|
+
console.log("comment on right property access");
|
|
226
|
+
}
|
|
227
|
+
`,
|
|
228
|
+
snapshot: `
|
|
229
|
+
if (object.property === object /* comment */ .property) {
|
|
230
|
+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
231
|
+
Comparing a value to itself is unnecessary and likely indicates a logic error.
|
|
232
|
+
console.log("comment on right property access");
|
|
233
|
+
}
|
|
234
|
+
`,
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
code: `
|
|
238
|
+
if (array[0] === array /* comment */ [0]) {
|
|
239
|
+
console.log("comment on right array access");
|
|
240
|
+
}
|
|
241
|
+
`,
|
|
242
|
+
snapshot: `
|
|
243
|
+
if (array[0] === array /* comment */ [0]) {
|
|
244
|
+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
245
|
+
Comparing a value to itself is unnecessary and likely indicates a logic error.
|
|
246
|
+
console.log("comment on right array access");
|
|
247
|
+
}
|
|
248
|
+
`,
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
code: `
|
|
252
|
+
if (/* before */ value === value /* after */) {
|
|
253
|
+
console.log("comments before and after");
|
|
254
|
+
}
|
|
255
|
+
`,
|
|
256
|
+
snapshot: `
|
|
257
|
+
if (/* before */ value === value /* after */) {
|
|
258
|
+
~~~~~~~~~~~~~~~
|
|
259
|
+
Comparing a value to itself is unnecessary and likely indicates a logic error.
|
|
260
|
+
console.log("comments before and after");
|
|
261
|
+
}
|
|
262
|
+
`,
|
|
263
|
+
},
|
|
264
|
+
{
|
|
265
|
+
code: `
|
|
266
|
+
const result = value /* a */ /* b */ === value /* c */ /* d */;
|
|
267
|
+
`,
|
|
268
|
+
snapshot: `
|
|
269
|
+
const result = value /* a */ /* b */ === value /* c */ /* d */;
|
|
270
|
+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
271
|
+
Comparing a value to itself is unnecessary and likely indicates a logic error.
|
|
272
|
+
`,
|
|
273
|
+
},
|
|
274
|
+
],
|
|
275
|
+
valid: [
|
|
276
|
+
`if (value1 === value2) { console.log("different values"); }`,
|
|
277
|
+
`if (value === other) { console.log("different values"); }`,
|
|
278
|
+
`if (object.property === object.otherProperty) { console.log("different properties"); }`,
|
|
279
|
+
`if (array[0] === array[1]) { console.log("different elements"); }`,
|
|
280
|
+
`const result = value1 == value2;`,
|
|
281
|
+
`const result = value1 != value2;`,
|
|
282
|
+
`const result = value1 !== value2;`,
|
|
283
|
+
`const result = value1 < value2;`,
|
|
284
|
+
`const result = value1 <= value2;`,
|
|
285
|
+
`const result = value1 > value2;`,
|
|
286
|
+
`const result = value1 >= value2;`,
|
|
287
|
+
`if (Number.isNaN(value)) { console.log("checking for NaN correctly"); }`,
|
|
288
|
+
`if (value1 /* comment */ === value2) { console.log("different with comment on left"); }`,
|
|
289
|
+
`if (value1 === value2 /* comment */) { console.log("different with comment on right"); }`,
|
|
290
|
+
`if (value1 /* left */ === value2 /* right */) { console.log("different with comments on both"); }`,
|
|
291
|
+
`if (object /* comment */ .property === object.otherProperty) { console.log("different properties with comment"); }`,
|
|
292
|
+
`if (object.property === object /* comment */ .otherProperty) { console.log("different properties with comment on right"); }`,
|
|
293
|
+
`if (array /* comment */ [0] === array[1]) { console.log("different elements with comment on left"); }`,
|
|
294
|
+
`if (array[0] === array /* comment */ [1]) { console.log("different elements with comment on right"); }`,
|
|
295
|
+
`if (/* before */ value1 === value2 /* after */) { console.log("different with surrounding comments"); }`,
|
|
296
|
+
`const result = value1 /* a */ /* b */ === value2 /* c */ /* d */;`,
|
|
297
|
+
],
|
|
298
|
+
});
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { typescriptLanguage } from "../language.js";
|
|
2
|
+
import { hasSameTokens } from "../utils/hasSameTokens.js";
|
|
3
|
+
import { isComparisonOperator } from "./utils/operators.js";
|
|
4
|
+
|
|
5
|
+
export default typescriptLanguage.createRule({
|
|
6
|
+
about: {
|
|
7
|
+
description: "Reports comparing a value to itself.",
|
|
8
|
+
id: "selfComparisons",
|
|
9
|
+
preset: "logical",
|
|
10
|
+
},
|
|
11
|
+
messages: {
|
|
12
|
+
noSelfComparison: {
|
|
13
|
+
primary:
|
|
14
|
+
"Comparing a value to itself is unnecessary and likely indicates a logic error.",
|
|
15
|
+
secondary: [
|
|
16
|
+
"Self-comparisons always evaluate to the same result for a given operator.",
|
|
17
|
+
"This pattern often indicates a copy-paste error or typo where different variables were intended.",
|
|
18
|
+
],
|
|
19
|
+
suggestions: [
|
|
20
|
+
"Verify that you intended to compare two different values.",
|
|
21
|
+
"If checking for NaN, use `Number.isNaN()` or `isNaN()` instead.",
|
|
22
|
+
],
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
setup(context) {
|
|
26
|
+
return {
|
|
27
|
+
visitors: {
|
|
28
|
+
BinaryExpression: (node) => {
|
|
29
|
+
if (
|
|
30
|
+
isComparisonOperator(node.operatorToken) &&
|
|
31
|
+
hasSameTokens(node.left, node.right, context.sourceFile)
|
|
32
|
+
) {
|
|
33
|
+
context.report({
|
|
34
|
+
message: "noSelfComparison",
|
|
35
|
+
range: {
|
|
36
|
+
begin: node.getStart(context.sourceFile),
|
|
37
|
+
end: node.getEnd(),
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
},
|
|
45
|
+
});
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { ruleTester } from "./ruleTester.js";
|
|
2
|
+
import rule from "./sequences.js";
|
|
3
|
+
|
|
4
|
+
ruleTester.describe(rule, {
|
|
5
|
+
invalid: [
|
|
6
|
+
{
|
|
7
|
+
code: `
|
|
8
|
+
const a = (1, 2);
|
|
9
|
+
`,
|
|
10
|
+
snapshot: `
|
|
11
|
+
const a = (1, 2);
|
|
12
|
+
~
|
|
13
|
+
The "sequence" (comma) operator is often confusing and a sign of mistaken logic.
|
|
14
|
+
`,
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
code: `
|
|
18
|
+
function f() {
|
|
19
|
+
return (g(), h());
|
|
20
|
+
}
|
|
21
|
+
`,
|
|
22
|
+
snapshot: `
|
|
23
|
+
function f() {
|
|
24
|
+
return (g(), h());
|
|
25
|
+
~
|
|
26
|
+
The "sequence" (comma) operator is often confusing and a sign of mistaken logic.
|
|
27
|
+
}
|
|
28
|
+
`,
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
code: `
|
|
32
|
+
for ((a = 1, b = 2); ; ) {
|
|
33
|
+
}
|
|
34
|
+
`,
|
|
35
|
+
snapshot: `
|
|
36
|
+
for ((a = 1, b = 2); ; ) {
|
|
37
|
+
~
|
|
38
|
+
The "sequence" (comma) operator is often confusing and a sign of mistaken logic.
|
|
39
|
+
}
|
|
40
|
+
`,
|
|
41
|
+
},
|
|
42
|
+
],
|
|
43
|
+
valid: [
|
|
44
|
+
`const a = [1, 2];`,
|
|
45
|
+
`const a = (1 + 2);`,
|
|
46
|
+
`function f() { g(); h(); }`,
|
|
47
|
+
`for (let i = 0; i < 10; i += 1) {}`,
|
|
48
|
+
],
|
|
49
|
+
});
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import * as ts from "typescript";
|
|
2
|
+
|
|
3
|
+
import { getTSNodeRange } from "../getTSNodeRange.js";
|
|
4
|
+
import { typescriptLanguage } from "../language.js";
|
|
5
|
+
|
|
6
|
+
export default typescriptLanguage.createRule({
|
|
7
|
+
about: {
|
|
8
|
+
description: "Reports using the comma operator in expressions.",
|
|
9
|
+
id: "sequences",
|
|
10
|
+
preset: "untyped",
|
|
11
|
+
},
|
|
12
|
+
messages: {
|
|
13
|
+
noSequences: {
|
|
14
|
+
primary:
|
|
15
|
+
'The "sequence" (comma) operator is often confusing and a sign of mistaken logic.',
|
|
16
|
+
secondary: [
|
|
17
|
+
"The comma operator can make code harder to read and may hide side effects.",
|
|
18
|
+
"Prefer separate expressions instead of using a single sequence.",
|
|
19
|
+
],
|
|
20
|
+
suggestions: ["Split the expression into separate statements."],
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
setup(context) {
|
|
24
|
+
return {
|
|
25
|
+
visitors: {
|
|
26
|
+
BinaryExpression: (node) => {
|
|
27
|
+
if (node.operatorToken.kind === ts.SyntaxKind.CommaToken) {
|
|
28
|
+
context.report({
|
|
29
|
+
message: "noSequences",
|
|
30
|
+
range: getTSNodeRange(node.operatorToken, context.sourceFile),
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
},
|
|
37
|
+
});
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { ruleTester } from "./ruleTester.js";
|
|
2
|
+
import rule from "./typeofComparisons.js";
|
|
3
|
+
|
|
4
|
+
ruleTester.describe(rule, {
|
|
5
|
+
invalid: [
|
|
6
|
+
{
|
|
7
|
+
code: `
|
|
8
|
+
if (typeof value === "") {
|
|
9
|
+
process();
|
|
10
|
+
}
|
|
11
|
+
`,
|
|
12
|
+
snapshot: `
|
|
13
|
+
if (typeof value === "") {
|
|
14
|
+
~~
|
|
15
|
+
This string literal is not one that the typeof operator will ever produce.
|
|
16
|
+
process();
|
|
17
|
+
}
|
|
18
|
+
`,
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
code: `
|
|
22
|
+
if (typeof data != "invalid") {
|
|
23
|
+
reject();
|
|
24
|
+
}
|
|
25
|
+
`,
|
|
26
|
+
snapshot: `
|
|
27
|
+
if (typeof data != "invalid") {
|
|
28
|
+
~~~~~~~~~
|
|
29
|
+
This string literal is not one that the typeof operator will ever produce.
|
|
30
|
+
reject();
|
|
31
|
+
}
|
|
32
|
+
`,
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
code: `
|
|
36
|
+
if ("invalid" === typeof flag) {
|
|
37
|
+
toggle();
|
|
38
|
+
}
|
|
39
|
+
`,
|
|
40
|
+
snapshot: `
|
|
41
|
+
if ("invalid" === typeof flag) {
|
|
42
|
+
~~~~~~~~~
|
|
43
|
+
This string literal is not one that the typeof operator will ever produce.
|
|
44
|
+
toggle();
|
|
45
|
+
}
|
|
46
|
+
`,
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
code: `
|
|
50
|
+
if (typeof value === "String") {
|
|
51
|
+
process();
|
|
52
|
+
}
|
|
53
|
+
`,
|
|
54
|
+
snapshot: `
|
|
55
|
+
if (typeof value === "String") {
|
|
56
|
+
~~~~~~~~
|
|
57
|
+
This string literal is not one that the typeof operator will ever produce.
|
|
58
|
+
process();
|
|
59
|
+
}
|
|
60
|
+
`,
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
code: `
|
|
64
|
+
const isValid = typeof input !== "array";
|
|
65
|
+
`,
|
|
66
|
+
snapshot: `
|
|
67
|
+
const isValid = typeof input !== "array";
|
|
68
|
+
~~~~~~~
|
|
69
|
+
This string literal is not one that the typeof operator will ever produce.
|
|
70
|
+
`,
|
|
71
|
+
},
|
|
72
|
+
],
|
|
73
|
+
valid: [
|
|
74
|
+
`if (typeof value === "string") { process(); }`,
|
|
75
|
+
`if (typeof variable == "undefined") { handle(); }`,
|
|
76
|
+
`if (typeof data != "number") { reject(); }`,
|
|
77
|
+
`if (typeof callback !== "function") { throw new Error("Invalid callback"); }`,
|
|
78
|
+
`if (typeof flag === "boolean") { toggle(); }`,
|
|
79
|
+
`if (typeof value === "object") { parse(); }`,
|
|
80
|
+
`if (typeof value === "symbol") { handle(); }`,
|
|
81
|
+
`if (typeof value === "bigint") { compute(); }`,
|
|
82
|
+
`if (typeof value === other) { process(); }`,
|
|
83
|
+
`if (typeof value === typeof other) { compare(); }`,
|
|
84
|
+
`const type = typeof value;`,
|
|
85
|
+
],
|
|
86
|
+
});
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import * as ts from "typescript";
|
|
2
|
+
|
|
3
|
+
import { getTSNodeRange } from "../getTSNodeRange.js";
|
|
4
|
+
import { typescriptLanguage } from "../language.js";
|
|
5
|
+
|
|
6
|
+
const validTypeofValues = new Set([
|
|
7
|
+
"bigint",
|
|
8
|
+
"boolean",
|
|
9
|
+
"function",
|
|
10
|
+
"number",
|
|
11
|
+
"object",
|
|
12
|
+
"string",
|
|
13
|
+
"symbol",
|
|
14
|
+
"undefined",
|
|
15
|
+
]);
|
|
16
|
+
|
|
17
|
+
// TODO: Reuse a shared getStaticValue-style utility?
|
|
18
|
+
function getStringValue(node: ts.Expression) {
|
|
19
|
+
return ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)
|
|
20
|
+
? node.text
|
|
21
|
+
: undefined;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function getTypeofOperand(node: ts.Expression) {
|
|
25
|
+
return ts.isTypeOfExpression(node) && node.expression;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export default typescriptLanguage.createRule({
|
|
29
|
+
about: {
|
|
30
|
+
description:
|
|
31
|
+
"Reports typeof expressions that compare impossible string literals.",
|
|
32
|
+
id: "typeofComparisons",
|
|
33
|
+
preset: "untyped",
|
|
34
|
+
},
|
|
35
|
+
messages: {
|
|
36
|
+
invalidValue: {
|
|
37
|
+
primary:
|
|
38
|
+
"This string literal is not one that the typeof operator will ever produce.",
|
|
39
|
+
secondary: [
|
|
40
|
+
"The typeof operator returns one of a specific set of string values.",
|
|
41
|
+
"Comparing typeof to an invalid string is usually a typo and will never match.",
|
|
42
|
+
'The only valid values are: "bigint", "boolean", "function", "number", "object", "string", "symbol", and "undefined".',
|
|
43
|
+
],
|
|
44
|
+
suggestions: [
|
|
45
|
+
"Check for typos and use one of the valid typeof return values.",
|
|
46
|
+
],
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
setup(context) {
|
|
50
|
+
function checkComparison(node: ts.BinaryExpression) {
|
|
51
|
+
const leftTypeofOperand = getTypeofOperand(node.left);
|
|
52
|
+
const rightTypeofOperand = getTypeofOperand(node.right);
|
|
53
|
+
|
|
54
|
+
let comparisonValue: ts.Expression | undefined;
|
|
55
|
+
if (leftTypeofOperand) {
|
|
56
|
+
comparisonValue = node.right;
|
|
57
|
+
} else if (rightTypeofOperand) {
|
|
58
|
+
comparisonValue = node.left;
|
|
59
|
+
} else {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const stringValue = getStringValue(comparisonValue);
|
|
64
|
+
if (stringValue != null && !validTypeofValues.has(stringValue)) {
|
|
65
|
+
context.report({
|
|
66
|
+
message: "invalidValue",
|
|
67
|
+
range: getTSNodeRange(comparisonValue, context.sourceFile),
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
visitors: {
|
|
74
|
+
BinaryExpression: (node) => {
|
|
75
|
+
if (
|
|
76
|
+
node.operatorToken.kind === ts.SyntaxKind.EqualsEqualsToken ||
|
|
77
|
+
node.operatorToken.kind === ts.SyntaxKind.EqualsEqualsEqualsToken ||
|
|
78
|
+
node.operatorToken.kind === ts.SyntaxKind.ExclamationEqualsToken ||
|
|
79
|
+
node.operatorToken.kind ===
|
|
80
|
+
ts.SyntaxKind.ExclamationEqualsEqualsToken
|
|
81
|
+
) {
|
|
82
|
+
checkComparison(node);
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
},
|
|
88
|
+
});
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import * as tsutils from "ts-api-utils";
|
|
2
|
+
import * as ts from "typescript";
|
|
3
|
+
|
|
4
|
+
export enum AnyType {
|
|
5
|
+
Any,
|
|
6
|
+
PromiseAny,
|
|
7
|
+
AnyArray,
|
|
8
|
+
Safe,
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @returns `AnyType.Any` if the type is `any`, `AnyType.AnyArray` if the type is `any[]` or `readonly any[]`, `AnyType.PromiseAny` if the type is `Promise<any>`,
|
|
13
|
+
* otherwise it returns `AnyType.Safe`.
|
|
14
|
+
*/
|
|
15
|
+
export function discriminateAnyType(
|
|
16
|
+
type: ts.Type,
|
|
17
|
+
checker: ts.TypeChecker,
|
|
18
|
+
program: ts.Program,
|
|
19
|
+
tsNode: ts.Node,
|
|
20
|
+
): AnyType {
|
|
21
|
+
return discriminateAnyTypeWorker(type, checker, program, tsNode, new Set());
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function discriminateAnyTypeWorker(
|
|
25
|
+
type: ts.Type,
|
|
26
|
+
checker: ts.TypeChecker,
|
|
27
|
+
program: ts.Program,
|
|
28
|
+
tsNode: ts.Node,
|
|
29
|
+
visited: Set<ts.Type>,
|
|
30
|
+
) {
|
|
31
|
+
if (visited.has(type)) {
|
|
32
|
+
return AnyType.Safe;
|
|
33
|
+
}
|
|
34
|
+
visited.add(type);
|
|
35
|
+
if (tsutils.isTypeFlagSet(type, ts.TypeFlags.Any)) {
|
|
36
|
+
return AnyType.Any;
|
|
37
|
+
}
|
|
38
|
+
if (
|
|
39
|
+
checker.isArrayType(type) &&
|
|
40
|
+
tsutils.isTypeFlagSet(
|
|
41
|
+
checker.getTypeArguments(type as ts.TypeReference)[0],
|
|
42
|
+
ts.TypeFlags.Any,
|
|
43
|
+
)
|
|
44
|
+
) {
|
|
45
|
+
return AnyType.AnyArray;
|
|
46
|
+
}
|
|
47
|
+
for (const part of tsutils.typeConstituents(type)) {
|
|
48
|
+
if (tsutils.isThenableType(checker, tsNode, part)) {
|
|
49
|
+
const awaitedType = checker.getAwaitedType(part);
|
|
50
|
+
if (awaitedType) {
|
|
51
|
+
const awaitedAnyType = discriminateAnyTypeWorker(
|
|
52
|
+
awaitedType,
|
|
53
|
+
checker,
|
|
54
|
+
program,
|
|
55
|
+
tsNode,
|
|
56
|
+
visited,
|
|
57
|
+
);
|
|
58
|
+
if (awaitedAnyType === AnyType.Any) {
|
|
59
|
+
return AnyType.PromiseAny;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return AnyType.Safe;
|
|
66
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import * as tsutils from "ts-api-utils";
|
|
2
|
+
import * as ts from "typescript";
|
|
3
|
+
|
|
4
|
+
export function getThisExpression(node: ts.Node): null | ts.ThisExpression {
|
|
5
|
+
while (true) {
|
|
6
|
+
node = skipParentheses(node);
|
|
7
|
+
if (ts.isCallExpression(node) || tsutils.isAccessExpression(node)) {
|
|
8
|
+
node = node.expression;
|
|
9
|
+
} else if (tsutils.isThisExpression(node)) {
|
|
10
|
+
return node;
|
|
11
|
+
} else {
|
|
12
|
+
break;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function skipParentheses(node: ts.Node): ts.Node {
|
|
20
|
+
while (ts.isParenthesizedExpression(node)) {
|
|
21
|
+
node = node.expression;
|
|
22
|
+
}
|
|
23
|
+
return node;
|
|
24
|
+
}
|