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.
- package/CHANGELOG.md +31 -0
- package/LICENSE +21 -0
- package/README.md +157 -5
- package/dist/cache.d.ts +9 -0
- package/dist/cache.d.ts.map +1 -0
- package/dist/cache.js +88 -0
- package/dist/cli.js +71 -3
- package/dist/codegen.d.ts +18 -0
- package/dist/codegen.d.ts.map +1 -0
- package/dist/codegen.js +607 -0
- package/dist/commands/add.d.ts +2 -0
- package/dist/commands/add.d.ts.map +1 -0
- package/dist/commands/add.js +101 -0
- package/dist/commands/lint.d.ts +10 -0
- package/dist/commands/lint.d.ts.map +1 -0
- package/dist/commands/lint.js +78 -0
- package/dist/commands/list.d.ts +2 -0
- package/dist/commands/list.d.ts.map +1 -0
- package/dist/commands/list.js +24 -0
- package/dist/commands/remove.d.ts +2 -0
- package/dist/commands/remove.d.ts.map +1 -0
- package/dist/commands/remove.js +31 -0
- package/dist/discover.d.ts +16 -0
- package/dist/discover.d.ts.map +1 -0
- package/dist/discover.js +44 -0
- package/dist/exec.d.ts +10 -0
- package/dist/exec.d.ts.map +1 -0
- package/dist/exec.js +34 -0
- package/dist/hash.d.ts +5 -0
- package/dist/hash.d.ts.map +1 -0
- package/dist/hash.js +33 -0
- package/dist/index.d.ts +7 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -1
- package/dist/paths.d.ts +2 -0
- package/dist/paths.d.ts.map +1 -0
- package/dist/paths.js +5 -0
- package/package.json +12 -9
- package/src/cache.ts +106 -0
- package/src/cli.ts +80 -2
- package/src/codegen.ts +640 -0
- package/src/commands/add.ts +118 -0
- package/src/commands/lint.ts +110 -0
- package/src/commands/list.ts +33 -0
- package/src/commands/remove.ts +41 -0
- package/src/discover.ts +69 -0
- package/src/exec.ts +50 -0
- package/src/hash.ts +45 -0
- package/src/index.ts +7 -1
- package/src/paths.ts +7 -0
package/dist/codegen.js
ADDED
|
@@ -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 @@
|
|
|
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"}
|