lintcn 0.2.0 → 0.4.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/src/codegen.ts CHANGED
@@ -1,17 +1,14 @@
1
1
  // Generate Go workspace files for building a custom tsgolint binary.
2
- // Creates:
3
- // .lintcn/go.work workspace for gopls (editor support)
4
- // .lintcn/go.mod — module declaration
5
- // build/go.work — build workspace in cache dir
6
- // build/wrapper/go.mod — wrapper module
7
- // build/wrapper/main.go — entry point with all rules
2
+ // With the fork (remorses/tsgolint) exposing pkg/runner.Run(), codegen is
3
+ // minimal: a 10-line main.go template + go.work with shim replaces.
4
+ // No regex surgery, no file copying, no fragile string manipulation.
8
5
 
9
6
  import fs from 'node:fs'
10
7
  import path from 'node:path'
11
8
  import type { RuleMetadata } from './discover.ts'
12
9
 
13
- // All replace directives needed from tsgolint's go.mod.
14
- // These redirect shim module paths to local directories inside the tsgolint source.
10
+ // Shim modules that need replace directives in go.work.
11
+ // These redirect module paths to local directories inside the tsgolint source.
15
12
  const SHIM_MODULES = [
16
13
  'ast',
17
14
  'bundled',
@@ -29,90 +26,13 @@ const SHIM_MODULES = [
29
26
  'vfs/osvfs',
30
27
  ] as const
31
28
 
32
- // Built-in tsgolint rules — all 50+ from cmd/tsgolint/main.go.
33
- // Each entry: [package_path_suffix, exported_var_name]
34
- const BUILTIN_RULES: [string, string][] = [
35
- ['await_thenable', 'AwaitThenableRule'],
36
- ['consistent_return', 'ConsistentReturnRule'],
37
- ['consistent_type_exports', 'ConsistentTypeExportsRule'],
38
- ['dot_notation', 'DotNotationRule'],
39
- ['no_array_delete', 'NoArrayDeleteRule'],
40
- ['no_base_to_string', 'NoBaseToStringRule'],
41
- ['no_confusing_void_expression', 'NoConfusingVoidExpressionRule'],
42
- ['no_deprecated', 'NoDeprecatedRule'],
43
- ['no_duplicate_type_constituents', 'NoDuplicateTypeConstituentsRule'],
44
- ['no_floating_promises', 'NoFloatingPromisesRule'],
45
- ['no_for_in_array', 'NoForInArrayRule'],
46
- ['no_implied_eval', 'NoImpliedEvalRule'],
47
- ['no_meaningless_void_operator', 'NoMeaninglessVoidOperatorRule'],
48
- ['no_misused_promises', 'NoMisusedPromisesRule'],
49
- ['no_misused_spread', 'NoMisusedSpreadRule'],
50
- ['no_mixed_enums', 'NoMixedEnumsRule'],
51
- ['no_redundant_type_constituents', 'NoRedundantTypeConstituentsRule'],
52
- ['no_unnecessary_boolean_literal_compare', 'NoUnnecessaryBooleanLiteralCompareRule'],
53
- ['no_unnecessary_condition', 'NoUnnecessaryConditionRule'],
54
- ['no_unnecessary_qualifier', 'NoUnnecessaryQualifierRule'],
55
- ['no_unnecessary_template_expression', 'NoUnnecessaryTemplateExpressionRule'],
56
- ['no_unnecessary_type_conversion', 'NoUnnecessaryTypeConversionRule'],
57
- ['no_unnecessary_type_arguments', 'NoUnnecessaryTypeArgumentsRule'],
58
- ['no_unnecessary_type_parameters', 'NoUnnecessaryTypeParametersRule'],
59
- ['no_unnecessary_type_assertion', 'NoUnnecessaryTypeAssertionRule'],
60
- ['no_useless_default_assignment', 'NoUselessDefaultAssignmentRule'],
61
- ['no_unsafe_argument', 'NoUnsafeArgumentRule'],
62
- ['no_unsafe_assignment', 'NoUnsafeAssignmentRule'],
63
- ['no_unsafe_call', 'NoUnsafeCallRule'],
64
- ['no_unsafe_enum_comparison', 'NoUnsafeEnumComparisonRule'],
65
- ['no_unsafe_member_access', 'NoUnsafeMemberAccessRule'],
66
- ['no_unsafe_return', 'NoUnsafeReturnRule'],
67
- ['no_unsafe_type_assertion', 'NoUnsafeTypeAssertionRule'],
68
- ['no_unsafe_unary_minus', 'NoUnsafeUnaryMinusRule'],
69
- ['non_nullable_type_assertion_style', 'NonNullableTypeAssertionStyleRule'],
70
- ['only_throw_error', 'OnlyThrowErrorRule'],
71
- ['prefer_find', 'PreferFindRule'],
72
- ['prefer_includes', 'PreferIncludesRule'],
73
- ['prefer_optional_chain', 'PreferOptionalChainRule'],
74
- ['prefer_nullish_coalescing', 'PreferNullishCoalescingRule'],
75
- ['prefer_promise_reject_errors', 'PreferPromiseRejectErrorsRule'],
76
- ['prefer_readonly_parameter_types', 'PreferReadonlyParameterTypesRule'],
77
- ['prefer_regexp_exec', 'PreferRegexpExecRule'],
78
- ['prefer_readonly', 'PreferReadonlyRule'],
79
- ['prefer_reduce_type_parameter', 'PreferReduceTypeParameterRule'],
80
- ['prefer_return_this_type', 'PreferReturnThisTypeRule'],
81
- ['prefer_string_starts_ends_with', 'PreferStringStartsEndsWithRule'],
82
- ['promise_function_async', 'PromiseFunctionAsyncRule'],
83
- ['related_getter_setter_pairs', 'RelatedGetterSetterPairsRule'],
84
- ['require_array_sort_compare', 'RequireArraySortCompareRule'],
85
- ['require_await', 'RequireAwaitRule'],
86
- ['restrict_plus_operands', 'RestrictPlusOperandsRule'],
87
- ['restrict_template_expressions', 'RestrictTemplateExpressionsRule'],
88
- ['return_await', 'ReturnAwaitRule'],
89
- ['strict_boolean_expressions', 'StrictBooleanExpressionsRule'],
90
- ['strict_void_return', 'StrictVoidReturnRule'],
91
- ['switch_exhaustiveness_check', 'SwitchExhaustivenessCheckRule'],
92
- ['unbound_method', 'UnboundMethodRule'],
93
- ['use_unknown_in_catch_callback_variable', 'UseUnknownInCatchCallbackVariableRule'],
94
- ]
95
-
96
29
  function generateReplaceDirectives(tsgolintRelPath: string): string {
97
30
  return SHIM_MODULES.map((mod) => {
98
31
  return `\tgithub.com/microsoft/typescript-go/shim/${mod} => ${tsgolintRelPath}/shim/${mod}`
99
32
  }).join('\n')
100
33
  }
101
34
 
102
- function generateShimRequires(): string {
103
- return SHIM_MODULES.map((mod) => {
104
- return `\tgithub.com/microsoft/typescript-go/shim/${mod} v0.0.0`
105
- }).join('\n')
106
- }
107
-
108
- /** Generate .lintcn/go.work and .lintcn/go.mod for editor/gopls support.
109
- *
110
- * Key learnings from testing:
111
- * - Module name MUST be a child path of github.com/typescript-eslint/tsgolint
112
- * so Go allows importing internal/ packages across the module boundary.
113
- * - go.work must `use` both .tsgolint AND .tsgolint/typescript-go since
114
- * tsgolint's own go.work (which does this) is ignored by the outer workspace.
115
- * - go.mod should be minimal (no requires) — the workspace resolves everything. */
35
+ /** Generate .lintcn/go.work and .lintcn/go.mod for editor/gopls support. */
116
36
  export function generateEditorGoFiles(lintcnDir: string): void {
117
37
  const goWork = `go 1.26
118
38
 
@@ -127,7 +47,8 @@ ${generateReplaceDirectives('./.tsgolint')}
127
47
  )
128
48
  `
129
49
 
130
- const goMod = `module github.com/typescript-eslint/tsgolint/lintcn-rules
50
+ // No child-path hack needed — pkg/ is public, any module name works
51
+ const goMod = `module lintcn-rules
131
52
 
132
53
  go 1.26
133
54
  `
@@ -148,7 +69,9 @@ go.sum
148
69
  }
149
70
  }
150
71
 
151
- /** Generate build workspace in cache dir for compiling the custom binary */
72
+ /** Generate build workspace for compiling the custom binary.
73
+ * With pkg/runner.Run(), the generated main.go is a static template —
74
+ * no regex surgery or file copying needed. */
152
75
  export function generateBuildWorkspace({
153
76
  buildDir,
154
77
  tsgolintDir,
@@ -176,7 +99,7 @@ export function generateBuildWorkspace({
176
99
  }
177
100
  fs.symlinkSync(path.resolve(lintcnDir), rulesLink)
178
101
 
179
- // go.work — must include typescript-go submodule and use child module paths
102
+ // go.work
180
103
  const goWork = `go 1.26
181
104
 
182
105
  use (
@@ -192,449 +115,41 @@ ${generateReplaceDirectives('./tsgolint')}
192
115
  `
193
116
  fs.writeFileSync(path.join(buildDir, 'go.work'), goWork)
194
117
 
195
- // wrapper/go.mod — must be child path of tsgolint for internal/ access
196
- const wrapperGoMod = `module github.com/typescript-eslint/tsgolint/lintcn-wrapper
118
+ // wrapper/go.mod — simple module name, no child-path hack needed
119
+ const wrapperGoMod = `module lintcn-wrapper
197
120
 
198
121
  go 1.26
199
-
200
- require (
201
- \tgithub.com/typescript-eslint/tsgolint v0.0.0
202
- \tgithub.com/typescript-eslint/tsgolint/lintcn-rules v0.0.0
203
- )
204
122
  `
205
123
  fs.writeFileSync(path.join(buildDir, 'wrapper', 'go.mod'), wrapperGoMod)
206
124
 
207
- // wrapper/main.go
125
+ // wrapper/main.go — simple template, no regex or string surgery
208
126
  const mainGo = generateMainGo(rules)
209
127
  fs.writeFileSync(path.join(buildDir, 'wrapper', 'main.go'), mainGo)
210
128
  }
211
129
 
212
- /** Generate the main.go that imports all built-in + custom rules.
213
- * This is essentially tsgolint's cmd/tsgolint/main.go with custom rules appended
214
- * to allRules. We import tsgolint's internal packages directly since go.work
215
- * allows cross-module internal imports. */
216
- function generateMainGo(customRules: RuleMetadata[]): string {
217
- const tsgolintPkg = 'github.com/typescript-eslint/tsgolint'
218
-
219
- // built-in rule imports
220
- const builtinImports = BUILTIN_RULES.map(([pkg]) => {
221
- return `\t"${tsgolintPkg}/internal/rules/${pkg}"`
130
+ /** Generate a minimal main.go that imports user rules and calls runner.Run().
131
+ * This is a static template no copying or patching of tsgolint source. */
132
+ function generateMainGo(rules: RuleMetadata[]): string {
133
+ const ruleEntries = rules.map((r) => {
134
+ return `\t\tlintcn.${r.varName},`
222
135
  }).join('\n')
223
136
 
224
- // built-in rule entries
225
- const builtinEntries = BUILTIN_RULES.map(([pkg, varName]) => {
226
- return `\t${pkg}.${varName},`
227
- }).join('\n')
228
-
229
- // custom rule entries (all in package lintcn via single import)
230
- const customEntries = customRules.map((r) => {
231
- return `\tlintcn.${r.varName},`
232
- }).join('\n')
233
-
234
- // only add lintcn import if there are custom rules
235
- const lintcnImport = customRules.length > 0 ? '\n\tlintcn "github.com/typescript-eslint/tsgolint/lintcn-rules"' : ''
236
-
237
137
  return `// Code generated by lintcn. DO NOT EDIT.
238
138
  package main
239
139
 
240
140
  import (
241
- \t"bufio"
242
- \t"flag"
243
- \t"fmt"
244
- \t"math"
245
141
  \t"os"
246
- \t"runtime"
247
- \t"runtime/pprof"
248
- \t"runtime/trace"
249
- \t"slices"
250
- \t"strconv"
251
- \t"strings"
252
- \t"sync"
253
- \t"time"
254
- \t"unicode"
255
-
256
- \t"${tsgolintPkg}/internal/diagnostic"
257
- \t"${tsgolintPkg}/internal/linter"
258
- \t"${tsgolintPkg}/internal/rule"
259
- \t"${tsgolintPkg}/internal/utils"
260
-
261
- ${builtinImports}${lintcnImport}
262
142
 
263
- \t"github.com/microsoft/typescript-go/shim/ast"
264
- \t"github.com/microsoft/typescript-go/shim/bundled"
265
- \t"github.com/microsoft/typescript-go/shim/core"
266
- \t"github.com/microsoft/typescript-go/shim/scanner"
267
- \t"github.com/microsoft/typescript-go/shim/tspath"
268
- \t"github.com/microsoft/typescript-go/shim/vfs/cachedvfs"
269
- \t"github.com/microsoft/typescript-go/shim/vfs/osvfs"
143
+ \t"github.com/typescript-eslint/tsgolint/pkg/rule"
144
+ \t"github.com/typescript-eslint/tsgolint/pkg/runner"
145
+ \tlintcn "lintcn-rules"
270
146
  )
271
147
 
272
- var allRules = []rule.Rule{
273
- ${builtinEntries}
274
- ${customEntries}
275
- }
276
-
277
- var allRulesByName = make(map[string]rule.Rule, len(allRules))
278
-
279
- func init() {
280
- \tfor _, rule := range allRules {
281
- \t\tallRulesByName[rule.Name] = rule
282
- \t}
283
- }
284
-
285
- // Below is copied from tsgolint's cmd/tsgolint/main.go.
286
- // We cannot import it directly because main packages are not importable in Go.
287
-
288
- func recordTrace(traceOut string) (func(), error) {
289
- \tif traceOut != "" {
290
- \t\tf, err := os.Create(traceOut)
291
- \t\tif err != nil {
292
- \t\t\treturn nil, fmt.Errorf("error creating trace file: %w", err)
293
- \t\t}
294
- \t\ttrace.Start(f)
295
- \t\treturn func() {
296
- \t\t\ttrace.Stop()
297
- \t\t\tf.Close()
298
- \t\t}, nil
299
- \t}
300
- \treturn func() {}, nil
301
- }
302
-
303
- func recordCpuprof(cpuprofOut string) (func(), error) {
304
- \tif cpuprofOut != "" {
305
- \t\tf, err := os.Create(cpuprofOut)
306
- \t\tif err != nil {
307
- \t\t\treturn nil, fmt.Errorf("error creating cpuprof file: %w", err)
308
- \t\t}
309
- \t\terr = pprof.StartCPUProfile(f)
310
- \t\tif err != nil {
311
- \t\t\treturn nil, fmt.Errorf("error starting cpu profiling: %w", err)
312
- \t\t}
313
- \t\treturn func() {
314
- \t\t\tpprof.StopCPUProfile()
315
- \t\t\tf.Close()
316
- \t\t}, nil
317
- \t}
318
- \treturn func() {}, nil
319
- }
320
-
321
- const spaces = " "
322
-
323
- func printDiagnostic(d rule.RuleDiagnostic, w *bufio.Writer, comparePathOptions tspath.ComparePathsOptions) {
324
- \tdiagnosticStart := d.Range.Pos()
325
- \tdiagnosticEnd := d.Range.End()
326
- \tdiagnosticStartLine, diagnosticStartColumn := scanner.GetECMALineAndUTF16CharacterOfPosition(d.SourceFile, diagnosticStart)
327
- \tdiagnosticEndline, _ := scanner.GetECMALineAndUTF16CharacterOfPosition(d.SourceFile, diagnosticEnd)
328
- \tlineMap := d.SourceFile.ECMALineMap()
329
- \ttext := d.SourceFile.Text()
330
- \tcodeboxStartLine := max(diagnosticStartLine-1, 0)
331
- \tcodeboxEndLine := min(diagnosticEndline+1, len(lineMap)-1)
332
- \tcodeboxStart := scanner.GetECMAPositionOfLineAndUTF16Character(d.SourceFile, codeboxStartLine, 0)
333
- \tvar codeboxEndColumn int
334
- \tif codeboxEndLine == len(lineMap)-1 {
335
- \t\tcodeboxEndColumn = len(text) - int(lineMap[len(lineMap)-1])
336
- \t} else {
337
- \t\tcodeboxEndColumn = int(lineMap[codeboxEndLine+1]-lineMap[codeboxEndLine]) - 1
338
- \t}
339
- \tcodeboxEnd := scanner.GetECMAPositionOfLineAndUTF16Character(d.SourceFile, codeboxEndLine, core.UTF16Offset(codeboxEndColumn))
340
- \tw.Write([]byte{' ', 0x1b, '[', '7', 'm', 0x1b, '[', '1', 'm', 0x1b, '[', '3', '8', ';', '5', ';', '3', '7', 'm', ' '})
341
- \tw.WriteString(d.RuleName)
342
- \tw.WriteString(" \\x1b[0m — ")
343
- \tmessageLineStart := 0
344
- \tfor i, char := range d.Message.Description {
345
- \t\tif char == '\\n' {
346
- \t\t\tw.WriteString(d.Message.Description[messageLineStart : i+1])
347
- \t\t\tmessageLineStart = i + 1
348
- \t\t\tw.WriteString(" \\x1b[2m│\\x1b[0m")
349
- \t\t\tw.WriteString(spaces[:len(d.RuleName)+1])
350
- \t\t}
351
- \t}
352
- \tif messageLineStart <= len(d.Message.Description) {
353
- \t\tw.WriteString(d.Message.Description[messageLineStart:len(d.Message.Description)])
354
- \t}
355
- \tw.WriteString("\\n \\x1b[2m╭─┴──────────(\\x1b[0m \\x1b[3m\\x1b[38;5;117m")
356
- \tw.WriteString(tspath.ConvertToRelativePath(d.SourceFile.FileName(), comparePathOptions))
357
- \tw.WriteByte(':')
358
- \tw.WriteString(strconv.Itoa(diagnosticStartLine + 1))
359
- \tw.WriteByte(':')
360
- \tw.WriteString(strconv.Itoa(int(diagnosticStartColumn) + 1))
361
- \tw.WriteString("\\x1b[0m \\x1b[2m)─────\\x1b[0m\\n")
362
- \tindentSize := math.MaxInt
363
- \tline := codeboxStartLine
364
- \tlineIndentCalculated := false
365
- \tlastNonSpaceIndex := -1
366
- \tlineStarts := make([]int, 13)
367
- \tlineEnds := make([]int, 13)
368
- \tif codeboxEndLine-codeboxStartLine >= len(lineEnds) {
369
- \t\tw.WriteString(" \\x1b[2m│\\x1b[0m Error range is too big. Skipping code block printing.\\n \\x1b[2m╰────────────────────────────────\\x1b[0m\\n\\n")
370
- \t\treturn
371
- \t}
372
- \tfor i, char := range text[codeboxStart:codeboxEnd] {
373
- \t\tif char == '\\n' {
374
- \t\t\tif line != codeboxEndLine {
375
- \t\t\t\tlineIndentCalculated = false
376
- \t\t\t\tlineEnds[line-codeboxStartLine] = lastNonSpaceIndex - int(lineMap[line]) + codeboxStart
377
- \t\t\t\tlastNonSpaceIndex = -1
378
- \t\t\t\tline++
379
- \t\t\t}
380
- \t\t\tcontinue
381
- \t\t}
382
- \t\tif !lineIndentCalculated && !unicode.IsSpace(char) {
383
- \t\t\tlineIndentCalculated = true
384
- \t\t\tlineStarts[line-codeboxStartLine] = i - int(lineMap[line]) + codeboxStart
385
- \t\t\tindentSize = min(indentSize, lineStarts[line-codeboxStartLine])
386
- \t\t}
387
- \t\tif lineIndentCalculated && !unicode.IsSpace(char) {
388
- \t\t\tlastNonSpaceIndex = i + 1
389
- \t\t}
390
- \t}
391
- \tif line == codeboxEndLine {
392
- \t\tlineEnds[line-codeboxStartLine] = lastNonSpaceIndex - int(lineMap[line]) + codeboxStart
393
- \t}
394
- \tdiagnosticHighlightActive := false
395
- \tlastLineNumber := strconv.Itoa(codeboxEndLine + 1)
396
- \tfor line := codeboxStartLine; line <= codeboxEndLine; line++ {
397
- \t\tw.WriteString(" \\x1b[2m│ ")
398
- \t\tif line == codeboxEndLine {
399
- \t\t\tw.WriteString(lastLineNumber)
400
- \t\t} else {
401
- \t\t\tnumber := strconv.Itoa(line + 1)
402
- \t\t\tif len(number) < len(lastLineNumber) {
403
- \t\t\t\tw.WriteByte(' ')
404
- \t\t\t}
405
- \t\t\tw.WriteString(number)
406
- \t\t}
407
- \t\tw.WriteString(" │\\x1b[0m ")
408
- \t\tlineTextStart := int(lineMap[line]) + indentSize
409
- \t\tunderlineStart := max(lineTextStart, int(lineMap[line])+lineStarts[line-codeboxStartLine])
410
- \t\tunderlineEnd := underlineStart
411
- \t\tlineTextEnd := max(int(lineMap[line])+lineEnds[line-codeboxStartLine], lineTextStart)
412
- \t\tif diagnosticHighlightActive {
413
- \t\t\tunderlineEnd = lineTextEnd
414
- \t\t} else if int(lineMap[line]) <= diagnosticStart && (line == len(lineMap) || diagnosticStart < int(lineMap[line+1])) {
415
- \t\t\tunderlineStart = min(max(lineTextStart, diagnosticStart), lineTextEnd)
416
- \t\t\tunderlineEnd = lineTextEnd
417
- \t\t\tdiagnosticHighlightActive = true
418
- \t\t}
419
- \t\tif int(lineMap[line]) <= diagnosticEnd && (line == len(lineMap) || diagnosticEnd < int(lineMap[line+1])) {
420
- \t\t\tunderlineEnd = min(max(underlineStart, diagnosticEnd), lineTextEnd)
421
- \t\t\tdiagnosticHighlightActive = false
422
- \t\t}
423
- \t\tif underlineStart != underlineEnd {
424
- \t\t\tw.WriteString(text[lineTextStart:underlineStart])
425
- \t\t\tw.Write([]byte{
426
- \t\t\t\t0x1b, '[', '4', 'm',
427
- \t\t\t\t0x1b, '[', '4', ':', '3', 'm',
428
- \t\t\t\t0x1b, '[', '5', '8', ':', '5', ':', '1', '6', '0', 'm',
429
- \t\t\t\t0x1b, '[', '3', '8', ';', '5', ';', '1', '6', '0', 'm',
430
- \t\t\t\t0x1b, '[', '2', '2', ';', '4', '9', 'm',
431
- \t\t\t})
432
- \t\t\tw.WriteString(text[underlineStart:underlineEnd])
433
- \t\t\tw.Write([]byte{0x1b, '[', '0', 'm'})
434
- \t\t\tw.WriteString(text[underlineEnd:lineTextEnd])
435
- \t\t} else if lineTextStart != lineTextEnd {
436
- \t\t\tw.WriteString(text[lineTextStart:lineTextEnd])
437
- \t\t}
438
- \t\tw.WriteByte('\\n')
439
- \t}
440
- \tw.WriteString(" \\x1b[2m╰────────────────────────────────\\x1b[0m\\n\\n")
441
- }
442
-
443
- const usage = \` lintcn - type-aware TypeScript linter with custom rules
444
-
445
- Usage:
446
- lintcn-binary [OPTIONS]
447
-
448
- Options:
449
- --tsconfig PATH Which tsconfig to use. Defaults to tsconfig.json.
450
- --list-files List matched files
451
- -h, --help Show help
452
- \`
453
-
454
- func runMain() int {
455
- \tflag.Usage = func() { fmt.Fprint(os.Stderr, usage) }
456
- \tvar (
457
- \t\thelp bool
458
- \t\ttsconfig string
459
- \t\tlistFiles bool
460
- \t\ttraceOut string
461
- \t\tcpuprofOut string
462
- \t\tsingleThreaded bool
463
- \t)
464
- \tflag.StringVar(&tsconfig, "tsconfig", "", "which tsconfig to use")
465
- \tflag.BoolVar(&listFiles, "list-files", false, "list matched files")
466
- \tflag.BoolVar(&help, "help", false, "show help")
467
- \tflag.BoolVar(&help, "h", false, "show help")
468
- \tflag.StringVar(&traceOut, "trace", "", "file to put trace to")
469
- \tflag.StringVar(&cpuprofOut, "cpuprof", "", "file to put cpu profiling to")
470
- \tflag.BoolVar(&singleThreaded, "singleThreaded", false, "run in single threaded mode")
471
- \tflag.Parse()
472
- \tif help {
473
- \t\tflag.Usage()
474
- \t\treturn 0
475
- \t}
476
- \ttimeBefore := time.Now()
477
- \tif done, err := recordTrace(traceOut); err != nil {
478
- \t\tos.Stderr.WriteString(err.Error())
479
- \t\treturn 1
480
- \t} else {
481
- \t\tdefer done()
482
- \t}
483
- \tif done, err := recordCpuprof(cpuprofOut); err != nil {
484
- \t\tos.Stderr.WriteString(err.Error())
485
- \t\treturn 1
486
- \t} else {
487
- \t\tdefer done()
488
- \t}
489
- \tcurrentDirectory, err := os.Getwd()
490
- \tif err != nil {
491
- \t\tfmt.Fprintf(os.Stderr, "error getting current directory: %v\\n", err)
492
- \t\treturn 1
493
- \t}
494
- \tcurrentDirectory = tspath.NormalizePath(currentDirectory)
495
- \tfs := bundled.WrapFS(cachedvfs.From(osvfs.FS()))
496
- \tvar configFileName string
497
- \tif tsconfig == "" {
498
- \t\tconfigFileName = tspath.ResolvePath(currentDirectory, "tsconfig.json")
499
- \t\tif !fs.FileExists(configFileName) {
500
- \t\t\tfs = utils.NewOverlayVFS(fs, map[string]string{
501
- \t\t\t\tconfigFileName: "{}",
502
- \t\t\t})
503
- \t\t}
504
- \t} else {
505
- \t\tconfigFileName = tspath.ResolvePath(currentDirectory, tsconfig)
506
- \t\tif !fs.FileExists(configFileName) {
507
- \t\t\tfmt.Fprintf(os.Stderr, "error: tsconfig %q doesn't exist", tsconfig)
508
- \t\t\treturn 1
509
- \t\t}
510
- \t}
511
- \tcurrentDirectory = tspath.GetDirectoryPath(configFileName)
512
- \thost := utils.CreateCompilerHost(currentDirectory, fs)
513
- \tcomparePathOptions := tspath.ComparePathsOptions{
514
- \t\tCurrentDirectory: host.GetCurrentDirectory(),
515
- \t\tUseCaseSensitiveFileNames: host.FS().UseCaseSensitiveFileNames(),
516
- \t}
517
- \tprogram, _, err := utils.CreateProgram(singleThreaded, fs, currentDirectory, configFileName, host, false)
518
- \tif err != nil {
519
- \t\tfmt.Fprintf(os.Stderr, "error creating TS program: %v", err)
520
- \t\treturn 1
521
- \t}
522
- \tif program == nil {
523
- \t\tfmt.Fprintf(os.Stderr, "error creating TS program")
524
- \t\treturn 1
525
- \t}
526
- \tfiles := []*ast.SourceFile{}
527
- \tcwdPath := string(tspath.ToPath("", currentDirectory, program.Host().FS().UseCaseSensitiveFileNames()).EnsureTrailingDirectorySeparator())
528
- \tvar matchedFiles strings.Builder
529
- \tfor _, file := range program.SourceFiles() {
530
- \t\tp := string(file.Path())
531
- \t\tif strings.Contains(p, "/node_modules/") {
532
- \t\t\tcontinue
533
- \t\t}
534
- \t\tif fileName, matched := strings.CutPrefix(p, cwdPath); matched {
535
- \t\t\tif listFiles {
536
- \t\t\t\tmatchedFiles.WriteString("Found file: ")
537
- \t\t\t\tmatchedFiles.WriteString(fileName)
538
- \t\t\t\tmatchedFiles.WriteByte('\\n')
539
- \t\t\t}
540
- \t\t\tfiles = append(files, file)
541
- \t\t}
542
- \t}
543
- \tif listFiles {
544
- \t\tos.Stdout.WriteString(matchedFiles.String())
545
- \t}
546
- \tslices.SortFunc(files, func(a *ast.SourceFile, b *ast.SourceFile) int {
547
- \t\treturn len(b.Text()) - len(a.Text())
548
- \t})
549
- \tvar wg sync.WaitGroup
550
- \tdiagnosticsChan := make(chan rule.RuleDiagnostic, 4096)
551
- \terrorsCount := 0
552
- \twg.Go(func() {
553
- \t\tw := bufio.NewWriterSize(os.Stdout, 4096*100)
554
- \t\tdefer w.Flush()
555
- \t\tfor d := range diagnosticsChan {
556
- \t\t\terrorsCount++
557
- \t\t\tif errorsCount == 1 {
558
- \t\t\t\tw.WriteByte('\\n')
559
- \t\t\t}
560
- \t\t\tprintDiagnostic(d, w, comparePathOptions)
561
- \t\t\tif w.Available() < 4096 {
562
- \t\t\t\tw.Flush()
563
- \t\t\t}
564
- \t\t}
565
- \t})
566
- \terr = linter.RunLinterOnProgram(
567
- \t\tutils.GetLogLevel(),
568
- \t\tprogram,
569
- \t\tfiles,
570
- \t\truntime.GOMAXPROCS(0),
571
- \t\tfunc(sourceFile *ast.SourceFile) []linter.ConfiguredRule {
572
- \t\t\treturn utils.Map(allRules, func(r rule.Rule) linter.ConfiguredRule {
573
- \t\t\t\treturn linter.ConfiguredRule{
574
- \t\t\t\t\tName: r.Name,
575
- \t\t\t\t\tRun: func(ctx rule.RuleContext) rule.RuleListeners {
576
- \t\t\t\t\t\treturn r.Run(ctx, nil)
577
- \t\t\t\t\t},
578
- \t\t\t\t}
579
- \t\t\t})
580
- \t\t},
581
- \t\tfunc(d rule.RuleDiagnostic) {
582
- \t\t\tdiagnosticsChan <- d
583
- \t\t},
584
- \t\tfunc(d diagnostic.Internal) {},
585
- \t\tlinter.Fixes{
586
- \t\t\tFix: true,
587
- \t\t\tFixSuggestions: true,
588
- \t\t},
589
- \t\tlinter.TypeErrors{
590
- \t\t\tReportSyntactic: false,
591
- \t\t\tReportSemantic: false,
592
- \t\t},
593
- \t)
594
- \tclose(diagnosticsChan)
595
- \tif err != nil {
596
- \t\tfmt.Fprintf(os.Stderr, "error running linter: %v\\n", err)
597
- \t\treturn 1
598
- \t}
599
- \twg.Wait()
600
- \terrorsColor := "\\x1b[1m"
601
- \tif errorsCount == 0 {
602
- \t\terrorsColor = "\\x1b[1;32m"
603
- \t}
604
- \terrorsText := "errors"
605
- \tif errorsCount == 1 {
606
- \t\terrorsText = "error"
607
- \t}
608
- \tfilesText := "files"
609
- \tif len(files) == 1 {
610
- \t\tfilesText = "file"
611
- \t}
612
- \trulesText := "rules"
613
- \tif len(allRules) == 1 {
614
- \t\trulesText = "rule"
615
- \t}
616
- \tthreadsCount := 1
617
- \tif !singleThreaded {
618
- \t\tthreadsCount = runtime.GOMAXPROCS(0)
619
- \t}
620
- \tfmt.Fprintf(
621
- \t\tos.Stdout,
622
- \t\t"Found %v%v\\x1b[0m %v \\x1b[2m(linted \\x1b[1m%v\\x1b[22m\\x1b[2m %v with \\x1b[1m%v\\x1b[22m\\x1b[2m %v in \\x1b[1m%v\\x1b[22m\\x1b[2m using \\x1b[1m%v\\x1b[22m\\x1b[2m threads)\\n",
623
- \t\terrorsColor,
624
- \t\terrorsCount,
625
- \t\terrorsText,
626
- \t\tlen(files),
627
- \t\tfilesText,
628
- \t\tlen(allRules),
629
- \t\trulesText,
630
- \t\ttime.Since(timeBefore).Round(time.Millisecond),
631
- \t\tthreadsCount,
632
- \t)
633
- \treturn 0
634
- }
635
-
636
148
  func main() {
637
- \tos.Exit(runMain())
149
+ \trules := []rule.Rule{
150
+ ${ruleEntries}
151
+ \t}
152
+ \tos.Exit(runner.Run(rules, os.Args[1:]))
638
153
  }
639
154
  `
640
155
  }