@ttsc/lint 0.18.4 → 0.19.1
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/README.md +125 -43
- package/go.mod +5 -4
- package/internal/rulecode/rulecode.go +88 -0
- package/lib/index.d.ts +10 -1
- package/lib/index.js +45 -17
- package/lib/index.js.map +1 -1
- package/lib/structures/ITtscLintConfig.d.ts +3 -0
- package/lib/structures/TtscLintRuleSetting.d.ts +3 -2
- package/lib/structures/format/ITtscLintFormat.d.ts +3 -2
- package/lib/structures/format/ITtscLintFormatSortImports.d.ts +20 -4
- package/lib/structures/rules/ITtscLintBoundariesRuleOptions.d.ts +80 -8
- package/lib/structures/rules/ITtscLintBoundariesRules.d.ts +6 -8
- package/lib/structures/rules/ITtscLintContributorRules.d.ts +18 -2
- package/lib/structures/rules/ITtscLintCoreRuleOptions.d.ts +347 -0
- package/lib/structures/rules/ITtscLintCoreRuleOptions.js +3 -0
- package/lib/structures/rules/ITtscLintCoreRuleOptions.js.map +1 -0
- package/lib/structures/rules/ITtscLintCoreRules.d.ts +101 -67
- package/lib/structures/rules/ITtscLintRegexpRules.d.ts +7 -2
- package/lib/structures/rules/ITtscLintRuleOptionsMap.d.ts +53 -7
- package/lib/structures/rules/ITtscLintRules.d.ts +6 -2
- package/lib/structures/rules/ITtscLintSolidRules.d.ts +0 -8
- package/lib/structures/rules/ITtscLintTypeScriptRuleOptions.d.ts +206 -0
- package/lib/structures/rules/ITtscLintTypeScriptRuleOptions.js +8 -0
- package/lib/structures/rules/ITtscLintTypeScriptRuleOptions.js.map +1 -0
- package/lib/structures/rules/ITtscLintTypeScriptRules.d.ts +55 -42
- package/lib/structures/rules/ITtscLintUnicornRuleOptions.d.ts +276 -0
- package/lib/structures/rules/ITtscLintUnicornRuleOptions.js +3 -0
- package/lib/structures/rules/ITtscLintUnicornRuleOptions.js.map +1 -0
- package/lib/structures/rules/ITtscLintUnicornRules.d.ts +44 -36
- package/lib/structures/rules/index.d.ts +3 -0
- package/lib/structures/rules/index.js +3 -0
- package/lib/structures/rules/index.js.map +1 -1
- package/linthost/ast_helpers.go +521 -23
- package/linthost/ast_selector_matcher.go +1628 -0
- package/linthost/ast_selector_parser.go +628 -0
- package/linthost/comment_scan.go +154 -0
- package/linthost/compile.go +129 -79
- package/linthost/config.go +352 -88
- package/linthost/config_format.go +15 -1
- package/linthost/contrib_adapter.go +47 -8
- package/linthost/declaration_rules.go +12 -11
- package/linthost/directives.go +9 -67
- package/linthost/dispatch.go +37 -3
- package/linthost/engine.go +368 -53
- package/linthost/escape_string.go +77 -0
- package/linthost/fix.go +133 -23
- package/linthost/flags_gen.go +18 -17
- package/linthost/format.go +73 -167
- package/linthost/format_editor_settings.go +134 -26
- package/linthost/grapheme_tables_gen.go +1920 -0
- package/linthost/host.go +159 -61
- package/linthost/literal_escape_scan.go +117 -0
- package/linthost/lsp.go +545 -98
- package/linthost/numeric_literal_precision.go +228 -0
- package/linthost/polyfill_browserslist.go +1439 -0
- package/linthost/polyfill_browserslist_config.go +603 -0
- package/linthost/polyfill_corejs.go +750 -0
- package/linthost/polyfill_data.go +113 -0
- package/linthost/polyfill_data_gen.json +39868 -0
- package/linthost/print_nodes_array.go +7 -1
- package/linthost/print_nodes_object.go +7 -1
- package/linthost/project_engine.go +231 -0
- package/linthost/project_rules.go +78 -0
- package/linthost/regex_clean.go +191 -0
- package/linthost/regex_tree.go +1544 -0
- package/linthost/regex_tree_optimizer.go +1685 -0
- package/linthost/regexp_cache.go +44 -0
- package/linthost/rule_codes.go +105 -0
- package/linthost/rule_codes.json +745 -0
- package/linthost/rules_ban_ts_comment.go +312 -0
- package/linthost/rules_boundaries.go +128 -43
- package/linthost/rules_boundaries_dependencies.go +1013 -0
- package/linthost/rules_core_extra.go +62 -92
- package/linthost/rules_cypress.go +1 -1
- package/linthost/rules_default_case.go +87 -11
- package/linthost/rules_dupes.go +38 -13
- package/linthost/rules_empty.go +188 -20
- package/linthost/rules_escape.go +62 -48
- package/linthost/rules_format_arrow_parens.go +19 -5
- package/linthost/rules_format_bracket_spacing.go +1 -1
- package/linthost/rules_format_clause_join.go +1 -1
- package/linthost/rules_format_declaration_header.go +31 -19
- package/linthost/rules_format_indent.go +1 -1
- package/linthost/rules_format_jsdoc.go +1 -1
- package/linthost/rules_format_orphan_semi.go +1 -1
- package/linthost/rules_format_parameter_properties.go +17 -7
- package/linthost/rules_format_print_width.go +1 -1
- package/linthost/rules_format_quote_props.go +1 -1
- package/linthost/rules_format_quotes.go +1 -1
- package/linthost/rules_format_semi.go +106 -79
- package/linthost/rules_format_sort_imports.go +79 -45
- package/linthost/rules_format_statement_split.go +1 -1
- package/linthost/rules_format_ternary_nullish_parens.go +1 -1
- package/linthost/rules_format_trailing_comma.go +64 -1
- package/linthost/rules_format_whitespace.go +1 -1
- package/linthost/rules_functional.go +21 -22
- package/linthost/rules_gap.go +80 -3
- package/linthost/rules_grouped_accessor_pairs.go +84 -25
- package/linthost/rules_guard_for_in.go +64 -147
- package/linthost/rules_jsx_a11y.go +232 -22
- package/linthost/rules_logic.go +84 -38
- package/linthost/rules_nextjs.go +105 -16
- package/linthost/rules_no_class_assign.go +70 -0
- package/linthost/rules_no_duplicate_imports.go +266 -0
- package/linthost/rules_no_else_return.go +156 -38
- package/linthost/rules_no_extend_native.go +113 -17
- package/linthost/rules_no_fallthrough.go +1013 -0
- package/linthost/rules_no_func_assign.go +70 -0
- package/linthost/rules_no_import_assign.go +278 -86
- package/linthost/rules_no_inner_declarations.go +186 -0
- package/linthost/rules_no_loop_func.go +438 -53
- package/linthost/rules_no_mixed_operators.go +208 -52
- package/linthost/rules_no_param_reassign.go +376 -61
- package/linthost/rules_no_promise_executor_return.go +156 -0
- package/linthost/rules_no_restricted_imports.go +1013 -57
- package/linthost/rules_no_restricted_syntax.go +190 -26
- package/linthost/rules_no_useless_constructor.go +6 -36
- package/linthost/rules_problems.go +154 -311
- package/linthost/rules_promise.go +2578 -109
- package/linthost/rules_react_extras.go +71 -45
- package/linthost/rules_react_hooks.go +18 -2
- package/linthost/rules_react_perf.go +1 -0
- package/linthost/rules_react_refresh.go +1 -1
- package/linthost/rules_regexp.go +230 -14
- package/linthost/rules_security.go +45 -19
- package/linthost/rules_self.go +21 -1
- package/linthost/rules_solid.go +13 -7
- package/linthost/rules_storybook.go +1 -1
- package/linthost/rules_suggestions.go +490 -84
- package/linthost/rules_testing_library.go +12 -1
- package/linthost/rules_ts.go +227 -54
- package/linthost/rules_ts_async.go +0 -131
- package/linthost/rules_ts_no_base_to_string.go +3 -4
- package/linthost/rules_ts_no_misused_promises.go +817 -0
- package/linthost/rules_ts_no_restricted_types.go +320 -34
- package/linthost/rules_ts_no_unnecessary_qualifier.go +132 -55
- package/linthost/rules_ts_no_unsafe_assignment.go +608 -60
- package/linthost/rules_ts_require_array_sort_compare.go +5 -5
- package/linthost/rules_ts_switch_exhaustiveness_check.go +556 -86
- package/linthost/rules_unicorn_better_regex.go +224 -16
- package/linthost/rules_unicorn_consistent_destructuring.go +1002 -7
- package/linthost/rules_unicorn_consistent_existence_index_check.go +208 -29
- package/linthost/rules_unicorn_consistent_function_scoping.go +1041 -8
- package/linthost/rules_unicorn_consistent_template_literal_escape.go +178 -7
- package/linthost/rules_unicorn_escape_case.go +30 -19
- package/linthost/rules_unicorn_filename_case.go +763 -16
- package/linthost/rules_unicorn_import_style.go +783 -17
- package/linthost/rules_unicorn_isolated_functions.go +934 -14
- package/linthost/rules_unicorn_no_hex_escape.go +26 -11
- package/linthost/rules_unicorn_no_typeof_undefined.go +234 -25
- package/linthost/rules_unicorn_no_unnecessary_polyfills.go +376 -21
- package/linthost/rules_unicorn_no_unused_properties.go +756 -19
- package/linthost/rules_unicorn_number_literal_case.go +58 -39
- package/linthost/rules_unicorn_prefer_at.go +7 -3
- package/linthost/rules_unicorn_prefer_number_properties.go +381 -48
- package/linthost/rules_unicorn_prefer_optional_catch_binding.go +24 -24
- package/linthost/rules_unicorn_prefer_simple_condition_first.go +597 -19
- package/linthost/rules_unicorn_prefer_string_raw.go +147 -16
- package/linthost/rules_unicorn_prevent_abbreviations.go +1963 -95
- package/linthost/rules_unicorn_string_content.go +611 -15
- package/linthost/rules_unicorn_switch_case_break_position.go +172 -15
- package/linthost/rules_unicorn_template_indent.go +923 -18
- package/linthost/rules_unicorn_text_encoding_identifier_case.go +202 -42
- package/linthost/rules_unicorn_throw_new_error.go +218 -26
- package/linthost/rules_var.go +829 -142
- package/linthost/string_length.go +222 -0
- package/package.json +4 -3
- package/rule/project.go +206 -0
- package/rule/rule.go +58 -1
- package/src/index.ts +58 -17
- package/src/structures/ITtscLintConfig.ts +3 -0
- package/src/structures/TtscLintRuleSetting.ts +3 -2
- package/src/structures/format/ITtscLintFormat.ts +3 -2
- package/src/structures/format/ITtscLintFormatSortImports.ts +21 -4
- package/src/structures/rules/ITtscLintBoundariesRuleOptions.ts +122 -8
- package/src/structures/rules/ITtscLintBoundariesRules.ts +6 -8
- package/src/structures/rules/ITtscLintContributorRules.ts +18 -2
- package/src/structures/rules/ITtscLintCoreRuleOptions.ts +465 -0
- package/src/structures/rules/ITtscLintCoreRules.ts +122 -67
- package/src/structures/rules/ITtscLintRegexpRules.ts +7 -2
- package/src/structures/rules/ITtscLintRuleOptionsMap.ts +88 -7
- package/src/structures/rules/ITtscLintRules.ts +7 -2
- package/src/structures/rules/ITtscLintSolidRules.ts +0 -9
- package/src/structures/rules/ITtscLintTypeScriptRuleOptions.ts +248 -0
- package/src/structures/rules/ITtscLintTypeScriptRules.ts +64 -42
- package/src/structures/rules/ITtscLintUnicornRuleOptions.ts +350 -0
- package/src/structures/rules/ITtscLintUnicornRules.ts +60 -36
- package/src/structures/rules/index.ts +3 -0
package/linthost/host.go
CHANGED
|
@@ -13,8 +13,10 @@ import (
|
|
|
13
13
|
"context"
|
|
14
14
|
"errors"
|
|
15
15
|
"fmt"
|
|
16
|
+
"os"
|
|
16
17
|
"path/filepath"
|
|
17
18
|
"strings"
|
|
19
|
+
"sync/atomic"
|
|
18
20
|
|
|
19
21
|
shimast "github.com/microsoft/typescript-go/shim/ast"
|
|
20
22
|
"github.com/microsoft/typescript-go/shim/bundled"
|
|
@@ -25,24 +27,29 @@ import (
|
|
|
25
27
|
shimtspath "github.com/microsoft/typescript-go/shim/tspath"
|
|
26
28
|
"github.com/microsoft/typescript-go/shim/vfs/cachedvfs"
|
|
27
29
|
"github.com/microsoft/typescript-go/shim/vfs/osvfs"
|
|
30
|
+
|
|
31
|
+
publicrule "github.com/samchon/ttsc/packages/lint/rule"
|
|
28
32
|
)
|
|
29
33
|
|
|
30
|
-
|
|
31
|
-
|
|
34
|
+
var programLifecycleSequence atomic.Uint64
|
|
35
|
+
|
|
36
|
+
// program bundles the tsgo Program with the parsed config and the standalone
|
|
37
|
+
// checker used only by type-aware lint rules.
|
|
32
38
|
type program struct {
|
|
33
|
-
cwd
|
|
34
|
-
tsProgram
|
|
35
|
-
parsed
|
|
36
|
-
checker
|
|
37
|
-
|
|
39
|
+
cwd string
|
|
40
|
+
tsProgram *shimcompiler.Program
|
|
41
|
+
parsed *tsoptions.ParsedCommandLine
|
|
42
|
+
checker *shimchecker.Checker
|
|
43
|
+
identity publicrule.ProjectIdentity
|
|
44
|
+
projectCycle *projectCycle
|
|
38
45
|
}
|
|
39
46
|
|
|
40
47
|
type loadProgramOptions struct {
|
|
41
48
|
forceEmit bool
|
|
42
49
|
forceNoEmit bool
|
|
43
50
|
outDir string
|
|
44
|
-
// needsRuleChecker asks loadProgram to
|
|
45
|
-
//
|
|
51
|
+
// needsRuleChecker asks loadProgram to create the standalone checker that
|
|
52
|
+
// type-aware lint rules receive through Context.Checker.
|
|
46
53
|
needsRuleChecker bool
|
|
47
54
|
// singleThreaded mirrors `tsgo --singleThreaded`: one checker, serial
|
|
48
55
|
// parse/check/emit.
|
|
@@ -54,11 +61,12 @@ type loadProgramOptions struct {
|
|
|
54
61
|
// `--target es2020`, …). They are parsed through TypeScript-Go's own
|
|
55
62
|
// command-line parser into a CompilerOptions overlay that wins over the
|
|
56
63
|
// tsconfig, exactly as tsgo's CLI merges them.
|
|
57
|
-
tsgoArgs
|
|
64
|
+
tsgoArgs []string
|
|
65
|
+
projectIdentity publicrule.ProjectIdentity
|
|
58
66
|
}
|
|
59
67
|
|
|
60
68
|
// loadProgram parses the given tsconfig and builds a Program. When
|
|
61
|
-
// needsRuleChecker is set, it also
|
|
69
|
+
// needsRuleChecker is set, it also creates a standalone checker for lint rules.
|
|
62
70
|
// Mirrors the canonical bootstrap pattern from
|
|
63
71
|
// `03-tsgo.md` — the only ttsc-specific bit is that `forceEmit`/
|
|
64
72
|
// `forceNoEmit`/`outDir` overrides are merged into the parsed config
|
|
@@ -111,20 +119,15 @@ func loadProgram(cwd, tsconfigPath string, options loadProgramOptions) (*program
|
|
|
111
119
|
overrideOutDir(cwd, parsed, options.outDir)
|
|
112
120
|
}
|
|
113
121
|
applyThreading(parsed, options.singleThreaded, options.checkers)
|
|
114
|
-
if options.needsRuleChecker {
|
|
115
|
-
forceSingleChecker(parsed)
|
|
116
|
-
}
|
|
117
122
|
|
|
118
|
-
//
|
|
119
|
-
//
|
|
120
|
-
//
|
|
121
|
-
//
|
|
122
|
-
//
|
|
123
|
-
//
|
|
124
|
-
//
|
|
125
|
-
//
|
|
126
|
-
// indexed-access alias) resolves to `any` on the borrowed checker. AST-only
|
|
127
|
-
// lint rules do not receive a checker, so they keep the user's checker pool.
|
|
123
|
+
// Keep the user's checker pool intact for parallel semantic diagnostics.
|
|
124
|
+
// Type-aware lint rules cannot borrow one member of that pool: they walk
|
|
125
|
+
// every source file through a single Context.Checker, while TypeScript-Go
|
|
126
|
+
// affinitizes files to different pool members and forbids mixing their type
|
|
127
|
+
// graphs. Instead, lint owns a standalone checker over the same Program.
|
|
128
|
+
// Every lint type is then produced by one checker, while the Program's pool
|
|
129
|
+
// remains free to check its file groups in parallel. The engine serializes
|
|
130
|
+
// type-aware walks, so this dedicated checker is never accessed concurrently.
|
|
128
131
|
tsProgram := shimcompiler.NewProgram(shimcompiler.ProgramOptions{
|
|
129
132
|
Config: parsed,
|
|
130
133
|
Host: host,
|
|
@@ -134,29 +137,145 @@ func loadProgram(cwd, tsconfigPath string, options loadProgramOptions) (*program
|
|
|
134
137
|
return nil, nil, errors.New("compiler.NewProgram returned nil")
|
|
135
138
|
}
|
|
136
139
|
var checker *shimchecker.Checker
|
|
137
|
-
var release func()
|
|
138
140
|
if options.needsRuleChecker {
|
|
139
|
-
checker,
|
|
141
|
+
checker, _ = shimchecker.NewChecker(tsProgram, nil)
|
|
140
142
|
}
|
|
141
143
|
return &program{
|
|
142
|
-
cwd:
|
|
143
|
-
tsProgram:
|
|
144
|
-
parsed:
|
|
145
|
-
checker:
|
|
146
|
-
|
|
144
|
+
cwd: cwd,
|
|
145
|
+
tsProgram: tsProgram,
|
|
146
|
+
parsed: parsed,
|
|
147
|
+
checker: checker,
|
|
148
|
+
identity: normalizeProjectIdentity(options.projectIdentity, cwd, resolved),
|
|
147
149
|
}, nil, nil
|
|
148
150
|
}
|
|
149
151
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
+
func normalizeProjectIdentity(
|
|
153
|
+
identity publicrule.ProjectIdentity,
|
|
154
|
+
cwd string,
|
|
155
|
+
configPath string,
|
|
156
|
+
) publicrule.ProjectIdentity {
|
|
157
|
+
if identity.InvocationCwd == "" {
|
|
158
|
+
identity.InvocationCwd = cwd
|
|
159
|
+
}
|
|
160
|
+
if identity.LogicalConfigPath == "" {
|
|
161
|
+
identity.LogicalConfigPath = absoluteProjectPath(identity.InvocationCwd, configPath)
|
|
162
|
+
}
|
|
163
|
+
if identity.LogicalProjectRoot == "" {
|
|
164
|
+
identity.LogicalProjectRoot = filepath.Dir(identity.LogicalConfigPath)
|
|
165
|
+
}
|
|
166
|
+
if identity.PhysicalConfigPath == "" {
|
|
167
|
+
identity.PhysicalConfigPath = realProjectPath(absoluteProjectPath(cwd, configPath))
|
|
168
|
+
}
|
|
169
|
+
if identity.PhysicalProjectRoot == "" {
|
|
170
|
+
identity.PhysicalProjectRoot = realProjectPath(cwd)
|
|
171
|
+
}
|
|
172
|
+
if identity.PluginConfigOrigin == "" {
|
|
173
|
+
if origin := os.Getenv("TTSC_PLUGIN_CONFIG_DIR"); origin != "" {
|
|
174
|
+
identity.PluginConfigOrigin = absoluteProjectPath(identity.InvocationCwd, origin)
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
identity.LifecycleID = fmt.Sprintf(
|
|
178
|
+
"%d:%d",
|
|
179
|
+
os.Getpid(),
|
|
180
|
+
programLifecycleSequence.Add(1),
|
|
181
|
+
)
|
|
182
|
+
return identity
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
func absoluteProjectPath(cwd string, target string) string {
|
|
186
|
+
if filepath.IsAbs(target) {
|
|
187
|
+
return filepath.Clean(target)
|
|
188
|
+
}
|
|
189
|
+
return filepath.Clean(filepath.Join(cwd, target))
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
func realProjectPath(target string) string {
|
|
193
|
+
original := filepath.Clean(target)
|
|
194
|
+
resolved := original
|
|
195
|
+
seen := make(map[string]struct{})
|
|
196
|
+
for range 255 {
|
|
197
|
+
key := filepath.Clean(resolved)
|
|
198
|
+
if _, exists := seen[key]; exists {
|
|
199
|
+
return original
|
|
200
|
+
}
|
|
201
|
+
seen[key] = struct{}{}
|
|
202
|
+
|
|
203
|
+
next, ok := resolveProjectPathAncestor(resolved)
|
|
204
|
+
if !ok {
|
|
205
|
+
return filepath.Clean(resolved)
|
|
206
|
+
}
|
|
207
|
+
resolved = next
|
|
208
|
+
}
|
|
209
|
+
return original
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// resolveProjectPathAncestor resolves the nearest existing or symlink-like
|
|
213
|
+
// ancestor and reattaches the remaining path. Besides ordinary links, this
|
|
214
|
+
// lets Windows expand an 8.3 short-name ancestor even when the target itself
|
|
215
|
+
// has not been created yet. Some directory junctions are readable through
|
|
216
|
+
// os.Readlink while EvalSymlinks either leaves them unchanged or fails on their
|
|
217
|
+
// children, so both resolution paths are retained.
|
|
218
|
+
func resolveProjectPathAncestor(target string) (string, bool) {
|
|
219
|
+
original := filepath.Clean(target)
|
|
220
|
+
probe := original
|
|
221
|
+
suffix := make([]string, 0)
|
|
222
|
+
evaluateAncestor := true
|
|
223
|
+
for {
|
|
224
|
+
if evaluateAncestor {
|
|
225
|
+
if evaluated, err := filepath.EvalSymlinks(probe); err == nil {
|
|
226
|
+
// EvalSymlinks resolves the probe's complete ancestry. If it leaves
|
|
227
|
+
// the spelling unchanged, retrying it on every parent cannot reveal
|
|
228
|
+
// anything new; retain only the os.Readlink walk for junctions that
|
|
229
|
+
// EvalSymlinks does not expose.
|
|
230
|
+
evaluateAncestor = false
|
|
231
|
+
candidate := filepath.Clean(evaluated)
|
|
232
|
+
for i := len(suffix) - 1; i >= 0; i-- {
|
|
233
|
+
candidate = filepath.Join(candidate, suffix[i])
|
|
234
|
+
}
|
|
235
|
+
candidate = filepath.Clean(candidate)
|
|
236
|
+
if candidate != original {
|
|
237
|
+
return candidate, true
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
if _, err := os.Readlink(probe); err == nil {
|
|
242
|
+
destination := resolveDirLink(probe)
|
|
243
|
+
for i := len(suffix) - 1; i >= 0; i-- {
|
|
244
|
+
destination = filepath.Join(destination, suffix[i])
|
|
245
|
+
}
|
|
246
|
+
destination = filepath.Clean(destination)
|
|
247
|
+
if destination != original {
|
|
248
|
+
return destination, true
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
parent := filepath.Dir(probe)
|
|
252
|
+
if parent == probe {
|
|
253
|
+
return "", false
|
|
254
|
+
}
|
|
255
|
+
suffix = append(suffix, filepath.Base(probe))
|
|
256
|
+
probe = parent
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
func (p *program) runLintCycle(engine *Engine) []*Finding {
|
|
261
|
+
if p == nil || engine == nil {
|
|
262
|
+
return nil
|
|
263
|
+
}
|
|
264
|
+
files := p.userSourceFiles()
|
|
265
|
+
if p.projectCycle == nil {
|
|
266
|
+
p.projectCycle = engine.evaluateProject(p.identity, files, p.checker)
|
|
267
|
+
}
|
|
268
|
+
fileFindings := engine.runFiles(files, p.checker, p.projectCycle.results, p.cwd)
|
|
269
|
+
return append(p.projectCycle.finalize(), fileFindings...)
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// close drops the standalone lint checker. Safe to call on a nil receiver and
|
|
273
|
+
// idempotent after the first call.
|
|
152
274
|
func (p *program) close() {
|
|
153
275
|
if p == nil {
|
|
154
276
|
return
|
|
155
277
|
}
|
|
156
|
-
|
|
157
|
-
p.releaseChecker()
|
|
158
|
-
p.releaseChecker = nil
|
|
159
|
-
}
|
|
278
|
+
p.checker = nil
|
|
160
279
|
}
|
|
161
280
|
|
|
162
281
|
// userSourceFiles returns the tsconfig-selected source files the lint engine
|
|
@@ -290,10 +409,9 @@ func parseTsgoArgs(args []string, host shimcompiler.CompilerHost) (*shimcore.Com
|
|
|
290
409
|
// CompilerOptions, and both Program.SingleThreaded() and the checker pool read
|
|
291
410
|
// them from there. SingleThreaded wins over Checkers, matching the pool.
|
|
292
411
|
//
|
|
293
|
-
//
|
|
294
|
-
//
|
|
295
|
-
//
|
|
296
|
-
// checker count. `--singleThreaded` still takes full effect.
|
|
412
|
+
// Type-aware lint rules use their own standalone checker, so this pool size is
|
|
413
|
+
// preserved for the Program's semantic diagnostics. `--singleThreaded` still
|
|
414
|
+
// takes full effect across the Program and the serial type-aware lint walk.
|
|
297
415
|
func applyThreading(parsed *tsoptions.ParsedCommandLine, singleThreaded bool, checkers int) {
|
|
298
416
|
if parsed == nil || parsed.ParsedConfig == nil || parsed.ParsedConfig.CompilerOptions == nil {
|
|
299
417
|
return
|
|
@@ -308,26 +426,6 @@ func applyThreading(parsed *tsoptions.ParsedCommandLine, singleThreaded bool, ch
|
|
|
308
426
|
}
|
|
309
427
|
}
|
|
310
428
|
|
|
311
|
-
// forceSingleChecker pins the TypeScript-Go checker pool to a single checker.
|
|
312
|
-
//
|
|
313
|
-
// The lint engine walks the program serially and obtains types through the
|
|
314
|
-
// single checker GetTypeChecker hands back. Rules query types on nodes from
|
|
315
|
-
// arbitrary source files, so the checker must be the same one that checked
|
|
316
|
-
// every file. A pool of size > 1 affinitizes files to distinct checkers;
|
|
317
|
-
// resolving a type whose declarations cross that boundary yields `any`.
|
|
318
|
-
// Parallel parsing and emit are unaffected — they do not consult the count.
|
|
319
|
-
func forceSingleChecker(parsed *tsoptions.ParsedCommandLine) {
|
|
320
|
-
if parsed == nil || parsed.ParsedConfig == nil || parsed.ParsedConfig.CompilerOptions == nil {
|
|
321
|
-
return
|
|
322
|
-
}
|
|
323
|
-
options := parsed.ParsedConfig.CompilerOptions
|
|
324
|
-
if options.SingleThreaded == shimcore.TSTrue {
|
|
325
|
-
return
|
|
326
|
-
}
|
|
327
|
-
one := 1
|
|
328
|
-
options.Checkers = &one
|
|
329
|
-
}
|
|
330
|
-
|
|
331
429
|
// overrideOutDir replaces the parsed config's OutDir with `outDir`.
|
|
332
430
|
// Relative outDir values are resolved against `cwd`; absolute paths are
|
|
333
431
|
// used as-is. Paths are converted to forward slashes for tsgo
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
// Raw-source scanning for the numeric escape sequences (`\xHH`, `\uHHHH`,
|
|
2
|
+
// `\u{HEX...}`) that `unicorn/no-hex-escape` and `unicorn/escape-case`
|
|
3
|
+
// police. Both rules read the raw literal source — the parser already
|
|
4
|
+
// decodes escapes into the `.Text` value, so a normal accessor would see
|
|
5
|
+
// `©` instead of `\xA9` — and both therefore need the same two guards a
|
|
6
|
+
// regex port cannot express in RE2:
|
|
7
|
+
//
|
|
8
|
+
// - Backslash parity: only a backslash preceded by an even-length
|
|
9
|
+
// backslash run opens an escape. Upstream spells this as the
|
|
10
|
+
// lookbehind `(?<=(?:^|[^\\])(?:\\\\)*\\)` and, in its byte-scanning
|
|
11
|
+
// rules, as `isActiveBackslash`. Without it, the second backslash of
|
|
12
|
+
// an escaped `\\` donates itself to a bogus match and `"\\x64"` (a
|
|
13
|
+
// backslash followed by the literal text `x64`) reports.
|
|
14
|
+
// - Fixed-width digit runs: `\x` takes exactly two hex digits and `\u`
|
|
15
|
+
// exactly four, so an escape never absorbs the literal hex-looking
|
|
16
|
+
// characters that follow it (`"\x41bcd"` is a canonical `\x41` plus
|
|
17
|
+
// the letters `bcd`). Only the braced `\u{HEX...}` form is variable
|
|
18
|
+
// width, and its `}` terminates it.
|
|
19
|
+
//
|
|
20
|
+
// Byte scanning is UTF-8 safe: every byte the scanner tests is ASCII, and
|
|
21
|
+
// a multi-byte sequence contains no ASCII bytes.
|
|
22
|
+
package linthost
|
|
23
|
+
|
|
24
|
+
// literalEscape is one numeric escape sequence found in raw literal source.
|
|
25
|
+
// `Prefix` is the character introducing it (`x` for `\xHH`, `u` for both
|
|
26
|
+
// `\uHHHH` and `\u{HEX...}`) and `Digits` is its hex payload with the `\u{`
|
|
27
|
+
// / `}` brackets stripped.
|
|
28
|
+
type literalEscape struct {
|
|
29
|
+
Prefix byte
|
|
30
|
+
Digits string
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// hasActiveLiteralEscape reports whether `text` contains an escape sequence
|
|
34
|
+
// accepted by `match` whose backslash is active — preceded by an even-length
|
|
35
|
+
// run of backslashes.
|
|
36
|
+
//
|
|
37
|
+
// `text` is the raw source of one string literal or template element token,
|
|
38
|
+
// delimiters included. Including them changes nothing: a quote, backtick,
|
|
39
|
+
// `}`, or `${` is never a backslash, so the parity of every run inside the
|
|
40
|
+
// payload is the same as it would be for the delimiter-free raw value
|
|
41
|
+
// upstream reads off `TemplateElement.value.raw`.
|
|
42
|
+
func hasActiveLiteralEscape(text string, match func(escape literalEscape) bool) bool {
|
|
43
|
+
for index := 0; index < len(text); {
|
|
44
|
+
if text[index] != '\\' {
|
|
45
|
+
index++
|
|
46
|
+
continue
|
|
47
|
+
}
|
|
48
|
+
run := 0
|
|
49
|
+
for index < len(text) && text[index] == '\\' {
|
|
50
|
+
run++
|
|
51
|
+
index++
|
|
52
|
+
}
|
|
53
|
+
// An even-length run is a sequence of escaped backslashes: each one
|
|
54
|
+
// consumes the next, none is left over to open an escape, and the text
|
|
55
|
+
// behind the run is literal. Only the last backslash of an odd-length
|
|
56
|
+
// run is active, and it opens the escape at `index`.
|
|
57
|
+
if run%2 == 0 {
|
|
58
|
+
continue
|
|
59
|
+
}
|
|
60
|
+
if escape, ok := literalEscapeAt(text[index:]); ok && match(escape) {
|
|
61
|
+
return true
|
|
62
|
+
}
|
|
63
|
+
// `text[index]` is the escaped character, never a backslash (the run
|
|
64
|
+
// above consumed every one), so the outer loop may step over it.
|
|
65
|
+
}
|
|
66
|
+
return false
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// literalEscapeAt parses the escape sequence opened at `text[0]`, the byte
|
|
70
|
+
// right after an active backslash. It recognizes exactly the three numeric
|
|
71
|
+
// forms of upstream's `x[\dA-Fa-f]{2}|u[\dA-Fa-f]{4}|u{[\dA-Fa-f]+}`
|
|
72
|
+
// alternation; any other escape (`\n`, `\\`, `\$`, a line continuation, an
|
|
73
|
+
// identifier escape) is not a numeric escape and fails the parse.
|
|
74
|
+
func literalEscapeAt(text string) (literalEscape, bool) {
|
|
75
|
+
if text == "" {
|
|
76
|
+
return literalEscape{}, false
|
|
77
|
+
}
|
|
78
|
+
switch text[0] {
|
|
79
|
+
case 'x':
|
|
80
|
+
if digits, ok := literalEscapeDigits(text[1:], 2); ok {
|
|
81
|
+
return literalEscape{Prefix: 'x', Digits: digits}, true
|
|
82
|
+
}
|
|
83
|
+
case 'u':
|
|
84
|
+
// The braced form is the only variable-width escape. `{` is not a hex
|
|
85
|
+
// digit, so the fixed-width `\uHHHH` form can never match here and the
|
|
86
|
+
// brace branch owns the decision.
|
|
87
|
+
if len(text) > 1 && text[1] == '{' {
|
|
88
|
+
end := 2
|
|
89
|
+
for end < len(text) && hexDigit(text[end]) >= 0 {
|
|
90
|
+
end++
|
|
91
|
+
}
|
|
92
|
+
if end == 2 || end >= len(text) || text[end] != '}' {
|
|
93
|
+
return literalEscape{}, false
|
|
94
|
+
}
|
|
95
|
+
return literalEscape{Prefix: 'u', Digits: text[2:end]}, true
|
|
96
|
+
}
|
|
97
|
+
if digits, ok := literalEscapeDigits(text[1:], 4); ok {
|
|
98
|
+
return literalEscape{Prefix: 'u', Digits: digits}, true
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return literalEscape{}, false
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// literalEscapeDigits returns the first `count` bytes of `text` when every
|
|
105
|
+
// one of them is a hex digit — the fixed-width digit run of a `\xHH` or
|
|
106
|
+
// `\uHHHH` escape.
|
|
107
|
+
func literalEscapeDigits(text string, count int) (string, bool) {
|
|
108
|
+
if len(text) < count {
|
|
109
|
+
return "", false
|
|
110
|
+
}
|
|
111
|
+
for index := 0; index < count; index++ {
|
|
112
|
+
if hexDigit(text[index]) < 0 {
|
|
113
|
+
return "", false
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return text[:count], true
|
|
117
|
+
}
|