@ttsc/lint 0.12.3 → 0.13.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/lib/defaultFormat.d.ts +10 -11
- package/lib/defaultFormat.js +10 -11
- package/lib/defaultFormat.js.map +1 -1
- package/lib/index.js +233 -135
- package/lib/index.js.map +1 -1
- package/lib/structures/ITtscLintConfig.d.ts +2 -2
- package/lib/structures/ITtscLintFormatConfig.d.ts +53 -55
- package/lib/structures/ITtscLintPluginConfig.d.ts +10 -77
- package/lib/structures/ITtscLintPluginMeta.d.ts +9 -1
- package/lib/structures/TtscLintRule.d.ts +10 -1
- package/lib/structures/TtscLintRuleMap.d.ts +2 -2
- package/lib/structures/TtscLintRuleOptions.d.ts +14 -0
- package/linthost/ast_helpers.go +80 -11
- package/linthost/compile.go +116 -112
- package/linthost/config.go +647 -687
- package/linthost/config_format.go +272 -247
- package/linthost/contrib_adapter.go +7 -0
- package/linthost/directives.go +116 -0
- package/linthost/dispatch.go +33 -33
- package/linthost/engine.go +156 -43
- package/linthost/fix.go +47 -27
- package/linthost/flags_gen.go +31 -0
- package/linthost/format.go +119 -3
- package/linthost/host.go +115 -5
- package/linthost/print_dispatch.go +128 -28
- package/linthost/print_doc.go +23 -0
- package/linthost/print_engine.go +168 -4
- package/linthost/print_nodes_array.go +17 -7
- package/linthost/print_nodes_call.go +138 -20
- package/linthost/print_nodes_function.go +353 -0
- package/linthost/print_nodes_imports.go +46 -29
- package/linthost/print_nodes_list.go +86 -5
- package/linthost/print_nodes_object.go +56 -11
- package/linthost/rules_arrays.go +5 -2
- package/linthost/rules_debugger.go +3 -2
- package/linthost/rules_dupes.go +7 -4
- package/linthost/rules_empty.go +3 -2
- package/linthost/rules_escape.go +35 -3
- package/linthost/rules_eval.go +3 -0
- package/linthost/rules_finally.go +11 -0
- package/linthost/rules_format_jsdoc.go +7 -0
- package/linthost/rules_format_print_width.go +270 -15
- package/linthost/rules_format_quotes.go +3 -0
- package/linthost/rules_format_sort_imports.go +4 -0
- package/linthost/rules_gap.go +75 -3
- package/linthost/rules_imports.go +5 -7
- package/linthost/rules_logic.go +75 -5
- package/linthost/rules_loops.go +4 -0
- package/linthost/rules_misc.go +4 -2
- package/linthost/rules_params.go +7 -11
- package/linthost/rules_problems.go +89 -22
- package/linthost/rules_promise.go +15 -0
- package/linthost/rules_protos.go +3 -2
- package/linthost/rules_self.go +6 -0
- package/linthost/rules_strings.go +10 -0
- package/linthost/rules_suggestions.go +174 -9
- package/linthost/rules_throw.go +2 -0
- package/linthost/rules_ts.go +13 -0
- package/linthost/rules_ts_extra.go +25 -8
- package/linthost/rules_var.go +15 -2
- package/package.json +3 -3
- package/plugin/main.go +4 -4
- package/rule/astutil/astutil.go +12 -0
- package/rule/rule.go +123 -123
- package/src/defaultFormat.ts +10 -11
- package/src/index.ts +257 -168
- package/src/structures/ITtscLintConfig.ts +2 -2
- package/src/structures/ITtscLintFormatConfig.ts +53 -55
- package/src/structures/ITtscLintPluginConfig.ts +10 -83
- package/src/structures/ITtscLintPluginMeta.ts +9 -1
- package/src/structures/TtscLintRule.ts +10 -1
- package/src/structures/TtscLintRuleMap.ts +17 -18
- package/src/structures/TtscLintRuleOptions.ts +15 -0
- package/linthost/eslint_runtime.go +0 -320
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
package linthost
|
|
2
2
|
|
|
3
3
|
import (
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
"encoding/json"
|
|
5
|
+
"fmt"
|
|
6
6
|
)
|
|
7
7
|
|
|
8
8
|
// expandFormatBlock translates a Prettier-style `format` block into the
|
|
@@ -10,9 +10,8 @@ import (
|
|
|
10
10
|
// The result is a `map[string]any` that mirrors what a user would
|
|
11
11
|
// have written under `rules` directly — entries like
|
|
12
12
|
// `"format/semi": ["off", {"prefer": "always"}]` — so the caller
|
|
13
|
-
// can route it through either `ParseRulesWithOptions`
|
|
14
|
-
// `parseExternalRuleMapInto`
|
|
15
|
-
// option-decoding logic.
|
|
13
|
+
// can route it through either `ParseRulesWithOptions` or
|
|
14
|
+
// `parseExternalRuleMapInto` without duplicating option-decoding logic.
|
|
16
15
|
//
|
|
17
16
|
// The block's default severity is off. That keeps check/build diagnostics
|
|
18
17
|
// independent from formatting policy unless the user explicitly sets
|
|
@@ -33,189 +32,206 @@ import (
|
|
|
33
32
|
// semantics) before invoking the existing parsers — `mergeRuleMaps`
|
|
34
33
|
// below performs the merge with the right precedence.
|
|
35
34
|
func expandFormatBlock(raw map[string]any) (map[string]any, error) {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
35
|
+
if raw == nil {
|
|
36
|
+
return map[string]any{}, nil
|
|
37
|
+
}
|
|
38
|
+
if err := rejectUnknownFormatKeys(raw); err != nil {
|
|
39
|
+
return nil, err
|
|
40
|
+
}
|
|
41
|
+
severity, err := formatBlockSeverity(raw)
|
|
42
|
+
if err != nil {
|
|
43
|
+
return nil, err
|
|
44
|
+
}
|
|
45
|
+
ruleEntry := func(options map[string]any) []any {
|
|
46
|
+
return []any{severity.String(), options}
|
|
47
|
+
}
|
|
48
|
+
out := map[string]any{}
|
|
50
49
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
50
|
+
// format/semi
|
|
51
|
+
semiPrefer := "always"
|
|
52
|
+
if v, ok := raw["semi"]; ok {
|
|
53
|
+
b, err := asBool("format.semi", v)
|
|
54
|
+
if err != nil {
|
|
55
|
+
return nil, err
|
|
56
|
+
}
|
|
57
|
+
if !b {
|
|
58
|
+
semiPrefer = "never"
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
out["format/semi"] = ruleEntry(map[string]any{"prefer": semiPrefer})
|
|
63
62
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
63
|
+
// format/quotes
|
|
64
|
+
quotesPrefer := "double"
|
|
65
|
+
if v, ok := raw["singleQuote"]; ok {
|
|
66
|
+
b, err := asBool("format.singleQuote", v)
|
|
67
|
+
if err != nil {
|
|
68
|
+
return nil, err
|
|
69
|
+
}
|
|
70
|
+
if b {
|
|
71
|
+
quotesPrefer = "single"
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
out["format/quotes"] = ruleEntry(map[string]any{"prefer": quotesPrefer})
|
|
76
75
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
76
|
+
// format/trailing-comma
|
|
77
|
+
tcMode := "all"
|
|
78
|
+
if v, ok := raw["trailingComma"]; ok {
|
|
79
|
+
s, err := asString("format.trailingComma", v)
|
|
80
|
+
if err != nil {
|
|
81
|
+
return nil, err
|
|
82
|
+
}
|
|
83
|
+
switch s {
|
|
84
|
+
case "all", "es5", "none":
|
|
85
|
+
tcMode = s
|
|
86
|
+
default:
|
|
87
|
+
return nil, fmt.Errorf("@ttsc/lint: format.trailingComma must be \"all\", \"es5\", or \"none\"; got %q", s)
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
out["format/trailing-comma"] = ruleEntry(map[string]any{"mode": tcMode})
|
|
92
91
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
92
|
+
// format/print-width
|
|
93
|
+
//
|
|
94
|
+
// `trailingComma` is mirrored into the print-width rule's options so
|
|
95
|
+
// the printer's broken-list reflow emits the same trailing-comma
|
|
96
|
+
// shape `format/trailing-comma` does. Without the mirror the two
|
|
97
|
+
// rules disagree on `es5` / `none` projects and oscillate on every
|
|
98
|
+
// cascade pass — the trailing-comma rule says "no comma" while the
|
|
99
|
+
// printer adds one back. See `printArgList` in print_nodes_call.go.
|
|
100
|
+
pwOpts := map[string]any{"trailingComma": tcMode}
|
|
101
|
+
if v, ok := raw["printWidth"]; ok {
|
|
102
|
+
n, err := asInt("format.printWidth", v)
|
|
103
|
+
if err != nil {
|
|
104
|
+
return nil, err
|
|
105
|
+
}
|
|
106
|
+
if n < 1 {
|
|
107
|
+
return nil, fmt.Errorf("@ttsc/lint: format.printWidth must be a positive integer; got %d", n)
|
|
108
|
+
}
|
|
109
|
+
pwOpts["printWidth"] = n
|
|
110
|
+
}
|
|
111
|
+
if v, ok := raw["tabWidth"]; ok {
|
|
112
|
+
n, err := asInt("format.tabWidth", v)
|
|
113
|
+
if err != nil {
|
|
114
|
+
return nil, err
|
|
115
|
+
}
|
|
116
|
+
if n < 1 {
|
|
117
|
+
return nil, fmt.Errorf("@ttsc/lint: format.tabWidth must be a positive integer; got %d", n)
|
|
118
|
+
}
|
|
119
|
+
pwOpts["tabWidth"] = n
|
|
120
|
+
}
|
|
121
|
+
if v, ok := raw["useTabs"]; ok {
|
|
122
|
+
b, err := asBool("format.useTabs", v)
|
|
123
|
+
if err != nil {
|
|
124
|
+
return nil, err
|
|
125
|
+
}
|
|
126
|
+
pwOpts["useTabs"] = b
|
|
127
|
+
}
|
|
128
|
+
if v, ok := raw["endOfLine"]; ok {
|
|
129
|
+
s, err := asString("format.endOfLine", v)
|
|
130
|
+
if err != nil {
|
|
131
|
+
return nil, err
|
|
132
|
+
}
|
|
133
|
+
if s != "lf" && s != "crlf" {
|
|
134
|
+
return nil, fmt.Errorf("@ttsc/lint: format.endOfLine must be \"lf\" or \"crlf\"; got %q", s)
|
|
135
|
+
}
|
|
136
|
+
pwOpts["endOfLine"] = s
|
|
137
|
+
}
|
|
138
|
+
out["format/print-width"] = ruleEntry(pwOpts)
|
|
127
139
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
140
|
+
// format/sort-imports — opt-in by `importOrder`.
|
|
141
|
+
if v, ok := raw["importOrder"]; ok {
|
|
142
|
+
siOpts := map[string]any{}
|
|
143
|
+
order, err := asStringSlice("format.importOrder", v)
|
|
144
|
+
if err != nil {
|
|
145
|
+
return nil, err
|
|
146
|
+
}
|
|
147
|
+
if len(order) == 0 {
|
|
148
|
+
return nil, fmt.Errorf("@ttsc/lint: format.importOrder must contain at least one entry; omit the field to keep format/sort-imports off")
|
|
149
|
+
}
|
|
150
|
+
siOpts["importOrder"] = order
|
|
151
|
+
if x, ok := raw["importOrderSeparation"]; ok {
|
|
152
|
+
b, err := asBool("format.importOrderSeparation", x)
|
|
153
|
+
if err != nil {
|
|
154
|
+
return nil, err
|
|
155
|
+
}
|
|
156
|
+
siOpts["importOrderSeparation"] = b
|
|
157
|
+
}
|
|
158
|
+
if x, ok := raw["importOrderSortSpecifiers"]; ok {
|
|
159
|
+
b, err := asBool("format.importOrderSortSpecifiers", x)
|
|
160
|
+
if err != nil {
|
|
161
|
+
return nil, err
|
|
162
|
+
}
|
|
163
|
+
siOpts["importOrderSortSpecifiers"] = b
|
|
164
|
+
}
|
|
165
|
+
if x, ok := raw["importOrderCaseInsensitive"]; ok {
|
|
166
|
+
b, err := asBool("format.importOrderCaseInsensitive", x)
|
|
167
|
+
if err != nil {
|
|
168
|
+
return nil, err
|
|
169
|
+
}
|
|
170
|
+
siOpts["importOrderCaseInsensitive"] = b
|
|
171
|
+
}
|
|
172
|
+
out["format/sort-imports"] = ruleEntry(siOpts)
|
|
173
|
+
}
|
|
162
174
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
175
|
+
// format/jsdoc — opt-in by `jsdoc` truthy (boolean or object).
|
|
176
|
+
if v, ok := raw["jsdoc"]; ok && v != nil {
|
|
177
|
+
jdOpts := map[string]any{}
|
|
178
|
+
enabled := false
|
|
179
|
+
switch j := v.(type) {
|
|
180
|
+
case bool:
|
|
181
|
+
enabled = j
|
|
182
|
+
case map[string]any:
|
|
183
|
+
enabled = true
|
|
184
|
+
for key, val := range j {
|
|
185
|
+
switch key {
|
|
186
|
+
case "tagSynonyms":
|
|
187
|
+
ts, ok := val.(map[string]any)
|
|
188
|
+
if !ok {
|
|
189
|
+
return nil, fmt.Errorf("@ttsc/lint: format.jsdoc.tagSynonyms must be an object, got %T", val)
|
|
190
|
+
}
|
|
191
|
+
// Element values must be strings; surface
|
|
192
|
+
// typos early instead of after a downstream
|
|
193
|
+
// JSON-decode failure.
|
|
194
|
+
for k, v := range ts {
|
|
195
|
+
if _, ok := v.(string); !ok {
|
|
196
|
+
return nil, fmt.Errorf("@ttsc/lint: format.jsdoc.tagSynonyms[%q] must be a string, got %T", k, v)
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
jdOpts["tagSynonyms"] = ts
|
|
200
|
+
case "sortTags":
|
|
201
|
+
b, err := asBool("format.jsdoc.sortTags", val)
|
|
202
|
+
if err != nil {
|
|
203
|
+
return nil, err
|
|
204
|
+
}
|
|
205
|
+
jdOpts["sortTags"] = b
|
|
206
|
+
default:
|
|
207
|
+
return nil, fmt.Errorf("@ttsc/lint: format.jsdoc unknown key %q (allowed: tagSynonyms, sortTags)", key)
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
default:
|
|
211
|
+
return nil, fmt.Errorf("@ttsc/lint: format.jsdoc must be a boolean or object, got %T", v)
|
|
212
|
+
}
|
|
213
|
+
if enabled {
|
|
214
|
+
out["format/jsdoc"] = ruleEntry(jdOpts)
|
|
215
|
+
}
|
|
216
|
+
}
|
|
205
217
|
|
|
206
|
-
|
|
218
|
+
return out, nil
|
|
207
219
|
}
|
|
208
220
|
|
|
221
|
+
// formatBlockSeverity extracts the optional `severity` field from a format
|
|
222
|
+
// block. Defaults to SeverityOff so that format rules never produce check/build
|
|
223
|
+
// diagnostics unless the user explicitly opts in. SeverityOff still allows
|
|
224
|
+
// `ttsc format` to apply formatting; it only suppresses error/warning output.
|
|
209
225
|
func formatBlockSeverity(raw map[string]any) (Severity, error) {
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
226
|
+
value, ok := raw["severity"]
|
|
227
|
+
if !ok || value == nil {
|
|
228
|
+
return SeverityOff, nil
|
|
229
|
+
}
|
|
230
|
+
severity, err := parseSeverity(value)
|
|
231
|
+
if err != nil {
|
|
232
|
+
return SeverityOff, fmt.Errorf("@ttsc/lint: format.severity: %w", err)
|
|
233
|
+
}
|
|
234
|
+
return severity, nil
|
|
219
235
|
}
|
|
220
236
|
|
|
221
237
|
// mergeRuleMaps overlays `overrides` on `base` and returns the merged
|
|
@@ -225,91 +241,100 @@ func formatBlockSeverity(raw map[string]any) (Severity, error) {
|
|
|
225
241
|
// `format/*` rule fully replaces the corresponding entry expanded
|
|
226
242
|
// from the `format` block.
|
|
227
243
|
func mergeRuleMaps(base, overrides map[string]any) map[string]any {
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
244
|
+
out := make(map[string]any, len(base)+len(overrides))
|
|
245
|
+
for k, v := range base {
|
|
246
|
+
out[k] = v
|
|
247
|
+
}
|
|
248
|
+
for k, v := range overrides {
|
|
249
|
+
out[k] = v
|
|
250
|
+
}
|
|
251
|
+
return out
|
|
236
252
|
}
|
|
237
253
|
|
|
254
|
+
// asBool coerces a raw config value to a bool, returning a typed error on
|
|
255
|
+
// failure. Used by expandFormatBlock to validate boolean format fields.
|
|
238
256
|
func asBool(field string, v any) (bool, error) {
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
257
|
+
if b, ok := v.(bool); ok {
|
|
258
|
+
return b, nil
|
|
259
|
+
}
|
|
260
|
+
return false, fmt.Errorf("@ttsc/lint: %s must be a boolean, got %T", field, v)
|
|
243
261
|
}
|
|
244
262
|
|
|
263
|
+
// asString coerces a raw config value to a string, returning a typed error on
|
|
264
|
+
// failure. Used by expandFormatBlock to validate string format fields.
|
|
245
265
|
func asString(field string, v any) (string, error) {
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
266
|
+
if s, ok := v.(string); ok {
|
|
267
|
+
return s, nil
|
|
268
|
+
}
|
|
269
|
+
return "", fmt.Errorf("@ttsc/lint: %s must be a string, got %T", field, v)
|
|
250
270
|
}
|
|
251
271
|
|
|
272
|
+
// asInt coerces a raw config value to an int. Accepts all integer-shaped Go
|
|
273
|
+
// numeric types plus float64 (the default JSON decode type) and json.Number,
|
|
274
|
+
// rejecting fractional float64 values since no format option takes a non-integer.
|
|
252
275
|
func asInt(field string, v any) (int, error) {
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
276
|
+
switch n := v.(type) {
|
|
277
|
+
case int:
|
|
278
|
+
return n, nil
|
|
279
|
+
case int32:
|
|
280
|
+
return int(n), nil
|
|
281
|
+
case int64:
|
|
282
|
+
return int(n), nil
|
|
283
|
+
case float64:
|
|
284
|
+
// JSON numbers decode as float64; coerce when integer-valued.
|
|
285
|
+
if float64(int(n)) == n {
|
|
286
|
+
return int(n), nil
|
|
287
|
+
}
|
|
288
|
+
case json.Number:
|
|
289
|
+
i, err := n.Int64()
|
|
290
|
+
if err == nil {
|
|
291
|
+
return int(i), nil
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
return 0, fmt.Errorf("@ttsc/lint: %s must be an integer, got %T", field, v)
|
|
272
295
|
}
|
|
273
296
|
|
|
297
|
+
// asStringSlice coerces a raw config value to a []string, returning a typed
|
|
298
|
+
// error on failure. Used by expandFormatBlock to validate importOrder.
|
|
274
299
|
func asStringSlice(field string, v any) ([]string, error) {
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
300
|
+
arr, ok := v.([]any)
|
|
301
|
+
if !ok {
|
|
302
|
+
return nil, fmt.Errorf("@ttsc/lint: %s must be an array of strings, got %T", field, v)
|
|
303
|
+
}
|
|
304
|
+
out := make([]string, 0, len(arr))
|
|
305
|
+
for i, item := range arr {
|
|
306
|
+
s, ok := item.(string)
|
|
307
|
+
if !ok {
|
|
308
|
+
return nil, fmt.Errorf("@ttsc/lint: %s[%d] must be a string, got %T", field, i, item)
|
|
309
|
+
}
|
|
310
|
+
out = append(out, s)
|
|
311
|
+
}
|
|
312
|
+
return out, nil
|
|
288
313
|
}
|
|
289
314
|
|
|
290
315
|
// rejectUnknownFormatKeys surfaces typos in top-level format-block
|
|
291
316
|
// keys at the boundary rather than silently ignoring them. The
|
|
292
317
|
// key set mirrors `ITtscLintFormatConfig` exactly.
|
|
293
318
|
func rejectUnknownFormatKeys(raw map[string]any) error {
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
319
|
+
allowed := map[string]struct{}{
|
|
320
|
+
"severity": {},
|
|
321
|
+
"semi": {},
|
|
322
|
+
"singleQuote": {},
|
|
323
|
+
"trailingComma": {},
|
|
324
|
+
"printWidth": {},
|
|
325
|
+
"tabWidth": {},
|
|
326
|
+
"useTabs": {},
|
|
327
|
+
"endOfLine": {},
|
|
328
|
+
"importOrder": {},
|
|
329
|
+
"importOrderSeparation": {},
|
|
330
|
+
"importOrderSortSpecifiers": {},
|
|
331
|
+
"importOrderCaseInsensitive": {},
|
|
332
|
+
"jsdoc": {},
|
|
333
|
+
}
|
|
334
|
+
for key := range raw {
|
|
335
|
+
if _, ok := allowed[key]; !ok {
|
|
336
|
+
return fmt.Errorf("@ttsc/lint: format unknown key %q; see ITtscLintFormatConfig for the allowed surface", key)
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
return nil
|
|
315
340
|
}
|
|
@@ -68,6 +68,13 @@ type contributorAdapter struct {
|
|
|
68
68
|
inner rule.Rule
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
+
// NeedsTypeChecker keeps contributor rules on the historical checker path.
|
|
72
|
+
// The public rule.Context exposes Checker and has no mandatory marker, so the
|
|
73
|
+
// host cannot safely infer that a third-party rule is AST-only.
|
|
74
|
+
func (a contributorAdapter) NeedsTypeChecker() bool {
|
|
75
|
+
return true
|
|
76
|
+
}
|
|
77
|
+
|
|
71
78
|
// formatContributorAdapter is the FormatRule-tagged variant of
|
|
72
79
|
// contributorAdapter. Wrapping the lint-only adapter (rather than
|
|
73
80
|
// duplicating its method set) keeps the marker addition trivial and
|