@ttsc/lint 0.7.2 → 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/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_ts_extra.go
CHANGED
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
package main
|
|
7
7
|
|
|
8
8
|
import (
|
|
9
|
-
|
|
9
|
+
"strings"
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
shimast "github.com/microsoft/typescript-go/shim/ast"
|
|
12
12
|
)
|
|
13
13
|
|
|
14
14
|
// no-confusing-non-null-assertion: `a! == b` reads ambiguously.
|
|
@@ -16,25 +16,25 @@ type noConfusingNonNullAssertion struct{}
|
|
|
16
16
|
|
|
17
17
|
func (noConfusingNonNullAssertion) Name() string { return "no-confusing-non-null-assertion" }
|
|
18
18
|
func (noConfusingNonNullAssertion) Visits() []shimast.Kind {
|
|
19
|
-
|
|
19
|
+
return []shimast.Kind{shimast.KindBinaryExpression}
|
|
20
20
|
}
|
|
21
21
|
func (noConfusingNonNullAssertion) Check(ctx *Context, node *shimast.Node) {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
22
|
+
expr := node.AsBinaryExpression()
|
|
23
|
+
if expr == nil || expr.OperatorToken == nil || expr.Left == nil {
|
|
24
|
+
return
|
|
25
|
+
}
|
|
26
|
+
switch expr.OperatorToken.Kind {
|
|
27
|
+
case shimast.KindEqualsEqualsToken,
|
|
28
|
+
shimast.KindEqualsEqualsEqualsToken,
|
|
29
|
+
shimast.KindExclamationEqualsToken,
|
|
30
|
+
shimast.KindExclamationEqualsEqualsToken,
|
|
31
|
+
shimast.KindEqualsToken:
|
|
32
|
+
default:
|
|
33
|
+
return
|
|
34
|
+
}
|
|
35
|
+
if expr.Left.Kind == shimast.KindNonNullExpression {
|
|
36
|
+
ctx.Report(node, "Confusing combination of non-null assertion and equality.")
|
|
37
|
+
}
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
// no-duplicate-enum-values: `enum E { A = 1, B = 1 }` — duplicate values
|
|
@@ -43,38 +43,38 @@ type noDuplicateEnumValues struct{}
|
|
|
43
43
|
|
|
44
44
|
func (noDuplicateEnumValues) Name() string { return "no-duplicate-enum-values" }
|
|
45
45
|
func (noDuplicateEnumValues) Visits() []shimast.Kind {
|
|
46
|
-
|
|
46
|
+
return []shimast.Kind{shimast.KindEnumDeclaration}
|
|
47
47
|
}
|
|
48
48
|
func (noDuplicateEnumValues) Check(ctx *Context, node *shimast.Node) {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
49
|
+
decl := node.AsEnumDeclaration()
|
|
50
|
+
if decl == nil || decl.Members == nil {
|
|
51
|
+
return
|
|
52
|
+
}
|
|
53
|
+
seen := map[string]bool{}
|
|
54
|
+
for _, member := range decl.Members.Nodes {
|
|
55
|
+
if member == nil {
|
|
56
|
+
continue
|
|
57
|
+
}
|
|
58
|
+
em := member.AsEnumMember()
|
|
59
|
+
if em == nil || em.Initializer == nil {
|
|
60
|
+
continue
|
|
61
|
+
}
|
|
62
|
+
init := em.Initializer
|
|
63
|
+
var key string
|
|
64
|
+
switch init.Kind {
|
|
65
|
+
case shimast.KindNumericLiteral, shimast.KindBigIntLiteral:
|
|
66
|
+
key = "n:" + numericLiteralText(init)
|
|
67
|
+
case shimast.KindStringLiteral, shimast.KindNoSubstitutionTemplateLiteral:
|
|
68
|
+
key = "s:" + stringLiteralText(init)
|
|
69
|
+
default:
|
|
70
|
+
continue
|
|
71
|
+
}
|
|
72
|
+
if seen[key] {
|
|
73
|
+
ctx.Report(member, "Duplicate enum member value.")
|
|
74
|
+
continue
|
|
75
|
+
}
|
|
76
|
+
seen[key] = true
|
|
77
|
+
}
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
// no-extra-non-null-assertion: `a!!` / `a!?.b` collapses two assertions.
|
|
@@ -82,16 +82,16 @@ type noExtraNonNullAssertion struct{}
|
|
|
82
82
|
|
|
83
83
|
func (noExtraNonNullAssertion) Name() string { return "no-extra-non-null-assertion" }
|
|
84
84
|
func (noExtraNonNullAssertion) Visits() []shimast.Kind {
|
|
85
|
-
|
|
85
|
+
return []shimast.Kind{shimast.KindNonNullExpression}
|
|
86
86
|
}
|
|
87
87
|
func (noExtraNonNullAssertion) Check(ctx *Context, node *shimast.Node) {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
88
|
+
parent := node.Parent
|
|
89
|
+
if parent == nil {
|
|
90
|
+
return
|
|
91
|
+
}
|
|
92
|
+
if parent.Kind == shimast.KindNonNullExpression {
|
|
93
|
+
ctx.Report(node, "Forbidden extra non-null assertion.")
|
|
94
|
+
}
|
|
95
95
|
}
|
|
96
96
|
|
|
97
97
|
// no-non-null-asserted-optional-chain: `foo?.bar!` — the chain produces
|
|
@@ -100,49 +100,49 @@ type noNonNullAssertedOptionalChain struct{}
|
|
|
100
100
|
|
|
101
101
|
func (noNonNullAssertedOptionalChain) Name() string { return "no-non-null-asserted-optional-chain" }
|
|
102
102
|
func (noNonNullAssertedOptionalChain) Visits() []shimast.Kind {
|
|
103
|
-
|
|
103
|
+
return []shimast.Kind{shimast.KindNonNullExpression}
|
|
104
104
|
}
|
|
105
105
|
func (noNonNullAssertedOptionalChain) Check(ctx *Context, node *shimast.Node) {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
106
|
+
inner := node.AsNonNullExpression()
|
|
107
|
+
if inner == nil || inner.Expression == nil {
|
|
108
|
+
return
|
|
109
|
+
}
|
|
110
|
+
if containsOptionalChain(inner.Expression) {
|
|
111
|
+
ctx.Report(node, "Optional chain expressions can return undefined; non-null assertion bypasses that check.")
|
|
112
|
+
}
|
|
113
113
|
}
|
|
114
114
|
|
|
115
115
|
func containsOptionalChain(node *shimast.Node) bool {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
116
|
+
if node == nil {
|
|
117
|
+
return false
|
|
118
|
+
}
|
|
119
|
+
switch node.Kind {
|
|
120
|
+
case shimast.KindPropertyAccessExpression:
|
|
121
|
+
access := node.AsPropertyAccessExpression()
|
|
122
|
+
if access != nil && access.QuestionDotToken != nil {
|
|
123
|
+
return true
|
|
124
|
+
}
|
|
125
|
+
if access != nil {
|
|
126
|
+
return containsOptionalChain(access.Expression)
|
|
127
|
+
}
|
|
128
|
+
case shimast.KindElementAccessExpression:
|
|
129
|
+
access := node.AsElementAccessExpression()
|
|
130
|
+
if access != nil && access.QuestionDotToken != nil {
|
|
131
|
+
return true
|
|
132
|
+
}
|
|
133
|
+
if access != nil {
|
|
134
|
+
return containsOptionalChain(access.Expression)
|
|
135
|
+
}
|
|
136
|
+
case shimast.KindCallExpression:
|
|
137
|
+
call := node.AsCallExpression()
|
|
138
|
+
if call != nil && call.QuestionDotToken != nil {
|
|
139
|
+
return true
|
|
140
|
+
}
|
|
141
|
+
if call != nil {
|
|
142
|
+
return containsOptionalChain(call.Expression)
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return false
|
|
146
146
|
}
|
|
147
147
|
|
|
148
148
|
// no-misused-new: declaring a `new` signature on a non-class interface
|
|
@@ -152,30 +152,30 @@ type noMisusedNew struct{}
|
|
|
152
152
|
|
|
153
153
|
func (noMisusedNew) Name() string { return "no-misused-new" }
|
|
154
154
|
func (noMisusedNew) Visits() []shimast.Kind {
|
|
155
|
-
|
|
155
|
+
return []shimast.Kind{shimast.KindInterfaceDeclaration, shimast.KindTypeAliasDeclaration}
|
|
156
156
|
}
|
|
157
157
|
func (noMisusedNew) Check(ctx *Context, node *shimast.Node) {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
158
|
+
if node.Kind != shimast.KindInterfaceDeclaration {
|
|
159
|
+
return
|
|
160
|
+
}
|
|
161
|
+
decl := node.AsInterfaceDeclaration()
|
|
162
|
+
if decl == nil || decl.Members == nil {
|
|
163
|
+
return
|
|
164
|
+
}
|
|
165
|
+
for _, member := range decl.Members.Nodes {
|
|
166
|
+
if member == nil {
|
|
167
|
+
continue
|
|
168
|
+
}
|
|
169
|
+
switch member.Kind {
|
|
170
|
+
case shimast.KindConstructor:
|
|
171
|
+
ctx.Report(member, "Interfaces cannot have constructors. Use a class instead.")
|
|
172
|
+
case shimast.KindMethodSignature:
|
|
173
|
+
ms := member.AsMethodSignatureDeclaration()
|
|
174
|
+
if ms != nil && identifierText(ms.Name()) == "constructor" {
|
|
175
|
+
ctx.Report(member, "Interfaces cannot have constructors. Use a class instead.")
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
179
|
}
|
|
180
180
|
|
|
181
181
|
// prefer-enum-initializers: every enum member should have an explicit
|
|
@@ -184,22 +184,22 @@ type preferEnumInitializers struct{}
|
|
|
184
184
|
|
|
185
185
|
func (preferEnumInitializers) Name() string { return "prefer-enum-initializers" }
|
|
186
186
|
func (preferEnumInitializers) Visits() []shimast.Kind {
|
|
187
|
-
|
|
187
|
+
return []shimast.Kind{shimast.KindEnumDeclaration}
|
|
188
188
|
}
|
|
189
189
|
func (preferEnumInitializers) Check(ctx *Context, node *shimast.Node) {
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
190
|
+
decl := node.AsEnumDeclaration()
|
|
191
|
+
if decl == nil || decl.Members == nil {
|
|
192
|
+
return
|
|
193
|
+
}
|
|
194
|
+
for _, member := range decl.Members.Nodes {
|
|
195
|
+
if member == nil {
|
|
196
|
+
continue
|
|
197
|
+
}
|
|
198
|
+
em := member.AsEnumMember()
|
|
199
|
+
if em != nil && em.Initializer == nil {
|
|
200
|
+
ctx.Report(member, "Enum member should have an explicit initializer.")
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
203
|
}
|
|
204
204
|
|
|
205
205
|
// prefer-for-of: `for (let i = 0; i < arr.length; i++) { use(arr[i]) }`
|
|
@@ -209,90 +209,90 @@ type preferForOf struct{}
|
|
|
209
209
|
func (preferForOf) Name() string { return "prefer-for-of" }
|
|
210
210
|
func (preferForOf) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindForStatement} }
|
|
211
211
|
func (preferForOf) Check(ctx *Context, node *shimast.Node) {
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
212
|
+
loop := node.AsForStatement()
|
|
213
|
+
if loop == nil || loop.Initializer == nil || loop.Condition == nil || loop.Incrementor == nil {
|
|
214
|
+
return
|
|
215
|
+
}
|
|
216
|
+
// Initializer: `let i = 0` (single declarator with name `i`).
|
|
217
|
+
init := loop.Initializer
|
|
218
|
+
if init.Kind != shimast.KindVariableDeclarationList {
|
|
219
|
+
return
|
|
220
|
+
}
|
|
221
|
+
list := init.AsVariableDeclarationList()
|
|
222
|
+
if list == nil || list.Declarations == nil || len(list.Declarations.Nodes) != 1 {
|
|
223
|
+
return
|
|
224
|
+
}
|
|
225
|
+
decl := list.Declarations.Nodes[0].AsVariableDeclaration()
|
|
226
|
+
if decl == nil {
|
|
227
|
+
return
|
|
228
|
+
}
|
|
229
|
+
counter := identifierText(decl.Name())
|
|
230
|
+
if counter == "" {
|
|
231
|
+
return
|
|
232
|
+
}
|
|
233
|
+
if numericLiteralText(decl.Initializer) != "0" {
|
|
234
|
+
return
|
|
235
|
+
}
|
|
236
|
+
// Condition: `i < <something>.length`.
|
|
237
|
+
cond := loop.Condition.AsBinaryExpression()
|
|
238
|
+
if cond == nil || cond.OperatorToken == nil {
|
|
239
|
+
return
|
|
240
|
+
}
|
|
241
|
+
if cond.OperatorToken.Kind != shimast.KindLessThanToken {
|
|
242
|
+
return
|
|
243
|
+
}
|
|
244
|
+
if identifierText(cond.Left) != counter {
|
|
245
|
+
return
|
|
246
|
+
}
|
|
247
|
+
if cond.Right == nil || cond.Right.Kind != shimast.KindPropertyAccessExpression {
|
|
248
|
+
return
|
|
249
|
+
}
|
|
250
|
+
rightAccess := cond.Right.AsPropertyAccessExpression()
|
|
251
|
+
if rightAccess == nil || identifierText(rightAccess.Name()) != "length" {
|
|
252
|
+
return
|
|
253
|
+
}
|
|
254
|
+
// Incrementor: `i++` or `++i`.
|
|
255
|
+
if !isCounterIncrement(loop.Incrementor, counter) {
|
|
256
|
+
return
|
|
257
|
+
}
|
|
258
|
+
ctx.Report(node, "Prefer a 'for-of' loop instead of a 'for' loop with this simple iteration.")
|
|
259
259
|
}
|
|
260
260
|
|
|
261
261
|
func isCounterIncrement(node *shimast.Node, counter string) bool {
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
262
|
+
switch node.Kind {
|
|
263
|
+
case shimast.KindPostfixUnaryExpression:
|
|
264
|
+
post := node.AsPostfixUnaryExpression()
|
|
265
|
+
return post != nil && post.Operator == shimast.KindPlusPlusToken && identifierText(post.Operand) == counter
|
|
266
|
+
case shimast.KindPrefixUnaryExpression:
|
|
267
|
+
pre := node.AsPrefixUnaryExpression()
|
|
268
|
+
return pre != nil && pre.Operator == shimast.KindPlusPlusToken && identifierText(pre.Operand) == counter
|
|
269
|
+
}
|
|
270
|
+
return false
|
|
271
271
|
}
|
|
272
272
|
|
|
273
273
|
// prefer-function-type: a single-call-signature interface or type alias
|
|
274
274
|
// is more readably written as a function type.
|
|
275
275
|
//
|
|
276
|
-
//
|
|
276
|
+
// interface F { (x: number): string } -> type F = (x: number) => string
|
|
277
277
|
type preferFunctionType struct{}
|
|
278
278
|
|
|
279
279
|
func (preferFunctionType) Name() string { return "prefer-function-type" }
|
|
280
280
|
func (preferFunctionType) Visits() []shimast.Kind {
|
|
281
|
-
|
|
281
|
+
return []shimast.Kind{shimast.KindInterfaceDeclaration}
|
|
282
282
|
}
|
|
283
283
|
func (preferFunctionType) Check(ctx *Context, node *shimast.Node) {
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
284
|
+
decl := node.AsInterfaceDeclaration()
|
|
285
|
+
if decl == nil || decl.Members == nil || len(decl.Members.Nodes) != 1 {
|
|
286
|
+
return
|
|
287
|
+
}
|
|
288
|
+
if decl.HeritageClauses != nil && len(decl.HeritageClauses.Nodes) > 0 {
|
|
289
|
+
return
|
|
290
|
+
}
|
|
291
|
+
member := decl.Members.Nodes[0]
|
|
292
|
+
if member == nil || member.Kind != shimast.KindCallSignature {
|
|
293
|
+
return
|
|
294
|
+
}
|
|
295
|
+
ctx.Report(node, "Interface only has a call signature; use 'type' alias and function type instead.")
|
|
296
296
|
}
|
|
297
297
|
|
|
298
298
|
// prefer-namespace-keyword: `module Foo {}` (TS namespace via `module`
|
|
@@ -301,20 +301,20 @@ type preferNamespaceKeyword struct{}
|
|
|
301
301
|
|
|
302
302
|
func (preferNamespaceKeyword) Name() string { return "prefer-namespace-keyword" }
|
|
303
303
|
func (preferNamespaceKeyword) Visits() []shimast.Kind {
|
|
304
|
-
|
|
304
|
+
return []shimast.Kind{shimast.KindModuleDeclaration}
|
|
305
305
|
}
|
|
306
306
|
func (preferNamespaceKeyword) Check(ctx *Context, node *shimast.Node) {
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
307
|
+
decl := node.AsModuleDeclaration()
|
|
308
|
+
if decl == nil || decl.Name() == nil {
|
|
309
|
+
return
|
|
310
|
+
}
|
|
311
|
+
if decl.Name().Kind == shimast.KindStringLiteral {
|
|
312
|
+
return // ambient module: `declare module "fs" {}` is fine
|
|
313
|
+
}
|
|
314
|
+
if decl.Keyword != shimast.KindModuleKeyword {
|
|
315
|
+
return
|
|
316
|
+
}
|
|
317
|
+
ctx.Report(node, "Use 'namespace' instead of 'module' to declare custom TypeScript modules.")
|
|
318
318
|
}
|
|
319
319
|
|
|
320
320
|
// triple-slash-reference: `/// <reference path="..." />` directives.
|
|
@@ -324,15 +324,15 @@ type tripleSlashReference struct{}
|
|
|
324
324
|
func (tripleSlashReference) Name() string { return "triple-slash-reference" }
|
|
325
325
|
func (tripleSlashReference) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindSourceFile} }
|
|
326
326
|
func (tripleSlashReference) Check(ctx *Context, node *shimast.Node) {
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
327
|
+
if ctx.File == nil {
|
|
328
|
+
return
|
|
329
|
+
}
|
|
330
|
+
for _, ref := range ctx.File.ReferencedFiles {
|
|
331
|
+
ctx.ReportRange(ref.Pos(), ref.End(), "Do not use triple slash references for "+ref.FileName+".")
|
|
332
|
+
}
|
|
333
|
+
for _, ref := range ctx.File.TypeReferenceDirectives {
|
|
334
|
+
ctx.ReportRange(ref.Pos(), ref.End(), "Do not use triple slash references for "+ref.FileName+".")
|
|
335
|
+
}
|
|
336
336
|
}
|
|
337
337
|
|
|
338
338
|
// no-array-delete: `delete arr[0]` leaves a sparse hole. Use `splice`.
|
|
@@ -341,23 +341,23 @@ type noArrayDelete struct{}
|
|
|
341
341
|
func (noArrayDelete) Name() string { return "no-array-delete" }
|
|
342
342
|
func (noArrayDelete) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindDeleteExpression} }
|
|
343
343
|
func (noArrayDelete) Check(ctx *Context, node *shimast.Node) {
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
344
|
+
del := node.AsDeleteExpression()
|
|
345
|
+
if del == nil || del.Expression == nil {
|
|
346
|
+
return
|
|
347
|
+
}
|
|
348
|
+
if del.Expression.Kind != shimast.KindElementAccessExpression {
|
|
349
|
+
return
|
|
350
|
+
}
|
|
351
|
+
access := del.Expression.AsElementAccessExpression()
|
|
352
|
+
if access == nil || access.ArgumentExpression == nil {
|
|
353
|
+
return
|
|
354
|
+
}
|
|
355
|
+
// Numeric subscript ⇒ likely-array delete. (Object delete via
|
|
356
|
+
// numeric key is rare.)
|
|
357
|
+
switch access.ArgumentExpression.Kind {
|
|
358
|
+
case shimast.KindNumericLiteral, shimast.KindIdentifier:
|
|
359
|
+
ctx.Report(node, "Using delete with an array expression is unsafe.")
|
|
360
|
+
}
|
|
361
361
|
}
|
|
362
362
|
|
|
363
363
|
// consistent-type-imports: `import { Foo } from "./types"` where Foo is
|
|
@@ -373,84 +373,84 @@ type consistentTypeImports struct{}
|
|
|
373
373
|
|
|
374
374
|
func (consistentTypeImports) Name() string { return "consistent-type-imports" }
|
|
375
375
|
func (consistentTypeImports) Visits() []shimast.Kind {
|
|
376
|
-
|
|
376
|
+
return []shimast.Kind{shimast.KindImportDeclaration}
|
|
377
377
|
}
|
|
378
378
|
func (consistentTypeImports) Check(ctx *Context, node *shimast.Node) {
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
379
|
+
decl := node.AsImportDeclaration()
|
|
380
|
+
if decl == nil || decl.ImportClause == nil {
|
|
381
|
+
return
|
|
382
|
+
}
|
|
383
|
+
clause := decl.ImportClause.AsImportClause()
|
|
384
|
+
if clause == nil {
|
|
385
|
+
return
|
|
386
|
+
}
|
|
387
|
+
if clause.PhaseModifier == shimast.KindTypeKeyword {
|
|
388
|
+
return // already `import type`.
|
|
389
|
+
}
|
|
390
|
+
if clause.NamedBindings == nil || clause.NamedBindings.Kind != shimast.KindNamedImports {
|
|
391
|
+
return
|
|
392
|
+
}
|
|
393
|
+
named := clause.NamedBindings.AsNamedImports()
|
|
394
|
+
if named == nil || named.Elements == nil {
|
|
395
|
+
return
|
|
396
|
+
}
|
|
397
|
+
names := []string{}
|
|
398
|
+
for _, spec := range named.Elements.Nodes {
|
|
399
|
+
if spec == nil {
|
|
400
|
+
continue
|
|
401
|
+
}
|
|
402
|
+
s := spec.AsImportSpecifier()
|
|
403
|
+
if s == nil || s.IsTypeOnly {
|
|
404
|
+
continue
|
|
405
|
+
}
|
|
406
|
+
if name := identifierText(s.Name()); name != "" {
|
|
407
|
+
names = append(names, name)
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
if len(names) == 0 {
|
|
411
|
+
return
|
|
412
|
+
}
|
|
413
|
+
if !allUsesAreTypeOnly(ctx.File.AsNode(), names) {
|
|
414
|
+
return
|
|
415
|
+
}
|
|
416
|
+
ctx.Report(node, "All imports in the declaration are only used as types. Use `import type`.")
|
|
417
417
|
}
|
|
418
418
|
|
|
419
419
|
func allUsesAreTypeOnly(root *shimast.Node, names []string) bool {
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
420
|
+
want := map[string]bool{}
|
|
421
|
+
for _, n := range names {
|
|
422
|
+
want[n] = true
|
|
423
|
+
}
|
|
424
|
+
allOk := true
|
|
425
|
+
var visit func(n *shimast.Node, inType bool)
|
|
426
|
+
visit = func(n *shimast.Node, inType bool) {
|
|
427
|
+
if n == nil || !allOk {
|
|
428
|
+
return
|
|
429
|
+
}
|
|
430
|
+
typeContext := inType
|
|
431
|
+
switch n.Kind {
|
|
432
|
+
case shimast.KindTypeReference,
|
|
433
|
+
shimast.KindTypeAliasDeclaration,
|
|
434
|
+
shimast.KindInterfaceDeclaration,
|
|
435
|
+
shimast.KindTypeQuery,
|
|
436
|
+
shimast.KindTypeOperator,
|
|
437
|
+
shimast.KindTypeLiteral:
|
|
438
|
+
typeContext = true
|
|
439
|
+
case shimast.KindIdentifier:
|
|
440
|
+
if !typeContext && want[identifierText(n)] {
|
|
441
|
+
allOk = false
|
|
442
|
+
return
|
|
443
|
+
}
|
|
444
|
+
case shimast.KindImportDeclaration:
|
|
445
|
+
return // don't descend into other imports
|
|
446
|
+
}
|
|
447
|
+
n.ForEachChild(func(c *shimast.Node) bool {
|
|
448
|
+
visit(c, typeContext)
|
|
449
|
+
return false
|
|
450
|
+
})
|
|
451
|
+
}
|
|
452
|
+
visit(root, false)
|
|
453
|
+
return allOk
|
|
454
454
|
}
|
|
455
455
|
|
|
456
456
|
// no-empty-object-type: `interface Foo {}` / `type Foo = {}`. ESLint
|
|
@@ -461,13 +461,13 @@ type noEmptyObjectType struct{}
|
|
|
461
461
|
func (noEmptyObjectType) Name() string { return "no-empty-object-type" }
|
|
462
462
|
func (noEmptyObjectType) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindTypeLiteral} }
|
|
463
463
|
func (noEmptyObjectType) Check(ctx *Context, node *shimast.Node) {
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
464
|
+
lit := node.AsTypeLiteralNode()
|
|
465
|
+
if lit == nil || lit.Members == nil {
|
|
466
|
+
return
|
|
467
|
+
}
|
|
468
|
+
if len(lit.Members.Nodes) == 0 {
|
|
469
|
+
ctx.Report(node, "The `{}` type is generally not what's intended; consider `Record<string, unknown>` or `unknown`.")
|
|
470
|
+
}
|
|
471
471
|
}
|
|
472
472
|
|
|
473
473
|
// array-type: `Array<T>` vs `T[]`. ESLint default mode prefers `T[]`.
|
|
@@ -476,22 +476,22 @@ type arrayType struct{}
|
|
|
476
476
|
func (arrayType) Name() string { return "array-type" }
|
|
477
477
|
func (arrayType) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindTypeReference} }
|
|
478
478
|
func (arrayType) Check(ctx *Context, node *shimast.Node) {
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
479
|
+
ref := node.AsTypeReferenceNode()
|
|
480
|
+
if ref == nil || ref.TypeName == nil {
|
|
481
|
+
return
|
|
482
|
+
}
|
|
483
|
+
name := identifierText(ref.TypeName)
|
|
484
|
+
if name != "Array" && name != "ReadonlyArray" {
|
|
485
|
+
return
|
|
486
|
+
}
|
|
487
|
+
if ref.TypeArguments == nil || len(ref.TypeArguments.Nodes) != 1 {
|
|
488
|
+
return
|
|
489
|
+
}
|
|
490
|
+
if name == "Array" {
|
|
491
|
+
ctx.Report(node, "Use 'T[]' instead of 'Array<T>'.")
|
|
492
|
+
} else {
|
|
493
|
+
ctx.Report(node, "Use 'readonly T[]' instead of 'ReadonlyArray<T>'.")
|
|
494
|
+
}
|
|
495
495
|
}
|
|
496
496
|
|
|
497
497
|
// consistent-indexed-object-style: `{ [key: string]: T }` vs
|
|
@@ -500,18 +500,18 @@ type consistentIndexedObjectStyle struct{}
|
|
|
500
500
|
|
|
501
501
|
func (consistentIndexedObjectStyle) Name() string { return "consistent-indexed-object-style" }
|
|
502
502
|
func (consistentIndexedObjectStyle) Visits() []shimast.Kind {
|
|
503
|
-
|
|
503
|
+
return []shimast.Kind{shimast.KindTypeLiteral}
|
|
504
504
|
}
|
|
505
505
|
func (consistentIndexedObjectStyle) Check(ctx *Context, node *shimast.Node) {
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
506
|
+
lit := node.AsTypeLiteralNode()
|
|
507
|
+
if lit == nil || lit.Members == nil || len(lit.Members.Nodes) != 1 {
|
|
508
|
+
return
|
|
509
|
+
}
|
|
510
|
+
member := lit.Members.Nodes[0]
|
|
511
|
+
if member == nil || member.Kind != shimast.KindIndexSignature {
|
|
512
|
+
return
|
|
513
|
+
}
|
|
514
|
+
ctx.Report(node, "An index signature is preferred to be a Record type.")
|
|
515
515
|
}
|
|
516
516
|
|
|
517
517
|
// no-explicit-any-rest-parameter — keeping this distinct from
|
|
@@ -528,24 +528,24 @@ type banTslintComment struct{}
|
|
|
528
528
|
func (banTslintComment) Name() string { return "ban-tslint-comment" }
|
|
529
529
|
func (banTslintComment) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindSourceFile} }
|
|
530
530
|
func (banTslintComment) Check(ctx *Context, node *shimast.Node) {
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
531
|
+
if ctx.File == nil {
|
|
532
|
+
return
|
|
533
|
+
}
|
|
534
|
+
text := ctx.File.Text()
|
|
535
|
+
for i := 0; i < len(text)-2; i++ {
|
|
536
|
+
if text[i] == '/' && text[i+1] == '/' {
|
|
537
|
+
// Find end of line.
|
|
538
|
+
end := i
|
|
539
|
+
for end < len(text) && text[end] != '\n' {
|
|
540
|
+
end++
|
|
541
|
+
}
|
|
542
|
+
line := text[i:end]
|
|
543
|
+
if strings.Contains(line, "tslint:") {
|
|
544
|
+
ctx.ReportRange(i, end, "tslint comment detected.")
|
|
545
|
+
}
|
|
546
|
+
i = end
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
549
|
}
|
|
550
550
|
|
|
551
551
|
// adjacent-overload-signatures: function/method overloads must be
|
|
@@ -555,122 +555,122 @@ type adjacentOverloadSignatures struct{}
|
|
|
555
555
|
|
|
556
556
|
func (adjacentOverloadSignatures) Name() string { return "adjacent-overload-signatures" }
|
|
557
557
|
func (adjacentOverloadSignatures) Visits() []shimast.Kind {
|
|
558
|
-
|
|
558
|
+
return []shimast.Kind{shimast.KindInterfaceDeclaration, shimast.KindTypeLiteral, shimast.KindClassDeclaration, shimast.KindClassExpression, shimast.KindModuleBlock, shimast.KindSourceFile}
|
|
559
559
|
}
|
|
560
560
|
func (adjacentOverloadSignatures) Check(ctx *Context, node *shimast.Node) {
|
|
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
|
-
|
|
561
|
+
members := containerMembers(node)
|
|
562
|
+
if len(members) == 0 {
|
|
563
|
+
return
|
|
564
|
+
}
|
|
565
|
+
type entry struct {
|
|
566
|
+
index int
|
|
567
|
+
name string
|
|
568
|
+
kind shimast.Kind
|
|
569
|
+
}
|
|
570
|
+
seen := []entry{}
|
|
571
|
+
for i, m := range members {
|
|
572
|
+
name, kind, ok := overloadName(m)
|
|
573
|
+
if !ok {
|
|
574
|
+
continue
|
|
575
|
+
}
|
|
576
|
+
for _, prev := range seen {
|
|
577
|
+
if prev.name == name && prev.kind == kind && prev.index < i-1 {
|
|
578
|
+
// Check there isn't already a same-name entry adjacent.
|
|
579
|
+
if i > 0 {
|
|
580
|
+
prevName, prevKind, _ := overloadName(members[i-1])
|
|
581
|
+
if prevName == name && prevKind == kind {
|
|
582
|
+
break
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
ctx.Report(m, "All "+name+" signatures should be adjacent.")
|
|
586
|
+
break
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
seen = append(seen, entry{index: i, name: name, kind: kind})
|
|
590
|
+
}
|
|
591
591
|
}
|
|
592
592
|
|
|
593
593
|
func containerMembers(node *shimast.Node) []*shimast.Node {
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
594
|
+
switch node.Kind {
|
|
595
|
+
case shimast.KindInterfaceDeclaration:
|
|
596
|
+
decl := node.AsInterfaceDeclaration()
|
|
597
|
+
if decl != nil && decl.Members != nil {
|
|
598
|
+
return decl.Members.Nodes
|
|
599
|
+
}
|
|
600
|
+
case shimast.KindTypeLiteral:
|
|
601
|
+
lit := node.AsTypeLiteralNode()
|
|
602
|
+
if lit != nil && lit.Members != nil {
|
|
603
|
+
return lit.Members.Nodes
|
|
604
|
+
}
|
|
605
|
+
case shimast.KindClassDeclaration:
|
|
606
|
+
decl := node.AsClassDeclaration()
|
|
607
|
+
if decl != nil && decl.Members != nil {
|
|
608
|
+
return decl.Members.Nodes
|
|
609
|
+
}
|
|
610
|
+
case shimast.KindClassExpression:
|
|
611
|
+
decl := node.AsClassExpression()
|
|
612
|
+
if decl != nil && decl.Members != nil {
|
|
613
|
+
return decl.Members.Nodes
|
|
614
|
+
}
|
|
615
|
+
case shimast.KindModuleBlock:
|
|
616
|
+
mb := node.AsModuleBlock()
|
|
617
|
+
if mb != nil && mb.Statements != nil {
|
|
618
|
+
return mb.Statements.Nodes
|
|
619
|
+
}
|
|
620
|
+
case shimast.KindSourceFile:
|
|
621
|
+
f := node.AsSourceFile()
|
|
622
|
+
if f != nil && f.Statements != nil {
|
|
623
|
+
return f.Statements.Nodes
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
return nil
|
|
627
627
|
}
|
|
628
628
|
|
|
629
629
|
func overloadName(m *shimast.Node) (string, shimast.Kind, bool) {
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
630
|
+
if m == nil {
|
|
631
|
+
return "", 0, false
|
|
632
|
+
}
|
|
633
|
+
switch m.Kind {
|
|
634
|
+
case shimast.KindMethodSignature:
|
|
635
|
+
ms := m.AsMethodSignatureDeclaration()
|
|
636
|
+
if ms != nil {
|
|
637
|
+
return identifierText(ms.Name()), m.Kind, true
|
|
638
|
+
}
|
|
639
|
+
case shimast.KindMethodDeclaration:
|
|
640
|
+
md := m.AsMethodDeclaration()
|
|
641
|
+
if md != nil {
|
|
642
|
+
return identifierText(md.Name()), m.Kind, true
|
|
643
|
+
}
|
|
644
|
+
case shimast.KindFunctionDeclaration:
|
|
645
|
+
fd := m.AsFunctionDeclaration()
|
|
646
|
+
if fd != nil {
|
|
647
|
+
return identifierText(fd.Name()), m.Kind, true
|
|
648
|
+
}
|
|
649
|
+
case shimast.KindCallSignature, shimast.KindConstructSignature:
|
|
650
|
+
return "(" + m.Kind.String() + ")", m.Kind, true
|
|
651
|
+
}
|
|
652
|
+
return "", 0, false
|
|
653
653
|
}
|
|
654
654
|
|
|
655
655
|
// no-this-alias-helper: shared helpers for ts rules above.
|
|
656
656
|
var _ = struct{}{}
|
|
657
657
|
|
|
658
658
|
func init() {
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
659
|
+
Register(noConfusingNonNullAssertion{})
|
|
660
|
+
Register(noDuplicateEnumValues{})
|
|
661
|
+
Register(noExtraNonNullAssertion{})
|
|
662
|
+
Register(noNonNullAssertedOptionalChain{})
|
|
663
|
+
Register(noMisusedNew{})
|
|
664
|
+
Register(preferEnumInitializers{})
|
|
665
|
+
Register(preferForOf{})
|
|
666
|
+
Register(preferFunctionType{})
|
|
667
|
+
Register(preferNamespaceKeyword{})
|
|
668
|
+
Register(tripleSlashReference{})
|
|
669
|
+
Register(noArrayDelete{})
|
|
670
|
+
Register(consistentTypeImports{})
|
|
671
|
+
Register(noEmptyObjectType{})
|
|
672
|
+
Register(arrayType{})
|
|
673
|
+
Register(consistentIndexedObjectStyle{})
|
|
674
|
+
Register(banTslintComment{})
|
|
675
|
+
Register(adjacentOverloadSignatures{})
|
|
676
676
|
}
|