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/CHANGELOG.md +33 -0
- package/dist/cache.d.ts +1 -1
- package/dist/cache.d.ts.map +1 -1
- package/dist/cache.js +85 -46
- package/dist/codegen.d.ts +4 -9
- package/dist/codegen.d.ts.map +1 -1
- package/dist/codegen.js +27 -506
- package/dist/commands/add.d.ts.map +1 -1
- package/dist/commands/add.js +29 -16
- package/dist/commands/lint.d.ts.map +1 -1
- package/dist/commands/lint.js +2 -5
- package/dist/commands/list.d.ts.map +1 -1
- package/dist/commands/list.js +3 -4
- package/dist/commands/remove.d.ts.map +1 -1
- package/dist/commands/remove.js +2 -5
- package/dist/discover.d.ts.map +1 -1
- package/dist/discover.js +8 -1
- package/dist/hash.d.ts.map +1 -1
- package/dist/hash.js +6 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/paths.d.ts +6 -0
- package/dist/paths.d.ts.map +1 -1
- package/dist/paths.js +23 -1
- package/package.json +2 -1
- package/src/cache.ts +95 -51
- package/src/codegen.ts +27 -512
- package/src/commands/add.ts +34 -19
- package/src/commands/lint.ts +2 -5
- package/src/commands/list.ts +3 -4
- package/src/commands/remove.ts +2 -6
- package/src/discover.ts +10 -1
- package/src/hash.ts +7 -2
- package/src/index.ts +1 -0
- package/src/paths.ts +25 -1
package/src/codegen.ts
CHANGED
|
@@ -1,17 +1,14 @@
|
|
|
1
1
|
// Generate Go workspace files for building a custom tsgolint binary.
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
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
|
-
//
|
|
14
|
-
// These redirect
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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 —
|
|
196
|
-
const wrapperGoMod = `module
|
|
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
|
|
213
|
-
* This is
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
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/
|
|
264
|
-
\t"github.com/
|
|
265
|
-
\
|
|
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
|
-
\
|
|
149
|
+
\trules := []rule.Rule{
|
|
150
|
+
${ruleEntries}
|
|
151
|
+
\t}
|
|
152
|
+
\tos.Exit(runner.Run(rules, os.Args[1:]))
|
|
638
153
|
}
|
|
639
154
|
`
|
|
640
155
|
}
|