@ttsc/lint 0.7.1 → 0.7.3
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 +62 -24
- 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
package/plugin/rules_problems.go
CHANGED
|
@@ -8,9 +8,9 @@
|
|
|
8
8
|
package main
|
|
9
9
|
|
|
10
10
|
import (
|
|
11
|
-
|
|
11
|
+
"strings"
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
shimast "github.com/microsoft/typescript-go/shim/ast"
|
|
14
14
|
)
|
|
15
15
|
|
|
16
16
|
// no-dupe-else-if: `if (a) {} else if (a) {}` — the second branch is
|
|
@@ -20,33 +20,33 @@ type noDupeElseIf struct{}
|
|
|
20
20
|
func (noDupeElseIf) Name() string { return "no-dupe-else-if" }
|
|
21
21
|
func (noDupeElseIf) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindIfStatement} }
|
|
22
22
|
func (noDupeElseIf) Check(ctx *Context, node *shimast.Node) {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
23
|
+
// Only fire on the *outermost* if; the recursion below scans the
|
|
24
|
+
// chain once.
|
|
25
|
+
if parent := node.Parent; parent != nil {
|
|
26
|
+
if parent.Kind == shimast.KindIfStatement {
|
|
27
|
+
outer := parent.AsIfStatement()
|
|
28
|
+
if outer != nil && outer.ElseStatement == node {
|
|
29
|
+
return
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
seen := map[string]bool{}
|
|
34
|
+
cur := node
|
|
35
|
+
for cur != nil && cur.Kind == shimast.KindIfStatement {
|
|
36
|
+
stmt := cur.AsIfStatement()
|
|
37
|
+
if stmt == nil || stmt.Expression == nil {
|
|
38
|
+
break
|
|
39
|
+
}
|
|
40
|
+
key := nodeText(ctx.File, stmt.Expression)
|
|
41
|
+
if key != "" {
|
|
42
|
+
if seen[key] {
|
|
43
|
+
ctx.Report(stmt.Expression, "This branch can never execute. Its condition is a duplicate of an earlier branch.")
|
|
44
|
+
} else {
|
|
45
|
+
seen[key] = true
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
cur = stmt.ElseStatement
|
|
49
|
+
}
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
// no-ex-assign: `try { } catch (e) { e = 1; }` — reassigning the catch
|
|
@@ -56,44 +56,44 @@ type noExAssign struct{}
|
|
|
56
56
|
func (noExAssign) Name() string { return "no-ex-assign" }
|
|
57
57
|
func (noExAssign) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindCatchClause} }
|
|
58
58
|
func (noExAssign) Check(ctx *Context, node *shimast.Node) {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
59
|
+
clause := node.AsCatchClause()
|
|
60
|
+
if clause == nil || clause.VariableDeclaration == nil || clause.Block == nil {
|
|
61
|
+
return
|
|
62
|
+
}
|
|
63
|
+
binding := clause.VariableDeclaration.AsVariableDeclaration()
|
|
64
|
+
if binding == nil {
|
|
65
|
+
return
|
|
66
|
+
}
|
|
67
|
+
name := identifierText(binding.Name())
|
|
68
|
+
if name == "" {
|
|
69
|
+
return
|
|
70
|
+
}
|
|
71
|
+
walkAssignments(clause.Block, name, func(target *shimast.Node) {
|
|
72
|
+
ctx.Report(target, "Do not assign to the exception parameter.")
|
|
73
|
+
})
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
// walkAssignments invokes `report` on every `<name> = ...` shape inside
|
|
77
77
|
// `root`. Used by no-ex-assign and friends.
|
|
78
78
|
func walkAssignments(root *shimast.Node, name string, report func(*shimast.Node)) {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
79
|
+
if root == nil {
|
|
80
|
+
return
|
|
81
|
+
}
|
|
82
|
+
root.ForEachChild(func(child *shimast.Node) bool {
|
|
83
|
+
if child == nil {
|
|
84
|
+
return false
|
|
85
|
+
}
|
|
86
|
+
if child.Kind == shimast.KindBinaryExpression {
|
|
87
|
+
expr := child.AsBinaryExpression()
|
|
88
|
+
if expr != nil && expr.OperatorToken != nil && isAssignmentOperator(expr.OperatorToken.Kind) {
|
|
89
|
+
if identifierText(expr.Left) == name {
|
|
90
|
+
report(expr.Left)
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
walkAssignments(child, name, report)
|
|
95
|
+
return false
|
|
96
|
+
})
|
|
97
97
|
}
|
|
98
98
|
|
|
99
99
|
// no-empty-character-class: `/[]/` matches nothing.
|
|
@@ -101,32 +101,32 @@ type noEmptyCharacterClass struct{}
|
|
|
101
101
|
|
|
102
102
|
func (noEmptyCharacterClass) Name() string { return "no-empty-character-class" }
|
|
103
103
|
func (noEmptyCharacterClass) Visits() []shimast.Kind {
|
|
104
|
-
|
|
104
|
+
return []shimast.Kind{shimast.KindRegularExpressionLiteral}
|
|
105
105
|
}
|
|
106
106
|
func (noEmptyCharacterClass) Check(ctx *Context, node *shimast.Node) {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
107
|
+
src := nodeText(ctx.File, node)
|
|
108
|
+
if hasEmptyCharClass(src) {
|
|
109
|
+
ctx.Report(node, "Empty class.")
|
|
110
|
+
}
|
|
111
111
|
}
|
|
112
112
|
|
|
113
113
|
func hasEmptyCharClass(src string) bool {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
114
|
+
// Walk the regex literal source manually, respecting escapes.
|
|
115
|
+
for i := 0; i < len(src); i++ {
|
|
116
|
+
switch src[i] {
|
|
117
|
+
case '\\':
|
|
118
|
+
i++ // skip escape
|
|
119
|
+
case '[':
|
|
120
|
+
j := i + 1
|
|
121
|
+
if j < len(src) && src[j] == '^' {
|
|
122
|
+
j++
|
|
123
|
+
}
|
|
124
|
+
if j < len(src) && src[j] == ']' {
|
|
125
|
+
return true
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return false
|
|
130
130
|
}
|
|
131
131
|
|
|
132
132
|
// no-misleading-character-class: `/[👍]/` — surrogate pairs in regex.
|
|
@@ -134,40 +134,40 @@ type noMisleadingCharacterClass struct{}
|
|
|
134
134
|
|
|
135
135
|
func (noMisleadingCharacterClass) Name() string { return "no-misleading-character-class" }
|
|
136
136
|
func (noMisleadingCharacterClass) Visits() []shimast.Kind {
|
|
137
|
-
|
|
137
|
+
return []shimast.Kind{shimast.KindRegularExpressionLiteral}
|
|
138
138
|
}
|
|
139
139
|
func (noMisleadingCharacterClass) Check(ctx *Context, node *shimast.Node) {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
140
|
+
src := nodeText(ctx.File, node)
|
|
141
|
+
if regexHasSurrogatePair(src) {
|
|
142
|
+
ctx.Report(node, "Unexpected surrogate pair in character class. Use the 'u' flag.")
|
|
143
|
+
}
|
|
144
144
|
}
|
|
145
145
|
|
|
146
146
|
func regexHasSurrogatePair(src string) bool {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
147
|
+
// Strip the trailing flags so we don't misread the `u` flag — it
|
|
148
|
+
// suppresses this rule.
|
|
149
|
+
end := strings.LastIndex(src, "/")
|
|
150
|
+
if end < 0 {
|
|
151
|
+
return false
|
|
152
|
+
}
|
|
153
|
+
flags := src[end+1:]
|
|
154
|
+
if strings.ContainsRune(flags, 'u') {
|
|
155
|
+
return false
|
|
156
|
+
}
|
|
157
|
+
body := src[:end]
|
|
158
|
+
in := false
|
|
159
|
+
for _, r := range body {
|
|
160
|
+
switch r {
|
|
161
|
+
case '[':
|
|
162
|
+
in = true
|
|
163
|
+
case ']':
|
|
164
|
+
in = false
|
|
165
|
+
}
|
|
166
|
+
if in && r >= 0x10000 {
|
|
167
|
+
return true
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return false
|
|
171
171
|
}
|
|
172
172
|
|
|
173
173
|
// no-loss-of-precision: `9007199254740993` — integer literal larger than
|
|
@@ -179,40 +179,40 @@ type noLossOfPrecision struct{}
|
|
|
179
179
|
func (noLossOfPrecision) Name() string { return "no-loss-of-precision" }
|
|
180
180
|
func (noLossOfPrecision) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindNumericLiteral} }
|
|
181
181
|
func (noLossOfPrecision) Check(ctx *Context, node *shimast.Node) {
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
182
|
+
source := strings.TrimSpace(nodeText(ctx.File, node))
|
|
183
|
+
if source == "" {
|
|
184
|
+
return
|
|
185
|
+
}
|
|
186
|
+
if numericLiteralLosesPrecision(source) {
|
|
187
|
+
ctx.Report(node, "This number literal will lose precision at runtime.")
|
|
188
|
+
}
|
|
189
189
|
}
|
|
190
190
|
|
|
191
191
|
func numericLiteralLosesPrecision(text string) bool {
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
192
|
+
// Strip underscore separators, exponents, decimal/hex/oct/binary
|
|
193
|
+
// markers — for the simple-base-10 integer case the round-trip
|
|
194
|
+
// parse → format check is sufficient.
|
|
195
|
+
clean := strings.ReplaceAll(text, "_", "")
|
|
196
|
+
if strings.ContainsAny(clean, "eE.xXoObB") {
|
|
197
|
+
return false
|
|
198
|
+
}
|
|
199
|
+
if len(clean) < 16 {
|
|
200
|
+
return false
|
|
201
|
+
}
|
|
202
|
+
// Trim leading zeros for comparison.
|
|
203
|
+
trimmed := strings.TrimLeft(clean, "0")
|
|
204
|
+
if trimmed == "" {
|
|
205
|
+
return false
|
|
206
|
+
}
|
|
207
|
+
// 2^53 = 9007199254740992; anything larger as an integer literal loses precision.
|
|
208
|
+
const maxSafe = "9007199254740992"
|
|
209
|
+
if len(trimmed) < len(maxSafe) {
|
|
210
|
+
return false
|
|
211
|
+
}
|
|
212
|
+
if len(trimmed) > len(maxSafe) {
|
|
213
|
+
return true
|
|
214
|
+
}
|
|
215
|
+
return trimmed > maxSafe
|
|
216
216
|
}
|
|
217
217
|
|
|
218
218
|
// no-class-assign: assigning to a class declaration's name.
|
|
@@ -221,17 +221,17 @@ type noClassAssign struct{}
|
|
|
221
221
|
func (noClassAssign) Name() string { return "no-class-assign" }
|
|
222
222
|
func (noClassAssign) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindClassDeclaration} }
|
|
223
223
|
func (noClassAssign) Check(ctx *Context, node *shimast.Node) {
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
224
|
+
decl := node.AsClassDeclaration()
|
|
225
|
+
if decl == nil || decl.Name() == nil {
|
|
226
|
+
return
|
|
227
|
+
}
|
|
228
|
+
name := identifierText(decl.Name())
|
|
229
|
+
if name == "" {
|
|
230
|
+
return
|
|
231
|
+
}
|
|
232
|
+
walkAssignments(ctx.File.AsNode(), name, func(target *shimast.Node) {
|
|
233
|
+
ctx.Report(target, "'"+name+"' is a class.")
|
|
234
|
+
})
|
|
235
235
|
}
|
|
236
236
|
|
|
237
237
|
// no-func-assign: same idea, but for function declarations.
|
|
@@ -240,17 +240,17 @@ type noFuncAssign struct{}
|
|
|
240
240
|
func (noFuncAssign) Name() string { return "no-func-assign" }
|
|
241
241
|
func (noFuncAssign) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindFunctionDeclaration} }
|
|
242
242
|
func (noFuncAssign) Check(ctx *Context, node *shimast.Node) {
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
243
|
+
decl := node.AsFunctionDeclaration()
|
|
244
|
+
if decl == nil || decl.Name() == nil {
|
|
245
|
+
return
|
|
246
|
+
}
|
|
247
|
+
name := identifierText(decl.Name())
|
|
248
|
+
if name == "" {
|
|
249
|
+
return
|
|
250
|
+
}
|
|
251
|
+
walkAssignments(ctx.File.AsNode(), name, func(target *shimast.Node) {
|
|
252
|
+
ctx.Report(target, "'"+name+"' is a function.")
|
|
253
|
+
})
|
|
254
254
|
}
|
|
255
255
|
|
|
256
256
|
// no-prototype-builtins: `obj.hasOwnProperty(x)` — should be
|
|
@@ -260,22 +260,22 @@ type noPrototypeBuiltins struct{}
|
|
|
260
260
|
func (noPrototypeBuiltins) Name() string { return "no-prototype-builtins" }
|
|
261
261
|
func (noPrototypeBuiltins) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindCallExpression} }
|
|
262
262
|
func (noPrototypeBuiltins) Check(ctx *Context, node *shimast.Node) {
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
263
|
+
call := node.AsCallExpression()
|
|
264
|
+
if call == nil || call.Expression == nil {
|
|
265
|
+
return
|
|
266
|
+
}
|
|
267
|
+
if call.Expression.Kind != shimast.KindPropertyAccessExpression {
|
|
268
|
+
return
|
|
269
|
+
}
|
|
270
|
+
access := call.Expression.AsPropertyAccessExpression()
|
|
271
|
+
if access == nil {
|
|
272
|
+
return
|
|
273
|
+
}
|
|
274
|
+
method := identifierText(access.Name())
|
|
275
|
+
switch method {
|
|
276
|
+
case "hasOwnProperty", "isPrototypeOf", "propertyIsEnumerable":
|
|
277
|
+
ctx.Report(node, "Do not access Object.prototype method '"+method+"' from target object.")
|
|
278
|
+
}
|
|
279
279
|
}
|
|
280
280
|
|
|
281
281
|
// no-async-promise-executor: `new Promise(async (resolve) => {...})`.
|
|
@@ -283,26 +283,26 @@ type noAsyncPromiseExecutor struct{}
|
|
|
283
283
|
|
|
284
284
|
func (noAsyncPromiseExecutor) Name() string { return "no-async-promise-executor" }
|
|
285
285
|
func (noAsyncPromiseExecutor) Visits() []shimast.Kind {
|
|
286
|
-
|
|
286
|
+
return []shimast.Kind{shimast.KindNewExpression}
|
|
287
287
|
}
|
|
288
288
|
func (noAsyncPromiseExecutor) Check(ctx *Context, node *shimast.Node) {
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
289
|
+
ne := node.AsNewExpression()
|
|
290
|
+
if ne == nil || identifierText(ne.Expression) != "Promise" {
|
|
291
|
+
return
|
|
292
|
+
}
|
|
293
|
+
if ne.Arguments == nil || len(ne.Arguments.Nodes) == 0 {
|
|
294
|
+
return
|
|
295
|
+
}
|
|
296
|
+
executor := ne.Arguments.Nodes[0]
|
|
297
|
+
if executor == nil {
|
|
298
|
+
return
|
|
299
|
+
}
|
|
300
|
+
if !isFunctionLikeKind(executor) {
|
|
301
|
+
return
|
|
302
|
+
}
|
|
303
|
+
if hasAsyncModifier(executor) {
|
|
304
|
+
ctx.Report(executor, "Promise executor functions should not be async.")
|
|
305
|
+
}
|
|
306
306
|
}
|
|
307
307
|
|
|
308
308
|
// no-promise-executor-return: `new Promise(() => 1)` — the value is
|
|
@@ -311,28 +311,28 @@ type noPromiseExecutorReturn struct{}
|
|
|
311
311
|
|
|
312
312
|
func (noPromiseExecutorReturn) Name() string { return "no-promise-executor-return" }
|
|
313
313
|
func (noPromiseExecutorReturn) Visits() []shimast.Kind {
|
|
314
|
-
|
|
314
|
+
return []shimast.Kind{shimast.KindNewExpression}
|
|
315
315
|
}
|
|
316
316
|
func (noPromiseExecutorReturn) Check(ctx *Context, node *shimast.Node) {
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
317
|
+
ne := node.AsNewExpression()
|
|
318
|
+
if ne == nil || identifierText(ne.Expression) != "Promise" {
|
|
319
|
+
return
|
|
320
|
+
}
|
|
321
|
+
if ne.Arguments == nil || len(ne.Arguments.Nodes) == 0 {
|
|
322
|
+
return
|
|
323
|
+
}
|
|
324
|
+
executor := ne.Arguments.Nodes[0]
|
|
325
|
+
if executor == nil || executor.Kind != shimast.KindArrowFunction {
|
|
326
|
+
return
|
|
327
|
+
}
|
|
328
|
+
arrow := executor.AsArrowFunction()
|
|
329
|
+
if arrow == nil || arrow.Body == nil {
|
|
330
|
+
return
|
|
331
|
+
}
|
|
332
|
+
// Concise arrow body returns the value implicitly.
|
|
333
|
+
if arrow.Body.Kind != shimast.KindBlock {
|
|
334
|
+
ctx.Report(arrow.Body, "Return values from promise executor functions cannot be read.")
|
|
335
|
+
}
|
|
336
336
|
}
|
|
337
337
|
|
|
338
338
|
// no-control-regex: `/\x00/` — control characters in regex are usually
|
|
@@ -342,56 +342,56 @@ type noControlRegex struct{}
|
|
|
342
342
|
|
|
343
343
|
func (noControlRegex) Name() string { return "no-control-regex" }
|
|
344
344
|
func (noControlRegex) Visits() []shimast.Kind {
|
|
345
|
-
|
|
345
|
+
return []shimast.Kind{shimast.KindRegularExpressionLiteral}
|
|
346
346
|
}
|
|
347
347
|
func (noControlRegex) Check(ctx *Context, node *shimast.Node) {
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
348
|
+
src := nodeText(ctx.File, node)
|
|
349
|
+
if regexContainsControl(src) {
|
|
350
|
+
ctx.Report(node, "Unexpected control character(s) in regular expression.")
|
|
351
|
+
}
|
|
352
352
|
}
|
|
353
353
|
|
|
354
354
|
func regexContainsControl(src string) bool {
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
355
|
+
for i := 0; i < len(src); i++ {
|
|
356
|
+
c := src[i]
|
|
357
|
+
if c == '\\' && i+1 < len(src) {
|
|
358
|
+
next := src[i+1]
|
|
359
|
+
if next == 'x' && i+3 < len(src) {
|
|
360
|
+
value := hexDigit(src[i+2])*16 + hexDigit(src[i+3])
|
|
361
|
+
if value >= 0 && value < 0x20 {
|
|
362
|
+
return true
|
|
363
|
+
}
|
|
364
|
+
i += 3
|
|
365
|
+
continue
|
|
366
|
+
}
|
|
367
|
+
if next == 'u' && i+5 < len(src) {
|
|
368
|
+
value := hexDigit(src[i+2])*4096 + hexDigit(src[i+3])*256 + hexDigit(src[i+4])*16 + hexDigit(src[i+5])
|
|
369
|
+
if value >= 0 && value < 0x20 {
|
|
370
|
+
return true
|
|
371
|
+
}
|
|
372
|
+
i += 5
|
|
373
|
+
continue
|
|
374
|
+
}
|
|
375
|
+
i++
|
|
376
|
+
continue
|
|
377
|
+
}
|
|
378
|
+
if c < 0x20 && c != '\t' && c != '\n' && c != '\r' {
|
|
379
|
+
return true
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
return false
|
|
383
383
|
}
|
|
384
384
|
|
|
385
385
|
func hexDigit(b byte) int {
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
386
|
+
switch {
|
|
387
|
+
case b >= '0' && b <= '9':
|
|
388
|
+
return int(b - '0')
|
|
389
|
+
case b >= 'a' && b <= 'f':
|
|
390
|
+
return int(b-'a') + 10
|
|
391
|
+
case b >= 'A' && b <= 'F':
|
|
392
|
+
return int(b-'A') + 10
|
|
393
|
+
}
|
|
394
|
+
return -1
|
|
395
395
|
}
|
|
396
396
|
|
|
397
397
|
// no-irregular-whitespace: zero-width spaces, NBSP, etc. The TS parser
|
|
@@ -401,30 +401,30 @@ type noIrregularWhitespace struct{}
|
|
|
401
401
|
func (noIrregularWhitespace) Name() string { return "no-irregular-whitespace" }
|
|
402
402
|
func (noIrregularWhitespace) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindSourceFile} }
|
|
403
403
|
func (noIrregularWhitespace) Check(ctx *Context, node *shimast.Node) {
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
404
|
+
if ctx.File == nil {
|
|
405
|
+
return
|
|
406
|
+
}
|
|
407
|
+
text := ctx.File.Text()
|
|
408
|
+
for i, r := range text {
|
|
409
|
+
if isIrregularWhitespace(r) {
|
|
410
|
+
ctx.ReportRange(i, i+len(string(r)), "Irregular whitespace not allowed.")
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
413
|
}
|
|
414
414
|
|
|
415
415
|
func isIrregularWhitespace(r rune) bool {
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
416
|
+
switch r {
|
|
417
|
+
case '\v', '\f',
|
|
418
|
+
0x00A0, 0x1680,
|
|
419
|
+
0x2000, 0x2001, 0x2002, 0x2003, 0x2004, 0x2005,
|
|
420
|
+
0x2006, 0x2007, 0x2008, 0x2009, 0x200A,
|
|
421
|
+
0x200B, 0x202F, 0x205F,
|
|
422
|
+
0x3000,
|
|
423
|
+
0x2028, 0x2029,
|
|
424
|
+
0xFEFF:
|
|
425
|
+
return true
|
|
426
|
+
}
|
|
427
|
+
return false
|
|
428
428
|
}
|
|
429
429
|
|
|
430
430
|
// no-fallthrough: `switch` cases that fall through to the next label
|
|
@@ -434,52 +434,52 @@ type noFallthrough struct{}
|
|
|
434
434
|
func (noFallthrough) Name() string { return "no-fallthrough" }
|
|
435
435
|
func (noFallthrough) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindSwitchStatement} }
|
|
436
436
|
func (noFallthrough) Check(ctx *Context, node *shimast.Node) {
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
437
|
+
sw := node.AsSwitchStatement()
|
|
438
|
+
if sw == nil || sw.CaseBlock == nil {
|
|
439
|
+
return
|
|
440
|
+
}
|
|
441
|
+
block := sw.CaseBlock.AsCaseBlock()
|
|
442
|
+
if block == nil || block.Clauses == nil {
|
|
443
|
+
return
|
|
444
|
+
}
|
|
445
|
+
clauses := block.Clauses.Nodes
|
|
446
|
+
for i := 0; i+1 < len(clauses); i++ {
|
|
447
|
+
clause := clauses[i].AsCaseOrDefaultClause()
|
|
448
|
+
if clause == nil || clause.Statements == nil {
|
|
449
|
+
continue
|
|
450
|
+
}
|
|
451
|
+
stmts := clause.Statements.Nodes
|
|
452
|
+
if len(stmts) == 0 {
|
|
453
|
+
continue // empty case is intentional, never a fallthrough.
|
|
454
|
+
}
|
|
455
|
+
if !isTerminating(stmts[len(stmts)-1]) {
|
|
456
|
+
ctx.Report(clauses[i+1], "Expected a 'break' statement before this case.")
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
459
|
}
|
|
460
460
|
|
|
461
461
|
func isTerminating(stmt *shimast.Node) bool {
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
462
|
+
if stmt == nil {
|
|
463
|
+
return false
|
|
464
|
+
}
|
|
465
|
+
switch stmt.Kind {
|
|
466
|
+
case shimast.KindBreakStatement,
|
|
467
|
+
shimast.KindContinueStatement,
|
|
468
|
+
shimast.KindReturnStatement,
|
|
469
|
+
shimast.KindThrowStatement:
|
|
470
|
+
return true
|
|
471
|
+
case shimast.KindBlock:
|
|
472
|
+
block := stmt.AsBlock()
|
|
473
|
+
if block == nil || block.Statements == nil {
|
|
474
|
+
return false
|
|
475
|
+
}
|
|
476
|
+
nodes := block.Statements.Nodes
|
|
477
|
+
if len(nodes) == 0 {
|
|
478
|
+
return false
|
|
479
|
+
}
|
|
480
|
+
return isTerminating(nodes[len(nodes)-1])
|
|
481
|
+
}
|
|
482
|
+
return false
|
|
483
483
|
}
|
|
484
484
|
|
|
485
485
|
// no-inner-declarations: `function foo() { if (x) { function bar() {} } }`
|
|
@@ -489,40 +489,40 @@ type noInnerDeclarations struct{}
|
|
|
489
489
|
|
|
490
490
|
func (noInnerDeclarations) Name() string { return "no-inner-declarations" }
|
|
491
491
|
func (noInnerDeclarations) Visits() []shimast.Kind {
|
|
492
|
-
|
|
492
|
+
return []shimast.Kind{shimast.KindFunctionDeclaration, shimast.KindVariableStatement}
|
|
493
493
|
}
|
|
494
494
|
func (noInnerDeclarations) Check(ctx *Context, node *shimast.Node) {
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
495
|
+
if node.Kind == shimast.KindVariableStatement {
|
|
496
|
+
stmt := node.AsVariableStatement()
|
|
497
|
+
if stmt == nil || stmt.DeclarationList == nil {
|
|
498
|
+
return
|
|
499
|
+
}
|
|
500
|
+
// Only `var` is hoisted oddly.
|
|
501
|
+
if !shimast.IsVar(stmt.DeclarationList) {
|
|
502
|
+
return
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
parent := node.Parent
|
|
506
|
+
if parent == nil {
|
|
507
|
+
return
|
|
508
|
+
}
|
|
509
|
+
switch parent.Kind {
|
|
510
|
+
case shimast.KindSourceFile, shimast.KindModuleBlock:
|
|
511
|
+
return
|
|
512
|
+
case shimast.KindBlock:
|
|
513
|
+
grand := parent.Parent
|
|
514
|
+
if grand == nil {
|
|
515
|
+
return
|
|
516
|
+
}
|
|
517
|
+
if isFunctionLikeKind(grand) {
|
|
518
|
+
return
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
what := "function"
|
|
522
|
+
if node.Kind == shimast.KindVariableStatement {
|
|
523
|
+
what = "variable"
|
|
524
|
+
}
|
|
525
|
+
ctx.Report(node, "Move "+what+" declaration to the function scope.")
|
|
526
526
|
}
|
|
527
527
|
|
|
528
528
|
// no-obj-calls: `Math()`, `JSON()` — these globals are objects, not
|
|
@@ -531,53 +531,53 @@ type noObjCalls struct{}
|
|
|
531
531
|
|
|
532
532
|
func (noObjCalls) Name() string { return "no-obj-calls" }
|
|
533
533
|
func (noObjCalls) Visits() []shimast.Kind {
|
|
534
|
-
|
|
534
|
+
return []shimast.Kind{shimast.KindCallExpression, shimast.KindNewExpression}
|
|
535
535
|
}
|
|
536
536
|
func (noObjCalls) Check(ctx *Context, node *shimast.Node) {
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
537
|
+
var callee *shimast.Node
|
|
538
|
+
if node.Kind == shimast.KindCallExpression {
|
|
539
|
+
callee = node.AsCallExpression().Expression
|
|
540
|
+
} else {
|
|
541
|
+
callee = node.AsNewExpression().Expression
|
|
542
|
+
}
|
|
543
|
+
switch identifierText(callee) {
|
|
544
|
+
case "Math", "JSON", "Reflect", "Atomics", "Intl":
|
|
545
|
+
ctx.Report(node, "'"+identifierText(callee)+"' is not a function.")
|
|
546
|
+
}
|
|
547
547
|
}
|
|
548
548
|
|
|
549
549
|
// hasAsyncModifier returns whether a function-like node carries the
|
|
550
550
|
// `async` keyword. Used by no-async-promise-executor.
|
|
551
551
|
func hasAsyncModifier(node *shimast.Node) bool {
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
552
|
+
if node == nil {
|
|
553
|
+
return false
|
|
554
|
+
}
|
|
555
|
+
mods := node.Modifiers()
|
|
556
|
+
if mods == nil {
|
|
557
|
+
return false
|
|
558
|
+
}
|
|
559
|
+
for _, m := range mods.Nodes {
|
|
560
|
+
if m != nil && m.Kind == shimast.KindAsyncKeyword {
|
|
561
|
+
return true
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
return false
|
|
565
565
|
}
|
|
566
566
|
|
|
567
567
|
func init() {
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
568
|
+
Register(noDupeElseIf{})
|
|
569
|
+
Register(noExAssign{})
|
|
570
|
+
Register(noEmptyCharacterClass{})
|
|
571
|
+
Register(noMisleadingCharacterClass{})
|
|
572
|
+
Register(noLossOfPrecision{})
|
|
573
|
+
Register(noClassAssign{})
|
|
574
|
+
Register(noFuncAssign{})
|
|
575
|
+
Register(noPrototypeBuiltins{})
|
|
576
|
+
Register(noAsyncPromiseExecutor{})
|
|
577
|
+
Register(noPromiseExecutorReturn{})
|
|
578
|
+
Register(noControlRegex{})
|
|
579
|
+
Register(noIrregularWhitespace{})
|
|
580
|
+
Register(noFallthrough{})
|
|
581
|
+
Register(noInnerDeclarations{})
|
|
582
|
+
Register(noObjCalls{})
|
|
583
583
|
}
|