@ttsc/lint 0.5.0-dev.20260429
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/LICENSE +21 -0
- package/README.md +204 -0
- package/go-plugin/go.mod +38 -0
- package/go-plugin/lint/ast_helpers.go +170 -0
- package/go-plugin/lint/compile.go +393 -0
- package/go-plugin/lint/config.go +134 -0
- package/go-plugin/lint/engine.go +259 -0
- package/go-plugin/lint/host.go +190 -0
- package/go-plugin/lint/rules_arrays.go +78 -0
- package/go-plugin/lint/rules_console.go +37 -0
- package/go-plugin/lint/rules_debugger.go +29 -0
- package/go-plugin/lint/rules_dupes.go +179 -0
- package/go-plugin/lint/rules_empty.go +110 -0
- package/go-plugin/lint/rules_eval.go +57 -0
- package/go-plugin/lint/rules_finally.go +123 -0
- package/go-plugin/lint/rules_logic.go +368 -0
- package/go-plugin/lint/rules_loops.go +107 -0
- package/go-plugin/lint/rules_misc.go +69 -0
- package/go-plugin/lint/rules_problems.go +571 -0
- package/go-plugin/lint/rules_protos.go +42 -0
- package/go-plugin/lint/rules_self.go +89 -0
- package/go-plugin/lint/rules_strings.go +123 -0
- package/go-plugin/lint/rules_suggestions.go +1236 -0
- package/go-plugin/lint/rules_throw.go +31 -0
- package/go-plugin/lint/rules_ts.go +250 -0
- package/go-plugin/lint/rules_ts_extra.go +654 -0
- package/go-plugin/lint/rules_var.go +40 -0
- package/go-plugin/main.go +50 -0
- package/index.cjs +28 -0
- package/package.json +41 -0
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
package lint
|
|
2
|
+
|
|
3
|
+
import shimast "github.com/microsoft/typescript-go/shim/ast"
|
|
4
|
+
|
|
5
|
+
// no-extra-boolean-cast: `if (!!x)`, `if (Boolean(x))`, `Boolean(!!x)` —
|
|
6
|
+
// the conversion is implicit in a boolean context.
|
|
7
|
+
// https://eslint.org/docs/latest/rules/no-extra-boolean-cast
|
|
8
|
+
type noExtraBooleanCast struct{}
|
|
9
|
+
|
|
10
|
+
func (noExtraBooleanCast) Name() string { return "no-extra-boolean-cast" }
|
|
11
|
+
func (noExtraBooleanCast) Visits() []shimast.Kind {
|
|
12
|
+
return []shimast.Kind{shimast.KindCallExpression, shimast.KindPrefixUnaryExpression}
|
|
13
|
+
}
|
|
14
|
+
func (noExtraBooleanCast) Check(ctx *Context, node *shimast.Node) {
|
|
15
|
+
switch node.Kind {
|
|
16
|
+
case shimast.KindCallExpression:
|
|
17
|
+
call := node.AsCallExpression()
|
|
18
|
+
if call == nil {
|
|
19
|
+
return
|
|
20
|
+
}
|
|
21
|
+
if identifierText(call.Expression) != "Boolean" {
|
|
22
|
+
return
|
|
23
|
+
}
|
|
24
|
+
if isInBooleanContext(node) {
|
|
25
|
+
ctx.Report(node, "Redundant Boolean call.")
|
|
26
|
+
}
|
|
27
|
+
case shimast.KindPrefixUnaryExpression:
|
|
28
|
+
outer := node.AsPrefixUnaryExpression()
|
|
29
|
+
if outer == nil || outer.Operator != shimast.KindExclamationToken {
|
|
30
|
+
return
|
|
31
|
+
}
|
|
32
|
+
operand := outer.Operand
|
|
33
|
+
if operand == nil || operand.Kind != shimast.KindPrefixUnaryExpression {
|
|
34
|
+
return
|
|
35
|
+
}
|
|
36
|
+
inner := operand.AsPrefixUnaryExpression()
|
|
37
|
+
if inner == nil || inner.Operator != shimast.KindExclamationToken {
|
|
38
|
+
return
|
|
39
|
+
}
|
|
40
|
+
if isInBooleanContext(node) {
|
|
41
|
+
ctx.Report(node, "Redundant double negation.")
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// isInBooleanContext walks up the parent chain to determine whether the
|
|
47
|
+
// expression's value is consumed as a boolean (test of an if/while/for/
|
|
48
|
+
// ternary, or operand of `!`).
|
|
49
|
+
func isInBooleanContext(node *shimast.Node) bool {
|
|
50
|
+
parent := node.Parent
|
|
51
|
+
for parent != nil && parent.Kind == shimast.KindParenthesizedExpression {
|
|
52
|
+
parent = parent.Parent
|
|
53
|
+
}
|
|
54
|
+
if parent == nil {
|
|
55
|
+
return false
|
|
56
|
+
}
|
|
57
|
+
switch parent.Kind {
|
|
58
|
+
case shimast.KindIfStatement:
|
|
59
|
+
return parent.AsIfStatement().Expression == skipParents(node)
|
|
60
|
+
case shimast.KindWhileStatement:
|
|
61
|
+
return parent.AsWhileStatement().Expression == skipParents(node)
|
|
62
|
+
case shimast.KindDoStatement:
|
|
63
|
+
return parent.AsDoStatement().Expression == skipParents(node)
|
|
64
|
+
case shimast.KindForStatement:
|
|
65
|
+
return parent.AsForStatement().Condition == skipParents(node)
|
|
66
|
+
case shimast.KindConditionalExpression:
|
|
67
|
+
return parent.AsConditionalExpression().Condition == skipParents(node)
|
|
68
|
+
case shimast.KindPrefixUnaryExpression:
|
|
69
|
+
return parent.AsPrefixUnaryExpression().Operator == shimast.KindExclamationToken
|
|
70
|
+
}
|
|
71
|
+
return false
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
func skipParents(node *shimast.Node) *shimast.Node {
|
|
75
|
+
for node != nil && node.Parent != nil && node.Parent.Kind == shimast.KindParenthesizedExpression {
|
|
76
|
+
node = node.Parent
|
|
77
|
+
}
|
|
78
|
+
return node
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// no-unsafe-negation: `!a in b` and `!a instanceof b` — the parser
|
|
82
|
+
// applies the negation to `a`, not to the whole comparison.
|
|
83
|
+
// https://eslint.org/docs/latest/rules/no-unsafe-negation
|
|
84
|
+
type noUnsafeNegation struct{}
|
|
85
|
+
|
|
86
|
+
func (noUnsafeNegation) Name() string { return "no-unsafe-negation" }
|
|
87
|
+
func (noUnsafeNegation) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindBinaryExpression} }
|
|
88
|
+
func (noUnsafeNegation) Check(ctx *Context, node *shimast.Node) {
|
|
89
|
+
expr := node.AsBinaryExpression()
|
|
90
|
+
if expr == nil || expr.OperatorToken == nil {
|
|
91
|
+
return
|
|
92
|
+
}
|
|
93
|
+
switch expr.OperatorToken.Kind {
|
|
94
|
+
case shimast.KindInKeyword, shimast.KindInstanceOfKeyword:
|
|
95
|
+
default:
|
|
96
|
+
return
|
|
97
|
+
}
|
|
98
|
+
if expr.Left == nil || expr.Left.Kind != shimast.KindPrefixUnaryExpression {
|
|
99
|
+
return
|
|
100
|
+
}
|
|
101
|
+
prefix := expr.Left.AsPrefixUnaryExpression()
|
|
102
|
+
if prefix == nil || prefix.Operator != shimast.KindExclamationToken {
|
|
103
|
+
return
|
|
104
|
+
}
|
|
105
|
+
ctx.Report(node, "Unexpected negating the left operand of a relational operator.")
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// eqeqeq: enforce `===` / `!==`. Default mode matches ESLint's `always`
|
|
109
|
+
// preset — no exceptions for null comparison.
|
|
110
|
+
// https://eslint.org/docs/latest/rules/eqeqeq
|
|
111
|
+
type eqeqeq struct{}
|
|
112
|
+
|
|
113
|
+
func (eqeqeq) Name() string { return "eqeqeq" }
|
|
114
|
+
func (eqeqeq) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindBinaryExpression} }
|
|
115
|
+
func (eqeqeq) Check(ctx *Context, node *shimast.Node) {
|
|
116
|
+
expr := node.AsBinaryExpression()
|
|
117
|
+
if expr == nil || expr.OperatorToken == nil {
|
|
118
|
+
return
|
|
119
|
+
}
|
|
120
|
+
switch expr.OperatorToken.Kind {
|
|
121
|
+
case shimast.KindEqualsEqualsToken:
|
|
122
|
+
ctx.ReportRange(expr.OperatorToken.Pos(), expr.OperatorToken.End(), "Expected '===' and instead saw '=='.")
|
|
123
|
+
case shimast.KindExclamationEqualsToken:
|
|
124
|
+
ctx.ReportRange(expr.OperatorToken.Pos(), expr.OperatorToken.End(), "Expected '!==' and instead saw '!='.")
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// use-isnan: `x === NaN` is always false. Use `Number.isNaN(x)`.
|
|
129
|
+
// https://eslint.org/docs/latest/rules/use-isnan
|
|
130
|
+
type useIsnan struct{}
|
|
131
|
+
|
|
132
|
+
func (useIsnan) Name() string { return "use-isnan" }
|
|
133
|
+
func (useIsnan) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindBinaryExpression} }
|
|
134
|
+
func (useIsnan) Check(ctx *Context, node *shimast.Node) {
|
|
135
|
+
expr := node.AsBinaryExpression()
|
|
136
|
+
if expr == nil || expr.OperatorToken == nil {
|
|
137
|
+
return
|
|
138
|
+
}
|
|
139
|
+
if !isComparisonOperator(expr.OperatorToken.Kind) {
|
|
140
|
+
return
|
|
141
|
+
}
|
|
142
|
+
if identifierText(expr.Left) == "NaN" || identifierText(expr.Right) == "NaN" {
|
|
143
|
+
ctx.Report(node, "Use the isNaN function to compare with NaN.")
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// valid-typeof: typeof expressions can only be compared to known type
|
|
148
|
+
// strings. Catches `typeof x === "stirng"`.
|
|
149
|
+
// https://eslint.org/docs/latest/rules/valid-typeof
|
|
150
|
+
type validTypeof struct{}
|
|
151
|
+
|
|
152
|
+
func (validTypeof) Name() string { return "valid-typeof" }
|
|
153
|
+
func (validTypeof) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindBinaryExpression} }
|
|
154
|
+
func (validTypeof) Check(ctx *Context, node *shimast.Node) {
|
|
155
|
+
expr := node.AsBinaryExpression()
|
|
156
|
+
if expr == nil || expr.OperatorToken == nil {
|
|
157
|
+
return
|
|
158
|
+
}
|
|
159
|
+
if !isComparisonOperator(expr.OperatorToken.Kind) {
|
|
160
|
+
return
|
|
161
|
+
}
|
|
162
|
+
left := stripParens(expr.Left)
|
|
163
|
+
right := stripParens(expr.Right)
|
|
164
|
+
var literal *shimast.Node
|
|
165
|
+
if left != nil && left.Kind == shimast.KindTypeOfExpression {
|
|
166
|
+
literal = right
|
|
167
|
+
} else if right != nil && right.Kind == shimast.KindTypeOfExpression {
|
|
168
|
+
literal = left
|
|
169
|
+
} else {
|
|
170
|
+
return
|
|
171
|
+
}
|
|
172
|
+
if literal == nil {
|
|
173
|
+
return
|
|
174
|
+
}
|
|
175
|
+
value := stringLiteralText(literal)
|
|
176
|
+
if value == "" {
|
|
177
|
+
return
|
|
178
|
+
}
|
|
179
|
+
if !isValidTypeofString(value) {
|
|
180
|
+
ctx.Report(literal, "Invalid typeof comparison value.")
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
func isValidTypeofString(value string) bool {
|
|
185
|
+
switch value {
|
|
186
|
+
case "undefined", "object", "boolean", "number", "string", "function", "symbol", "bigint":
|
|
187
|
+
return true
|
|
188
|
+
}
|
|
189
|
+
return false
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// no-compare-neg-zero: `x === -0`. Comparison ignores the sign — use
|
|
193
|
+
// `Object.is(x, -0)` if you really mean it.
|
|
194
|
+
// https://eslint.org/docs/latest/rules/no-compare-neg-zero
|
|
195
|
+
type noCompareNegZero struct{}
|
|
196
|
+
|
|
197
|
+
func (noCompareNegZero) Name() string { return "no-compare-neg-zero" }
|
|
198
|
+
func (noCompareNegZero) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindBinaryExpression} }
|
|
199
|
+
func (noCompareNegZero) Check(ctx *Context, node *shimast.Node) {
|
|
200
|
+
expr := node.AsBinaryExpression()
|
|
201
|
+
if expr == nil || expr.OperatorToken == nil {
|
|
202
|
+
return
|
|
203
|
+
}
|
|
204
|
+
if !isComparisonOperator(expr.OperatorToken.Kind) {
|
|
205
|
+
return
|
|
206
|
+
}
|
|
207
|
+
if isNegZero(expr.Left) || isNegZero(expr.Right) {
|
|
208
|
+
ctx.Report(node, "Do not use the '-0' literal in comparisons.")
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
func isNegZero(node *shimast.Node) bool {
|
|
213
|
+
if node == nil || node.Kind != shimast.KindPrefixUnaryExpression {
|
|
214
|
+
return false
|
|
215
|
+
}
|
|
216
|
+
prefix := node.AsPrefixUnaryExpression()
|
|
217
|
+
if prefix == nil || prefix.Operator != shimast.KindMinusToken || prefix.Operand == nil {
|
|
218
|
+
return false
|
|
219
|
+
}
|
|
220
|
+
return numericLiteralText(prefix.Operand) == "0"
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// no-cond-assign: `if (a = b)` is almost always a typo for `if (a == b)`.
|
|
224
|
+
// Default mode matches ESLint's `except-parens` — wrapping in `( )`
|
|
225
|
+
// silences the rule.
|
|
226
|
+
// https://eslint.org/docs/latest/rules/no-cond-assign
|
|
227
|
+
type noCondAssign struct{}
|
|
228
|
+
|
|
229
|
+
func (noCondAssign) Name() string { return "no-cond-assign" }
|
|
230
|
+
func (noCondAssign) Visits() []shimast.Kind {
|
|
231
|
+
return []shimast.Kind{
|
|
232
|
+
shimast.KindIfStatement,
|
|
233
|
+
shimast.KindWhileStatement,
|
|
234
|
+
shimast.KindDoStatement,
|
|
235
|
+
shimast.KindForStatement,
|
|
236
|
+
shimast.KindConditionalExpression,
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
func (noCondAssign) Check(ctx *Context, node *shimast.Node) {
|
|
240
|
+
var test *shimast.Node
|
|
241
|
+
switch node.Kind {
|
|
242
|
+
case shimast.KindIfStatement:
|
|
243
|
+
test = node.AsIfStatement().Expression
|
|
244
|
+
case shimast.KindWhileStatement:
|
|
245
|
+
test = node.AsWhileStatement().Expression
|
|
246
|
+
case shimast.KindDoStatement:
|
|
247
|
+
test = node.AsDoStatement().Expression
|
|
248
|
+
case shimast.KindForStatement:
|
|
249
|
+
test = node.AsForStatement().Condition
|
|
250
|
+
case shimast.KindConditionalExpression:
|
|
251
|
+
test = node.AsConditionalExpression().Condition
|
|
252
|
+
}
|
|
253
|
+
if test == nil {
|
|
254
|
+
return
|
|
255
|
+
}
|
|
256
|
+
// `except-parens`: an explicitly parenthesized assignment is OK.
|
|
257
|
+
if test.Kind == shimast.KindParenthesizedExpression {
|
|
258
|
+
return
|
|
259
|
+
}
|
|
260
|
+
if isAssignmentExpression(test) {
|
|
261
|
+
ctx.Report(test, "Expected a conditional expression and instead saw an assignment.")
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
func isAssignmentExpression(node *shimast.Node) bool {
|
|
266
|
+
if node == nil || node.Kind != shimast.KindBinaryExpression {
|
|
267
|
+
return false
|
|
268
|
+
}
|
|
269
|
+
expr := node.AsBinaryExpression()
|
|
270
|
+
if expr == nil || expr.OperatorToken == nil {
|
|
271
|
+
return false
|
|
272
|
+
}
|
|
273
|
+
return isAssignmentOperator(expr.OperatorToken.Kind)
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
func isAssignmentOperator(kind shimast.Kind) bool {
|
|
277
|
+
switch kind {
|
|
278
|
+
case shimast.KindEqualsToken,
|
|
279
|
+
shimast.KindPlusEqualsToken,
|
|
280
|
+
shimast.KindMinusEqualsToken:
|
|
281
|
+
return true
|
|
282
|
+
}
|
|
283
|
+
return false
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// no-constant-condition: `if (true)`, `while (1)`, `if (literal)`. Often
|
|
287
|
+
// the result of leftover debug code or a typo.
|
|
288
|
+
// https://eslint.org/docs/latest/rules/no-constant-condition
|
|
289
|
+
type noConstantCondition struct{}
|
|
290
|
+
|
|
291
|
+
func (noConstantCondition) Name() string { return "no-constant-condition" }
|
|
292
|
+
func (noConstantCondition) Visits() []shimast.Kind {
|
|
293
|
+
return []shimast.Kind{
|
|
294
|
+
shimast.KindIfStatement,
|
|
295
|
+
shimast.KindWhileStatement,
|
|
296
|
+
shimast.KindDoStatement,
|
|
297
|
+
shimast.KindForStatement,
|
|
298
|
+
shimast.KindConditionalExpression,
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
func (noConstantCondition) Check(ctx *Context, node *shimast.Node) {
|
|
302
|
+
var test *shimast.Node
|
|
303
|
+
switch node.Kind {
|
|
304
|
+
case shimast.KindIfStatement:
|
|
305
|
+
test = node.AsIfStatement().Expression
|
|
306
|
+
case shimast.KindWhileStatement:
|
|
307
|
+
test = node.AsWhileStatement().Expression
|
|
308
|
+
case shimast.KindDoStatement:
|
|
309
|
+
test = node.AsDoStatement().Expression
|
|
310
|
+
case shimast.KindForStatement:
|
|
311
|
+
test = node.AsForStatement().Condition
|
|
312
|
+
case shimast.KindConditionalExpression:
|
|
313
|
+
test = node.AsConditionalExpression().Condition
|
|
314
|
+
}
|
|
315
|
+
test = stripParens(test)
|
|
316
|
+
if test == nil {
|
|
317
|
+
return // covers `for (;;)` — omitted condition is idiomatic.
|
|
318
|
+
}
|
|
319
|
+
// Allow `while (true)` since it's a common deliberate idiom.
|
|
320
|
+
if node.Kind == shimast.KindWhileStatement {
|
|
321
|
+
if v, ok := isLiteralBoolean(test); ok && v {
|
|
322
|
+
return
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
if isConstantTruthyOrFalsy(test) {
|
|
326
|
+
ctx.Report(test, "Unexpected constant condition.")
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
func isConstantTruthyOrFalsy(node *shimast.Node) bool {
|
|
331
|
+
if node == nil {
|
|
332
|
+
return false
|
|
333
|
+
}
|
|
334
|
+
switch node.Kind {
|
|
335
|
+
case
|
|
336
|
+
shimast.KindNumericLiteral,
|
|
337
|
+
shimast.KindBigIntLiteral,
|
|
338
|
+
shimast.KindStringLiteral,
|
|
339
|
+
shimast.KindNoSubstitutionTemplateLiteral,
|
|
340
|
+
shimast.KindRegularExpressionLiteral,
|
|
341
|
+
shimast.KindTrueKeyword,
|
|
342
|
+
shimast.KindFalseKeyword,
|
|
343
|
+
shimast.KindNullKeyword,
|
|
344
|
+
shimast.KindArrayLiteralExpression,
|
|
345
|
+
shimast.KindObjectLiteralExpression,
|
|
346
|
+
shimast.KindArrowFunction,
|
|
347
|
+
shimast.KindFunctionExpression:
|
|
348
|
+
return true
|
|
349
|
+
case shimast.KindPrefixUnaryExpression:
|
|
350
|
+
prefix := node.AsPrefixUnaryExpression()
|
|
351
|
+
if prefix == nil {
|
|
352
|
+
return false
|
|
353
|
+
}
|
|
354
|
+
return isConstantTruthyOrFalsy(prefix.Operand)
|
|
355
|
+
}
|
|
356
|
+
return false
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
func init() {
|
|
360
|
+
Register(noExtraBooleanCast{})
|
|
361
|
+
Register(noUnsafeNegation{})
|
|
362
|
+
Register(eqeqeq{})
|
|
363
|
+
Register(useIsnan{})
|
|
364
|
+
Register(validTypeof{})
|
|
365
|
+
Register(noCompareNegZero{})
|
|
366
|
+
Register(noCondAssign{})
|
|
367
|
+
Register(noConstantCondition{})
|
|
368
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
package lint
|
|
2
|
+
|
|
3
|
+
import shimast "github.com/microsoft/typescript-go/shim/ast"
|
|
4
|
+
|
|
5
|
+
// for-direction: `for (var i = 10; i < 20; i--)` will never terminate.
|
|
6
|
+
// https://eslint.org/docs/latest/rules/for-direction
|
|
7
|
+
type forDirection struct{}
|
|
8
|
+
|
|
9
|
+
func (forDirection) Name() string { return "for-direction" }
|
|
10
|
+
func (forDirection) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindForStatement} }
|
|
11
|
+
func (forDirection) Check(ctx *Context, node *shimast.Node) {
|
|
12
|
+
loop := node.AsForStatement()
|
|
13
|
+
if loop == nil || loop.Condition == nil || loop.Incrementor == nil {
|
|
14
|
+
return
|
|
15
|
+
}
|
|
16
|
+
cond := loop.Condition
|
|
17
|
+
if cond.Kind != shimast.KindBinaryExpression {
|
|
18
|
+
return
|
|
19
|
+
}
|
|
20
|
+
bin := cond.AsBinaryExpression()
|
|
21
|
+
if bin == nil || bin.OperatorToken == nil {
|
|
22
|
+
return
|
|
23
|
+
}
|
|
24
|
+
leftName := identifierText(bin.Left)
|
|
25
|
+
rightName := identifierText(bin.Right)
|
|
26
|
+
if leftName == "" && rightName == "" {
|
|
27
|
+
return
|
|
28
|
+
}
|
|
29
|
+
directionFromCondition := 0
|
|
30
|
+
switch bin.OperatorToken.Kind {
|
|
31
|
+
case shimast.KindLessThanToken, shimast.KindLessThanEqualsToken:
|
|
32
|
+
// `i < limit` requires increment
|
|
33
|
+
if leftName != "" {
|
|
34
|
+
directionFromCondition = +1
|
|
35
|
+
} else {
|
|
36
|
+
directionFromCondition = -1
|
|
37
|
+
}
|
|
38
|
+
case shimast.KindGreaterThanToken, shimast.KindGreaterThanEqualsToken:
|
|
39
|
+
if leftName != "" {
|
|
40
|
+
directionFromCondition = -1
|
|
41
|
+
} else {
|
|
42
|
+
directionFromCondition = +1
|
|
43
|
+
}
|
|
44
|
+
default:
|
|
45
|
+
return
|
|
46
|
+
}
|
|
47
|
+
counterName := leftName
|
|
48
|
+
if counterName == "" {
|
|
49
|
+
counterName = rightName
|
|
50
|
+
}
|
|
51
|
+
directionFromIncr := updateDirection(loop.Incrementor, counterName)
|
|
52
|
+
if directionFromIncr == 0 {
|
|
53
|
+
return
|
|
54
|
+
}
|
|
55
|
+
if directionFromIncr*directionFromCondition < 0 {
|
|
56
|
+
ctx.Report(loop.Incrementor, "The update clause in this loop moves the variable in the wrong direction.")
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
func updateDirection(node *shimast.Node, counter string) int {
|
|
61
|
+
if node == nil || counter == "" {
|
|
62
|
+
return 0
|
|
63
|
+
}
|
|
64
|
+
switch node.Kind {
|
|
65
|
+
case shimast.KindPostfixUnaryExpression:
|
|
66
|
+
post := node.AsPostfixUnaryExpression()
|
|
67
|
+
if post == nil || identifierText(post.Operand) != counter {
|
|
68
|
+
return 0
|
|
69
|
+
}
|
|
70
|
+
switch post.Operator {
|
|
71
|
+
case shimast.KindPlusPlusToken:
|
|
72
|
+
return +1
|
|
73
|
+
case shimast.KindMinusMinusToken:
|
|
74
|
+
return -1
|
|
75
|
+
}
|
|
76
|
+
case shimast.KindPrefixUnaryExpression:
|
|
77
|
+
pre := node.AsPrefixUnaryExpression()
|
|
78
|
+
if pre == nil || identifierText(pre.Operand) != counter {
|
|
79
|
+
return 0
|
|
80
|
+
}
|
|
81
|
+
switch pre.Operator {
|
|
82
|
+
case shimast.KindPlusPlusToken:
|
|
83
|
+
return +1
|
|
84
|
+
case shimast.KindMinusMinusToken:
|
|
85
|
+
return -1
|
|
86
|
+
}
|
|
87
|
+
case shimast.KindBinaryExpression:
|
|
88
|
+
bin := node.AsBinaryExpression()
|
|
89
|
+
if bin == nil || bin.OperatorToken == nil {
|
|
90
|
+
return 0
|
|
91
|
+
}
|
|
92
|
+
if identifierText(bin.Left) != counter {
|
|
93
|
+
return 0
|
|
94
|
+
}
|
|
95
|
+
switch bin.OperatorToken.Kind {
|
|
96
|
+
case shimast.KindPlusEqualsToken:
|
|
97
|
+
return +1
|
|
98
|
+
case shimast.KindMinusEqualsToken:
|
|
99
|
+
return -1
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return 0
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
func init() {
|
|
106
|
+
Register(forDirection{})
|
|
107
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
package lint
|
|
2
|
+
|
|
3
|
+
import shimast "github.com/microsoft/typescript-go/shim/ast"
|
|
4
|
+
|
|
5
|
+
// radix: `parseInt(x)` without an explicit radix can hit the legacy
|
|
6
|
+
// "leading 0 means octal" trap. ESLint default mode requires the radix.
|
|
7
|
+
// https://eslint.org/docs/latest/rules/radix
|
|
8
|
+
type radix struct{}
|
|
9
|
+
|
|
10
|
+
func (radix) Name() string { return "radix" }
|
|
11
|
+
func (radix) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindCallExpression} }
|
|
12
|
+
func (radix) Check(ctx *Context, node *shimast.Node) {
|
|
13
|
+
call := node.AsCallExpression()
|
|
14
|
+
if call == nil {
|
|
15
|
+
return
|
|
16
|
+
}
|
|
17
|
+
name := callCalleeName(call)
|
|
18
|
+
if name != "parseInt" && name != "Number.parseInt" && !isMatchingPropertyAccess(call.Expression, "Number", "parseInt") {
|
|
19
|
+
return
|
|
20
|
+
}
|
|
21
|
+
args := 0
|
|
22
|
+
if call.Arguments != nil {
|
|
23
|
+
args = len(call.Arguments.Nodes)
|
|
24
|
+
}
|
|
25
|
+
if args == 0 {
|
|
26
|
+
return
|
|
27
|
+
}
|
|
28
|
+
if args == 1 {
|
|
29
|
+
ctx.Report(node, "Missing radix parameter.")
|
|
30
|
+
return
|
|
31
|
+
}
|
|
32
|
+
radixArg := call.Arguments.Nodes[1]
|
|
33
|
+
radixArg = stripParens(radixArg)
|
|
34
|
+
if radixArg == nil {
|
|
35
|
+
ctx.Report(node, "Missing radix parameter.")
|
|
36
|
+
return
|
|
37
|
+
}
|
|
38
|
+
if radixArg.Kind == shimast.KindNumericLiteral {
|
|
39
|
+
text := numericLiteralText(radixArg)
|
|
40
|
+
if text == "10" || text == "16" || text == "8" || text == "2" {
|
|
41
|
+
return
|
|
42
|
+
}
|
|
43
|
+
ctx.Report(radixArg, "Invalid radix parameter.")
|
|
44
|
+
return
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// no-new-wrappers: `new String("")`, `new Number(0)`, `new Boolean(false)`
|
|
49
|
+
// build wrapper objects rarely intended.
|
|
50
|
+
// https://eslint.org/docs/latest/rules/no-new-wrappers
|
|
51
|
+
type noNewWrappers struct{}
|
|
52
|
+
|
|
53
|
+
func (noNewWrappers) Name() string { return "no-new-wrappers" }
|
|
54
|
+
func (noNewWrappers) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindNewExpression} }
|
|
55
|
+
func (noNewWrappers) Check(ctx *Context, node *shimast.Node) {
|
|
56
|
+
ne := node.AsNewExpression()
|
|
57
|
+
if ne == nil {
|
|
58
|
+
return
|
|
59
|
+
}
|
|
60
|
+
switch identifierText(ne.Expression) {
|
|
61
|
+
case "String", "Number", "Boolean":
|
|
62
|
+
ctx.Report(node, "Do not use "+identifierText(ne.Expression)+" as a constructor.")
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
func init() {
|
|
67
|
+
Register(radix{})
|
|
68
|
+
Register(noNewWrappers{})
|
|
69
|
+
}
|