@ttsc/lint 0.18.4 → 0.19.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/README.md +95 -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 +160 -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 +151 -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 +145 -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/config.go
CHANGED
|
@@ -86,30 +86,64 @@ func FindLintEntry(entries []PluginEntry) (*PluginEntry, error) {
|
|
|
86
86
|
// rule name (e.g. "no-var").
|
|
87
87
|
type RuleConfig map[string]Severity
|
|
88
88
|
|
|
89
|
-
// RuleOptionsMap captures the rule-specific options
|
|
90
|
-
// name. Severity-only rules never appear here.
|
|
91
|
-
// JSON
|
|
92
|
-
//
|
|
93
|
-
// demand.
|
|
89
|
+
// RuleOptionsMap captures the rule-specific options payload, keyed by rule
|
|
90
|
+
// name. Severity-only rules never appear here. A single option slot preserves
|
|
91
|
+
// its JSON shape; multiple positional slots are encoded as an array. Each rule
|
|
92
|
+
// decodes the payload according to its public option type on demand.
|
|
94
93
|
type RuleOptionsMap map[string]json.RawMessage
|
|
95
94
|
|
|
96
|
-
//
|
|
95
|
+
// ProjectRuleSetting is the global declaration resolved for one registered
|
|
96
|
+
// project rule. Declared distinguishes a missing entry from an explicit off.
|
|
97
|
+
type ProjectRuleSetting struct {
|
|
98
|
+
Declared bool
|
|
99
|
+
Severity Severity
|
|
100
|
+
Options json.RawMessage
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// ResolvedRuleConfig is the complete rule setting that applies to one source
|
|
104
|
+
// file. Rules and Options are folded from the same matching config entries so
|
|
105
|
+
// an option tuple can never cross a files/ignores boundary independently of
|
|
106
|
+
// its severity.
|
|
107
|
+
//
|
|
97
108
|
// `Ignored` means an `ignores`-only config entry matched the file and the
|
|
98
|
-
// engine should skip linting it entirely.
|
|
109
|
+
// engine should skip linting it entirely. `OutOfScope` means the store has at
|
|
110
|
+
// least one rule-bearing entry but none applies to this file. Keeping the two
|
|
111
|
+
// states distinct lets wrappers preserve entry-local ignores: one entry may
|
|
112
|
+
// reject a file while another matching entry still contributes rules.
|
|
99
113
|
type ResolvedRuleConfig struct {
|
|
100
114
|
Rules RuleConfig
|
|
101
|
-
|
|
115
|
+
Options RuleOptionsMap
|
|
116
|
+
// OptionsResolved distinguishes an authoritative empty per-file option map
|
|
117
|
+
// from a legacy custom resolver that still supplies options exclusively via
|
|
118
|
+
// RuleResolver.RuleOptions.
|
|
119
|
+
OptionsResolved bool
|
|
120
|
+
Ignored bool
|
|
121
|
+
OutOfScope bool
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// RuleOptions returns the file-resolved option payload for name. Built-in
|
|
125
|
+
// aliases are normalized on lookup so the same key selects both severity and
|
|
126
|
+
// options.
|
|
127
|
+
func (r ResolvedRuleConfig) RuleOptions(name string) json.RawMessage {
|
|
128
|
+
if raw := r.Options[name]; len(raw) > 0 {
|
|
129
|
+
return raw
|
|
130
|
+
}
|
|
131
|
+
if raw := r.Options[normalizeBuiltinRuleName(name)]; len(raw) > 0 {
|
|
132
|
+
return raw
|
|
133
|
+
}
|
|
134
|
+
return nil
|
|
102
135
|
}
|
|
103
136
|
|
|
104
137
|
// RuleResolver is the engine-facing view of a resolved lint configuration.
|
|
105
138
|
// Implementations include RuleConfig (severity-only, no options),
|
|
106
139
|
// InlineRuleResolver (a severity map plus per-rule options), and *ConfigStore
|
|
107
|
-
// (a parsed lint config file, with per-file glob resolution
|
|
108
|
-
// options
|
|
140
|
+
// (a parsed lint config file, with per-file glob resolution for both severity
|
|
141
|
+
// and options).
|
|
109
142
|
type RuleResolver interface {
|
|
110
|
-
// ResolveRules returns the effective
|
|
111
|
-
// Implementations that support `files`/`ignores`
|
|
112
|
-
//
|
|
143
|
+
// ResolveRules returns the effective severities and option payloads for the
|
|
144
|
+
// given source file. Implementations that support `files`/`ignores`
|
|
145
|
+
// patterns apply both halves of each rule setting here; flat RuleConfig
|
|
146
|
+
// always returns all severities unchanged and no options.
|
|
113
147
|
ResolveRules(fileName string) ResolvedRuleConfig
|
|
114
148
|
// ActiveRuleNames returns the sorted names of every rule that is not SeverityOff
|
|
115
149
|
// in at least one config entry. Used to build the engine's dispatch table.
|
|
@@ -117,16 +151,78 @@ type RuleResolver interface {
|
|
|
117
151
|
// EnabledRuleConfig returns the project-wide severity map for rules that are
|
|
118
152
|
// not SeverityOff. Where multiple entries disagree, SeverityError wins.
|
|
119
153
|
EnabledRuleConfig() RuleConfig
|
|
120
|
-
// RuleOptions
|
|
121
|
-
//
|
|
122
|
-
//
|
|
154
|
+
// RuleOptions is the file-agnostic compatibility lookup used by flat and
|
|
155
|
+
// metadata-only consumers. Runtime file binding reads
|
|
156
|
+
// ResolveRules(fileName).RuleOptions(name), which is authoritative for
|
|
157
|
+
// scoped resolvers. Returns nil for severity-only and unknown rules.
|
|
123
158
|
RuleOptions(name string) json.RawMessage
|
|
159
|
+
// ResolveProjectRules folds global declarations for registered project-rule
|
|
160
|
+
// names. A mention under a files selector is rejected because project state
|
|
161
|
+
// has no file identity.
|
|
162
|
+
ResolveProjectRules(names []string) (map[string]ProjectRuleSetting, error)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// RuleOptionsVariantsResolver is an optional extension for resolvers that can
|
|
166
|
+
// declare more than one option payload for a rule. Engine construction uses
|
|
167
|
+
// it to validate every files/extends variant before any file is visited.
|
|
168
|
+
// Custom resolvers that omit this interface remain compatible through the
|
|
169
|
+
// single RuleResolver.RuleOptions fallback.
|
|
170
|
+
type RuleOptionsVariantsResolver interface {
|
|
171
|
+
RuleOptionsVariants(name string) []json.RawMessage
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// resolvedRuleOptionsVariants returns every option shape a rule may receive.
|
|
175
|
+
// File-scoped resolvers expose all declarations through the internal
|
|
176
|
+
// extension; flat and external resolvers retain the RuleOptions fallback.
|
|
177
|
+
func resolvedRuleOptionsVariants(resolver RuleResolver, name string) []json.RawMessage {
|
|
178
|
+
if variants, ok := resolver.(RuleOptionsVariantsResolver); ok {
|
|
179
|
+
if values := variants.RuleOptionsVariants(name); len(values) > 0 {
|
|
180
|
+
return values
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return []json.RawMessage{append(json.RawMessage(nil), resolver.RuleOptions(name)...)}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// boundProjectRuleResolver retains the one project-wide resolution performed
|
|
187
|
+
// while loading a config. Engine construction reads a defensive copy instead
|
|
188
|
+
// of folding the extends chain a second time.
|
|
189
|
+
type boundProjectRuleResolver struct {
|
|
190
|
+
RuleResolver
|
|
191
|
+
settings map[string]ProjectRuleSetting
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
func bindProjectRuleResolver(resolver RuleResolver) (RuleResolver, error) {
|
|
195
|
+
if resolver == nil {
|
|
196
|
+
resolver = RuleConfig{}
|
|
197
|
+
}
|
|
198
|
+
settings, err := resolver.ResolveProjectRules(allProjectRuleNames())
|
|
199
|
+
if err != nil {
|
|
200
|
+
return nil, err
|
|
201
|
+
}
|
|
202
|
+
return boundProjectRuleResolver{RuleResolver: resolver, settings: settings}, nil
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
func (r boundProjectRuleResolver) ResolveProjectRules(names []string) (map[string]ProjectRuleSetting, error) {
|
|
206
|
+
settings := make(map[string]ProjectRuleSetting, len(names))
|
|
207
|
+
for _, name := range names {
|
|
208
|
+
setting := r.settings[name]
|
|
209
|
+
setting.Options = append(json.RawMessage(nil), setting.Options...)
|
|
210
|
+
settings[name] = setting
|
|
211
|
+
}
|
|
212
|
+
return settings, nil
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
func (r boundProjectRuleResolver) RuleOptionsVariants(name string) []json.RawMessage {
|
|
216
|
+
return resolvedRuleOptionsVariants(r.RuleResolver, name)
|
|
124
217
|
}
|
|
125
218
|
|
|
126
219
|
// ResolveRules implements RuleResolver. A flat RuleConfig has no glob scoping,
|
|
127
220
|
// so every file receives the full map unchanged.
|
|
128
221
|
func (c RuleConfig) ResolveRules(string) ResolvedRuleConfig {
|
|
129
|
-
return ResolvedRuleConfig{
|
|
222
|
+
return ResolvedRuleConfig{
|
|
223
|
+
Rules: normalizeRuleConfigKeys(c),
|
|
224
|
+
OptionsResolved: true,
|
|
225
|
+
}
|
|
130
226
|
}
|
|
131
227
|
|
|
132
228
|
// ActiveRuleNames implements RuleResolver. Returns rule names whose severity
|
|
@@ -152,6 +248,17 @@ func (c RuleConfig) EnabledRuleConfig() RuleConfig {
|
|
|
152
248
|
// predate option support.
|
|
153
249
|
func (RuleConfig) RuleOptions(string) json.RawMessage { return nil }
|
|
154
250
|
|
|
251
|
+
// ResolveProjectRules treats a flat RuleConfig as one global declaration.
|
|
252
|
+
func (c RuleConfig) ResolveProjectRules(names []string) (map[string]ProjectRuleSetting, error) {
|
|
253
|
+
normalized := normalizeRuleConfigKeys(c)
|
|
254
|
+
out := make(map[string]ProjectRuleSetting, len(names))
|
|
255
|
+
for _, name := range names {
|
|
256
|
+
severity, declared := normalized[normalizeBuiltinRuleName(name)]
|
|
257
|
+
out[name] = ProjectRuleSetting{Declared: declared, Severity: severity}
|
|
258
|
+
}
|
|
259
|
+
return out, nil
|
|
260
|
+
}
|
|
261
|
+
|
|
155
262
|
func normalizeRuleConfigKeys(c RuleConfig) RuleConfig {
|
|
156
263
|
if len(c) == 0 {
|
|
157
264
|
return c
|
|
@@ -174,7 +281,22 @@ type InlineRuleResolver struct {
|
|
|
174
281
|
// ResolveRules implements RuleResolver. Inline rules have no glob scoping;
|
|
175
282
|
// the full map applies to every file.
|
|
176
283
|
func (r InlineRuleResolver) ResolveRules(string) ResolvedRuleConfig {
|
|
177
|
-
return ResolvedRuleConfig{
|
|
284
|
+
return ResolvedRuleConfig{
|
|
285
|
+
Rules: normalizeRuleConfigKeys(r.Rules),
|
|
286
|
+
Options: normalizeRuleOptionsKeys(r.Options),
|
|
287
|
+
OptionsResolved: true,
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
func normalizeRuleOptionsKeys(options RuleOptionsMap) RuleOptionsMap {
|
|
292
|
+
if len(options) == 0 {
|
|
293
|
+
return nil
|
|
294
|
+
}
|
|
295
|
+
normalized := make(RuleOptionsMap, len(options))
|
|
296
|
+
for name, raw := range options {
|
|
297
|
+
normalized[normalizeBuiltinRuleName(name)] = append(json.RawMessage(nil), raw...)
|
|
298
|
+
}
|
|
299
|
+
return normalized
|
|
178
300
|
}
|
|
179
301
|
|
|
180
302
|
// ActiveRuleNames implements RuleResolver by delegating to the inner RuleConfig.
|
|
@@ -204,11 +326,25 @@ func (r InlineRuleResolver) RuleOptions(name string) json.RawMessage {
|
|
|
204
326
|
return nil
|
|
205
327
|
}
|
|
206
328
|
|
|
329
|
+
// ResolveProjectRules treats inline rules as global and preserves their
|
|
330
|
+
// explicit options tuple.
|
|
331
|
+
func (r InlineRuleResolver) ResolveProjectRules(names []string) (map[string]ProjectRuleSetting, error) {
|
|
332
|
+
settings, _ := r.Rules.ResolveProjectRules(names)
|
|
333
|
+
for _, name := range names {
|
|
334
|
+
setting := settings[name]
|
|
335
|
+
setting.Options = append(json.RawMessage(nil), r.RuleOptions(name)...)
|
|
336
|
+
settings[name] = setting
|
|
337
|
+
}
|
|
338
|
+
return settings, nil
|
|
339
|
+
}
|
|
340
|
+
|
|
207
341
|
// ConfigStore holds the parsed representation of a lint config file. It
|
|
208
342
|
// implements RuleResolver with per-file glob scoping: ResolveRules walks the
|
|
209
|
-
// entries in declaration order and
|
|
210
|
-
//
|
|
211
|
-
//
|
|
343
|
+
// entries in declaration order and folds each matching rule's severity and
|
|
344
|
+
// options together. A later option tuple replaces the inherited payload; a
|
|
345
|
+
// later severity-only declaration preserves options from an earlier matching
|
|
346
|
+
// entry and cannot borrow them from an entry that did not match the file.
|
|
347
|
+
// https://eslint.org/docs/latest/use/configure/rules#using-configuration-files
|
|
212
348
|
//
|
|
213
349
|
// A config file is a single `ITtscLintConfig` object. Its `extends` field
|
|
214
350
|
// names another config file to fold in first; the extends chain produces one
|
|
@@ -216,27 +352,64 @@ func (r InlineRuleResolver) RuleOptions(name string) json.RawMessage {
|
|
|
216
352
|
// extending file's own entry so local rules win on collision.
|
|
217
353
|
type ConfigStore struct {
|
|
218
354
|
entries []ConfigEntry
|
|
219
|
-
// options is a flat rule-name → JSON map. Options are not scoped by
|
|
220
|
-
// `files` / `ignores`: a rule's behavior is a single project-wide
|
|
221
|
-
// configuration even when its severity is per-file. The simplification
|
|
222
|
-
// matches prettier-style options (one setting per project) while
|
|
223
|
-
// keeping severity layering intact.
|
|
224
|
-
options RuleOptionsMap
|
|
225
355
|
}
|
|
226
356
|
|
|
227
|
-
// RuleOptions implements RuleResolver
|
|
357
|
+
// RuleOptions implements the file-agnostic RuleResolver compatibility method.
|
|
358
|
+
// Engine execution does not use this representative value: ResolveRules
|
|
359
|
+
// carries the matching file's options. Callers that only understand the older
|
|
360
|
+
// interface observe the final declared tuple, preserving the former flat
|
|
361
|
+
// resolver behavior without storing a second source of truth.
|
|
228
362
|
func (s *ConfigStore) RuleOptions(name string) json.RawMessage {
|
|
229
363
|
if s == nil {
|
|
230
364
|
return nil
|
|
231
365
|
}
|
|
232
|
-
|
|
233
|
-
|
|
366
|
+
canonical := normalizeBuiltinRuleName(name)
|
|
367
|
+
var selected json.RawMessage
|
|
368
|
+
for _, entry := range s.entries {
|
|
369
|
+
if raw := entry.Options[canonical]; len(raw) > 0 {
|
|
370
|
+
selected = raw
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
return append(json.RawMessage(nil), selected...)
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// flattenOptions returns the final declared payload for each rule without
|
|
377
|
+
// claiming that it applies to any particular file. Metadata-only consumers
|
|
378
|
+
// use this to enumerate option-bearing rules; execution always uses
|
|
379
|
+
// ResolveRules instead.
|
|
380
|
+
func (s *ConfigStore) flattenOptions() RuleOptionsMap {
|
|
381
|
+
if s == nil {
|
|
382
|
+
return nil
|
|
383
|
+
}
|
|
384
|
+
options := RuleOptionsMap{}
|
|
385
|
+
for _, entry := range s.entries {
|
|
386
|
+
for name, raw := range entry.Options {
|
|
387
|
+
options[normalizeBuiltinRuleName(name)] = append(json.RawMessage(nil), raw...)
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
return options
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// RuleOptionsVariants exposes every entry-local payload (including a nil
|
|
394
|
+
// severity-only declaration) so engine construction validates the full
|
|
395
|
+
// files/extends surface rather than whichever tuple happened to be parsed
|
|
396
|
+
// last.
|
|
397
|
+
func (s *ConfigStore) RuleOptionsVariants(name string) []json.RawMessage {
|
|
398
|
+
if s == nil {
|
|
399
|
+
return nil
|
|
234
400
|
}
|
|
235
401
|
canonical := normalizeBuiltinRuleName(name)
|
|
236
|
-
|
|
237
|
-
|
|
402
|
+
variants := make([]json.RawMessage, 0)
|
|
403
|
+
for _, entry := range s.entries {
|
|
404
|
+
if entry.IgnoreOnly {
|
|
405
|
+
continue
|
|
406
|
+
}
|
|
407
|
+
if _, declared := entry.Rules[canonical]; !declared {
|
|
408
|
+
continue
|
|
409
|
+
}
|
|
410
|
+
variants = append(variants, append(json.RawMessage(nil), entry.Options[canonical]...))
|
|
238
411
|
}
|
|
239
|
-
return
|
|
412
|
+
return variants
|
|
240
413
|
}
|
|
241
414
|
|
|
242
415
|
// ConfigEntry is the parsed form of one config file in the extends chain.
|
|
@@ -245,11 +418,13 @@ func (s *ConfigStore) RuleOptions(name string) json.RawMessage {
|
|
|
245
418
|
// — these are evaluated first in ResolveRules and short-circuit the walk when
|
|
246
419
|
// matched.
|
|
247
420
|
type ConfigEntry struct {
|
|
248
|
-
BaseDir
|
|
249
|
-
Files
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
421
|
+
BaseDir string
|
|
422
|
+
Files []string
|
|
423
|
+
HasFilesSelector bool
|
|
424
|
+
Ignores []string
|
|
425
|
+
Rules RuleConfig
|
|
426
|
+
Options RuleOptionsMap
|
|
427
|
+
IgnoreOnly bool
|
|
253
428
|
}
|
|
254
429
|
|
|
255
430
|
// ResolveRules implements RuleResolver. Ignore-only entries are checked first;
|
|
@@ -258,23 +433,40 @@ type ConfigEntry struct {
|
|
|
258
433
|
// entry wins (later entries shadow earlier ones for the same rule name).
|
|
259
434
|
func (s *ConfigStore) ResolveRules(fileName string) ResolvedRuleConfig {
|
|
260
435
|
if s == nil {
|
|
261
|
-
return ResolvedRuleConfig{Rules: RuleConfig{}}
|
|
436
|
+
return ResolvedRuleConfig{Rules: RuleConfig{}, OptionsResolved: true}
|
|
262
437
|
}
|
|
263
438
|
for _, entry := range s.entries {
|
|
264
439
|
if entry.IgnoreOnly && entry.matchesIgnores(fileName) {
|
|
265
|
-
return ResolvedRuleConfig{Rules: RuleConfig{}, Ignored: true}
|
|
440
|
+
return ResolvedRuleConfig{Rules: RuleConfig{}, OptionsResolved: true, Ignored: true}
|
|
266
441
|
}
|
|
267
442
|
}
|
|
268
443
|
out := RuleConfig{}
|
|
444
|
+
options := RuleOptionsMap{}
|
|
445
|
+
hasEntries := false
|
|
446
|
+
matchedEntry := false
|
|
269
447
|
for _, entry := range s.entries {
|
|
270
|
-
if entry.IgnoreOnly
|
|
448
|
+
if entry.IgnoreOnly {
|
|
449
|
+
continue
|
|
450
|
+
}
|
|
451
|
+
hasEntries = true
|
|
452
|
+
if !entry.matchesFile(fileName) {
|
|
271
453
|
continue
|
|
272
454
|
}
|
|
455
|
+
matchedEntry = true
|
|
273
456
|
for name, sev := range entry.Rules {
|
|
274
|
-
|
|
457
|
+
canonical := normalizeBuiltinRuleName(name)
|
|
458
|
+
out[canonical] = sev
|
|
459
|
+
if raw := entry.Options[canonical]; len(raw) > 0 {
|
|
460
|
+
options[canonical] = append(json.RawMessage(nil), raw...)
|
|
461
|
+
}
|
|
275
462
|
}
|
|
276
463
|
}
|
|
277
|
-
return ResolvedRuleConfig{
|
|
464
|
+
return ResolvedRuleConfig{
|
|
465
|
+
Rules: out,
|
|
466
|
+
Options: options,
|
|
467
|
+
OptionsResolved: true,
|
|
468
|
+
OutOfScope: hasEntries && !matchedEntry,
|
|
469
|
+
}
|
|
278
470
|
}
|
|
279
471
|
|
|
280
472
|
// ActiveRuleNames implements RuleResolver. Returns the sorted union of all rule
|
|
@@ -324,6 +516,47 @@ func (s *ConfigStore) EnabledRuleConfig() RuleConfig {
|
|
|
324
516
|
return out
|
|
325
517
|
}
|
|
326
518
|
|
|
519
|
+
// ResolveProjectRules folds the extends-expanded entries base-first. Only
|
|
520
|
+
// global entries participate; any project-rule mention under files is an
|
|
521
|
+
// invalid configuration, including off declarations and option tuples.
|
|
522
|
+
func (s *ConfigStore) ResolveProjectRules(names []string) (map[string]ProjectRuleSetting, error) {
|
|
523
|
+
out := make(map[string]ProjectRuleSetting, len(names))
|
|
524
|
+
wanted := make(map[string]string, len(names))
|
|
525
|
+
for _, name := range names {
|
|
526
|
+
canonical := normalizeBuiltinRuleName(name)
|
|
527
|
+
wanted[canonical] = name
|
|
528
|
+
out[name] = ProjectRuleSetting{}
|
|
529
|
+
}
|
|
530
|
+
if s == nil {
|
|
531
|
+
return out, nil
|
|
532
|
+
}
|
|
533
|
+
for _, entry := range s.entries {
|
|
534
|
+
if entry.IgnoreOnly {
|
|
535
|
+
continue
|
|
536
|
+
}
|
|
537
|
+
for configuredName, severity := range entry.Rules {
|
|
538
|
+
name, projectRule := wanted[normalizeBuiltinRuleName(configuredName)]
|
|
539
|
+
if !projectRule {
|
|
540
|
+
continue
|
|
541
|
+
}
|
|
542
|
+
if entry.HasFilesSelector {
|
|
543
|
+
return nil, fmt.Errorf(
|
|
544
|
+
"@ttsc/lint: project rule %q cannot be configured in an entry with files",
|
|
545
|
+
name,
|
|
546
|
+
)
|
|
547
|
+
}
|
|
548
|
+
setting := out[name]
|
|
549
|
+
setting.Declared = true
|
|
550
|
+
setting.Severity = severity
|
|
551
|
+
if raw := entry.Options[normalizeBuiltinRuleName(configuredName)]; len(raw) > 0 {
|
|
552
|
+
setting.Options = append(json.RawMessage(nil), raw...)
|
|
553
|
+
}
|
|
554
|
+
out[name] = setting
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
return out, nil
|
|
558
|
+
}
|
|
559
|
+
|
|
327
560
|
// Flatten returns the unconstrained union of all non-ignore-only entries,
|
|
328
561
|
// including SeverityOff rules. Used by LoadRuleConfig (callers that expect a
|
|
329
562
|
// plain RuleConfig). Later entries shadow earlier ones for the same rule name.
|
|
@@ -372,9 +605,9 @@ func ParseRules(raw any) (RuleConfig, error) {
|
|
|
372
605
|
}
|
|
373
606
|
|
|
374
607
|
// ParseRulesWithOptions accepts either a severity literal or a
|
|
375
|
-
// `[severity, options]` tuple per rule and returns the severity map
|
|
608
|
+
// `[severity, ...options]` tuple per rule and returns the severity map
|
|
376
609
|
// alongside an options map keyed by rule name. The options map only
|
|
377
|
-
// contains entries for rules whose configuration
|
|
610
|
+
// contains entries for rules whose configuration carries option slots.
|
|
378
611
|
func ParseRulesWithOptions(raw any) (RuleConfig, RuleOptionsMap, error) {
|
|
379
612
|
if raw == nil {
|
|
380
613
|
return RuleConfig{}, RuleOptionsMap{}, nil
|
|
@@ -398,10 +631,10 @@ func ParseRulesWithOptions(raw any) (RuleConfig, RuleOptionsMap, error) {
|
|
|
398
631
|
return cfg, opts, nil
|
|
399
632
|
}
|
|
400
633
|
|
|
401
|
-
// parseRuleEntry splits a rule entry into its severity and
|
|
402
|
-
//
|
|
403
|
-
//
|
|
404
|
-
//
|
|
634
|
+
// parseRuleEntry splits a rule entry into its severity and optional positional
|
|
635
|
+
// options. A single option keeps its JSON shape for existing object-option
|
|
636
|
+
// rules. Two or more options become a JSON array so canonical ESLint rules
|
|
637
|
+
// with several positional slots can decode them without parser special cases.
|
|
405
638
|
func parseRuleEntry(value any) (Severity, json.RawMessage, error) {
|
|
406
639
|
if tuple, ok := value.([]any); ok {
|
|
407
640
|
if len(tuple) == 0 {
|
|
@@ -414,21 +647,14 @@ func parseRuleEntry(value any) (Severity, json.RawMessage, error) {
|
|
|
414
647
|
if len(tuple) == 1 {
|
|
415
648
|
return sev, nil, nil
|
|
416
649
|
}
|
|
417
|
-
if len(tuple)
|
|
418
|
-
return SeverityOff, nil, fmt.Errorf("severity tuple must be [severity] or [severity, options], got %d elements", len(tuple))
|
|
419
|
-
}
|
|
420
|
-
if tuple[1] == nil {
|
|
650
|
+
if len(tuple) == 2 && tuple[1] == nil {
|
|
421
651
|
return sev, nil, nil
|
|
422
652
|
}
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
// object, and silently encoding a non-object slot would land in
|
|
427
|
-
// DecodeOptions as a decode error that every rule discards. Fail
|
|
428
|
-
// loudly so users discover the proper `["error", { … }]` form.
|
|
429
|
-
return SeverityOff, nil, fmt.Errorf("severity tuple's options slot must be an object, got %T", tuple[1])
|
|
653
|
+
var payload any = tuple[1]
|
|
654
|
+
if len(tuple) > 2 {
|
|
655
|
+
payload = tuple[1:]
|
|
430
656
|
}
|
|
431
|
-
encoded, err := json.Marshal(
|
|
657
|
+
encoded, err := json.Marshal(payload)
|
|
432
658
|
if err != nil {
|
|
433
659
|
return SeverityOff, nil, fmt.Errorf("encode options: %w", err)
|
|
434
660
|
}
|
|
@@ -558,6 +784,7 @@ func collectConfigObject(store *ConfigStore, raw any, baseDir, path string, chai
|
|
|
558
784
|
}
|
|
559
785
|
}
|
|
560
786
|
|
|
787
|
+
_, hasFilesSelector := obj["files"]
|
|
561
788
|
files, err := parsePatternList(obj["files"], path+".files")
|
|
562
789
|
if err != nil {
|
|
563
790
|
return err
|
|
@@ -626,38 +853,38 @@ func collectConfigObject(store *ConfigStore, raw any, baseDir, path string, chai
|
|
|
626
853
|
}
|
|
627
854
|
merged := mergeRuleMaps(formatRulesRaw, rulesMap)
|
|
628
855
|
if len(merged) > 0 {
|
|
629
|
-
parsed, err := parseExternalRuleMapInto(merged, path+".rules"
|
|
856
|
+
parsed, entryOptions, err := parseExternalRuleMapInto(merged, path+".rules")
|
|
630
857
|
if err != nil {
|
|
631
858
|
return err
|
|
632
859
|
}
|
|
633
860
|
store.entries = append(store.entries, ConfigEntry{
|
|
634
|
-
BaseDir:
|
|
635
|
-
Files:
|
|
636
|
-
|
|
637
|
-
|
|
861
|
+
BaseDir: baseDir,
|
|
862
|
+
Files: files,
|
|
863
|
+
HasFilesSelector: hasFilesSelector,
|
|
864
|
+
Ignores: ignores,
|
|
865
|
+
Rules: parsed,
|
|
866
|
+
Options: entryOptions,
|
|
638
867
|
})
|
|
639
868
|
}
|
|
640
869
|
}
|
|
641
870
|
return nil
|
|
642
871
|
}
|
|
643
872
|
|
|
644
|
-
// parseExternalRuleMapInto parses
|
|
645
|
-
//
|
|
646
|
-
//
|
|
647
|
-
func parseExternalRuleMapInto(raw any, path string
|
|
873
|
+
// parseExternalRuleMapInto parses one entry's rules and option tuples. Options
|
|
874
|
+
// stay on the ConfigEntry that owns their files/ignores scope; no project-wide
|
|
875
|
+
// mirror is created.
|
|
876
|
+
func parseExternalRuleMapInto(raw any, path string) (RuleConfig, RuleOptionsMap, error) {
|
|
648
877
|
out := RuleConfig{}
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
if err := collectExternalRuleMapWithOptions(out, store.options, raw, path); err != nil {
|
|
653
|
-
return nil, err
|
|
878
|
+
entryOptions := RuleOptionsMap{}
|
|
879
|
+
if err := collectExternalRuleMapWithOptions(out, entryOptions, raw, path); err != nil {
|
|
880
|
+
return nil, nil, err
|
|
654
881
|
}
|
|
655
|
-
return out, nil
|
|
882
|
+
return out, entryOptions, nil
|
|
656
883
|
}
|
|
657
884
|
|
|
658
|
-
// collectExternalRuleMapWithOptions also records the rule's
|
|
659
|
-
// when the entry is a `[severity, options]` tuple. `opts` may be nil
|
|
660
|
-
//
|
|
885
|
+
// collectExternalRuleMapWithOptions also records the rule's option payload
|
|
886
|
+
// when the entry is a `[severity, ...options]` tuple. `opts` may be nil when
|
|
887
|
+
// the caller does not need option capture.
|
|
661
888
|
func collectExternalRuleMapWithOptions(out RuleConfig, opts RuleOptionsMap, raw any, path string) error {
|
|
662
889
|
dict, ok := raw.(map[string]any)
|
|
663
890
|
if !ok {
|
|
@@ -865,6 +1092,30 @@ func findLintConfigFileFrom(dir string) (string, error) {
|
|
|
865
1092
|
}
|
|
866
1093
|
}
|
|
867
1094
|
|
|
1095
|
+
// pluginConfigDirEnv mirrors driver.PluginConfigDirEnv from
|
|
1096
|
+
// `packages/ttsc/driver`: the environment variable through which the ttsc
|
|
1097
|
+
// launcher passes the project root that plugin config-file discovery and
|
|
1098
|
+
// relative "configFile" resolution anchor at. It matters when the compiled
|
|
1099
|
+
// tsconfig is a generated wrapper outside the project (e.g. @ttsc/unplugin's
|
|
1100
|
+
// compiler-options overlay in the system temp directory) — the tsconfig
|
|
1101
|
+
// directory then no longer identifies the project. The constant is inlined
|
|
1102
|
+
// here instead of imported because @ttsc/lint deliberately avoids a
|
|
1103
|
+
// dependency on the in-tree ttsc module (see host.go).
|
|
1104
|
+
const pluginConfigDirEnv = "TTSC_PLUGIN_CONFIG_DIR"
|
|
1105
|
+
|
|
1106
|
+
// explicitPluginConfigDir returns the launcher-provided config anchor from
|
|
1107
|
+
// pluginConfigDirEnv, resolved against cwd, or "" when the channel is unset.
|
|
1108
|
+
func explicitPluginConfigDir(cwd string) string {
|
|
1109
|
+
dir := strings.TrimSpace(os.Getenv(pluginConfigDirEnv))
|
|
1110
|
+
if dir == "" {
|
|
1111
|
+
return ""
|
|
1112
|
+
}
|
|
1113
|
+
if !filepath.IsAbs(dir) && cwd != "" {
|
|
1114
|
+
dir = filepath.Join(cwd, dir)
|
|
1115
|
+
}
|
|
1116
|
+
return filepath.Clean(dir)
|
|
1117
|
+
}
|
|
1118
|
+
|
|
868
1119
|
// resolveConfigFilePath resolves a user-supplied config path to an absolute
|
|
869
1120
|
// path. Absolute paths are returned unchanged; relative paths are joined to the
|
|
870
1121
|
// tsconfig directory (or cwd when no tsconfig is set).
|
|
@@ -877,13 +1128,18 @@ func resolveConfigFilePath(configPath, cwd, tsconfigPath string) string {
|
|
|
877
1128
|
|
|
878
1129
|
// discoveryConfigBaseDirs returns the ordered directories from which
|
|
879
1130
|
// auto-discovery walks upward when no explicit config path is provided. The
|
|
880
|
-
//
|
|
881
|
-
//
|
|
882
|
-
//
|
|
883
|
-
//
|
|
884
|
-
//
|
|
885
|
-
//
|
|
1131
|
+
// launcher's explicit project-root channel (pluginConfigDirEnv) is the single
|
|
1132
|
+
// origin when set: it names the real project even when the tsconfig is a
|
|
1133
|
+
// generated wrapper in a temp directory, and keeps the wrapper's temp-tree
|
|
1134
|
+
// ancestry out of the walk. Otherwise the tsconfig directory comes first so
|
|
1135
|
+
// that nested package configs are found relative to the tsconfig that
|
|
1136
|
+
// triggered the lint run; the working directory follows as a fallback so a
|
|
1137
|
+
// caller that points at an out-of-tree tsconfig still discovers the project's
|
|
1138
|
+
// lint config instead of failing on the tsconfig dir's empty ancestry.
|
|
886
1139
|
func discoveryConfigBaseDirs(cwd, tsconfigPath string) []string {
|
|
1140
|
+
if explicit := explicitPluginConfigDir(cwd); explicit != "" {
|
|
1141
|
+
return []string{explicit}
|
|
1142
|
+
}
|
|
887
1143
|
origins := make([]string, 0, 2)
|
|
888
1144
|
if tsconfigPath != "" {
|
|
889
1145
|
resolvedTsconfig := tsconfigPath
|
|
@@ -914,10 +1170,16 @@ func containsPath(paths []string, candidate string) bool {
|
|
|
914
1170
|
return false
|
|
915
1171
|
}
|
|
916
1172
|
|
|
917
|
-
// tsconfigBaseDir returns the directory
|
|
918
|
-
//
|
|
919
|
-
//
|
|
1173
|
+
// tsconfigBaseDir returns the base directory for relative config paths
|
|
1174
|
+
// supplied in the tsconfig plugin entry. The launcher's explicit project-root
|
|
1175
|
+
// channel (pluginConfigDirEnv) wins when set — the tsconfig may be a
|
|
1176
|
+
// generated wrapper in a temp directory that no longer identifies the
|
|
1177
|
+
// project — otherwise the directory containing the tsconfig is used, falling
|
|
1178
|
+
// back to cwd when tsconfigPath is empty.
|
|
920
1179
|
func tsconfigBaseDir(cwd, tsconfigPath string) string {
|
|
1180
|
+
if explicit := explicitPluginConfigDir(cwd); explicit != "" {
|
|
1181
|
+
return explicit
|
|
1182
|
+
}
|
|
921
1183
|
if tsconfigPath == "" {
|
|
922
1184
|
return cwd
|
|
923
1185
|
}
|
|
@@ -1804,10 +2066,12 @@ func matchAnyPattern(baseDir string, patterns []string, fileName string) bool {
|
|
|
1804
2066
|
if abs, err := filepath.Abs(base); err == nil {
|
|
1805
2067
|
base = abs
|
|
1806
2068
|
}
|
|
2069
|
+
base = realProjectPath(base)
|
|
1807
2070
|
file := fileName
|
|
1808
2071
|
if abs, err := filepath.Abs(file); err == nil {
|
|
1809
2072
|
file = abs
|
|
1810
2073
|
}
|
|
2074
|
+
file = realProjectPath(file)
|
|
1811
2075
|
if candidate, err := filepath.Rel(base, file); err == nil {
|
|
1812
2076
|
if candidate == ".." || strings.HasPrefix(candidate, ".."+string(filepath.Separator)) {
|
|
1813
2077
|
return false
|
|
@@ -221,12 +221,20 @@ func expandFormatBlock(raw map[string]any) (map[string]any, error) {
|
|
|
221
221
|
out["format/whitespace"] = ruleEntry(wsOpts)
|
|
222
222
|
|
|
223
223
|
// formatSortImports, opt-in by `sortImports` (a boolean or options object).
|
|
224
|
+
// The top-level `endOfLine` is threaded in (like the other layout rules)
|
|
225
|
+
// so the rebuilt import block joins declarations with the file's line
|
|
226
|
+
// ending instead of a hard-coded LF. `endOfLine` is not a sortImports
|
|
227
|
+
// sub-key, so it is injected here rather than validated by
|
|
228
|
+
// expandSortImportsBlock.
|
|
224
229
|
if v, ok := raw["sortImports"]; ok && v != nil {
|
|
225
230
|
siOpts, enabled, err := expandSortImportsBlock(v)
|
|
226
231
|
if err != nil {
|
|
227
232
|
return nil, err
|
|
228
233
|
}
|
|
229
234
|
if enabled {
|
|
235
|
+
if eol, ok := layoutOpts["endOfLine"]; ok {
|
|
236
|
+
siOpts["endOfLine"] = eol
|
|
237
|
+
}
|
|
230
238
|
out["format/sort-imports"] = ruleEntry(siOpts)
|
|
231
239
|
}
|
|
232
240
|
}
|
|
@@ -316,8 +324,14 @@ func expandSortImportsBlock(v any) (map[string]any, bool, error) {
|
|
|
316
324
|
return nil, false, err
|
|
317
325
|
}
|
|
318
326
|
opts["combineTypeAndValue"] = b
|
|
327
|
+
case "unsafeSortRuntimeImports":
|
|
328
|
+
b, err := asBool("format.sortImports.unsafeSortRuntimeImports", val)
|
|
329
|
+
if err != nil {
|
|
330
|
+
return nil, false, err
|
|
331
|
+
}
|
|
332
|
+
opts["unsafeSortRuntimeImports"] = b
|
|
319
333
|
default:
|
|
320
|
-
return nil, false, fmt.Errorf("@ttsc/lint: format.sortImports unknown key %q (allowed: order, caseSensitive, combineTypeAndValue)", key)
|
|
334
|
+
return nil, false, fmt.Errorf("@ttsc/lint: format.sortImports unknown key %q (allowed: order, caseSensitive, combineTypeAndValue, unsafeSortRuntimeImports)", key)
|
|
321
335
|
}
|
|
322
336
|
}
|
|
323
337
|
return opts, true, nil
|