@ttsc/lint 0.12.4 → 0.13.1
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 +225 -135
- package/lib/index.js.map +1 -1
- package/lib/structures/ITtscLintPluginConfig.d.ts +10 -77
- package/lib/structures/TtscLintRuleOptions.d.ts +14 -0
- package/linthost/ast_helpers.go +68 -16
- package/linthost/compile.go +117 -119
- package/linthost/config.go +518 -707
- package/linthost/config_format.go +16 -4
- package/linthost/contrib_adapter.go +7 -0
- package/linthost/directives.go +44 -0
- package/linthost/engine.go +152 -44
- package/linthost/fix.go +24 -33
- package/linthost/flags_gen.go +33 -0
- package/linthost/format.go +96 -3
- package/linthost/host.go +144 -8
- package/linthost/print_dispatch.go +121 -23
- package/linthost/print_doc.go +19 -0
- package/linthost/print_engine.go +168 -4
- package/linthost/print_nodes_array.go +17 -7
- package/linthost/print_nodes_call.go +129 -20
- package/linthost/print_nodes_function.go +353 -0
- package/linthost/print_nodes_imports.go +46 -29
- package/linthost/print_nodes_list.go +86 -5
- package/linthost/print_nodes_object.go +56 -11
- package/linthost/rules_escape.go +20 -3
- package/linthost/rules_format_print_width.go +267 -15
- package/linthost/rules_gap.go +55 -3
- package/linthost/rules_logic.go +64 -5
- package/linthost/rules_problems.go +65 -21
- package/linthost/rules_promise.go +3 -0
- package/linthost/rules_suggestions.go +160 -5
- package/linthost/rules_var.go +7 -1
- package/package.json +3 -3
- package/src/index.ts +243 -168
- package/src/structures/ITtscLintPluginConfig.ts +10 -83
- package/src/structures/TtscLintRuleOptions.ts +15 -0
- package/linthost/eslint_runtime.go +0 -351
|
@@ -89,6 +89,20 @@ export interface ITtscLintPrintWidthRuleOptions {
|
|
|
89
89
|
* @default "lf"
|
|
90
90
|
*/
|
|
91
91
|
endOfLine?: "lf" | "crlf";
|
|
92
|
+
/**
|
|
93
|
+
* Trailing-comma policy the reflow honors when it breaks a list across
|
|
94
|
+
* lines. Mirrors prettier's `trailingComma` and must match the
|
|
95
|
+
* `format/trailing-comma` rule's `mode`; otherwise the two rules
|
|
96
|
+
* disagree on every cascade pass and oscillate against each other.
|
|
97
|
+
*
|
|
98
|
+
* When a `format` block is configured, `format.trailingComma` is mirrored
|
|
99
|
+
* into this option automatically. Set it directly only when overriding
|
|
100
|
+
* the print-width rule via a `rules` tuple — see the conflict-resolution
|
|
101
|
+
* notes in the README.
|
|
102
|
+
*
|
|
103
|
+
* @default "all"
|
|
104
|
+
*/
|
|
105
|
+
trailingComma?: "all" | "es5" | "none";
|
|
92
106
|
}
|
|
93
107
|
/** `format/jsdoc` rule options. */
|
|
94
108
|
export interface ITtscLintJsdocRuleOptions {
|
package/linthost/ast_helpers.go
CHANGED
|
@@ -280,31 +280,83 @@ func walkDescendants(node *shimast.Node, visit func(*shimast.Node)) {
|
|
|
280
280
|
})
|
|
281
281
|
}
|
|
282
282
|
|
|
283
|
-
//
|
|
284
|
-
//
|
|
285
|
-
//
|
|
286
|
-
//
|
|
287
|
-
//
|
|
288
|
-
|
|
283
|
+
// assignmentTargetNames collects the identifier names written by an
|
|
284
|
+
// assignment's left-hand side. A bare Identifier yields one name. A
|
|
285
|
+
// destructuring-assignment target — parsed as an ArrayLiteralExpression
|
|
286
|
+
// (`[a, b] = …`) or ObjectLiteralExpression (`({a} = …)`) rather than a
|
|
287
|
+
// binding pattern — is walked so every nested write position is counted:
|
|
288
|
+
// array elements, object property values, shorthand properties, defaults
|
|
289
|
+
// (`[a = 1]`, `{a = 1}`), nested patterns, and rest elements.
|
|
290
|
+
//
|
|
291
|
+
// Property names in `{key: target}` are read positions, not writes, so
|
|
292
|
+
// only the property value contributes; member-access targets (`obj.x`)
|
|
293
|
+
// declare no local binding and are skipped. Returns nil for other shapes.
|
|
294
|
+
func assignmentTargetNames(node *shimast.Node) []string {
|
|
289
295
|
if node == nil {
|
|
290
296
|
return nil
|
|
291
297
|
}
|
|
292
298
|
if name := identifierText(node); name != "" {
|
|
293
299
|
return []string{name}
|
|
294
300
|
}
|
|
295
|
-
if node.Kind != shimast.KindObjectBindingPattern && node.Kind != shimast.KindArrayBindingPattern {
|
|
296
|
-
return nil
|
|
297
|
-
}
|
|
298
301
|
var names []string
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
+
collectAssignmentTargetNames(node, &names)
|
|
303
|
+
return names
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// collectAssignmentTargetNames appends to `names` every identifier in a
|
|
307
|
+
// destructuring-assignment target. It descends only through write-target
|
|
308
|
+
// positions so reads (object property keys, computed-member expressions)
|
|
309
|
+
// never count as reassignments.
|
|
310
|
+
func collectAssignmentTargetNames(node *shimast.Node, names *[]string) {
|
|
311
|
+
if node == nil {
|
|
312
|
+
return
|
|
313
|
+
}
|
|
314
|
+
switch node.Kind {
|
|
315
|
+
case shimast.KindIdentifier:
|
|
316
|
+
if name := identifierText(node); name != "" {
|
|
317
|
+
*names = append(*names, name)
|
|
302
318
|
}
|
|
303
|
-
|
|
304
|
-
|
|
319
|
+
case shimast.KindParenthesizedExpression:
|
|
320
|
+
collectAssignmentTargetNames(stripParens(node), names)
|
|
321
|
+
case shimast.KindArrayLiteralExpression:
|
|
322
|
+
if arr := node.AsArrayLiteralExpression(); arr != nil && arr.Elements != nil {
|
|
323
|
+
for _, el := range arr.Elements.Nodes {
|
|
324
|
+
collectAssignmentTargetNames(el, names)
|
|
325
|
+
}
|
|
305
326
|
}
|
|
306
|
-
|
|
307
|
-
|
|
327
|
+
case shimast.KindObjectLiteralExpression:
|
|
328
|
+
if obj := node.AsObjectLiteralExpression(); obj != nil && obj.Properties != nil {
|
|
329
|
+
for _, prop := range obj.Properties.Nodes {
|
|
330
|
+
collectAssignmentTargetNames(prop, names)
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
case shimast.KindSpreadElement:
|
|
334
|
+
if spread := node.AsSpreadElement(); spread != nil {
|
|
335
|
+
collectAssignmentTargetNames(spread.Expression, names)
|
|
336
|
+
}
|
|
337
|
+
case shimast.KindSpreadAssignment:
|
|
338
|
+
if spread := node.AsSpreadAssignment(); spread != nil {
|
|
339
|
+
collectAssignmentTargetNames(spread.Expression, names)
|
|
340
|
+
}
|
|
341
|
+
case shimast.KindShorthandPropertyAssignment:
|
|
342
|
+
// `{a}` and `{a = 1}` — the property name is the write target; any
|
|
343
|
+
// ObjectAssignmentInitializer is a default value, not a target.
|
|
344
|
+
if short := node.AsShorthandPropertyAssignment(); short != nil {
|
|
345
|
+
collectAssignmentTargetNames(short.Name(), names)
|
|
346
|
+
}
|
|
347
|
+
case shimast.KindPropertyAssignment:
|
|
348
|
+
// `{key: target}` — only the value (initializer) is written to.
|
|
349
|
+
if assignment := node.AsPropertyAssignment(); assignment != nil {
|
|
350
|
+
collectAssignmentTargetNames(assignment.Initializer, names)
|
|
351
|
+
}
|
|
352
|
+
case shimast.KindBinaryExpression:
|
|
353
|
+
// A default inside a pattern (`[a = 1]`, `{key: a = 1}`) parses as an
|
|
354
|
+
// `=` BinaryExpression; only its left side is the write target.
|
|
355
|
+
if expr := node.AsBinaryExpression(); expr != nil &&
|
|
356
|
+
expr.OperatorToken != nil && expr.OperatorToken.Kind == shimast.KindEqualsToken {
|
|
357
|
+
collectAssignmentTargetNames(expr.Left, names)
|
|
358
|
+
}
|
|
359
|
+
}
|
|
308
360
|
}
|
|
309
361
|
|
|
310
362
|
// isLiteralLike reports whether `node` (after stripping parentheses) is a
|
package/linthost/compile.go
CHANGED
|
@@ -13,6 +13,7 @@ package linthost
|
|
|
13
13
|
|
|
14
14
|
import (
|
|
15
15
|
"context"
|
|
16
|
+
"encoding/json"
|
|
16
17
|
"errors"
|
|
17
18
|
"flag"
|
|
18
19
|
"fmt"
|
|
@@ -20,6 +21,7 @@ import (
|
|
|
20
21
|
"os"
|
|
21
22
|
"path/filepath"
|
|
22
23
|
"strings"
|
|
24
|
+
"time"
|
|
23
25
|
|
|
24
26
|
shimast "github.com/microsoft/typescript-go/shim/ast"
|
|
25
27
|
shimcompiler "github.com/microsoft/typescript-go/shim/compiler"
|
|
@@ -59,26 +61,42 @@ func RunTransform(args []string) int {
|
|
|
59
61
|
tsconfig := fs.String("tsconfig", "tsconfig.json", "tsconfig owning --file")
|
|
60
62
|
cwd := fs.String("cwd", "", "override the working directory")
|
|
61
63
|
pluginsJSON := fs.String("plugins-json", "", "ttsc plugin manifest JSON")
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
})); err != nil {
|
|
64
|
+
singleThreaded := fs.Bool("singleThreaded", false, "run TypeScript-Go single-threaded")
|
|
65
|
+
checkers := fs.Int("checkers", 0, "type-checker pool size (0 = TypeScript-Go default)")
|
|
66
|
+
tsgoArgsRaw := fs.String("tsgo-args", "", "JSON array of forwarded tsgo CLI flags")
|
|
67
|
+
_ = fs.Bool("diagnostics", false, "print @ttsc/lint diagnostics timing")
|
|
68
|
+
_ = fs.Bool("extendedDiagnostics", false, "print @ttsc/lint diagnostics timing")
|
|
69
|
+
if err := fs.Parse(filterKnownFlags(args, LintFlagAllowList)); err != nil {
|
|
69
70
|
return 2
|
|
70
71
|
}
|
|
71
72
|
if *file == "" {
|
|
72
73
|
fmt.Fprintln(os.Stderr, "@ttsc/lint transform: --file is required")
|
|
73
74
|
return 2
|
|
74
75
|
}
|
|
76
|
+
tsgoArgs, err := decodeTsgoArgs(*tsgoArgsRaw)
|
|
77
|
+
if err != nil {
|
|
78
|
+
fmt.Fprintln(os.Stderr, err)
|
|
79
|
+
return 2
|
|
80
|
+
}
|
|
75
81
|
resolvedCwd, err := resolveCwd(*cwd)
|
|
76
82
|
if err != nil {
|
|
77
83
|
fmt.Fprintln(os.Stderr, err)
|
|
78
84
|
return 2
|
|
79
85
|
}
|
|
86
|
+
rules, err := loadRules(*pluginsJSON, resolvedCwd, *tsconfig)
|
|
87
|
+
if err != nil {
|
|
88
|
+
fmt.Fprintln(os.Stderr, err)
|
|
89
|
+
return 2
|
|
90
|
+
}
|
|
91
|
+
engine := NewEngineWithResolver(rules)
|
|
92
|
+
engine.SetSerial(*singleThreaded)
|
|
93
|
+
|
|
80
94
|
prog, parseDiags, err := loadProgram(resolvedCwd, *tsconfig, loadProgramOptions{
|
|
81
|
-
forceEmit:
|
|
95
|
+
forceEmit: true,
|
|
96
|
+
needsRuleChecker: engine.NeedsTypeChecker(),
|
|
97
|
+
singleThreaded: *singleThreaded,
|
|
98
|
+
checkers: *checkers,
|
|
99
|
+
tsgoArgs: tsgoArgs,
|
|
82
100
|
})
|
|
83
101
|
if err != nil {
|
|
84
102
|
fmt.Fprintf(os.Stderr, "@ttsc/lint: %v\n", err)
|
|
@@ -90,21 +108,12 @@ func RunTransform(args []string) int {
|
|
|
90
108
|
}
|
|
91
109
|
defer prog.close()
|
|
92
110
|
|
|
93
|
-
|
|
111
|
+
astDiags, lintDiags, err := collectDiagnostics(prog, engine)
|
|
94
112
|
if err != nil {
|
|
95
113
|
fmt.Fprintln(os.Stderr, err)
|
|
96
114
|
return 2
|
|
97
115
|
}
|
|
98
|
-
engine
|
|
99
|
-
|
|
100
|
-
astDiags, lintDiags, externalRan, err := collectDiagnostics(prog, engine)
|
|
101
|
-
if err != nil {
|
|
102
|
-
fmt.Fprintln(os.Stderr, err)
|
|
103
|
-
return 2
|
|
104
|
-
}
|
|
105
|
-
if !externalRan {
|
|
106
|
-
warnUnknownRules(os.Stderr, engine.UnknownRules())
|
|
107
|
-
}
|
|
116
|
+
warnUnknownRules(os.Stderr, engine.UnknownRules())
|
|
108
117
|
if errors := shimdw.FormatMixedDiagnostics(os.Stderr, astDiags, lintDiags, resolvedCwd); errors > 0 {
|
|
109
118
|
return 2
|
|
110
119
|
}
|
|
@@ -158,14 +167,18 @@ func RunTransform(args []string) int {
|
|
|
158
167
|
}
|
|
159
168
|
|
|
160
169
|
type subcommandOpts struct {
|
|
161
|
-
cwd
|
|
162
|
-
tsconfig
|
|
163
|
-
pluginsJSON
|
|
164
|
-
emit
|
|
165
|
-
noEmit
|
|
166
|
-
quiet
|
|
167
|
-
verbose
|
|
168
|
-
|
|
170
|
+
cwd string
|
|
171
|
+
tsconfig string
|
|
172
|
+
pluginsJSON string
|
|
173
|
+
emit bool
|
|
174
|
+
noEmit bool
|
|
175
|
+
quiet bool
|
|
176
|
+
verbose bool
|
|
177
|
+
diagnostics bool
|
|
178
|
+
outDir string
|
|
179
|
+
singleThreaded bool
|
|
180
|
+
checkers int
|
|
181
|
+
tsgoArgs []string
|
|
169
182
|
}
|
|
170
183
|
|
|
171
184
|
// parseSubcommandFlags parses the shared flag set used by the `check`,
|
|
@@ -181,46 +194,76 @@ func parseSubcommandFlags(name string, args []string) (*subcommandOpts, error) {
|
|
|
181
194
|
noEmit := fs.Bool("noEmit", false, "")
|
|
182
195
|
quiet := fs.Bool("quiet", false, "")
|
|
183
196
|
verbose := fs.Bool("verbose", false, "")
|
|
197
|
+
diagnostics := fs.Bool("diagnostics", false, "")
|
|
198
|
+
extendedDiagnostics := fs.Bool("extendedDiagnostics", false, "")
|
|
184
199
|
outDir := fs.String("outDir", "", "")
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
"outDir": true,
|
|
190
|
-
"plugins-json": true,
|
|
191
|
-
"quiet": false,
|
|
192
|
-
"tsconfig": true,
|
|
193
|
-
"verbose": false,
|
|
194
|
-
})); err != nil {
|
|
200
|
+
singleThreaded := fs.Bool("singleThreaded", false, "")
|
|
201
|
+
checkers := fs.Int("checkers", 0, "")
|
|
202
|
+
tsgoArgsRaw := fs.String("tsgo-args", "", "")
|
|
203
|
+
if err := fs.Parse(filterKnownFlags(args, LintFlagAllowList)); err != nil {
|
|
195
204
|
return nil, err
|
|
196
205
|
}
|
|
197
206
|
if *emit && *noEmit {
|
|
198
207
|
return nil, errors.New("@ttsc/lint: --emit and --noEmit are mutually exclusive")
|
|
199
208
|
}
|
|
209
|
+
tsgoArgs, err := decodeTsgoArgs(*tsgoArgsRaw)
|
|
210
|
+
if err != nil {
|
|
211
|
+
return nil, err
|
|
212
|
+
}
|
|
200
213
|
resolvedCwd, err := resolveCwd(*cwd)
|
|
201
214
|
if err != nil {
|
|
202
215
|
return nil, err
|
|
203
216
|
}
|
|
204
217
|
return &subcommandOpts{
|
|
205
|
-
cwd:
|
|
206
|
-
tsconfig:
|
|
207
|
-
pluginsJSON:
|
|
208
|
-
emit:
|
|
209
|
-
noEmit:
|
|
210
|
-
quiet:
|
|
211
|
-
verbose:
|
|
212
|
-
|
|
218
|
+
cwd: resolvedCwd,
|
|
219
|
+
tsconfig: *tsconfig,
|
|
220
|
+
pluginsJSON: *pluginsJSON,
|
|
221
|
+
emit: *emit,
|
|
222
|
+
noEmit: *noEmit,
|
|
223
|
+
quiet: *quiet,
|
|
224
|
+
verbose: *verbose,
|
|
225
|
+
diagnostics: *diagnostics || *extendedDiagnostics,
|
|
226
|
+
outDir: *outDir,
|
|
227
|
+
singleThreaded: *singleThreaded,
|
|
228
|
+
checkers: *checkers,
|
|
229
|
+
tsgoArgs: tsgoArgs,
|
|
213
230
|
}, nil
|
|
214
231
|
}
|
|
215
232
|
|
|
233
|
+
// decodeTsgoArgs decodes the JSON-array value of the `--tsgo-args` flag — the
|
|
234
|
+
// tsgo CLI flags the `ttsc` launcher forwarded — into a string slice. An empty
|
|
235
|
+
// flag yields a nil slice.
|
|
236
|
+
func decodeTsgoArgs(raw string) ([]string, error) {
|
|
237
|
+
if raw == "" {
|
|
238
|
+
return nil, nil
|
|
239
|
+
}
|
|
240
|
+
var args []string
|
|
241
|
+
if err := json.Unmarshal([]byte(raw), &args); err != nil {
|
|
242
|
+
return nil, fmt.Errorf("@ttsc/lint: invalid --tsgo-args: %w", err)
|
|
243
|
+
}
|
|
244
|
+
return args, nil
|
|
245
|
+
}
|
|
246
|
+
|
|
216
247
|
// runProject is the shared body of RunCheck and RunBuild. It loads the
|
|
217
248
|
// program, collects diagnostics, renders them, and optionally emits
|
|
218
249
|
// JavaScript output when the config allows it.
|
|
219
250
|
func runProject(opts *subcommandOpts) int {
|
|
251
|
+
rules, err := loadRules(opts.pluginsJSON, opts.cwd, opts.tsconfig)
|
|
252
|
+
if err != nil {
|
|
253
|
+
fmt.Fprintln(os.Stderr, err)
|
|
254
|
+
return 2
|
|
255
|
+
}
|
|
256
|
+
engine := NewEngineWithResolver(rules)
|
|
257
|
+
engine.SetSerial(opts.singleThreaded)
|
|
258
|
+
|
|
220
259
|
prog, parseDiags, err := loadProgram(opts.cwd, opts.tsconfig, loadProgramOptions{
|
|
221
|
-
forceEmit:
|
|
222
|
-
forceNoEmit:
|
|
223
|
-
outDir:
|
|
260
|
+
forceEmit: opts.emit,
|
|
261
|
+
forceNoEmit: opts.noEmit,
|
|
262
|
+
outDir: opts.outDir,
|
|
263
|
+
needsRuleChecker: engine.NeedsTypeChecker(),
|
|
264
|
+
singleThreaded: opts.singleThreaded,
|
|
265
|
+
checkers: opts.checkers,
|
|
266
|
+
tsgoArgs: opts.tsgoArgs,
|
|
224
267
|
})
|
|
225
268
|
if err != nil {
|
|
226
269
|
fmt.Fprintf(os.Stderr, "@ttsc/lint: %v\n", err)
|
|
@@ -232,21 +275,13 @@ func runProject(opts *subcommandOpts) int {
|
|
|
232
275
|
}
|
|
233
276
|
defer prog.close()
|
|
234
277
|
|
|
235
|
-
|
|
236
|
-
if err != nil {
|
|
237
|
-
fmt.Fprintln(os.Stderr, err)
|
|
238
|
-
return 2
|
|
239
|
-
}
|
|
240
|
-
engine := NewEngineWithResolver(rules)
|
|
241
|
-
|
|
242
|
-
astDiags, lintDiags, externalRan, err := collectDiagnostics(prog, engine)
|
|
278
|
+
astDiags, lintDiags, diagnosticsTiming, err := collectDiagnosticsTimed(prog, engine)
|
|
243
279
|
if err != nil {
|
|
244
280
|
fmt.Fprintln(os.Stderr, err)
|
|
245
281
|
return 2
|
|
246
282
|
}
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
}
|
|
283
|
+
printLintDiagnosticsTiming(os.Stdout, opts.diagnostics, diagnosticsTiming)
|
|
284
|
+
warnUnknownRules(os.Stderr, engine.UnknownRules())
|
|
250
285
|
if errCount := shimdw.FormatMixedDiagnostics(os.Stderr, astDiags, lintDiags, opts.cwd); errCount > 0 {
|
|
251
286
|
return 2
|
|
252
287
|
}
|
|
@@ -298,8 +333,9 @@ func loadRules(pluginsJSON, cwd, tsconfigPath string) (RuleResolver, error) {
|
|
|
298
333
|
}
|
|
299
334
|
|
|
300
335
|
// warnUnknownRules writes one warning line per name in `unknown` to `w`.
|
|
301
|
-
// Called after engine construction
|
|
302
|
-
//
|
|
336
|
+
// Called after engine construction so a config that names a rule the native
|
|
337
|
+
// engine does not implement surfaces a loud warning instead of silently
|
|
338
|
+
// linting nothing for that rule.
|
|
303
339
|
func warnUnknownRules(w io.Writer, unknown []string) {
|
|
304
340
|
for _, name := range unknown {
|
|
305
341
|
fmt.Fprintf(w, "@ttsc/lint: ignoring unknown rule %q\n", name)
|
|
@@ -344,10 +380,22 @@ func filterKnownFlags(args []string, known map[string]bool) []string {
|
|
|
344
380
|
// collectDiagnostics merges tsgo typecheck diagnostics with the lint
|
|
345
381
|
// engine's findings. The renderer takes the two slices and walks them in
|
|
346
382
|
// source order, so we don't need to interleave here.
|
|
347
|
-
func collectDiagnostics(prog *program, engine *Engine) ([]*shimast.Diagnostic, []*shimdw.LintDiagnostic,
|
|
383
|
+
func collectDiagnostics(prog *program, engine *Engine) ([]*shimast.Diagnostic, []*shimdw.LintDiagnostic, error) {
|
|
384
|
+
astDiags, lintDiags, _, err := collectDiagnosticsTimed(prog, engine)
|
|
385
|
+
return astDiags, lintDiags, err
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
type lintDiagnosticsTiming struct {
|
|
389
|
+
lint time.Duration
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
func collectDiagnosticsTimed(prog *program, engine *Engine) ([]*shimast.Diagnostic, []*shimdw.LintDiagnostic, lintDiagnosticsTiming, error) {
|
|
393
|
+
timing := lintDiagnosticsTiming{}
|
|
348
394
|
astDiags := prog.programDiagnostics()
|
|
349
395
|
files := prog.userSourceFiles()
|
|
396
|
+
lintStarted := time.Now()
|
|
350
397
|
findings := engine.Run(files, prog.checker)
|
|
398
|
+
timing.lint = time.Since(lintStarted)
|
|
351
399
|
nativeDiags := make([]*shimdw.LintDiagnostic, 0, len(findings))
|
|
352
400
|
for _, finding := range findings {
|
|
353
401
|
category := shimdw.LintCategoryError
|
|
@@ -363,68 +411,18 @@ func collectDiagnostics(prog *program, engine *Engine) ([]*shimast.Diagnostic, [
|
|
|
363
411
|
fmt.Sprintf("[%s] %s", finding.Rule, finding.Message),
|
|
364
412
|
))
|
|
365
413
|
}
|
|
366
|
-
|
|
367
|
-
return nil, nil, false, err
|
|
368
|
-
} else if ran {
|
|
369
|
-
return astDiags, mergeNativeAndExternalDiagnostics(nativeDiags, externalDiags), true, nil
|
|
370
|
-
}
|
|
371
|
-
return astDiags, nativeDiags, false, nil
|
|
414
|
+
return astDiags, nativeDiags, timing, nil
|
|
372
415
|
}
|
|
373
416
|
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
// the external runner's output is authoritative for rules it covered.
|
|
378
|
-
func mergeNativeAndExternalDiagnostics(nativeDiags, externalDiags []*shimdw.LintDiagnostic) []*shimdw.LintDiagnostic {
|
|
379
|
-
if len(nativeDiags) == 0 {
|
|
380
|
-
return externalDiags
|
|
381
|
-
}
|
|
382
|
-
if len(externalDiags) == 0 {
|
|
383
|
-
return nativeDiags
|
|
384
|
-
}
|
|
385
|
-
externalRules := make(map[string]struct{})
|
|
386
|
-
for _, diag := range externalDiags {
|
|
387
|
-
if rule := canonicalLintRule(lintDiagnosticRule(diag)); rule != "" {
|
|
388
|
-
externalRules[rule] = struct{}{}
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
out := make([]*shimdw.LintDiagnostic, 0, len(nativeDiags)+len(externalDiags))
|
|
392
|
-
for _, diag := range nativeDiags {
|
|
393
|
-
rule := canonicalLintRule(lintDiagnosticRule(diag))
|
|
394
|
-
if _, exists := externalRules[rule]; rule != "" && exists {
|
|
395
|
-
continue
|
|
396
|
-
}
|
|
397
|
-
out = append(out, diag)
|
|
417
|
+
func printLintDiagnosticsTiming(w io.Writer, enabled bool, timing lintDiagnosticsTiming) {
|
|
418
|
+
if !enabled {
|
|
419
|
+
return
|
|
398
420
|
}
|
|
399
|
-
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
// lintDiagnosticRule extracts the rule name from the `[rule-name]` prefix
|
|
403
|
-
// that `collectDiagnostics` injects into every lint diagnostic message.
|
|
404
|
-
// Returns "" when the message does not follow the expected format.
|
|
405
|
-
func lintDiagnosticRule(diag *shimdw.LintDiagnostic) string {
|
|
406
|
-
if diag == nil {
|
|
407
|
-
return ""
|
|
408
|
-
}
|
|
409
|
-
message := diag.Message()
|
|
410
|
-
if !strings.HasPrefix(message, "[") {
|
|
411
|
-
return ""
|
|
412
|
-
}
|
|
413
|
-
end := strings.Index(message, "]")
|
|
414
|
-
if end <= 1 {
|
|
415
|
-
return ""
|
|
416
|
-
}
|
|
417
|
-
return message[1:end]
|
|
421
|
+
fmt.Fprintf(w, "@ttsc/lint time: %s\n", formatTimingSeconds(timing.lint))
|
|
418
422
|
}
|
|
419
423
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
// all compare equal during deduplication.
|
|
423
|
-
func canonicalLintRule(rule string) string {
|
|
424
|
-
rule = strings.TrimSpace(rule)
|
|
425
|
-
rule = strings.TrimPrefix(rule, "@typescript-eslint/")
|
|
426
|
-
rule = strings.TrimPrefix(rule, "typescript-eslint/")
|
|
427
|
-
return rule
|
|
424
|
+
func formatTimingSeconds(duration time.Duration) string {
|
|
425
|
+
return fmt.Sprintf("%.3fs", duration.Seconds())
|
|
428
426
|
}
|
|
429
427
|
|
|
430
428
|
// RuleCode hashes a rule name into a stable, positive int32 so the
|