@ttsc/lint 0.12.4 → 0.13.0
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 +227 -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 +91 -124
- 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 -43
- package/linthost/fix.go +24 -33
- package/linthost/flags_gen.go +31 -0
- package/linthost/format.go +96 -3
- package/linthost/host.go +104 -5
- 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 +1 -1
- package/package.json +3 -3
- package/src/index.ts +245 -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"
|
|
@@ -59,26 +60,40 @@ func RunTransform(args []string) int {
|
|
|
59
60
|
tsconfig := fs.String("tsconfig", "tsconfig.json", "tsconfig owning --file")
|
|
60
61
|
cwd := fs.String("cwd", "", "override the working directory")
|
|
61
62
|
pluginsJSON := fs.String("plugins-json", "", "ttsc plugin manifest JSON")
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
"plugins-json": true,
|
|
67
|
-
"tsconfig": true,
|
|
68
|
-
})); err != nil {
|
|
63
|
+
singleThreaded := fs.Bool("singleThreaded", false, "run TypeScript-Go single-threaded")
|
|
64
|
+
checkers := fs.Int("checkers", 0, "type-checker pool size (0 = TypeScript-Go default)")
|
|
65
|
+
tsgoArgsRaw := fs.String("tsgo-args", "", "JSON array of forwarded tsgo CLI flags")
|
|
66
|
+
if err := fs.Parse(filterKnownFlags(args, LintFlagAllowList)); err != nil {
|
|
69
67
|
return 2
|
|
70
68
|
}
|
|
71
69
|
if *file == "" {
|
|
72
70
|
fmt.Fprintln(os.Stderr, "@ttsc/lint transform: --file is required")
|
|
73
71
|
return 2
|
|
74
72
|
}
|
|
73
|
+
tsgoArgs, err := decodeTsgoArgs(*tsgoArgsRaw)
|
|
74
|
+
if err != nil {
|
|
75
|
+
fmt.Fprintln(os.Stderr, err)
|
|
76
|
+
return 2
|
|
77
|
+
}
|
|
75
78
|
resolvedCwd, err := resolveCwd(*cwd)
|
|
76
79
|
if err != nil {
|
|
77
80
|
fmt.Fprintln(os.Stderr, err)
|
|
78
81
|
return 2
|
|
79
82
|
}
|
|
83
|
+
rules, err := loadRules(*pluginsJSON, resolvedCwd, *tsconfig)
|
|
84
|
+
if err != nil {
|
|
85
|
+
fmt.Fprintln(os.Stderr, err)
|
|
86
|
+
return 2
|
|
87
|
+
}
|
|
88
|
+
engine := NewEngineWithResolver(rules)
|
|
89
|
+
engine.SetSerial(*singleThreaded)
|
|
90
|
+
|
|
80
91
|
prog, parseDiags, err := loadProgram(resolvedCwd, *tsconfig, loadProgramOptions{
|
|
81
|
-
forceEmit:
|
|
92
|
+
forceEmit: true,
|
|
93
|
+
needsRuleChecker: engine.NeedsTypeChecker(),
|
|
94
|
+
singleThreaded: *singleThreaded,
|
|
95
|
+
checkers: *checkers,
|
|
96
|
+
tsgoArgs: tsgoArgs,
|
|
82
97
|
})
|
|
83
98
|
if err != nil {
|
|
84
99
|
fmt.Fprintf(os.Stderr, "@ttsc/lint: %v\n", err)
|
|
@@ -90,21 +105,12 @@ func RunTransform(args []string) int {
|
|
|
90
105
|
}
|
|
91
106
|
defer prog.close()
|
|
92
107
|
|
|
93
|
-
|
|
94
|
-
if err != nil {
|
|
95
|
-
fmt.Fprintln(os.Stderr, err)
|
|
96
|
-
return 2
|
|
97
|
-
}
|
|
98
|
-
engine := NewEngineWithResolver(rules)
|
|
99
|
-
|
|
100
|
-
astDiags, lintDiags, externalRan, err := collectDiagnostics(prog, engine)
|
|
108
|
+
astDiags, lintDiags, err := collectDiagnostics(prog, engine)
|
|
101
109
|
if err != nil {
|
|
102
110
|
fmt.Fprintln(os.Stderr, err)
|
|
103
111
|
return 2
|
|
104
112
|
}
|
|
105
|
-
|
|
106
|
-
warnUnknownRules(os.Stderr, engine.UnknownRules())
|
|
107
|
-
}
|
|
113
|
+
warnUnknownRules(os.Stderr, engine.UnknownRules())
|
|
108
114
|
if errors := shimdw.FormatMixedDiagnostics(os.Stderr, astDiags, lintDiags, resolvedCwd); errors > 0 {
|
|
109
115
|
return 2
|
|
110
116
|
}
|
|
@@ -158,14 +164,17 @@ func RunTransform(args []string) int {
|
|
|
158
164
|
}
|
|
159
165
|
|
|
160
166
|
type subcommandOpts struct {
|
|
161
|
-
cwd
|
|
162
|
-
tsconfig
|
|
163
|
-
pluginsJSON
|
|
164
|
-
emit
|
|
165
|
-
noEmit
|
|
166
|
-
quiet
|
|
167
|
-
verbose
|
|
168
|
-
outDir
|
|
167
|
+
cwd string
|
|
168
|
+
tsconfig string
|
|
169
|
+
pluginsJSON string
|
|
170
|
+
emit bool
|
|
171
|
+
noEmit bool
|
|
172
|
+
quiet bool
|
|
173
|
+
verbose bool
|
|
174
|
+
outDir string
|
|
175
|
+
singleThreaded bool
|
|
176
|
+
checkers int
|
|
177
|
+
tsgoArgs []string
|
|
169
178
|
}
|
|
170
179
|
|
|
171
180
|
// parseSubcommandFlags parses the shared flag set used by the `check`,
|
|
@@ -182,45 +191,72 @@ func parseSubcommandFlags(name string, args []string) (*subcommandOpts, error) {
|
|
|
182
191
|
quiet := fs.Bool("quiet", false, "")
|
|
183
192
|
verbose := fs.Bool("verbose", false, "")
|
|
184
193
|
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 {
|
|
194
|
+
singleThreaded := fs.Bool("singleThreaded", false, "")
|
|
195
|
+
checkers := fs.Int("checkers", 0, "")
|
|
196
|
+
tsgoArgsRaw := fs.String("tsgo-args", "", "")
|
|
197
|
+
if err := fs.Parse(filterKnownFlags(args, LintFlagAllowList)); err != nil {
|
|
195
198
|
return nil, err
|
|
196
199
|
}
|
|
197
200
|
if *emit && *noEmit {
|
|
198
201
|
return nil, errors.New("@ttsc/lint: --emit and --noEmit are mutually exclusive")
|
|
199
202
|
}
|
|
203
|
+
tsgoArgs, err := decodeTsgoArgs(*tsgoArgsRaw)
|
|
204
|
+
if err != nil {
|
|
205
|
+
return nil, err
|
|
206
|
+
}
|
|
200
207
|
resolvedCwd, err := resolveCwd(*cwd)
|
|
201
208
|
if err != nil {
|
|
202
209
|
return nil, err
|
|
203
210
|
}
|
|
204
211
|
return &subcommandOpts{
|
|
205
|
-
cwd:
|
|
206
|
-
tsconfig:
|
|
207
|
-
pluginsJSON:
|
|
208
|
-
emit:
|
|
209
|
-
noEmit:
|
|
210
|
-
quiet:
|
|
211
|
-
verbose:
|
|
212
|
-
outDir:
|
|
212
|
+
cwd: resolvedCwd,
|
|
213
|
+
tsconfig: *tsconfig,
|
|
214
|
+
pluginsJSON: *pluginsJSON,
|
|
215
|
+
emit: *emit,
|
|
216
|
+
noEmit: *noEmit,
|
|
217
|
+
quiet: *quiet,
|
|
218
|
+
verbose: *verbose,
|
|
219
|
+
outDir: *outDir,
|
|
220
|
+
singleThreaded: *singleThreaded,
|
|
221
|
+
checkers: *checkers,
|
|
222
|
+
tsgoArgs: tsgoArgs,
|
|
213
223
|
}, nil
|
|
214
224
|
}
|
|
215
225
|
|
|
226
|
+
// decodeTsgoArgs decodes the JSON-array value of the `--tsgo-args` flag — the
|
|
227
|
+
// tsgo CLI flags the `ttsc` launcher forwarded — into a string slice. An empty
|
|
228
|
+
// flag yields a nil slice.
|
|
229
|
+
func decodeTsgoArgs(raw string) ([]string, error) {
|
|
230
|
+
if raw == "" {
|
|
231
|
+
return nil, nil
|
|
232
|
+
}
|
|
233
|
+
var args []string
|
|
234
|
+
if err := json.Unmarshal([]byte(raw), &args); err != nil {
|
|
235
|
+
return nil, fmt.Errorf("@ttsc/lint: invalid --tsgo-args: %w", err)
|
|
236
|
+
}
|
|
237
|
+
return args, nil
|
|
238
|
+
}
|
|
239
|
+
|
|
216
240
|
// runProject is the shared body of RunCheck and RunBuild. It loads the
|
|
217
241
|
// program, collects diagnostics, renders them, and optionally emits
|
|
218
242
|
// JavaScript output when the config allows it.
|
|
219
243
|
func runProject(opts *subcommandOpts) int {
|
|
244
|
+
rules, err := loadRules(opts.pluginsJSON, opts.cwd, opts.tsconfig)
|
|
245
|
+
if err != nil {
|
|
246
|
+
fmt.Fprintln(os.Stderr, err)
|
|
247
|
+
return 2
|
|
248
|
+
}
|
|
249
|
+
engine := NewEngineWithResolver(rules)
|
|
250
|
+
engine.SetSerial(opts.singleThreaded)
|
|
251
|
+
|
|
220
252
|
prog, parseDiags, err := loadProgram(opts.cwd, opts.tsconfig, loadProgramOptions{
|
|
221
|
-
forceEmit:
|
|
222
|
-
forceNoEmit:
|
|
223
|
-
outDir:
|
|
253
|
+
forceEmit: opts.emit,
|
|
254
|
+
forceNoEmit: opts.noEmit,
|
|
255
|
+
outDir: opts.outDir,
|
|
256
|
+
needsRuleChecker: engine.NeedsTypeChecker(),
|
|
257
|
+
singleThreaded: opts.singleThreaded,
|
|
258
|
+
checkers: opts.checkers,
|
|
259
|
+
tsgoArgs: opts.tsgoArgs,
|
|
224
260
|
})
|
|
225
261
|
if err != nil {
|
|
226
262
|
fmt.Fprintf(os.Stderr, "@ttsc/lint: %v\n", err)
|
|
@@ -232,21 +268,12 @@ func runProject(opts *subcommandOpts) int {
|
|
|
232
268
|
}
|
|
233
269
|
defer prog.close()
|
|
234
270
|
|
|
235
|
-
|
|
271
|
+
astDiags, lintDiags, err := collectDiagnostics(prog, engine)
|
|
236
272
|
if err != nil {
|
|
237
273
|
fmt.Fprintln(os.Stderr, err)
|
|
238
274
|
return 2
|
|
239
275
|
}
|
|
240
|
-
engine
|
|
241
|
-
|
|
242
|
-
astDiags, lintDiags, externalRan, err := collectDiagnostics(prog, engine)
|
|
243
|
-
if err != nil {
|
|
244
|
-
fmt.Fprintln(os.Stderr, err)
|
|
245
|
-
return 2
|
|
246
|
-
}
|
|
247
|
-
if !externalRan {
|
|
248
|
-
warnUnknownRules(os.Stderr, engine.UnknownRules())
|
|
249
|
-
}
|
|
276
|
+
warnUnknownRules(os.Stderr, engine.UnknownRules())
|
|
250
277
|
if errCount := shimdw.FormatMixedDiagnostics(os.Stderr, astDiags, lintDiags, opts.cwd); errCount > 0 {
|
|
251
278
|
return 2
|
|
252
279
|
}
|
|
@@ -298,8 +325,9 @@ func loadRules(pluginsJSON, cwd, tsconfigPath string) (RuleResolver, error) {
|
|
|
298
325
|
}
|
|
299
326
|
|
|
300
327
|
// warnUnknownRules writes one warning line per name in `unknown` to `w`.
|
|
301
|
-
// Called after engine construction
|
|
302
|
-
//
|
|
328
|
+
// Called after engine construction so a config that names a rule the native
|
|
329
|
+
// engine does not implement surfaces a loud warning instead of silently
|
|
330
|
+
// linting nothing for that rule.
|
|
303
331
|
func warnUnknownRules(w io.Writer, unknown []string) {
|
|
304
332
|
for _, name := range unknown {
|
|
305
333
|
fmt.Fprintf(w, "@ttsc/lint: ignoring unknown rule %q\n", name)
|
|
@@ -344,7 +372,7 @@ func filterKnownFlags(args []string, known map[string]bool) []string {
|
|
|
344
372
|
// collectDiagnostics merges tsgo typecheck diagnostics with the lint
|
|
345
373
|
// engine's findings. The renderer takes the two slices and walks them in
|
|
346
374
|
// source order, so we don't need to interleave here.
|
|
347
|
-
func collectDiagnostics(prog *program, engine *Engine) ([]*shimast.Diagnostic, []*shimdw.LintDiagnostic,
|
|
375
|
+
func collectDiagnostics(prog *program, engine *Engine) ([]*shimast.Diagnostic, []*shimdw.LintDiagnostic, error) {
|
|
348
376
|
astDiags := prog.programDiagnostics()
|
|
349
377
|
files := prog.userSourceFiles()
|
|
350
378
|
findings := engine.Run(files, prog.checker)
|
|
@@ -363,68 +391,7 @@ func collectDiagnostics(prog *program, engine *Engine) ([]*shimast.Diagnostic, [
|
|
|
363
391
|
fmt.Sprintf("[%s] %s", finding.Rule, finding.Message),
|
|
364
392
|
))
|
|
365
393
|
}
|
|
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
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
// mergeNativeAndExternalDiagnostics combines native lint findings with
|
|
375
|
-
// external ESLint diagnostics. Any native diagnostic whose rule name also
|
|
376
|
-
// appears in the external set is dropped to avoid duplicate reporting —
|
|
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)
|
|
398
|
-
}
|
|
399
|
-
return append(out, externalDiags...)
|
|
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]
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
// canonicalLintRule trims ESLint namespace prefixes from `rule` so that
|
|
421
|
-
// `no-var`, `@typescript-eslint/no-var`, and `typescript-eslint/no-var`
|
|
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
|
|
394
|
+
return astDiags, nativeDiags, nil
|
|
428
395
|
}
|
|
429
396
|
|
|
430
397
|
// RuleCode hashes a rule name into a stable, positive int32 so the
|