@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/engine.go
CHANGED
|
@@ -18,50 +18,50 @@
|
|
|
18
18
|
package main
|
|
19
19
|
|
|
20
20
|
import (
|
|
21
|
-
|
|
21
|
+
"sort"
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
shimast "github.com/microsoft/typescript-go/shim/ast"
|
|
24
|
+
shimchecker "github.com/microsoft/typescript-go/shim/checker"
|
|
25
|
+
shimscanner "github.com/microsoft/typescript-go/shim/scanner"
|
|
26
26
|
)
|
|
27
27
|
|
|
28
28
|
// Rule is the contract every lint rule satisfies.
|
|
29
29
|
type Rule interface {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
30
|
+
// Name is the identifier that users put in their `rules` map. Use
|
|
31
|
+
// the same names as `eslint` / `@typescript-eslint` where possible —
|
|
32
|
+
// this plugin is a host, not a renaming exercise.
|
|
33
|
+
Name() string
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
35
|
+
// Visits returns the AST kinds the rule cares about. The engine only
|
|
36
|
+
// dispatches to rules that registered for the visited node's kind,
|
|
37
|
+
// which keeps the per-node hot path linear in active rules rather
|
|
38
|
+
// than total rules.
|
|
39
|
+
Visits() []shimast.Kind
|
|
40
40
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
41
|
+
// Check is invoked once per relevant node. Use `ctx.Report` to emit
|
|
42
|
+
// findings.
|
|
43
|
+
Check(ctx *Context, node *shimast.Node)
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
// Context is the per-(file, rule) handle the engine passes to `Check`.
|
|
47
47
|
type Context struct {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
File *shimast.SourceFile
|
|
49
|
+
Checker *shimchecker.Checker
|
|
50
|
+
Severity Severity
|
|
51
51
|
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
rule Rule
|
|
53
|
+
collect func(*Finding)
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
// Finding is one rule-emitted diagnostic before it gets converted into a
|
|
57
57
|
// driver Diagnostic.
|
|
58
58
|
type Finding struct {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
59
|
+
Rule string
|
|
60
|
+
Severity Severity
|
|
61
|
+
File *shimast.SourceFile
|
|
62
|
+
Pos int
|
|
63
|
+
End int
|
|
64
|
+
Message string
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
// Report records a finding at the given node's source range. The pos is
|
|
@@ -69,50 +69,49 @@ type Finding struct {
|
|
|
69
69
|
// `path:line:col` banner points at the offending token, not the start of
|
|
70
70
|
// the surrounding indentation. A finding is silently dropped if the
|
|
71
71
|
// configured severity is `off` (defensive — the engine already filters
|
|
72
|
-
// by severity before calling Check, but
|
|
73
|
-
// the future).
|
|
72
|
+
// by severity before calling Check, but Report is the final gate).
|
|
74
73
|
func (c *Context) Report(node *shimast.Node, message string) {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
74
|
+
if c.Severity == SeverityOff || node == nil {
|
|
75
|
+
return
|
|
76
|
+
}
|
|
77
|
+
pos := node.Pos()
|
|
78
|
+
if c.File != nil {
|
|
79
|
+
pos = shimscanner.SkipTrivia(c.File.Text(), pos)
|
|
80
|
+
}
|
|
81
|
+
c.collect(&Finding{
|
|
82
|
+
Rule: c.rule.Name(),
|
|
83
|
+
Severity: c.Severity,
|
|
84
|
+
File: c.File,
|
|
85
|
+
Pos: pos,
|
|
86
|
+
End: node.End(),
|
|
87
|
+
Message: message,
|
|
88
|
+
})
|
|
90
89
|
}
|
|
91
90
|
|
|
92
91
|
// ReportRange records a finding at an explicit byte range inside the
|
|
93
92
|
// current file. Use this when the rule wants to highlight a sub-token of
|
|
94
93
|
// a node (e.g. an operator inside a BinaryExpression).
|
|
95
94
|
func (c *Context) ReportRange(pos, end int, message string) {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
95
|
+
if c.Severity == SeverityOff || c.File == nil {
|
|
96
|
+
return
|
|
97
|
+
}
|
|
98
|
+
if end <= pos {
|
|
99
|
+
end = pos + 1
|
|
100
|
+
}
|
|
101
|
+
c.collect(&Finding{
|
|
102
|
+
Rule: c.rule.Name(),
|
|
103
|
+
Severity: c.Severity,
|
|
104
|
+
File: c.File,
|
|
105
|
+
Pos: pos,
|
|
106
|
+
End: end,
|
|
107
|
+
Message: message,
|
|
108
|
+
})
|
|
110
109
|
}
|
|
111
110
|
|
|
112
111
|
// registry stores the package-global rule list keyed by name. Tests can
|
|
113
112
|
// also reach into it via `LookupRule`.
|
|
114
113
|
type registry struct {
|
|
115
|
-
|
|
114
|
+
rules map[string]Rule
|
|
116
115
|
}
|
|
117
116
|
|
|
118
117
|
var registered = ®istry{rules: map[string]Rule{}}
|
|
@@ -120,13 +119,13 @@ var registered = ®istry{rules: map[string]Rule{}}
|
|
|
120
119
|
// Register adds a rule to the global registry. Called from each rule's
|
|
121
120
|
// `init()`. Duplicate names are a programmer error and panic.
|
|
122
121
|
func Register(rule Rule) {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
122
|
+
if rule == nil {
|
|
123
|
+
panic("@ttsc/lint: Register called with nil rule")
|
|
124
|
+
}
|
|
125
|
+
if _, exists := registered.rules[rule.Name()]; exists {
|
|
126
|
+
panic("@ttsc/lint: rule " + rule.Name() + " registered twice")
|
|
127
|
+
}
|
|
128
|
+
registered.rules[rule.Name()] = rule
|
|
130
129
|
}
|
|
131
130
|
|
|
132
131
|
// LookupRule returns the registered rule by name, or nil if missing.
|
|
@@ -135,21 +134,21 @@ func LookupRule(name string) Rule { return registered.rules[name] }
|
|
|
135
134
|
// AllRuleNames returns the registry sorted alphabetically. Useful for
|
|
136
135
|
// `--list-rules` style introspection and stable test snapshots.
|
|
137
136
|
func AllRuleNames() []string {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
137
|
+
names := make([]string, 0, len(registered.rules))
|
|
138
|
+
for n := range registered.rules {
|
|
139
|
+
names = append(names, n)
|
|
140
|
+
}
|
|
141
|
+
sort.Strings(names)
|
|
142
|
+
return names
|
|
144
143
|
}
|
|
145
144
|
|
|
146
145
|
// Engine binds a rule configuration to a Program and walks the AST once
|
|
147
146
|
// per source file, dispatching each visited node to its interested rules.
|
|
148
147
|
type Engine struct {
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
148
|
+
config RuleConfig
|
|
149
|
+
rules map[shimast.Kind][]Rule
|
|
150
|
+
enabled map[string]Severity
|
|
151
|
+
unknown []string
|
|
153
152
|
}
|
|
154
153
|
|
|
155
154
|
// NewEngine returns an engine configured for `config`. Rules whose
|
|
@@ -157,27 +156,27 @@ type Engine struct {
|
|
|
157
156
|
// an unknown rule are recorded so the caller can surface them as a
|
|
158
157
|
// configuration warning rather than a silent typo.
|
|
159
158
|
func NewEngine(config RuleConfig) *Engine {
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
159
|
+
eng := &Engine{
|
|
160
|
+
config: config,
|
|
161
|
+
rules: make(map[shimast.Kind][]Rule),
|
|
162
|
+
enabled: make(map[string]Severity),
|
|
163
|
+
}
|
|
164
|
+
for name, sev := range config {
|
|
165
|
+
rule, ok := registered.rules[name]
|
|
166
|
+
if !ok {
|
|
167
|
+
eng.unknown = append(eng.unknown, name)
|
|
168
|
+
continue
|
|
169
|
+
}
|
|
170
|
+
if sev == SeverityOff {
|
|
171
|
+
continue
|
|
172
|
+
}
|
|
173
|
+
eng.enabled[name] = sev
|
|
174
|
+
for _, kind := range rule.Visits() {
|
|
175
|
+
eng.rules[kind] = append(eng.rules[kind], rule)
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
sort.Strings(eng.unknown)
|
|
179
|
+
return eng
|
|
181
180
|
}
|
|
182
181
|
|
|
183
182
|
// UnknownRules returns the names of rules that appeared in the config but
|
|
@@ -191,69 +190,69 @@ func (e *Engine) EnabledRules() map[string]Severity { return e.enabled }
|
|
|
191
190
|
// Run walks every non-declaration source file in the program and
|
|
192
191
|
// returns the collected findings.
|
|
193
192
|
func (e *Engine) Run(files []*shimast.SourceFile, checker *shimchecker.Checker) []*Finding {
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
193
|
+
var findings []*Finding
|
|
194
|
+
for _, file := range files {
|
|
195
|
+
if file == nil || file.IsDeclarationFile {
|
|
196
|
+
continue
|
|
197
|
+
}
|
|
198
|
+
findings = append(findings, e.runFile(file, checker)...)
|
|
199
|
+
}
|
|
200
|
+
return findings
|
|
202
201
|
}
|
|
203
202
|
|
|
204
203
|
// runFile is the per-file driver. The visitor is allocated once per file
|
|
205
204
|
// to keep the per-node hot path branch-free; it visits children
|
|
206
205
|
// post-order so parents see their already-checked subtrees.
|
|
207
206
|
func (e *Engine) runFile(file *shimast.SourceFile, checker *shimchecker.Checker) []*Finding {
|
|
208
|
-
|
|
209
|
-
|
|
207
|
+
var collected []*Finding
|
|
208
|
+
collect := func(f *Finding) { collected = append(collected, f) }
|
|
210
209
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
210
|
+
var walk func(node *shimast.Node)
|
|
211
|
+
walk = func(node *shimast.Node) {
|
|
212
|
+
if node == nil {
|
|
213
|
+
return
|
|
214
|
+
}
|
|
215
|
+
if rules, ok := e.rules[node.Kind]; ok {
|
|
216
|
+
for _, rule := range rules {
|
|
217
|
+
ctx := &Context{
|
|
218
|
+
File: file,
|
|
219
|
+
Checker: checker,
|
|
220
|
+
Severity: e.enabled[rule.Name()],
|
|
221
|
+
rule: rule,
|
|
222
|
+
collect: collect,
|
|
223
|
+
}
|
|
224
|
+
rule.Check(ctx, node)
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
node.ForEachChild(func(child *shimast.Node) bool {
|
|
228
|
+
walk(child)
|
|
229
|
+
return false // visit every child
|
|
230
|
+
})
|
|
231
|
+
}
|
|
233
232
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
233
|
+
// SourceFile dispatches into its statement list directly; we walk
|
|
234
|
+
// statements explicitly so the file node itself can be inspected by
|
|
235
|
+
// rules (e.g., `ban-ts-comment` reads CommentDirectives off the
|
|
236
|
+
// SourceFile).
|
|
237
|
+
if rules, ok := e.rules[shimast.KindSourceFile]; ok {
|
|
238
|
+
for _, rule := range rules {
|
|
239
|
+
ctx := &Context{
|
|
240
|
+
File: file,
|
|
241
|
+
Checker: checker,
|
|
242
|
+
Severity: e.enabled[rule.Name()],
|
|
243
|
+
rule: rule,
|
|
244
|
+
collect: collect,
|
|
245
|
+
}
|
|
246
|
+
rule.Check(ctx, file.AsNode())
|
|
247
|
+
}
|
|
248
|
+
}
|
|
250
249
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
250
|
+
statements := file.Statements
|
|
251
|
+
if statements == nil {
|
|
252
|
+
return collected
|
|
253
|
+
}
|
|
254
|
+
for _, stmt := range statements.Nodes {
|
|
255
|
+
walk(stmt)
|
|
256
|
+
}
|
|
257
|
+
return collected
|
|
259
258
|
}
|