@ttsc/lint 0.11.0-dev.20260518-2 → 0.12.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 +67 -141
- package/lib/{structures/defaultFormat.d.ts → defaultFormat.d.ts} +3 -3
- package/lib/{structures/defaultFormat.js → defaultFormat.js} +3 -3
- package/lib/defaultFormat.js.map +1 -0
- package/lib/index.d.ts +1 -38
- package/lib/index.js +7 -5
- package/lib/index.js.map +1 -1
- package/lib/structures/ITtscLintConfig.d.ts +27 -0
- package/lib/structures/{PluginRuleNames.js → ITtscLintConfig.js} +1 -1
- package/lib/structures/ITtscLintConfig.js.map +1 -0
- package/lib/structures/{TtscLintFormatConfig.d.ts → ITtscLintFormatConfig.d.ts} +17 -16
- package/lib/structures/{TtscLintConfig.js → ITtscLintFormatConfig.js} +1 -1
- package/lib/structures/ITtscLintFormatConfig.js.map +1 -0
- package/lib/structures/ITtscLintPluginConfig.d.ts +6 -6
- package/lib/structures/TtscLintRuleMap.d.ts +11 -9
- package/lib/structures/TtscLintRuleOptions.d.ts +118 -144
- package/lib/structures/index.d.ts +2 -7
- package/lib/structures/index.js +2 -7
- package/lib/structures/index.js.map +1 -1
- package/linthost/config.go +1 -6
- package/linthost/config_format.go +253 -298
- package/linthost/format.go +91 -1
- package/package.json +3 -3
- package/rule/rule.go +124 -124
- package/src/{structures/defaultFormat.ts → defaultFormat.ts} +5 -5
- package/src/index.ts +7 -5
- package/src/structures/ITtscLintConfig.ts +33 -0
- package/src/structures/{TtscLintFormatConfig.ts → ITtscLintFormatConfig.ts} +17 -16
- package/src/structures/ITtscLintPluginConfig.ts +6 -6
- package/src/structures/TtscLintRuleMap.ts +17 -13
- package/src/structures/TtscLintRuleOptions.ts +118 -144
- package/src/structures/index.ts +2 -7
- package/lib/defineConfig.d.ts +0 -55
- package/lib/defineConfig.js +0 -39
- package/lib/defineConfig.js.map +0 -1
- package/lib/structures/PluginRuleNames.d.ts +0 -14
- package/lib/structures/PluginRuleNames.js.map +0 -1
- package/lib/structures/TtscLintConfig.d.ts +0 -27
- package/lib/structures/TtscLintConfig.js.map +0 -1
- package/lib/structures/TtscLintConfigEntry.d.ts +0 -39
- package/lib/structures/TtscLintConfigEntry.js +0 -3
- package/lib/structures/TtscLintConfigEntry.js.map +0 -1
- package/lib/structures/TtscLintFormatConfig.js +0 -3
- package/lib/structures/TtscLintFormatConfig.js.map +0 -1
- package/lib/structures/TtscLintPlugins.d.ts +0 -9
- package/lib/structures/TtscLintPlugins.js +0 -3
- package/lib/structures/TtscLintPlugins.js.map +0 -1
- package/lib/structures/TtscLintRuleEntry.d.ts +0 -22
- package/lib/structures/TtscLintRuleEntry.js +0 -3
- package/lib/structures/TtscLintRuleEntry.js.map +0 -1
- package/lib/structures/defaultFormat.js.map +0 -1
- package/src/defineConfig.ts +0 -69
- package/src/structures/PluginRuleNames.ts +0 -19
- package/src/structures/TtscLintConfig.ts +0 -31
- package/src/structures/TtscLintConfigEntry.ts +0 -50
- package/src/structures/TtscLintPlugins.ts +0 -10
- package/src/structures/TtscLintRuleEntry.ts +0 -28
|
@@ -1,19 +1,24 @@
|
|
|
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
|
|
9
9
|
// per-rule severity + options shape the existing rule parsers consume.
|
|
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
|
-
// `"format/semi": ["
|
|
12
|
+
// `"format/semi": ["off", {"prefer": "always"}]` — so the caller
|
|
13
13
|
// can route it through either `ParseRulesWithOptions` (inline path) or
|
|
14
14
|
// `parseExternalRuleMapInto` (flat-config path) without duplicating
|
|
15
15
|
// option-decoding logic.
|
|
16
16
|
//
|
|
17
|
+
// The block's default severity is off. That keeps check/build diagnostics
|
|
18
|
+
// independent from formatting policy unless the user explicitly sets
|
|
19
|
+
// `format.severity`; the options still exist so `ttsc format` can apply the
|
|
20
|
+
// formatter from the same config block.
|
|
21
|
+
//
|
|
17
22
|
// The block's enablement matrix:
|
|
18
23
|
//
|
|
19
24
|
// - `format/semi` — always on. `semi: false` flips to `prefer: "never"`.
|
|
@@ -23,193 +28,194 @@ import (
|
|
|
23
28
|
// - `format/sort-imports` — opt-in by setting `importOrder`.
|
|
24
29
|
// - `format/jsdoc` — opt-in by setting `jsdoc` truthy.
|
|
25
30
|
//
|
|
26
|
-
// `severity` controls the per-finding severity for every emitted entry.
|
|
27
|
-
// Default is "warning". Setting it to "off" returns an empty map so the
|
|
28
|
-
// rest of the loader treats the block as if it were absent.
|
|
29
|
-
//
|
|
30
31
|
// The returned map is the raw form rules parsers expect. Callers MUST
|
|
31
32
|
// merge any user-supplied `rules` map on top of this one (rules-wins
|
|
32
33
|
// semantics) before invoking the existing parsers — `mergeRuleMaps`
|
|
33
34
|
// below performs the merge with the right precedence.
|
|
34
35
|
func expandFormatBlock(raw map[string]any) (map[string]any, error) {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
if severityIsOff(severity) {
|
|
50
|
-
// `severity: "off"` collapses the whole block to a no-op so the
|
|
51
|
-
// engine never registers any of the format rules.
|
|
52
|
-
return map[string]any{}, nil
|
|
53
|
-
}
|
|
36
|
+
if raw == nil {
|
|
37
|
+
return map[string]any{}, nil
|
|
38
|
+
}
|
|
39
|
+
if err := rejectUnknownFormatKeys(raw); err != nil {
|
|
40
|
+
return nil, err
|
|
41
|
+
}
|
|
42
|
+
severity, err := formatBlockSeverity(raw)
|
|
43
|
+
if err != nil {
|
|
44
|
+
return nil, err
|
|
45
|
+
}
|
|
46
|
+
ruleEntry := func(options map[string]any) []any {
|
|
47
|
+
return []any{severity.String(), options}
|
|
48
|
+
}
|
|
49
|
+
out := map[string]any{}
|
|
54
50
|
|
|
55
|
-
|
|
51
|
+
// format/semi
|
|
52
|
+
semiPrefer := "always"
|
|
53
|
+
if v, ok := raw["semi"]; ok {
|
|
54
|
+
b, err := asBool("format.semi", v)
|
|
55
|
+
if err != nil {
|
|
56
|
+
return nil, err
|
|
57
|
+
}
|
|
58
|
+
if !b {
|
|
59
|
+
semiPrefer = "never"
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
out["format/semi"] = ruleEntry(map[string]any{"prefer": semiPrefer})
|
|
56
63
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
64
|
+
// format/quotes
|
|
65
|
+
quotesPrefer := "double"
|
|
66
|
+
if v, ok := raw["singleQuote"]; ok {
|
|
67
|
+
b, err := asBool("format.singleQuote", v)
|
|
68
|
+
if err != nil {
|
|
69
|
+
return nil, err
|
|
70
|
+
}
|
|
71
|
+
if b {
|
|
72
|
+
quotesPrefer = "single"
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
out["format/quotes"] = ruleEntry(map[string]any{"prefer": quotesPrefer})
|
|
69
76
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
77
|
+
// format/trailing-comma
|
|
78
|
+
tcMode := "all"
|
|
79
|
+
if v, ok := raw["trailingComma"]; ok {
|
|
80
|
+
s, err := asString("format.trailingComma", v)
|
|
81
|
+
if err != nil {
|
|
82
|
+
return nil, err
|
|
83
|
+
}
|
|
84
|
+
switch s {
|
|
85
|
+
case "all", "es5", "none":
|
|
86
|
+
tcMode = s
|
|
87
|
+
default:
|
|
88
|
+
return nil, fmt.Errorf("@ttsc/lint: format.trailingComma must be \"all\", \"es5\", or \"none\"; got %q", s)
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
out["format/trailing-comma"] = ruleEntry(map[string]any{"mode": tcMode})
|
|
82
92
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
93
|
+
// format/print-width
|
|
94
|
+
pwOpts := map[string]any{}
|
|
95
|
+
if v, ok := raw["printWidth"]; ok {
|
|
96
|
+
n, err := asInt("format.printWidth", v)
|
|
97
|
+
if err != nil {
|
|
98
|
+
return nil, err
|
|
99
|
+
}
|
|
100
|
+
pwOpts["printWidth"] = n
|
|
101
|
+
}
|
|
102
|
+
if v, ok := raw["tabWidth"]; ok {
|
|
103
|
+
n, err := asInt("format.tabWidth", v)
|
|
104
|
+
if err != nil {
|
|
105
|
+
return nil, err
|
|
106
|
+
}
|
|
107
|
+
pwOpts["tabWidth"] = n
|
|
108
|
+
}
|
|
109
|
+
if v, ok := raw["useTabs"]; ok {
|
|
110
|
+
b, err := asBool("format.useTabs", v)
|
|
111
|
+
if err != nil {
|
|
112
|
+
return nil, err
|
|
113
|
+
}
|
|
114
|
+
pwOpts["useTabs"] = b
|
|
115
|
+
}
|
|
116
|
+
if v, ok := raw["endOfLine"]; ok {
|
|
117
|
+
s, err := asString("format.endOfLine", v)
|
|
118
|
+
if err != nil {
|
|
119
|
+
return nil, err
|
|
120
|
+
}
|
|
121
|
+
if s != "lf" && s != "crlf" {
|
|
122
|
+
return nil, fmt.Errorf("@ttsc/lint: format.endOfLine must be \"lf\" or \"crlf\"; got %q", s)
|
|
123
|
+
}
|
|
124
|
+
pwOpts["endOfLine"] = s
|
|
125
|
+
}
|
|
126
|
+
out["format/print-width"] = ruleEntry(pwOpts)
|
|
98
127
|
|
|
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
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
128
|
+
// format/sort-imports — opt-in by `importOrder`.
|
|
129
|
+
if v, ok := raw["importOrder"]; ok {
|
|
130
|
+
siOpts := map[string]any{}
|
|
131
|
+
order, err := asStringSlice("format.importOrder", v)
|
|
132
|
+
if err != nil {
|
|
133
|
+
return nil, err
|
|
134
|
+
}
|
|
135
|
+
if len(order) == 0 {
|
|
136
|
+
return nil, fmt.Errorf("@ttsc/lint: format.importOrder must contain at least one entry; omit the field to keep format/sort-imports off")
|
|
137
|
+
}
|
|
138
|
+
siOpts["importOrder"] = order
|
|
139
|
+
if x, ok := raw["importOrderSeparation"]; ok {
|
|
140
|
+
b, err := asBool("format.importOrderSeparation", x)
|
|
141
|
+
if err != nil {
|
|
142
|
+
return nil, err
|
|
143
|
+
}
|
|
144
|
+
siOpts["importOrderSeparation"] = b
|
|
145
|
+
}
|
|
146
|
+
if x, ok := raw["importOrderSortSpecifiers"]; ok {
|
|
147
|
+
b, err := asBool("format.importOrderSortSpecifiers", x)
|
|
148
|
+
if err != nil {
|
|
149
|
+
return nil, err
|
|
150
|
+
}
|
|
151
|
+
siOpts["importOrderSortSpecifiers"] = b
|
|
152
|
+
}
|
|
153
|
+
if x, ok := raw["importOrderCaseInsensitive"]; ok {
|
|
154
|
+
b, err := asBool("format.importOrderCaseInsensitive", x)
|
|
155
|
+
if err != nil {
|
|
156
|
+
return nil, err
|
|
157
|
+
}
|
|
158
|
+
siOpts["importOrderCaseInsensitive"] = b
|
|
159
|
+
}
|
|
160
|
+
out["format/sort-imports"] = ruleEntry(siOpts)
|
|
161
|
+
}
|
|
133
162
|
|
|
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
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
163
|
+
// format/jsdoc — opt-in by `jsdoc` truthy (boolean or object).
|
|
164
|
+
if v, ok := raw["jsdoc"]; ok && v != nil {
|
|
165
|
+
jdOpts := map[string]any{}
|
|
166
|
+
enabled := false
|
|
167
|
+
switch j := v.(type) {
|
|
168
|
+
case bool:
|
|
169
|
+
enabled = j
|
|
170
|
+
case map[string]any:
|
|
171
|
+
enabled = true
|
|
172
|
+
for key, val := range j {
|
|
173
|
+
switch key {
|
|
174
|
+
case "tagSynonyms":
|
|
175
|
+
ts, ok := val.(map[string]any)
|
|
176
|
+
if !ok {
|
|
177
|
+
return nil, fmt.Errorf("@ttsc/lint: format.jsdoc.tagSynonyms must be an object, got %T", val)
|
|
178
|
+
}
|
|
179
|
+
// Element values must be strings; surface
|
|
180
|
+
// typos early instead of after a downstream
|
|
181
|
+
// JSON-decode failure.
|
|
182
|
+
for k, v := range ts {
|
|
183
|
+
if _, ok := v.(string); !ok {
|
|
184
|
+
return nil, fmt.Errorf("@ttsc/lint: format.jsdoc.tagSynonyms[%q] must be a string, got %T", k, v)
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
jdOpts["tagSynonyms"] = ts
|
|
188
|
+
case "sortTags":
|
|
189
|
+
b, err := asBool("format.jsdoc.sortTags", val)
|
|
190
|
+
if err != nil {
|
|
191
|
+
return nil, err
|
|
192
|
+
}
|
|
193
|
+
jdOpts["sortTags"] = b
|
|
194
|
+
default:
|
|
195
|
+
return nil, fmt.Errorf("@ttsc/lint: format.jsdoc unknown key %q (allowed: tagSynonyms, sortTags)", key)
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
default:
|
|
199
|
+
return nil, fmt.Errorf("@ttsc/lint: format.jsdoc must be a boolean or object, got %T", v)
|
|
200
|
+
}
|
|
201
|
+
if enabled {
|
|
202
|
+
out["format/jsdoc"] = ruleEntry(jdOpts)
|
|
203
|
+
}
|
|
204
|
+
}
|
|
168
205
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
jdOpts := map[string]any{}
|
|
172
|
-
enabled := false
|
|
173
|
-
switch j := v.(type) {
|
|
174
|
-
case bool:
|
|
175
|
-
enabled = j
|
|
176
|
-
case map[string]any:
|
|
177
|
-
enabled = true
|
|
178
|
-
for key, val := range j {
|
|
179
|
-
switch key {
|
|
180
|
-
case "tagSynonyms":
|
|
181
|
-
ts, ok := val.(map[string]any)
|
|
182
|
-
if !ok {
|
|
183
|
-
return nil, fmt.Errorf("@ttsc/lint: format.jsdoc.tagSynonyms must be an object, got %T", val)
|
|
184
|
-
}
|
|
185
|
-
// Element values must be strings; surface
|
|
186
|
-
// typos early instead of after a downstream
|
|
187
|
-
// JSON-decode failure.
|
|
188
|
-
for k, v := range ts {
|
|
189
|
-
if _, ok := v.(string); !ok {
|
|
190
|
-
return nil, fmt.Errorf("@ttsc/lint: format.jsdoc.tagSynonyms[%q] must be a string, got %T", k, v)
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
jdOpts["tagSynonyms"] = ts
|
|
194
|
-
case "sortTags":
|
|
195
|
-
b, err := asBool("format.jsdoc.sortTags", val)
|
|
196
|
-
if err != nil {
|
|
197
|
-
return nil, err
|
|
198
|
-
}
|
|
199
|
-
jdOpts["sortTags"] = b
|
|
200
|
-
default:
|
|
201
|
-
return nil, fmt.Errorf("@ttsc/lint: format.jsdoc unknown key %q (allowed: tagSynonyms, sortTags)", key)
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
default:
|
|
205
|
-
return nil, fmt.Errorf("@ttsc/lint: format.jsdoc must be a boolean or object, got %T", v)
|
|
206
|
-
}
|
|
207
|
-
if enabled {
|
|
208
|
-
out["format/jsdoc"] = []any{severity, jdOpts}
|
|
209
|
-
}
|
|
210
|
-
}
|
|
206
|
+
return out, nil
|
|
207
|
+
}
|
|
211
208
|
|
|
212
|
-
|
|
209
|
+
func formatBlockSeverity(raw map[string]any) (Severity, error) {
|
|
210
|
+
value, ok := raw["severity"]
|
|
211
|
+
if !ok || value == nil {
|
|
212
|
+
return SeverityOff, nil
|
|
213
|
+
}
|
|
214
|
+
severity, err := parseSeverity(value)
|
|
215
|
+
if err != nil {
|
|
216
|
+
return SeverityOff, fmt.Errorf("@ttsc/lint: format.severity: %w", err)
|
|
217
|
+
}
|
|
218
|
+
return severity, nil
|
|
213
219
|
}
|
|
214
220
|
|
|
215
221
|
// mergeRuleMaps overlays `overrides` on `base` and returns the merged
|
|
@@ -219,142 +225,91 @@ func expandFormatBlock(raw map[string]any) (map[string]any, error) {
|
|
|
219
225
|
// `format/*` rule fully replaces the corresponding entry expanded
|
|
220
226
|
// from the `format` block.
|
|
221
227
|
func mergeRuleMaps(base, overrides map[string]any) map[string]any {
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
228
|
+
out := make(map[string]any, len(base)+len(overrides))
|
|
229
|
+
for k, v := range base {
|
|
230
|
+
out[k] = v
|
|
231
|
+
}
|
|
232
|
+
for k, v := range overrides {
|
|
233
|
+
out[k] = v
|
|
234
|
+
}
|
|
235
|
+
return out
|
|
230
236
|
}
|
|
231
237
|
|
|
232
238
|
func asBool(field string, v any) (bool, error) {
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
239
|
+
if b, ok := v.(bool); ok {
|
|
240
|
+
return b, nil
|
|
241
|
+
}
|
|
242
|
+
return false, fmt.Errorf("@ttsc/lint: %s must be a boolean, got %T", field, v)
|
|
237
243
|
}
|
|
238
244
|
|
|
239
245
|
func asString(field string, v any) (string, error) {
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
246
|
+
if s, ok := v.(string); ok {
|
|
247
|
+
return s, nil
|
|
248
|
+
}
|
|
249
|
+
return "", fmt.Errorf("@ttsc/lint: %s must be a string, got %T", field, v)
|
|
244
250
|
}
|
|
245
251
|
|
|
246
252
|
func asInt(field string, v any) (int, error) {
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
253
|
+
switch n := v.(type) {
|
|
254
|
+
case int:
|
|
255
|
+
return n, nil
|
|
256
|
+
case int32:
|
|
257
|
+
return int(n), nil
|
|
258
|
+
case int64:
|
|
259
|
+
return int(n), nil
|
|
260
|
+
case float64:
|
|
261
|
+
// JSON numbers decode as float64; coerce when integer-valued.
|
|
262
|
+
if float64(int(n)) == n {
|
|
263
|
+
return int(n), nil
|
|
264
|
+
}
|
|
265
|
+
case json.Number:
|
|
266
|
+
i, err := n.Int64()
|
|
267
|
+
if err == nil {
|
|
268
|
+
return int(i), nil
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
return 0, fmt.Errorf("@ttsc/lint: %s must be an integer, got %T", field, v)
|
|
266
272
|
}
|
|
267
273
|
|
|
268
274
|
func asStringSlice(field string, v any) ([]string, error) {
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
func severityIsOff(s string) bool {
|
|
285
|
-
switch s {
|
|
286
|
-
case "off":
|
|
287
|
-
return true
|
|
288
|
-
case "0":
|
|
289
|
-
return true
|
|
290
|
-
}
|
|
291
|
-
return false
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
// parseFormatSeverity validates a `format.severity` value (string or
|
|
295
|
-
// numeric) against the documented allow-list and returns the
|
|
296
|
-
// canonical string form. The strict allow-list catches typos
|
|
297
|
-
// (`"warm"`, `"fatal"`) at the format block's boundary instead of
|
|
298
|
-
// letting them leak through into per-rule entries where the eventual
|
|
299
|
-
// error message blames the rule by name.
|
|
300
|
-
func parseFormatSeverity(v any) (string, error) {
|
|
301
|
-
switch t := v.(type) {
|
|
302
|
-
case string:
|
|
303
|
-
switch t {
|
|
304
|
-
case "off", "warn", "warning", "error":
|
|
305
|
-
return t, nil
|
|
306
|
-
}
|
|
307
|
-
return "", fmt.Errorf("@ttsc/lint: format.severity must be one of \"off\", \"warn\", \"warning\", \"error\"; got %q", t)
|
|
308
|
-
case float64:
|
|
309
|
-
if t != float64(int(t)) {
|
|
310
|
-
return "", fmt.Errorf("@ttsc/lint: format.severity numeric form must be 0, 1, or 2; got %v", t)
|
|
311
|
-
}
|
|
312
|
-
switch int(t) {
|
|
313
|
-
case 0:
|
|
314
|
-
return "off", nil
|
|
315
|
-
case 1:
|
|
316
|
-
return "warn", nil
|
|
317
|
-
case 2:
|
|
318
|
-
return "error", nil
|
|
319
|
-
}
|
|
320
|
-
return "", fmt.Errorf("@ttsc/lint: format.severity numeric form must be 0, 1, or 2; got %d", int(t))
|
|
321
|
-
case int:
|
|
322
|
-
switch t {
|
|
323
|
-
case 0:
|
|
324
|
-
return "off", nil
|
|
325
|
-
case 1:
|
|
326
|
-
return "warn", nil
|
|
327
|
-
case 2:
|
|
328
|
-
return "error", nil
|
|
329
|
-
}
|
|
330
|
-
return "", fmt.Errorf("@ttsc/lint: format.severity numeric form must be 0, 1, or 2; got %d", t)
|
|
331
|
-
}
|
|
332
|
-
return "", fmt.Errorf("@ttsc/lint: format.severity must be a string or number, got %T", v)
|
|
275
|
+
arr, ok := v.([]any)
|
|
276
|
+
if !ok {
|
|
277
|
+
return nil, fmt.Errorf("@ttsc/lint: %s must be an array of strings, got %T", field, v)
|
|
278
|
+
}
|
|
279
|
+
out := make([]string, 0, len(arr))
|
|
280
|
+
for i, item := range arr {
|
|
281
|
+
s, ok := item.(string)
|
|
282
|
+
if !ok {
|
|
283
|
+
return nil, fmt.Errorf("@ttsc/lint: %s[%d] must be a string, got %T", field, i, item)
|
|
284
|
+
}
|
|
285
|
+
out = append(out, s)
|
|
286
|
+
}
|
|
287
|
+
return out, nil
|
|
333
288
|
}
|
|
334
289
|
|
|
335
290
|
// rejectUnknownFormatKeys surfaces typos in top-level format-block
|
|
336
291
|
// keys at the boundary rather than silently ignoring them. The
|
|
337
|
-
//
|
|
292
|
+
// key set mirrors `ITtscLintFormatConfig` exactly.
|
|
338
293
|
func rejectUnknownFormatKeys(raw map[string]any) error {
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
294
|
+
allowed := map[string]struct{}{
|
|
295
|
+
"severity": {},
|
|
296
|
+
"semi": {},
|
|
297
|
+
"singleQuote": {},
|
|
298
|
+
"trailingComma": {},
|
|
299
|
+
"printWidth": {},
|
|
300
|
+
"tabWidth": {},
|
|
301
|
+
"useTabs": {},
|
|
302
|
+
"endOfLine": {},
|
|
303
|
+
"importOrder": {},
|
|
304
|
+
"importOrderSeparation": {},
|
|
305
|
+
"importOrderSortSpecifiers": {},
|
|
306
|
+
"importOrderCaseInsensitive": {},
|
|
307
|
+
"jsdoc": {},
|
|
308
|
+
}
|
|
309
|
+
for key := range raw {
|
|
310
|
+
if _, ok := allowed[key]; !ok {
|
|
311
|
+
return fmt.Errorf("@ttsc/lint: format unknown key %q; see ITtscLintFormatConfig for the allowed surface", key)
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
return nil
|
|
360
315
|
}
|