@ttsc/lint 0.7.2 → 0.8.0-dev.20260505
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/README.md +3 -3
- package/lib/index.js.map +1 -1
- package/package.json +3 -3
- package/plugin/ast_helpers.go +158 -158
- package/plugin/compile.go +303 -303
- package/plugin/config.go +322 -322
- package/plugin/engine.go +152 -153
- package/plugin/host.go +128 -128
- package/plugin/main.go +21 -21
- package/plugin/rules_arrays.go +51 -51
- package/plugin/rules_console.go +22 -23
- package/plugin/rules_debugger.go +4 -4
- package/plugin/rules_dupes.go +134 -134
- package/plugin/rules_empty.go +68 -68
- package/plugin/rules_eval.go +28 -28
- package/plugin/rules_finally.go +91 -91
- package/plugin/rules_gap.go +188 -188
- package/plugin/rules_logic.go +268 -268
- package/plugin/rules_loops.go +89 -89
- package/plugin/rules_misc.go +43 -43
- package/plugin/rules_problems.go +396 -396
- package/plugin/rules_protos.go +17 -17
- package/plugin/rules_self.go +56 -56
- package/plugin/rules_strings.go +65 -65
- package/plugin/rules_suggestions.go +863 -863
- package/plugin/rules_throw.go +16 -16
- package/plugin/rules_ts.go +161 -161
- package/plugin/rules_ts_extra.go +467 -467
- package/plugin/rules_var.go +86 -86
- package/src/index.ts +1 -4
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
package main
|
|
5
5
|
|
|
6
6
|
import (
|
|
7
|
-
|
|
7
|
+
"strings"
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
shimast "github.com/microsoft/typescript-go/shim/ast"
|
|
10
10
|
)
|
|
11
11
|
|
|
12
12
|
// no-alert: `alert()` / `confirm()` / `prompt()`. Rarely the right
|
|
@@ -16,14 +16,14 @@ type noAlert struct{}
|
|
|
16
16
|
func (noAlert) Name() string { return "no-alert" }
|
|
17
17
|
func (noAlert) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindCallExpression} }
|
|
18
18
|
func (noAlert) Check(ctx *Context, node *shimast.Node) {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
19
|
+
call := node.AsCallExpression()
|
|
20
|
+
if call == nil {
|
|
21
|
+
return
|
|
22
|
+
}
|
|
23
|
+
switch callCalleeName(call) {
|
|
24
|
+
case "alert", "confirm", "prompt":
|
|
25
|
+
ctx.Report(node, "Unexpected "+callCalleeName(call)+".")
|
|
26
|
+
}
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
// no-bitwise: `&`, `|`, `^`, `~`, `<<`, `>>`, `>>>` — almost always a
|
|
@@ -32,38 +32,38 @@ type noBitwise struct{}
|
|
|
32
32
|
|
|
33
33
|
func (noBitwise) Name() string { return "no-bitwise" }
|
|
34
34
|
func (noBitwise) Visits() []shimast.Kind {
|
|
35
|
-
|
|
35
|
+
return []shimast.Kind{shimast.KindBinaryExpression, shimast.KindPrefixUnaryExpression}
|
|
36
36
|
}
|
|
37
37
|
func (noBitwise) Check(ctx *Context, node *shimast.Node) {
|
|
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
|
-
|
|
38
|
+
if node.Kind == shimast.KindBinaryExpression {
|
|
39
|
+
expr := node.AsBinaryExpression()
|
|
40
|
+
if expr == nil || expr.OperatorToken == nil {
|
|
41
|
+
return
|
|
42
|
+
}
|
|
43
|
+
switch expr.OperatorToken.Kind {
|
|
44
|
+
case shimast.KindAmpersandToken,
|
|
45
|
+
shimast.KindBarToken,
|
|
46
|
+
shimast.KindCaretToken,
|
|
47
|
+
shimast.KindLessThanLessThanToken,
|
|
48
|
+
shimast.KindGreaterThanGreaterThanToken,
|
|
49
|
+
shimast.KindGreaterThanGreaterThanGreaterThanToken,
|
|
50
|
+
shimast.KindAmpersandEqualsToken,
|
|
51
|
+
shimast.KindBarEqualsToken,
|
|
52
|
+
shimast.KindCaretEqualsToken,
|
|
53
|
+
shimast.KindLessThanLessThanEqualsToken,
|
|
54
|
+
shimast.KindGreaterThanGreaterThanEqualsToken,
|
|
55
|
+
shimast.KindGreaterThanGreaterThanGreaterThanEqualsToken:
|
|
56
|
+
ctx.Report(node, "Unexpected use of bitwise operator.")
|
|
57
|
+
}
|
|
58
|
+
return
|
|
59
|
+
}
|
|
60
|
+
prefix := node.AsPrefixUnaryExpression()
|
|
61
|
+
if prefix == nil {
|
|
62
|
+
return
|
|
63
|
+
}
|
|
64
|
+
if prefix.Operator == shimast.KindTildeToken {
|
|
65
|
+
ctx.Report(node, "Unexpected use of bitwise operator.")
|
|
66
|
+
}
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
// no-caller: `arguments.caller` / `arguments.callee` — strict-mode
|
|
@@ -73,17 +73,17 @@ type noCaller struct{}
|
|
|
73
73
|
func (noCaller) Name() string { return "no-caller" }
|
|
74
74
|
func (noCaller) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindPropertyAccessExpression} }
|
|
75
75
|
func (noCaller) Check(ctx *Context, node *shimast.Node) {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
76
|
+
access := node.AsPropertyAccessExpression()
|
|
77
|
+
if access == nil {
|
|
78
|
+
return
|
|
79
|
+
}
|
|
80
|
+
if identifierText(access.Expression) != "arguments" {
|
|
81
|
+
return
|
|
82
|
+
}
|
|
83
|
+
switch identifierText(access.Name()) {
|
|
84
|
+
case "caller", "callee":
|
|
85
|
+
ctx.Report(node, "Avoid arguments."+identifierText(access.Name())+".")
|
|
86
|
+
}
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
// no-case-declarations: `switch (x) { case 1: let y = 2; break; }` —
|
|
@@ -92,29 +92,29 @@ type noCaseDeclarations struct{}
|
|
|
92
92
|
|
|
93
93
|
func (noCaseDeclarations) Name() string { return "no-case-declarations" }
|
|
94
94
|
func (noCaseDeclarations) Visits() []shimast.Kind {
|
|
95
|
-
|
|
95
|
+
return []shimast.Kind{shimast.KindCaseClause, shimast.KindDefaultClause}
|
|
96
96
|
}
|
|
97
97
|
func (noCaseDeclarations) Check(ctx *Context, node *shimast.Node) {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
98
|
+
clause := node.AsCaseOrDefaultClause()
|
|
99
|
+
if clause == nil || clause.Statements == nil {
|
|
100
|
+
return
|
|
101
|
+
}
|
|
102
|
+
for _, stmt := range clause.Statements.Nodes {
|
|
103
|
+
if stmt == nil {
|
|
104
|
+
continue
|
|
105
|
+
}
|
|
106
|
+
if stmt.Kind == shimast.KindVariableStatement {
|
|
107
|
+
vstmt := stmt.AsVariableStatement()
|
|
108
|
+
if vstmt != nil && vstmt.DeclarationList != nil && !shimast.IsVar(vstmt.DeclarationList) {
|
|
109
|
+
ctx.Report(stmt, "Unexpected lexical declaration in case block.")
|
|
110
|
+
continue
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
switch stmt.Kind {
|
|
114
|
+
case shimast.KindFunctionDeclaration, shimast.KindClassDeclaration:
|
|
115
|
+
ctx.Report(stmt, "Unexpected lexical declaration in case block.")
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
118
|
}
|
|
119
119
|
|
|
120
120
|
// no-continue: `continue` keyword. ESLint flags it as a code-smell
|
|
@@ -124,7 +124,7 @@ type noContinue struct{}
|
|
|
124
124
|
func (noContinue) Name() string { return "no-continue" }
|
|
125
125
|
func (noContinue) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindContinueStatement} }
|
|
126
126
|
func (noContinue) Check(ctx *Context, node *shimast.Node) {
|
|
127
|
-
|
|
127
|
+
ctx.Report(node, "Unexpected use of continue statement.")
|
|
128
128
|
}
|
|
129
129
|
|
|
130
130
|
// no-delete-var: `delete x` where `x` is a variable. Strict-mode error.
|
|
@@ -133,13 +133,13 @@ type noDeleteVar struct{}
|
|
|
133
133
|
func (noDeleteVar) Name() string { return "no-delete-var" }
|
|
134
134
|
func (noDeleteVar) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindDeleteExpression} }
|
|
135
135
|
func (noDeleteVar) Check(ctx *Context, node *shimast.Node) {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
136
|
+
del := node.AsDeleteExpression()
|
|
137
|
+
if del == nil {
|
|
138
|
+
return
|
|
139
|
+
}
|
|
140
|
+
if del.Expression != nil && del.Expression.Kind == shimast.KindIdentifier {
|
|
141
|
+
ctx.Report(node, "Variables should not be deleted.")
|
|
142
|
+
}
|
|
143
143
|
}
|
|
144
144
|
|
|
145
145
|
// no-eq-null: `x == null` — ambiguous with eqeqeq's `null` exception
|
|
@@ -149,20 +149,20 @@ type noEqNull struct{}
|
|
|
149
149
|
func (noEqNull) Name() string { return "no-eq-null" }
|
|
150
150
|
func (noEqNull) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindBinaryExpression} }
|
|
151
151
|
func (noEqNull) Check(ctx *Context, node *shimast.Node) {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
152
|
+
expr := node.AsBinaryExpression()
|
|
153
|
+
if expr == nil || expr.OperatorToken == nil {
|
|
154
|
+
return
|
|
155
|
+
}
|
|
156
|
+
if expr.OperatorToken.Kind != shimast.KindEqualsEqualsToken && expr.OperatorToken.Kind != shimast.KindExclamationEqualsToken {
|
|
157
|
+
return
|
|
158
|
+
}
|
|
159
|
+
if isNullLiteral(expr.Left) || isNullLiteral(expr.Right) {
|
|
160
|
+
ctx.Report(node, "Use '===' to compare with null.")
|
|
161
|
+
}
|
|
162
162
|
}
|
|
163
163
|
|
|
164
164
|
func isNullLiteral(node *shimast.Node) bool {
|
|
165
|
-
|
|
165
|
+
return node != nil && node.Kind == shimast.KindNullKeyword
|
|
166
166
|
}
|
|
167
167
|
|
|
168
168
|
// no-extra-bind: `(function () {}).bind(this)` where `this` isn't used
|
|
@@ -173,60 +173,60 @@ type noExtraBind struct{}
|
|
|
173
173
|
func (noExtraBind) Name() string { return "no-extra-bind" }
|
|
174
174
|
func (noExtraBind) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindCallExpression} }
|
|
175
175
|
func (noExtraBind) Check(ctx *Context, node *shimast.Node) {
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
176
|
+
call := node.AsCallExpression()
|
|
177
|
+
if call == nil || call.Expression == nil {
|
|
178
|
+
return
|
|
179
|
+
}
|
|
180
|
+
if call.Expression.Kind != shimast.KindPropertyAccessExpression {
|
|
181
|
+
return
|
|
182
|
+
}
|
|
183
|
+
access := call.Expression.AsPropertyAccessExpression()
|
|
184
|
+
if access == nil || identifierText(access.Name()) != "bind" {
|
|
185
|
+
return
|
|
186
|
+
}
|
|
187
|
+
target := stripParens(access.Expression)
|
|
188
|
+
if target == nil {
|
|
189
|
+
return
|
|
190
|
+
}
|
|
191
|
+
if target.Kind != shimast.KindArrowFunction && target.Kind != shimast.KindFunctionExpression {
|
|
192
|
+
return
|
|
193
|
+
}
|
|
194
|
+
// Arrow functions don't have their own `this`; `.bind` is always
|
|
195
|
+
// useless on them.
|
|
196
|
+
if target.Kind == shimast.KindArrowFunction {
|
|
197
|
+
ctx.Report(node, "The function binding is unnecessary.")
|
|
198
|
+
return
|
|
199
|
+
}
|
|
200
|
+
body := target.Body()
|
|
201
|
+
if body != nil && !bodyReferencesThis(body) {
|
|
202
|
+
ctx.Report(node, "The function binding is unnecessary.")
|
|
203
|
+
}
|
|
204
204
|
}
|
|
205
205
|
|
|
206
206
|
func bodyReferencesThis(node *shimast.Node) bool {
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
207
|
+
if node == nil {
|
|
208
|
+
return false
|
|
209
|
+
}
|
|
210
|
+
if node.Kind == shimast.KindThisKeyword {
|
|
211
|
+
return true
|
|
212
|
+
}
|
|
213
|
+
// Don't descend into nested function-likes — their `this` is
|
|
214
|
+
// independent.
|
|
215
|
+
if isFunctionLikeKind(node) && node.Parent != nil {
|
|
216
|
+
return false
|
|
217
|
+
}
|
|
218
|
+
found := false
|
|
219
|
+
node.ForEachChild(func(child *shimast.Node) bool {
|
|
220
|
+
if found {
|
|
221
|
+
return true
|
|
222
|
+
}
|
|
223
|
+
if bodyReferencesThis(child) {
|
|
224
|
+
found = true
|
|
225
|
+
return true
|
|
226
|
+
}
|
|
227
|
+
return false
|
|
228
|
+
})
|
|
229
|
+
return found
|
|
230
230
|
}
|
|
231
231
|
|
|
232
232
|
// no-labels: labels (`outer: for (...) { break outer; }`) are
|
|
@@ -236,7 +236,7 @@ type noLabels struct{}
|
|
|
236
236
|
func (noLabels) Name() string { return "no-labels" }
|
|
237
237
|
func (noLabels) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindLabeledStatement} }
|
|
238
238
|
func (noLabels) Check(ctx *Context, node *shimast.Node) {
|
|
239
|
-
|
|
239
|
+
ctx.Report(node, "Unexpected labeled statement.")
|
|
240
240
|
}
|
|
241
241
|
|
|
242
242
|
// no-lone-blocks: `{ doStuff(); }` outside a control flow context —
|
|
@@ -246,45 +246,45 @@ type noLoneBlocks struct{}
|
|
|
246
246
|
func (noLoneBlocks) Name() string { return "no-lone-blocks" }
|
|
247
247
|
func (noLoneBlocks) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindBlock} }
|
|
248
248
|
func (noLoneBlocks) Check(ctx *Context, node *shimast.Node) {
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
249
|
+
parent := node.Parent
|
|
250
|
+
if parent == nil {
|
|
251
|
+
return
|
|
252
|
+
}
|
|
253
|
+
switch parent.Kind {
|
|
254
|
+
case shimast.KindBlock, shimast.KindSourceFile, shimast.KindModuleBlock:
|
|
255
|
+
default:
|
|
256
|
+
return
|
|
257
|
+
}
|
|
258
|
+
// Skip blocks that are themselves a function/method body — those
|
|
259
|
+
// are tracked by isFunctionLikeKind on the parent.
|
|
260
|
+
if isFunctionLikeKind(parent) {
|
|
261
|
+
return
|
|
262
|
+
}
|
|
263
|
+
block := node.AsBlock()
|
|
264
|
+
if block == nil || block.Statements == nil {
|
|
265
|
+
return
|
|
266
|
+
}
|
|
267
|
+
// Empty block is `no-empty`'s domain.
|
|
268
|
+
if len(block.Statements.Nodes) == 0 {
|
|
269
|
+
return
|
|
270
|
+
}
|
|
271
|
+
// Allow blocks whose only contents are block-scoped declarations
|
|
272
|
+
// (`{ const x = 1; }` is occasionally used to limit scope).
|
|
273
|
+
for _, stmt := range block.Statements.Nodes {
|
|
274
|
+
if stmt == nil {
|
|
275
|
+
continue
|
|
276
|
+
}
|
|
277
|
+
if stmt.Kind == shimast.KindVariableStatement {
|
|
278
|
+
vstmt := stmt.AsVariableStatement()
|
|
279
|
+
if vstmt != nil && vstmt.DeclarationList != nil && !shimast.IsVar(vstmt.DeclarationList) {
|
|
280
|
+
return
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
if stmt.Kind == shimast.KindClassDeclaration || stmt.Kind == shimast.KindFunctionDeclaration {
|
|
284
|
+
return
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
ctx.Report(node, "Block is redundant.")
|
|
288
288
|
}
|
|
289
289
|
|
|
290
290
|
// no-lonely-if: `else { if (...) {...} }` should be `else if (...)`.
|
|
@@ -293,26 +293,26 @@ type noLonelyIf struct{}
|
|
|
293
293
|
func (noLonelyIf) Name() string { return "no-lonely-if" }
|
|
294
294
|
func (noLonelyIf) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindIfStatement} }
|
|
295
295
|
func (noLonelyIf) Check(ctx *Context, node *shimast.Node) {
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
296
|
+
parent := node.Parent
|
|
297
|
+
if parent == nil || parent.Kind != shimast.KindBlock {
|
|
298
|
+
return
|
|
299
|
+
}
|
|
300
|
+
block := parent.AsBlock()
|
|
301
|
+
if block == nil || block.Statements == nil {
|
|
302
|
+
return
|
|
303
|
+
}
|
|
304
|
+
if len(block.Statements.Nodes) != 1 {
|
|
305
|
+
return
|
|
306
|
+
}
|
|
307
|
+
grand := parent.Parent
|
|
308
|
+
if grand == nil || grand.Kind != shimast.KindIfStatement {
|
|
309
|
+
return
|
|
310
|
+
}
|
|
311
|
+
gif := grand.AsIfStatement()
|
|
312
|
+
if gif == nil || gif.ElseStatement != parent {
|
|
313
|
+
return
|
|
314
|
+
}
|
|
315
|
+
ctx.Report(node, "Unexpected if as the only statement in an else block.")
|
|
316
316
|
}
|
|
317
317
|
|
|
318
318
|
// no-multi-assign: `a = b = 1`. Confusing right-to-left chains.
|
|
@@ -321,19 +321,19 @@ type noMultiAssign struct{}
|
|
|
321
321
|
func (noMultiAssign) Name() string { return "no-multi-assign" }
|
|
322
322
|
func (noMultiAssign) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindBinaryExpression} }
|
|
323
323
|
func (noMultiAssign) Check(ctx *Context, node *shimast.Node) {
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
324
|
+
expr := node.AsBinaryExpression()
|
|
325
|
+
if expr == nil || expr.OperatorToken == nil {
|
|
326
|
+
return
|
|
327
|
+
}
|
|
328
|
+
if expr.OperatorToken.Kind != shimast.KindEqualsToken {
|
|
329
|
+
return
|
|
330
|
+
}
|
|
331
|
+
if expr.Right != nil && expr.Right.Kind == shimast.KindBinaryExpression {
|
|
332
|
+
inner := expr.Right.AsBinaryExpression()
|
|
333
|
+
if inner != nil && inner.OperatorToken != nil && inner.OperatorToken.Kind == shimast.KindEqualsToken {
|
|
334
|
+
ctx.Report(node, "Unexpected chained assignment.")
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
337
|
}
|
|
338
338
|
|
|
339
339
|
// no-negated-condition: `if (!x) {} else {}`. Easier to read with the
|
|
@@ -342,53 +342,53 @@ type noNegatedCondition struct{}
|
|
|
342
342
|
|
|
343
343
|
func (noNegatedCondition) Name() string { return "no-negated-condition" }
|
|
344
344
|
func (noNegatedCondition) Visits() []shimast.Kind {
|
|
345
|
-
|
|
345
|
+
return []shimast.Kind{shimast.KindIfStatement, shimast.KindConditionalExpression}
|
|
346
346
|
}
|
|
347
347
|
func (noNegatedCondition) Check(ctx *Context, node *shimast.Node) {
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
348
|
+
if node.Kind == shimast.KindIfStatement {
|
|
349
|
+
stmt := node.AsIfStatement()
|
|
350
|
+
if stmt == nil || stmt.ElseStatement == nil {
|
|
351
|
+
return
|
|
352
|
+
}
|
|
353
|
+
// Allow `else if` chains — the branches aren't symmetric.
|
|
354
|
+
if stmt.ElseStatement.Kind == shimast.KindIfStatement {
|
|
355
|
+
return
|
|
356
|
+
}
|
|
357
|
+
if isNegatedExpression(stmt.Expression) {
|
|
358
|
+
ctx.Report(node, "Unexpected negated condition.")
|
|
359
|
+
}
|
|
360
|
+
return
|
|
361
|
+
}
|
|
362
|
+
cond := node.AsConditionalExpression()
|
|
363
|
+
if cond == nil {
|
|
364
|
+
return
|
|
365
|
+
}
|
|
366
|
+
if isNegatedExpression(cond.Condition) {
|
|
367
|
+
ctx.Report(node, "Unexpected negated condition.")
|
|
368
|
+
}
|
|
369
369
|
}
|
|
370
370
|
|
|
371
371
|
func isNegatedExpression(node *shimast.Node) bool {
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
372
|
+
expr := stripParens(node)
|
|
373
|
+
if expr == nil {
|
|
374
|
+
return false
|
|
375
|
+
}
|
|
376
|
+
if expr.Kind == shimast.KindPrefixUnaryExpression {
|
|
377
|
+
prefix := expr.AsPrefixUnaryExpression()
|
|
378
|
+
if prefix != nil && prefix.Operator == shimast.KindExclamationToken {
|
|
379
|
+
return true
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
if expr.Kind == shimast.KindBinaryExpression {
|
|
383
|
+
bin := expr.AsBinaryExpression()
|
|
384
|
+
if bin != nil && bin.OperatorToken != nil {
|
|
385
|
+
switch bin.OperatorToken.Kind {
|
|
386
|
+
case shimast.KindExclamationEqualsToken, shimast.KindExclamationEqualsEqualsToken:
|
|
387
|
+
return true
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
return false
|
|
392
392
|
}
|
|
393
393
|
|
|
394
394
|
// no-nested-ternary: `a ? b : c ? d : e`.
|
|
@@ -396,21 +396,21 @@ type noNestedTernary struct{}
|
|
|
396
396
|
|
|
397
397
|
func (noNestedTernary) Name() string { return "no-nested-ternary" }
|
|
398
398
|
func (noNestedTernary) Visits() []shimast.Kind {
|
|
399
|
-
|
|
399
|
+
return []shimast.Kind{shimast.KindConditionalExpression}
|
|
400
400
|
}
|
|
401
401
|
func (noNestedTernary) Check(ctx *Context, node *shimast.Node) {
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
402
|
+
cond := node.AsConditionalExpression()
|
|
403
|
+
if cond == nil {
|
|
404
|
+
return
|
|
405
|
+
}
|
|
406
|
+
if hasConditional(cond.WhenTrue) || hasConditional(cond.WhenFalse) {
|
|
407
|
+
ctx.Report(node, "Do not nest ternary expressions.")
|
|
408
|
+
}
|
|
409
409
|
}
|
|
410
410
|
|
|
411
411
|
func hasConditional(node *shimast.Node) bool {
|
|
412
|
-
|
|
413
|
-
|
|
412
|
+
expr := stripParens(node)
|
|
413
|
+
return expr != nil && expr.Kind == shimast.KindConditionalExpression
|
|
414
414
|
}
|
|
415
415
|
|
|
416
416
|
// no-new: `new Foo()` whose result is discarded. Either store it or
|
|
@@ -420,13 +420,13 @@ type noNew struct{}
|
|
|
420
420
|
func (noNew) Name() string { return "no-new" }
|
|
421
421
|
func (noNew) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindExpressionStatement} }
|
|
422
422
|
func (noNew) Check(ctx *Context, node *shimast.Node) {
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
423
|
+
stmt := node.AsExpressionStatement()
|
|
424
|
+
if stmt == nil || stmt.Expression == nil {
|
|
425
|
+
return
|
|
426
|
+
}
|
|
427
|
+
if stmt.Expression.Kind == shimast.KindNewExpression {
|
|
428
|
+
ctx.Report(node, "Do not use 'new' for side effects.")
|
|
429
|
+
}
|
|
430
430
|
}
|
|
431
431
|
|
|
432
432
|
// no-new-func: `new Function("...")` — a third form of dynamic eval.
|
|
@@ -434,18 +434,18 @@ type noNewFunc struct{}
|
|
|
434
434
|
|
|
435
435
|
func (noNewFunc) Name() string { return "no-new-func" }
|
|
436
436
|
func (noNewFunc) Visits() []shimast.Kind {
|
|
437
|
-
|
|
437
|
+
return []shimast.Kind{shimast.KindNewExpression, shimast.KindCallExpression}
|
|
438
438
|
}
|
|
439
439
|
func (noNewFunc) Check(ctx *Context, node *shimast.Node) {
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
440
|
+
var callee *shimast.Node
|
|
441
|
+
if node.Kind == shimast.KindNewExpression {
|
|
442
|
+
callee = node.AsNewExpression().Expression
|
|
443
|
+
} else {
|
|
444
|
+
callee = node.AsCallExpression().Expression
|
|
445
|
+
}
|
|
446
|
+
if identifierText(callee) == "Function" {
|
|
447
|
+
ctx.Report(node, "The Function constructor is eval.")
|
|
448
|
+
}
|
|
449
449
|
}
|
|
450
450
|
|
|
451
451
|
// no-object-constructor: `new Object()` / `Object()` — same shape as
|
|
@@ -454,30 +454,30 @@ type noObjectConstructor struct{}
|
|
|
454
454
|
|
|
455
455
|
func (noObjectConstructor) Name() string { return "no-object-constructor" }
|
|
456
456
|
func (noObjectConstructor) Visits() []shimast.Kind {
|
|
457
|
-
|
|
457
|
+
return []shimast.Kind{shimast.KindNewExpression, shimast.KindCallExpression}
|
|
458
458
|
}
|
|
459
459
|
func (noObjectConstructor) Check(ctx *Context, node *shimast.Node) {
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
460
|
+
var callee *shimast.Node
|
|
461
|
+
var argCount int
|
|
462
|
+
if node.Kind == shimast.KindNewExpression {
|
|
463
|
+
ne := node.AsNewExpression()
|
|
464
|
+
callee = ne.Expression
|
|
465
|
+
if ne.Arguments != nil {
|
|
466
|
+
argCount = len(ne.Arguments.Nodes)
|
|
467
|
+
}
|
|
468
|
+
} else {
|
|
469
|
+
call := node.AsCallExpression()
|
|
470
|
+
callee = call.Expression
|
|
471
|
+
if call.Arguments != nil {
|
|
472
|
+
argCount = len(call.Arguments.Nodes)
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
if argCount != 0 {
|
|
476
|
+
return // 1+ args is a "make a wrapper", not "make an empty object".
|
|
477
|
+
}
|
|
478
|
+
if identifierText(callee) == "Object" {
|
|
479
|
+
ctx.Report(node, "The object literal notation {} is preferable.")
|
|
480
|
+
}
|
|
481
481
|
}
|
|
482
482
|
|
|
483
483
|
// no-octal-escape: `"\251"` — octal escapes in string literals are
|
|
@@ -486,36 +486,36 @@ type noOctalEscape struct{}
|
|
|
486
486
|
|
|
487
487
|
func (noOctalEscape) Name() string { return "no-octal-escape" }
|
|
488
488
|
func (noOctalEscape) Visits() []shimast.Kind {
|
|
489
|
-
|
|
489
|
+
return []shimast.Kind{shimast.KindStringLiteral, shimast.KindNoSubstitutionTemplateLiteral}
|
|
490
490
|
}
|
|
491
491
|
func (noOctalEscape) Check(ctx *Context, node *shimast.Node) {
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
492
|
+
src := nodeText(ctx.File, node)
|
|
493
|
+
if hasOctalEscape(src) {
|
|
494
|
+
ctx.Report(node, "Don't use octal escape sequences.")
|
|
495
|
+
}
|
|
496
496
|
}
|
|
497
497
|
|
|
498
498
|
func hasOctalEscape(src string) bool {
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
499
|
+
for i := 0; i < len(src)-1; i++ {
|
|
500
|
+
if src[i] != '\\' {
|
|
501
|
+
continue
|
|
502
|
+
}
|
|
503
|
+
next := src[i+1]
|
|
504
|
+
// A literal `\0` followed by a non-digit is not an octal
|
|
505
|
+
// escape, just NUL — those are allowed.
|
|
506
|
+
if next < '0' || next > '7' {
|
|
507
|
+
i++
|
|
508
|
+
continue
|
|
509
|
+
}
|
|
510
|
+
if next == '0' {
|
|
511
|
+
if i+2 >= len(src) || src[i+2] < '0' || src[i+2] > '9' {
|
|
512
|
+
i++
|
|
513
|
+
continue
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
return true
|
|
517
|
+
}
|
|
518
|
+
return false
|
|
519
519
|
}
|
|
520
520
|
|
|
521
521
|
// no-plusplus: `++x` / `x++`. Equivalent to `x += 1`, considered less
|
|
@@ -524,19 +524,19 @@ type noPlusPlus struct{}
|
|
|
524
524
|
|
|
525
525
|
func (noPlusPlus) Name() string { return "no-plusplus" }
|
|
526
526
|
func (noPlusPlus) Visits() []shimast.Kind {
|
|
527
|
-
|
|
527
|
+
return []shimast.Kind{shimast.KindPrefixUnaryExpression, shimast.KindPostfixUnaryExpression}
|
|
528
528
|
}
|
|
529
529
|
func (noPlusPlus) Check(ctx *Context, node *shimast.Node) {
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
530
|
+
var op shimast.Kind
|
|
531
|
+
if node.Kind == shimast.KindPrefixUnaryExpression {
|
|
532
|
+
op = node.AsPrefixUnaryExpression().Operator
|
|
533
|
+
} else {
|
|
534
|
+
op = node.AsPostfixUnaryExpression().Operator
|
|
535
|
+
}
|
|
536
|
+
switch op {
|
|
537
|
+
case shimast.KindPlusPlusToken, shimast.KindMinusMinusToken:
|
|
538
|
+
ctx.Report(node, "Unary operator '++'/'--' used.")
|
|
539
|
+
}
|
|
540
540
|
}
|
|
541
541
|
|
|
542
542
|
// no-regex-spaces: multiple spaces in a regex literal — confusing
|
|
@@ -545,50 +545,50 @@ type noRegexSpaces struct{}
|
|
|
545
545
|
|
|
546
546
|
func (noRegexSpaces) Name() string { return "no-regex-spaces" }
|
|
547
547
|
func (noRegexSpaces) Visits() []shimast.Kind {
|
|
548
|
-
|
|
548
|
+
return []shimast.Kind{shimast.KindRegularExpressionLiteral}
|
|
549
549
|
}
|
|
550
550
|
func (noRegexSpaces) Check(ctx *Context, node *shimast.Node) {
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
551
|
+
src := nodeText(ctx.File, node)
|
|
552
|
+
if regexHasMultipleSpaces(src) {
|
|
553
|
+
ctx.Report(node, "Spaces are hard to count. Use {N}.")
|
|
554
|
+
}
|
|
555
555
|
}
|
|
556
556
|
|
|
557
557
|
func regexHasMultipleSpaces(src string) bool {
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
558
|
+
// Strip trailing flags.
|
|
559
|
+
end := strings.LastIndex(src, "/")
|
|
560
|
+
if end <= 0 {
|
|
561
|
+
return false
|
|
562
|
+
}
|
|
563
|
+
body := src[:end]
|
|
564
|
+
inClass := false
|
|
565
|
+
run := 0
|
|
566
|
+
for i := 0; i < len(body); i++ {
|
|
567
|
+
c := body[i]
|
|
568
|
+
switch c {
|
|
569
|
+
case '\\':
|
|
570
|
+
i++
|
|
571
|
+
run = 0
|
|
572
|
+
case '[':
|
|
573
|
+
inClass = true
|
|
574
|
+
run = 0
|
|
575
|
+
case ']':
|
|
576
|
+
inClass = false
|
|
577
|
+
run = 0
|
|
578
|
+
case ' ':
|
|
579
|
+
if inClass {
|
|
580
|
+
run = 0
|
|
581
|
+
continue
|
|
582
|
+
}
|
|
583
|
+
run++
|
|
584
|
+
if run >= 2 {
|
|
585
|
+
return true
|
|
586
|
+
}
|
|
587
|
+
default:
|
|
588
|
+
run = 0
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
return false
|
|
592
592
|
}
|
|
593
593
|
|
|
594
594
|
// no-return-assign: `return a = b` mixes assignment with return.
|
|
@@ -596,27 +596,27 @@ type noReturnAssign struct{}
|
|
|
596
596
|
|
|
597
597
|
func (noReturnAssign) Name() string { return "no-return-assign" }
|
|
598
598
|
func (noReturnAssign) Visits() []shimast.Kind {
|
|
599
|
-
|
|
599
|
+
return []shimast.Kind{shimast.KindReturnStatement, shimast.KindArrowFunction}
|
|
600
600
|
}
|
|
601
601
|
func (noReturnAssign) Check(ctx *Context, node *shimast.Node) {
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
602
|
+
switch node.Kind {
|
|
603
|
+
case shimast.KindReturnStatement:
|
|
604
|
+
ret := node.AsReturnStatement()
|
|
605
|
+
if ret == nil || ret.Expression == nil {
|
|
606
|
+
return
|
|
607
|
+
}
|
|
608
|
+
if isAssignmentExpression(stripParens(ret.Expression)) {
|
|
609
|
+
ctx.Report(node, "Return statement should not contain assignment.")
|
|
610
|
+
}
|
|
611
|
+
case shimast.KindArrowFunction:
|
|
612
|
+
arrow := node.AsArrowFunction()
|
|
613
|
+
if arrow == nil || arrow.Body == nil || arrow.Body.Kind == shimast.KindBlock {
|
|
614
|
+
return
|
|
615
|
+
}
|
|
616
|
+
if isAssignmentExpression(stripParens(arrow.Body)) {
|
|
617
|
+
ctx.Report(node, "Arrow function should not return an assignment.")
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
620
|
}
|
|
621
621
|
|
|
622
622
|
// no-sequences: `(a, b)` — comma operator. Almost always a confusing
|
|
@@ -626,25 +626,25 @@ type noSequences struct{}
|
|
|
626
626
|
func (noSequences) Name() string { return "no-sequences" }
|
|
627
627
|
func (noSequences) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindBinaryExpression} }
|
|
628
628
|
func (noSequences) Check(ctx *Context, node *shimast.Node) {
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
629
|
+
expr := node.AsBinaryExpression()
|
|
630
|
+
if expr == nil || expr.OperatorToken == nil {
|
|
631
|
+
return
|
|
632
|
+
}
|
|
633
|
+
if expr.OperatorToken.Kind != shimast.KindCommaToken {
|
|
634
|
+
return
|
|
635
|
+
}
|
|
636
|
+
// `for (a; b; c)` headers naturally use the comma operator;
|
|
637
|
+
// suppress when the parent is a ForStatement initializer/incrementor.
|
|
638
|
+
parent := node.Parent
|
|
639
|
+
if parent != nil && parent.Kind == shimast.KindForStatement {
|
|
640
|
+
return
|
|
641
|
+
}
|
|
642
|
+
// Allow when wrapped in parens (the canonical "I really mean it"
|
|
643
|
+
// idiom).
|
|
644
|
+
if parent != nil && parent.Kind == shimast.KindParenthesizedExpression {
|
|
645
|
+
return
|
|
646
|
+
}
|
|
647
|
+
ctx.Report(node, "Unexpected use of comma operator.")
|
|
648
648
|
}
|
|
649
649
|
|
|
650
650
|
// no-shadow-restricted-names: redeclaring `undefined`, `NaN`, `Infinity`,
|
|
@@ -653,26 +653,26 @@ type noShadowRestrictedNames struct{}
|
|
|
653
653
|
|
|
654
654
|
func (noShadowRestrictedNames) Name() string { return "no-shadow-restricted-names" }
|
|
655
655
|
func (noShadowRestrictedNames) Visits() []shimast.Kind {
|
|
656
|
-
|
|
656
|
+
return []shimast.Kind{shimast.KindVariableDeclaration, shimast.KindParameter, shimast.KindFunctionDeclaration}
|
|
657
657
|
}
|
|
658
658
|
func (noShadowRestrictedNames) Check(ctx *Context, node *shimast.Node) {
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
659
|
+
var nameNode *shimast.Node
|
|
660
|
+
switch node.Kind {
|
|
661
|
+
case shimast.KindVariableDeclaration:
|
|
662
|
+
nameNode = node.AsVariableDeclaration().Name()
|
|
663
|
+
case shimast.KindParameter:
|
|
664
|
+
nameNode = node.AsParameterDeclaration().Name()
|
|
665
|
+
case shimast.KindFunctionDeclaration:
|
|
666
|
+
nameNode = node.AsFunctionDeclaration().Name()
|
|
667
|
+
}
|
|
668
|
+
name := identifierText(nameNode)
|
|
669
|
+
if name == "" {
|
|
670
|
+
return
|
|
671
|
+
}
|
|
672
|
+
switch name {
|
|
673
|
+
case "undefined", "NaN", "Infinity", "arguments", "eval":
|
|
674
|
+
ctx.Report(node, "Shadowing of global property '"+name+"'.")
|
|
675
|
+
}
|
|
676
676
|
}
|
|
677
677
|
|
|
678
678
|
// no-undefined: literal `undefined` (vs `void 0`). Easier to misuse
|
|
@@ -682,39 +682,39 @@ type noUndefined struct{}
|
|
|
682
682
|
func (noUndefined) Name() string { return "no-undefined" }
|
|
683
683
|
func (noUndefined) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindIdentifier} }
|
|
684
684
|
func (noUndefined) Check(ctx *Context, node *shimast.Node) {
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
685
|
+
if identifierText(node) != "undefined" {
|
|
686
|
+
return
|
|
687
|
+
}
|
|
688
|
+
parent := node.Parent
|
|
689
|
+
if parent == nil {
|
|
690
|
+
return
|
|
691
|
+
}
|
|
692
|
+
// Don't flag a declaration *named* `undefined` (no-shadow-restricted-names
|
|
693
|
+
// covers that), or a member-access / object-key position
|
|
694
|
+
// (`x.undefined`, `{ undefined: 1 }`).
|
|
695
|
+
switch parent.Kind {
|
|
696
|
+
case shimast.KindParameter:
|
|
697
|
+
decl := parent.AsParameterDeclaration()
|
|
698
|
+
if decl != nil && decl.Name() != nil && nodesShareLoc(decl.Name(), node) {
|
|
699
|
+
return
|
|
700
|
+
}
|
|
701
|
+
case shimast.KindVariableDeclaration:
|
|
702
|
+
decl := parent.AsVariableDeclaration()
|
|
703
|
+
if decl != nil && decl.Name() != nil && nodesShareLoc(decl.Name(), node) {
|
|
704
|
+
return
|
|
705
|
+
}
|
|
706
|
+
case shimast.KindPropertyAccessExpression:
|
|
707
|
+
access := parent.AsPropertyAccessExpression()
|
|
708
|
+
if access != nil && access.Name() != nil && nodesShareLoc(access.Name(), node) {
|
|
709
|
+
return
|
|
710
|
+
}
|
|
711
|
+
case shimast.KindPropertyAssignment:
|
|
712
|
+
assign := parent.AsPropertyAssignment()
|
|
713
|
+
if assign != nil && assign.Name() != nil && nodesShareLoc(assign.Name(), node) {
|
|
714
|
+
return
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
ctx.Report(node, "Unexpected use of undefined.")
|
|
718
718
|
}
|
|
719
719
|
|
|
720
720
|
// nodesShareLoc reports whether two `*ast.Node` references describe the
|
|
@@ -722,10 +722,10 @@ func (noUndefined) Check(ctx *Context, node *shimast.Node) {
|
|
|
722
722
|
// parser exposes its fields through accessor methods that may return
|
|
723
723
|
// fresh wrappers; comparing positions works regardless.
|
|
724
724
|
func nodesShareLoc(a, b *shimast.Node) bool {
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
725
|
+
if a == nil || b == nil {
|
|
726
|
+
return false
|
|
727
|
+
}
|
|
728
|
+
return a == b || (a.Pos() == b.Pos() && a.End() == b.End())
|
|
729
729
|
}
|
|
730
730
|
|
|
731
731
|
// no-unneeded-ternary: `x ? true : false` → `Boolean(x)` / `!!x`.
|
|
@@ -733,20 +733,20 @@ type noUnneededTernary struct{}
|
|
|
733
733
|
|
|
734
734
|
func (noUnneededTernary) Name() string { return "no-unneeded-ternary" }
|
|
735
735
|
func (noUnneededTernary) Visits() []shimast.Kind {
|
|
736
|
-
|
|
736
|
+
return []shimast.Kind{shimast.KindConditionalExpression}
|
|
737
737
|
}
|
|
738
738
|
func (noUnneededTernary) Check(ctx *Context, node *shimast.Node) {
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
739
|
+
cond := node.AsConditionalExpression()
|
|
740
|
+
if cond == nil {
|
|
741
|
+
return
|
|
742
|
+
}
|
|
743
|
+
t := stripParens(cond.WhenTrue)
|
|
744
|
+
f := stripParens(cond.WhenFalse)
|
|
745
|
+
tBool, tOk := isLiteralBoolean(t)
|
|
746
|
+
fBool, fOk := isLiteralBoolean(f)
|
|
747
|
+
if tOk && fOk && tBool != fBool {
|
|
748
|
+
ctx.Report(node, "Unnecessary use of conditional expression for boolean.")
|
|
749
|
+
}
|
|
750
750
|
}
|
|
751
751
|
|
|
752
752
|
// no-unused-expressions: an expression statement whose value isn't used.
|
|
@@ -755,61 +755,61 @@ type noUnusedExpressions struct{}
|
|
|
755
755
|
|
|
756
756
|
func (noUnusedExpressions) Name() string { return "no-unused-expressions" }
|
|
757
757
|
func (noUnusedExpressions) Visits() []shimast.Kind {
|
|
758
|
-
|
|
758
|
+
return []shimast.Kind{shimast.KindExpressionStatement}
|
|
759
759
|
}
|
|
760
760
|
func (noUnusedExpressions) Check(ctx *Context, node *shimast.Node) {
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
761
|
+
stmt := node.AsExpressionStatement()
|
|
762
|
+
if stmt == nil || stmt.Expression == nil {
|
|
763
|
+
return
|
|
764
|
+
}
|
|
765
|
+
if isProductiveExpression(stmt.Expression) {
|
|
766
|
+
return
|
|
767
|
+
}
|
|
768
|
+
ctx.Report(node, "Expected an assignment or function call and instead saw an expression.")
|
|
769
769
|
}
|
|
770
770
|
|
|
771
771
|
func isProductiveExpression(node *shimast.Node) bool {
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
772
|
+
expr := stripParens(node)
|
|
773
|
+
if expr == nil {
|
|
774
|
+
return false
|
|
775
|
+
}
|
|
776
|
+
switch expr.Kind {
|
|
777
|
+
case shimast.KindCallExpression,
|
|
778
|
+
shimast.KindNewExpression,
|
|
779
|
+
shimast.KindAwaitExpression,
|
|
780
|
+
shimast.KindYieldExpression,
|
|
781
|
+
shimast.KindDeleteExpression,
|
|
782
|
+
shimast.KindBinaryExpression,
|
|
783
|
+
shimast.KindPrefixUnaryExpression,
|
|
784
|
+
shimast.KindPostfixUnaryExpression,
|
|
785
|
+
shimast.KindTaggedTemplateExpression:
|
|
786
|
+
// These can have side effects. The narrower checks
|
|
787
|
+
// (no-cond-assign, no-bitwise) handle the suspicious shapes.
|
|
788
|
+
switch expr.Kind {
|
|
789
|
+
case shimast.KindBinaryExpression:
|
|
790
|
+
bin := expr.AsBinaryExpression()
|
|
791
|
+
if bin != nil && bin.OperatorToken != nil && isAssignmentOperator(bin.OperatorToken.Kind) {
|
|
792
|
+
return true
|
|
793
|
+
}
|
|
794
|
+
return false
|
|
795
|
+
case shimast.KindPrefixUnaryExpression:
|
|
796
|
+
prefix := expr.AsPrefixUnaryExpression()
|
|
797
|
+
if prefix != nil && (prefix.Operator == shimast.KindPlusPlusToken || prefix.Operator == shimast.KindMinusMinusToken) {
|
|
798
|
+
return true
|
|
799
|
+
}
|
|
800
|
+
return false
|
|
801
|
+
case shimast.KindPostfixUnaryExpression:
|
|
802
|
+
return true
|
|
803
|
+
}
|
|
804
|
+
return true
|
|
805
|
+
case shimast.KindStringLiteral:
|
|
806
|
+
// "use strict" prologue.
|
|
807
|
+
text := expr.AsStringLiteral()
|
|
808
|
+
if text != nil && (text.Text == "use strict" || text.Text == "use asm") {
|
|
809
|
+
return true
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
return false
|
|
813
813
|
}
|
|
814
814
|
|
|
815
815
|
// no-useless-call: `func.call(undefined, ...args)` / `func.apply(undefined, args)`
|
|
@@ -819,26 +819,26 @@ type noUselessCall struct{}
|
|
|
819
819
|
func (noUselessCall) Name() string { return "no-useless-call" }
|
|
820
820
|
func (noUselessCall) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindCallExpression} }
|
|
821
821
|
func (noUselessCall) Check(ctx *Context, node *shimast.Node) {
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
822
|
+
call := node.AsCallExpression()
|
|
823
|
+
if call == nil || call.Expression == nil || call.Expression.Kind != shimast.KindPropertyAccessExpression {
|
|
824
|
+
return
|
|
825
|
+
}
|
|
826
|
+
access := call.Expression.AsPropertyAccessExpression()
|
|
827
|
+
method := identifierText(access.Name())
|
|
828
|
+
if method != "call" && method != "apply" {
|
|
829
|
+
return
|
|
830
|
+
}
|
|
831
|
+
if call.Arguments == nil || len(call.Arguments.Nodes) == 0 {
|
|
832
|
+
return
|
|
833
|
+
}
|
|
834
|
+
first := call.Arguments.Nodes[0]
|
|
835
|
+
first = stripParens(first)
|
|
836
|
+
if first == nil {
|
|
837
|
+
return
|
|
838
|
+
}
|
|
839
|
+
if first.Kind == shimast.KindNullKeyword || identifierText(first) == "undefined" {
|
|
840
|
+
ctx.Report(node, "Unnecessary "+method+"().")
|
|
841
|
+
}
|
|
842
842
|
}
|
|
843
843
|
|
|
844
844
|
// no-useless-computed-key: `{ ["foo"]: 1 }` could be `{ foo: 1 }`.
|
|
@@ -846,34 +846,34 @@ type noUselessComputedKey struct{}
|
|
|
846
846
|
|
|
847
847
|
func (noUselessComputedKey) Name() string { return "no-useless-computed-key" }
|
|
848
848
|
func (noUselessComputedKey) Visits() []shimast.Kind {
|
|
849
|
-
|
|
849
|
+
return []shimast.Kind{shimast.KindPropertyAssignment, shimast.KindMethodDeclaration}
|
|
850
850
|
}
|
|
851
851
|
func (noUselessComputedKey) Check(ctx *Context, node *shimast.Node) {
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
852
|
+
var name *shimast.Node
|
|
853
|
+
switch node.Kind {
|
|
854
|
+
case shimast.KindPropertyAssignment:
|
|
855
|
+
name = node.AsPropertyAssignment().Name()
|
|
856
|
+
case shimast.KindMethodDeclaration:
|
|
857
|
+
name = node.AsMethodDeclaration().Name()
|
|
858
|
+
}
|
|
859
|
+
if name == nil || name.Kind != shimast.KindComputedPropertyName {
|
|
860
|
+
return
|
|
861
|
+
}
|
|
862
|
+
computed := name.AsComputedPropertyName()
|
|
863
|
+
if computed == nil || computed.Expression == nil {
|
|
864
|
+
return
|
|
865
|
+
}
|
|
866
|
+
// Only fire when the computed key is a string / numeric / template
|
|
867
|
+
// literal — a bare identifier inside `[ ]` reads its *value* and is
|
|
868
|
+
// not equivalent to the same identifier as a static key.
|
|
869
|
+
expr := stripParens(computed.Expression)
|
|
870
|
+
switch expr.Kind {
|
|
871
|
+
case shimast.KindStringLiteral,
|
|
872
|
+
shimast.KindNoSubstitutionTemplateLiteral,
|
|
873
|
+
shimast.KindNumericLiteral,
|
|
874
|
+
shimast.KindBigIntLiteral:
|
|
875
|
+
ctx.Report(name, "Unnecessarily computed property key.")
|
|
876
|
+
}
|
|
877
877
|
}
|
|
878
878
|
|
|
879
879
|
// no-useless-rename: `import { x as x } from ...` / `const { x: x } = obj`
|
|
@@ -882,35 +882,35 @@ type noUselessRename struct{}
|
|
|
882
882
|
|
|
883
883
|
func (noUselessRename) Name() string { return "no-useless-rename" }
|
|
884
884
|
func (noUselessRename) Visits() []shimast.Kind {
|
|
885
|
-
|
|
885
|
+
return []shimast.Kind{shimast.KindImportSpecifier, shimast.KindExportSpecifier, shimast.KindBindingElement}
|
|
886
886
|
}
|
|
887
887
|
func (noUselessRename) Check(ctx *Context, node *shimast.Node) {
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
888
|
+
switch node.Kind {
|
|
889
|
+
case shimast.KindImportSpecifier:
|
|
890
|
+
spec := node.AsImportSpecifier()
|
|
891
|
+
if spec == nil || spec.PropertyName == nil {
|
|
892
|
+
return
|
|
893
|
+
}
|
|
894
|
+
if identifierText(spec.PropertyName) == identifierText(spec.Name()) {
|
|
895
|
+
ctx.Report(node, "Import { x as x } is redundant.")
|
|
896
|
+
}
|
|
897
|
+
case shimast.KindExportSpecifier:
|
|
898
|
+
spec := node.AsExportSpecifier()
|
|
899
|
+
if spec == nil || spec.PropertyName == nil {
|
|
900
|
+
return
|
|
901
|
+
}
|
|
902
|
+
if identifierText(spec.PropertyName) == identifierText(spec.Name()) {
|
|
903
|
+
ctx.Report(node, "Export { x as x } is redundant.")
|
|
904
|
+
}
|
|
905
|
+
case shimast.KindBindingElement:
|
|
906
|
+
el := node.AsBindingElement()
|
|
907
|
+
if el == nil || el.PropertyName == nil {
|
|
908
|
+
return
|
|
909
|
+
}
|
|
910
|
+
if identifierText(el.PropertyName) == identifierText(el.Name()) {
|
|
911
|
+
ctx.Report(node, "Destructuring rename to the same name is redundant.")
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
914
|
}
|
|
915
915
|
|
|
916
916
|
// object-shorthand: `{ x: x }` → `{ x }`.
|
|
@@ -919,18 +919,18 @@ type objectShorthand struct{}
|
|
|
919
919
|
func (objectShorthand) Name() string { return "object-shorthand" }
|
|
920
920
|
func (objectShorthand) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindPropertyAssignment} }
|
|
921
921
|
func (objectShorthand) Check(ctx *Context, node *shimast.Node) {
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
922
|
+
prop := node.AsPropertyAssignment()
|
|
923
|
+
if prop == nil || prop.Name() == nil || prop.Initializer == nil {
|
|
924
|
+
return
|
|
925
|
+
}
|
|
926
|
+
keyName := identifierText(prop.Name())
|
|
927
|
+
valueName := identifierText(prop.Initializer)
|
|
928
|
+
if keyName == "" || valueName == "" {
|
|
929
|
+
return
|
|
930
|
+
}
|
|
931
|
+
if keyName == valueName {
|
|
932
|
+
ctx.Report(node, "Expected property shorthand.")
|
|
933
|
+
}
|
|
934
934
|
}
|
|
935
935
|
|
|
936
936
|
// operator-assignment: `x = x + 1` → `x += 1`.
|
|
@@ -938,39 +938,39 @@ type operatorAssignment struct{}
|
|
|
938
938
|
|
|
939
939
|
func (operatorAssignment) Name() string { return "operator-assignment" }
|
|
940
940
|
func (operatorAssignment) Visits() []shimast.Kind {
|
|
941
|
-
|
|
941
|
+
return []shimast.Kind{shimast.KindBinaryExpression}
|
|
942
942
|
}
|
|
943
943
|
func (operatorAssignment) Check(ctx *Context, node *shimast.Node) {
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
944
|
+
expr := node.AsBinaryExpression()
|
|
945
|
+
if expr == nil || expr.OperatorToken == nil {
|
|
946
|
+
return
|
|
947
|
+
}
|
|
948
|
+
if expr.OperatorToken.Kind != shimast.KindEqualsToken {
|
|
949
|
+
return
|
|
950
|
+
}
|
|
951
|
+
if expr.Right == nil || expr.Right.Kind != shimast.KindBinaryExpression {
|
|
952
|
+
return
|
|
953
|
+
}
|
|
954
|
+
right := expr.Right.AsBinaryExpression()
|
|
955
|
+
if right == nil || right.OperatorToken == nil {
|
|
956
|
+
return
|
|
957
|
+
}
|
|
958
|
+
if !isCompoundEligibleOperator(right.OperatorToken.Kind) {
|
|
959
|
+
return
|
|
960
|
+
}
|
|
961
|
+
if nodeText(ctx.File, expr.Left) == nodeText(ctx.File, right.Left) {
|
|
962
|
+
ctx.Report(node, "Assignment can be replaced with compound operator.")
|
|
963
|
+
}
|
|
964
964
|
}
|
|
965
965
|
|
|
966
966
|
func isCompoundEligibleOperator(kind shimast.Kind) bool {
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
967
|
+
switch kind {
|
|
968
|
+
case shimast.KindPlusToken, shimast.KindAsteriskToken, shimast.KindSlashToken,
|
|
969
|
+
shimast.KindAsteriskAsteriskToken, shimast.KindAmpersandToken, shimast.KindBarToken,
|
|
970
|
+
shimast.KindCaretToken:
|
|
971
|
+
return true
|
|
972
|
+
}
|
|
973
|
+
return false
|
|
974
974
|
}
|
|
975
975
|
|
|
976
976
|
// prefer-exponentiation-operator: `Math.pow(a, b)` → `a ** b`.
|
|
@@ -978,17 +978,17 @@ type preferExponentiationOperator struct{}
|
|
|
978
978
|
|
|
979
979
|
func (preferExponentiationOperator) Name() string { return "prefer-exponentiation-operator" }
|
|
980
980
|
func (preferExponentiationOperator) Visits() []shimast.Kind {
|
|
981
|
-
|
|
981
|
+
return []shimast.Kind{shimast.KindCallExpression}
|
|
982
982
|
}
|
|
983
983
|
func (preferExponentiationOperator) Check(ctx *Context, node *shimast.Node) {
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
984
|
+
call := node.AsCallExpression()
|
|
985
|
+
if call == nil {
|
|
986
|
+
return
|
|
987
|
+
}
|
|
988
|
+
if !isMatchingPropertyAccess(call.Expression, "Math", "pow") {
|
|
989
|
+
return
|
|
990
|
+
}
|
|
991
|
+
ctx.Report(node, "Use the '**' operator instead of 'Math.pow'.")
|
|
992
992
|
}
|
|
993
993
|
|
|
994
994
|
// prefer-spread: `fn.apply(null, args)` → `fn(...args)`.
|
|
@@ -997,29 +997,29 @@ type preferSpread struct{}
|
|
|
997
997
|
func (preferSpread) Name() string { return "prefer-spread" }
|
|
998
998
|
func (preferSpread) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindCallExpression} }
|
|
999
999
|
func (preferSpread) Check(ctx *Context, node *shimast.Node) {
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1000
|
+
call := node.AsCallExpression()
|
|
1001
|
+
if call == nil || call.Expression == nil {
|
|
1002
|
+
return
|
|
1003
|
+
}
|
|
1004
|
+
if call.Expression.Kind != shimast.KindPropertyAccessExpression {
|
|
1005
|
+
return
|
|
1006
|
+
}
|
|
1007
|
+
access := call.Expression.AsPropertyAccessExpression()
|
|
1008
|
+
if access == nil || identifierText(access.Name()) != "apply" {
|
|
1009
|
+
return
|
|
1010
|
+
}
|
|
1011
|
+
if call.Arguments == nil || len(call.Arguments.Nodes) != 2 {
|
|
1012
|
+
return
|
|
1013
|
+
}
|
|
1014
|
+
first := stripParens(call.Arguments.Nodes[0])
|
|
1015
|
+
if first == nil {
|
|
1016
|
+
return
|
|
1017
|
+
}
|
|
1018
|
+
// ESLint default: only fire when the `this` arg is null/undefined,
|
|
1019
|
+
// which is the canonical "I just want to spread" pattern.
|
|
1020
|
+
if first.Kind == shimast.KindNullKeyword || identifierText(first) == "undefined" {
|
|
1021
|
+
ctx.Report(node, "Use the spread operator instead of '.apply()'.")
|
|
1022
|
+
}
|
|
1023
1023
|
}
|
|
1024
1024
|
|
|
1025
1025
|
// prefer-template: string concatenation that would read better as a
|
|
@@ -1030,44 +1030,44 @@ type preferTemplate struct{}
|
|
|
1030
1030
|
func (preferTemplate) Name() string { return "prefer-template" }
|
|
1031
1031
|
func (preferTemplate) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindBinaryExpression} }
|
|
1032
1032
|
func (preferTemplate) Check(ctx *Context, node *shimast.Node) {
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1033
|
+
expr := node.AsBinaryExpression()
|
|
1034
|
+
if expr == nil || expr.OperatorToken == nil {
|
|
1035
|
+
return
|
|
1036
|
+
}
|
|
1037
|
+
if expr.OperatorToken.Kind != shimast.KindPlusToken {
|
|
1038
|
+
return
|
|
1039
|
+
}
|
|
1040
|
+
// Skip when the parent is also a string-concat — only the topmost
|
|
1041
|
+
// `+` chain emits one finding.
|
|
1042
|
+
parent := node.Parent
|
|
1043
|
+
if parent != nil && parent.Kind == shimast.KindBinaryExpression {
|
|
1044
|
+
parentBin := parent.AsBinaryExpression()
|
|
1045
|
+
if parentBin != nil && parentBin.OperatorToken != nil && parentBin.OperatorToken.Kind == shimast.KindPlusToken {
|
|
1046
|
+
return
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
hasString, hasOther := concatChainShape(node)
|
|
1050
|
+
if hasString && hasOther {
|
|
1051
|
+
ctx.Report(node, "Unexpected string concatenation.")
|
|
1052
|
+
}
|
|
1053
1053
|
}
|
|
1054
1054
|
|
|
1055
1055
|
func concatChainShape(node *shimast.Node) (hasString bool, hasOther bool) {
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1056
|
+
if node == nil {
|
|
1057
|
+
return false, false
|
|
1058
|
+
}
|
|
1059
|
+
if node.Kind == shimast.KindBinaryExpression {
|
|
1060
|
+
bin := node.AsBinaryExpression()
|
|
1061
|
+
if bin != nil && bin.OperatorToken != nil && bin.OperatorToken.Kind == shimast.KindPlusToken {
|
|
1062
|
+
ls, lo := concatChainShape(bin.Left)
|
|
1063
|
+
rs, ro := concatChainShape(bin.Right)
|
|
1064
|
+
return ls || rs, lo || ro
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
if isStringLikeLiteral(stripParens(node)) {
|
|
1068
|
+
return true, false
|
|
1069
|
+
}
|
|
1070
|
+
return false, true
|
|
1071
1071
|
}
|
|
1072
1072
|
|
|
1073
1073
|
// require-yield: `function* gen() { return 1; }` — generators that
|
|
@@ -1076,58 +1076,58 @@ type requireYield struct{}
|
|
|
1076
1076
|
|
|
1077
1077
|
func (requireYield) Name() string { return "require-yield" }
|
|
1078
1078
|
func (requireYield) Visits() []shimast.Kind {
|
|
1079
|
-
|
|
1079
|
+
return []shimast.Kind{shimast.KindFunctionDeclaration, shimast.KindFunctionExpression, shimast.KindMethodDeclaration}
|
|
1080
1080
|
}
|
|
1081
1081
|
func (requireYield) Check(ctx *Context, node *shimast.Node) {
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1082
|
+
if !hasAsteriskModifier(node) {
|
|
1083
|
+
return
|
|
1084
|
+
}
|
|
1085
|
+
body := node.Body()
|
|
1086
|
+
if body == nil {
|
|
1087
|
+
return
|
|
1088
|
+
}
|
|
1089
|
+
if !subtreeContainsYield(body) {
|
|
1090
|
+
ctx.Report(node, "This generator function does not have 'yield'.")
|
|
1091
|
+
}
|
|
1092
1092
|
}
|
|
1093
1093
|
|
|
1094
1094
|
func hasAsteriskModifier(node *shimast.Node) bool {
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1095
|
+
if node == nil {
|
|
1096
|
+
return false
|
|
1097
|
+
}
|
|
1098
|
+
switch node.Kind {
|
|
1099
|
+
case shimast.KindFunctionDeclaration:
|
|
1100
|
+
decl := node.AsFunctionDeclaration()
|
|
1101
|
+
return decl != nil && decl.AsteriskToken != nil
|
|
1102
|
+
case shimast.KindFunctionExpression:
|
|
1103
|
+
decl := node.AsFunctionExpression()
|
|
1104
|
+
return decl != nil && decl.AsteriskToken != nil
|
|
1105
|
+
case shimast.KindMethodDeclaration:
|
|
1106
|
+
decl := node.AsMethodDeclaration()
|
|
1107
|
+
return decl != nil && decl.AsteriskToken != nil
|
|
1108
|
+
}
|
|
1109
|
+
return false
|
|
1110
1110
|
}
|
|
1111
1111
|
|
|
1112
1112
|
func subtreeContainsYield(node *shimast.Node) bool {
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1113
|
+
if node == nil {
|
|
1114
|
+
return false
|
|
1115
|
+
}
|
|
1116
|
+
if node.Kind == shimast.KindYieldExpression {
|
|
1117
|
+
return true
|
|
1118
|
+
}
|
|
1119
|
+
if isFunctionLikeKind(node) && node.Parent != nil {
|
|
1120
|
+
return false
|
|
1121
|
+
}
|
|
1122
|
+
found := false
|
|
1123
|
+
node.ForEachChild(func(child *shimast.Node) bool {
|
|
1124
|
+
if subtreeContainsYield(child) {
|
|
1125
|
+
found = true
|
|
1126
|
+
return true
|
|
1127
|
+
}
|
|
1128
|
+
return false
|
|
1129
|
+
})
|
|
1130
|
+
return found
|
|
1131
1131
|
}
|
|
1132
1132
|
|
|
1133
1133
|
// vars-on-top: `var` declarations should appear at the top of their
|
|
@@ -1137,65 +1137,65 @@ type varsOnTop struct{}
|
|
|
1137
1137
|
func (varsOnTop) Name() string { return "vars-on-top" }
|
|
1138
1138
|
func (varsOnTop) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindVariableStatement} }
|
|
1139
1139
|
func (varsOnTop) Check(ctx *Context, node *shimast.Node) {
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1140
|
+
stmt := node.AsVariableStatement()
|
|
1141
|
+
if stmt == nil || stmt.DeclarationList == nil {
|
|
1142
|
+
return
|
|
1143
|
+
}
|
|
1144
|
+
if !shimast.IsVar(stmt.DeclarationList) {
|
|
1145
|
+
return
|
|
1146
|
+
}
|
|
1147
|
+
parent := node.Parent
|
|
1148
|
+
if parent == nil {
|
|
1149
|
+
return
|
|
1150
|
+
}
|
|
1151
|
+
switch parent.Kind {
|
|
1152
|
+
case shimast.KindSourceFile, shimast.KindModuleBlock:
|
|
1153
|
+
case shimast.KindBlock:
|
|
1154
|
+
grand := parent.Parent
|
|
1155
|
+
if grand == nil || !isFunctionLikeKind(grand) {
|
|
1156
|
+
ctx.Report(node, "All 'var' declarations must be at the top of the function scope.")
|
|
1157
|
+
return
|
|
1158
|
+
}
|
|
1159
|
+
default:
|
|
1160
|
+
ctx.Report(node, "All 'var' declarations must be at the top of the function scope.")
|
|
1161
|
+
return
|
|
1162
|
+
}
|
|
1163
|
+
// Same-block: must be the first non-trivial statement.
|
|
1164
|
+
siblings := parentStatements(parent)
|
|
1165
|
+
for _, sib := range siblings {
|
|
1166
|
+
if sib == node {
|
|
1167
|
+
return
|
|
1168
|
+
}
|
|
1169
|
+
if sib.Kind == shimast.KindVariableStatement {
|
|
1170
|
+
continue
|
|
1171
|
+
}
|
|
1172
|
+
ctx.Report(node, "All 'var' declarations must be at the top of the function scope.")
|
|
1173
|
+
return
|
|
1174
|
+
}
|
|
1175
1175
|
}
|
|
1176
1176
|
|
|
1177
1177
|
func parentStatements(parent *shimast.Node) []*shimast.Node {
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1178
|
+
if parent == nil {
|
|
1179
|
+
return nil
|
|
1180
|
+
}
|
|
1181
|
+
switch parent.Kind {
|
|
1182
|
+
case shimast.KindBlock:
|
|
1183
|
+
block := parent.AsBlock()
|
|
1184
|
+
if block != nil && block.Statements != nil {
|
|
1185
|
+
return block.Statements.Nodes
|
|
1186
|
+
}
|
|
1187
|
+
case shimast.KindSourceFile:
|
|
1188
|
+
file := parent.AsSourceFile()
|
|
1189
|
+
if file != nil && file.Statements != nil {
|
|
1190
|
+
return file.Statements.Nodes
|
|
1191
|
+
}
|
|
1192
|
+
case shimast.KindModuleBlock:
|
|
1193
|
+
mb := parent.AsModuleBlock()
|
|
1194
|
+
if mb != nil && mb.Statements != nil {
|
|
1195
|
+
return mb.Statements.Nodes
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
return nil
|
|
1199
1199
|
}
|
|
1200
1200
|
|
|
1201
1201
|
// yoda: `if (1 === x)` — ESLint flags literals on the left of a
|
|
@@ -1205,54 +1205,54 @@ type yoda struct{}
|
|
|
1205
1205
|
func (yoda) Name() string { return "yoda" }
|
|
1206
1206
|
func (yoda) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindBinaryExpression} }
|
|
1207
1207
|
func (yoda) Check(ctx *Context, node *shimast.Node) {
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1208
|
+
expr := node.AsBinaryExpression()
|
|
1209
|
+
if expr == nil || expr.OperatorToken == nil {
|
|
1210
|
+
return
|
|
1211
|
+
}
|
|
1212
|
+
if !isComparisonOperator(expr.OperatorToken.Kind) {
|
|
1213
|
+
return
|
|
1214
|
+
}
|
|
1215
|
+
if isLiteralExpression(stripParens(expr.Left)) && !isLiteralExpression(stripParens(expr.Right)) {
|
|
1216
|
+
ctx.Report(node, "Expected literal to be on the right side of comparison.")
|
|
1217
|
+
}
|
|
1218
1218
|
}
|
|
1219
1219
|
|
|
1220
1220
|
func init() {
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1221
|
+
Register(noAlert{})
|
|
1222
|
+
Register(noBitwise{})
|
|
1223
|
+
Register(noCaller{})
|
|
1224
|
+
Register(noCaseDeclarations{})
|
|
1225
|
+
Register(noContinue{})
|
|
1226
|
+
Register(noDeleteVar{})
|
|
1227
|
+
Register(noEqNull{})
|
|
1228
|
+
Register(noExtraBind{})
|
|
1229
|
+
Register(noLabels{})
|
|
1230
|
+
Register(noLoneBlocks{})
|
|
1231
|
+
Register(noLonelyIf{})
|
|
1232
|
+
Register(noMultiAssign{})
|
|
1233
|
+
Register(noNegatedCondition{})
|
|
1234
|
+
Register(noNestedTernary{})
|
|
1235
|
+
Register(noNew{})
|
|
1236
|
+
Register(noNewFunc{})
|
|
1237
|
+
Register(noObjectConstructor{})
|
|
1238
|
+
Register(noOctalEscape{})
|
|
1239
|
+
Register(noPlusPlus{})
|
|
1240
|
+
Register(noRegexSpaces{})
|
|
1241
|
+
Register(noReturnAssign{})
|
|
1242
|
+
Register(noSequences{})
|
|
1243
|
+
Register(noShadowRestrictedNames{})
|
|
1244
|
+
Register(noUndefined{})
|
|
1245
|
+
Register(noUnneededTernary{})
|
|
1246
|
+
Register(noUnusedExpressions{})
|
|
1247
|
+
Register(noUselessCall{})
|
|
1248
|
+
Register(noUselessComputedKey{})
|
|
1249
|
+
Register(noUselessRename{})
|
|
1250
|
+
Register(objectShorthand{})
|
|
1251
|
+
Register(operatorAssignment{})
|
|
1252
|
+
Register(preferExponentiationOperator{})
|
|
1253
|
+
Register(preferSpread{})
|
|
1254
|
+
Register(preferTemplate{})
|
|
1255
|
+
Register(requireYield{})
|
|
1256
|
+
Register(varsOnTop{})
|
|
1257
|
+
Register(yoda{})
|
|
1258
1258
|
}
|