lintcn 0.2.0 → 0.3.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
@@ -4,7 +4,7 @@
4
4
  // .lintcn/go.mod — module declaration
5
5
  // build/go.work — build workspace in cache dir
6
6
  // build/wrapper/go.mod — wrapper module
7
- // build/wrapper/main.go — entry point with all rules
7
+ // build/wrapper/main.go — tsgolint main.go with custom rules appended
8
8
 
9
9
  import fs from 'node:fs'
10
10
  import path from 'node:path'
@@ -29,82 +29,12 @@ const SHIM_MODULES = [
29
29
  'vfs/osvfs',
30
30
  ] as const
31
31
 
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
32
  function generateReplaceDirectives(tsgolintRelPath: string): string {
97
33
  return SHIM_MODULES.map((mod) => {
98
34
  return `\tgithub.com/microsoft/typescript-go/shim/${mod} => ${tsgolintRelPath}/shim/${mod}`
99
35
  }).join('\n')
100
36
  }
101
37
 
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
38
  /** Generate .lintcn/go.work and .lintcn/go.mod for editor/gopls support.
109
39
  *
110
40
  * Key learnings from testing:
@@ -148,7 +78,10 @@ go.sum
148
78
  }
149
79
  }
150
80
 
151
- /** Generate build workspace in cache dir for compiling the custom binary */
81
+ /** Generate build workspace in cache dir for compiling the custom binary.
82
+ * Instead of hardcoding the built-in rule list, we copy tsgolint's actual
83
+ * main.go and inject custom rule imports + entries. This way the generated
84
+ * code always matches the pinned tsgolint version. */
152
85
  export function generateBuildWorkspace({
153
86
  buildDir,
154
87
  tsgolintDir,
@@ -192,449 +125,92 @@ ${generateReplaceDirectives('./tsgolint')}
192
125
  `
193
126
  fs.writeFileSync(path.join(buildDir, 'go.work'), goWork)
194
127
 
195
- // wrapper/go.mod — must be child path of tsgolint for internal/ access
128
+ // wrapper/go.mod — must be child path of tsgolint for internal/ access.
129
+ // Minimal: no require block. The workspace resolves all dependencies.
130
+ // Adding explicit requires with v0.0.0 triggers Go proxy lookups that fail.
196
131
  const wrapperGoMod = `module github.com/typescript-eslint/tsgolint/lintcn-wrapper
197
132
 
198
133
  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
134
  `
205
135
  fs.writeFileSync(path.join(buildDir, 'wrapper', 'go.mod'), wrapperGoMod)
206
136
 
207
- // wrapper/main.go
208
- const mainGo = generateMainGo(rules)
209
- fs.writeFileSync(path.join(buildDir, 'wrapper', 'main.go'), mainGo)
210
- }
137
+ // copy all supporting .go files from cmd/tsgolint/ (headless, payload, etc.)
138
+ const wrapperDir = path.join(buildDir, 'wrapper')
139
+ copyTsgolintCmdFiles(tsgolintDir, wrapperDir)
211
140
 
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'
141
+ // wrapper/main.go copy from tsgolint and inject custom rules
142
+ const mainGo = generateMainGoFromSource(tsgolintDir, rules)
143
+ fs.writeFileSync(path.join(wrapperDir, 'main.go'), mainGo)
144
+ }
218
145
 
219
- // built-in rule imports
220
- const builtinImports = BUILTIN_RULES.map(([pkg]) => {
221
- return `\t"${tsgolintPkg}/internal/rules/${pkg}"`
222
- }).join('\n')
146
+ /** Copy tsgolint's main.go and transform it to only include custom rules.
147
+ * Two targeted string operations on the copied source:
148
+ * 1. Remove all /internal/rules/ import lines (built-in rule packages)
149
+ * 2. Replace allRules body with only custom lintcn.* entries
150
+ * Everything else (printDiagnostic, runMain, headless) stays untouched. */
151
+ function generateMainGoFromSource(tsgolintDir: string, customRules: RuleMetadata[]): string {
152
+ const mainGoPath = path.join(tsgolintDir, 'cmd', 'tsgolint', 'main.go')
153
+ const original = fs.readFileSync(mainGoPath, 'utf-8')
154
+
155
+ // 1. Remove built-in rule import lines, add lintcn import
156
+ const lines = original.split('\n')
157
+ const filtered = lines.filter((line) => {
158
+ return !line.includes('/internal/rules/')
159
+ })
160
+
161
+ // Insert lintcn import before the first shim import (microsoft/typescript-go)
162
+ const lintcnImport = `\tlintcn "github.com/typescript-eslint/tsgolint/lintcn-rules"`
163
+ let shimImportIndex = -1
164
+ for (let i = 0; i < filtered.length; i++) {
165
+ if (filtered[i].includes('microsoft/typescript-go/shim')) {
166
+ shimImportIndex = i
167
+ break
168
+ }
169
+ }
170
+ if (shimImportIndex === -1) {
171
+ throw new Error(
172
+ 'Failed to find shim import in tsgolint main.go. The source layout may have changed.',
173
+ )
174
+ }
175
+ if (customRules.length > 0) {
176
+ filtered.splice(shimImportIndex, 0, lintcnImport, '')
177
+ }
223
178
 
224
- // built-in rule entries
225
- const builtinEntries = BUILTIN_RULES.map(([pkg, varName]) => {
226
- return `\t${pkg}.${varName},`
227
- }).join('\n')
179
+ let mainGo = filtered.join('\n')
228
180
 
229
- // custom rule entries (all in package lintcn via single import)
181
+ // 2. Replace allRules body with only custom entries
230
182
  const customEntries = customRules.map((r) => {
231
183
  return `\tlintcn.${r.varName},`
232
184
  }).join('\n')
233
185
 
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
- return `// Code generated by lintcn. DO NOT EDIT.
238
- package main
239
-
240
- import (
241
- \t"bufio"
242
- \t"flag"
243
- \t"fmt"
244
- \t"math"
245
- \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
-
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"
270
- )
271
-
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
186
+ const allRulesPattern = /var allRules = \[]rule\.Rule\{[^}]*\}/s
187
+ if (!allRulesPattern.test(mainGo)) {
188
+ throw new Error(
189
+ 'Failed to find allRules slice in tsgolint main.go. The source layout may have changed.',
190
+ )
191
+ }
444
192
 
445
- Usage:
446
- lintcn-binary [OPTIONS]
193
+ mainGo = mainGo.replace(
194
+ allRulesPattern,
195
+ `var allRules = []rule.Rule{\n${customEntries}\n}`,
196
+ )
447
197
 
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
- \`
198
+ // assertion: verify custom rules are present
199
+ if (customRules.length > 0 && !mainGo.includes(`lintcn.${customRules[0].varName}`)) {
200
+ throw new Error('Custom rule injection verification failed.')
201
+ }
453
202
 
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
203
+ return mainGo
634
204
  }
635
205
 
636
- func main() {
637
- \tos.Exit(runMain())
638
- }
639
- `
206
+ /** Copy all supporting .go files from cmd/tsgolint/ into the wrapper dir.
207
+ * main.go is generated separately with custom rules injected. */
208
+ export function copyTsgolintCmdFiles(tsgolintDir: string, wrapperDir: string): void {
209
+ const cmdDir = path.join(tsgolintDir, 'cmd', 'tsgolint')
210
+ const files = fs.readdirSync(cmdDir).filter((f) => {
211
+ return f.endsWith('.go') && f !== 'main.go' && !f.endsWith('_test.go')
212
+ })
213
+ for (const file of files) {
214
+ fs.copyFileSync(path.join(cmdDir, file), path.join(wrapperDir, file))
215
+ }
640
216
  }