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