@ttsc/lint 0.12.3 → 0.12.4

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.
Files changed (62) hide show
  1. package/lib/defaultFormat.d.ts +10 -11
  2. package/lib/defaultFormat.js +10 -11
  3. package/lib/defaultFormat.js.map +1 -1
  4. package/lib/index.js +10 -4
  5. package/lib/index.js.map +1 -1
  6. package/lib/structures/ITtscLintConfig.d.ts +2 -2
  7. package/lib/structures/ITtscLintFormatConfig.d.ts +53 -55
  8. package/lib/structures/ITtscLintPluginMeta.d.ts +9 -1
  9. package/lib/structures/TtscLintRule.d.ts +10 -1
  10. package/lib/structures/TtscLintRuleMap.d.ts +2 -2
  11. package/linthost/ast_helpers.go +17 -0
  12. package/linthost/compile.go +37 -0
  13. package/linthost/config.go +184 -35
  14. package/linthost/config_format.go +257 -244
  15. package/linthost/directives.go +72 -0
  16. package/linthost/dispatch.go +33 -33
  17. package/linthost/engine.go +4 -0
  18. package/linthost/eslint_runtime.go +31 -0
  19. package/linthost/fix.go +29 -0
  20. package/linthost/format.go +23 -0
  21. package/linthost/host.go +11 -0
  22. package/linthost/print_dispatch.go +7 -5
  23. package/linthost/print_doc.go +4 -0
  24. package/linthost/print_nodes_call.go +9 -0
  25. package/linthost/rules_arrays.go +5 -2
  26. package/linthost/rules_debugger.go +3 -2
  27. package/linthost/rules_dupes.go +7 -4
  28. package/linthost/rules_empty.go +3 -2
  29. package/linthost/rules_escape.go +15 -0
  30. package/linthost/rules_eval.go +3 -0
  31. package/linthost/rules_finally.go +11 -0
  32. package/linthost/rules_format_jsdoc.go +7 -0
  33. package/linthost/rules_format_print_width.go +3 -0
  34. package/linthost/rules_format_quotes.go +3 -0
  35. package/linthost/rules_format_sort_imports.go +4 -0
  36. package/linthost/rules_gap.go +20 -0
  37. package/linthost/rules_imports.go +5 -7
  38. package/linthost/rules_logic.go +11 -0
  39. package/linthost/rules_loops.go +4 -0
  40. package/linthost/rules_misc.go +4 -2
  41. package/linthost/rules_params.go +7 -11
  42. package/linthost/rules_problems.go +24 -1
  43. package/linthost/rules_promise.go +12 -0
  44. package/linthost/rules_protos.go +3 -2
  45. package/linthost/rules_self.go +6 -0
  46. package/linthost/rules_strings.go +10 -0
  47. package/linthost/rules_suggestions.go +14 -4
  48. package/linthost/rules_throw.go +2 -0
  49. package/linthost/rules_ts.go +13 -0
  50. package/linthost/rules_ts_extra.go +25 -8
  51. package/linthost/rules_var.go +14 -1
  52. package/package.json +3 -3
  53. package/plugin/main.go +4 -4
  54. package/rule/astutil/astutil.go +12 -0
  55. package/rule/rule.go +123 -123
  56. package/src/defaultFormat.ts +10 -11
  57. package/src/index.ts +16 -4
  58. package/src/structures/ITtscLintConfig.ts +2 -2
  59. package/src/structures/ITtscLintFormatConfig.ts +53 -55
  60. package/src/structures/ITtscLintPluginMeta.ts +9 -1
  61. package/src/structures/TtscLintRule.ts +10 -1
  62. package/src/structures/TtscLintRuleMap.ts +17 -18
@@ -1,8 +1,8 @@
1
1
  package linthost
2
2
 
3
3
  import (
4
- "encoding/json"
5
- "fmt"
4
+ "encoding/json"
5
+ "fmt"
6
6
  )
7
7
 
8
8
  // expandFormatBlock translates a Prettier-style `format` block into the
@@ -33,189 +33,193 @@ import (
33
33
  // semantics) before invoking the existing parsers — `mergeRuleMaps`
34
34
  // below performs the merge with the right precedence.
35
35
  func expandFormatBlock(raw map[string]any) (map[string]any, error) {
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{}
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{}
50
50
 
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})
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})
63
63
 
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})
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})
76
76
 
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})
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})
92
92
 
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)
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)
127
127
 
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
- }
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
+ }
162
162
 
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
- }
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
+ }
205
205
 
206
- return out, nil
206
+ return out, nil
207
207
  }
208
208
 
209
+ // formatBlockSeverity extracts the optional `severity` field from a format
210
+ // block. Defaults to SeverityOff so that format rules never produce check/build
211
+ // diagnostics unless the user explicitly opts in. SeverityOff still allows
212
+ // `ttsc format` to apply formatting; it only suppresses error/warning output.
209
213
  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
214
+ value, ok := raw["severity"]
215
+ if !ok || value == nil {
216
+ return SeverityOff, nil
217
+ }
218
+ severity, err := parseSeverity(value)
219
+ if err != nil {
220
+ return SeverityOff, fmt.Errorf("@ttsc/lint: format.severity: %w", err)
221
+ }
222
+ return severity, nil
219
223
  }
220
224
 
221
225
  // mergeRuleMaps overlays `overrides` on `base` and returns the merged
@@ -225,91 +229,100 @@ func formatBlockSeverity(raw map[string]any) (Severity, error) {
225
229
  // `format/*` rule fully replaces the corresponding entry expanded
226
230
  // from the `format` block.
227
231
  func mergeRuleMaps(base, overrides map[string]any) map[string]any {
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
232
+ out := make(map[string]any, len(base)+len(overrides))
233
+ for k, v := range base {
234
+ out[k] = v
235
+ }
236
+ for k, v := range overrides {
237
+ out[k] = v
238
+ }
239
+ return out
236
240
  }
237
241
 
242
+ // asBool coerces a raw config value to a bool, returning a typed error on
243
+ // failure. Used by expandFormatBlock to validate boolean format fields.
238
244
  func asBool(field string, v any) (bool, error) {
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)
245
+ if b, ok := v.(bool); ok {
246
+ return b, nil
247
+ }
248
+ return false, fmt.Errorf("@ttsc/lint: %s must be a boolean, got %T", field, v)
243
249
  }
244
250
 
251
+ // asString coerces a raw config value to a string, returning a typed error on
252
+ // failure. Used by expandFormatBlock to validate string format fields.
245
253
  func asString(field string, v any) (string, error) {
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)
254
+ if s, ok := v.(string); ok {
255
+ return s, nil
256
+ }
257
+ return "", fmt.Errorf("@ttsc/lint: %s must be a string, got %T", field, v)
250
258
  }
251
259
 
260
+ // asInt coerces a raw config value to an int. Accepts all integer-shaped Go
261
+ // numeric types plus float64 (the default JSON decode type) and json.Number,
262
+ // rejecting fractional float64 values since no format option takes a non-integer.
252
263
  func asInt(field string, v any) (int, error) {
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)
264
+ switch n := v.(type) {
265
+ case int:
266
+ return n, nil
267
+ case int32:
268
+ return int(n), nil
269
+ case int64:
270
+ return int(n), nil
271
+ case float64:
272
+ // JSON numbers decode as float64; coerce when integer-valued.
273
+ if float64(int(n)) == n {
274
+ return int(n), nil
275
+ }
276
+ case json.Number:
277
+ i, err := n.Int64()
278
+ if err == nil {
279
+ return int(i), nil
280
+ }
281
+ }
282
+ return 0, fmt.Errorf("@ttsc/lint: %s must be an integer, got %T", field, v)
272
283
  }
273
284
 
285
+ // asStringSlice coerces a raw config value to a []string, returning a typed
286
+ // error on failure. Used by expandFormatBlock to validate importOrder.
274
287
  func asStringSlice(field string, v any) ([]string, error) {
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
288
+ arr, ok := v.([]any)
289
+ if !ok {
290
+ return nil, fmt.Errorf("@ttsc/lint: %s must be an array of strings, got %T", field, v)
291
+ }
292
+ out := make([]string, 0, len(arr))
293
+ for i, item := range arr {
294
+ s, ok := item.(string)
295
+ if !ok {
296
+ return nil, fmt.Errorf("@ttsc/lint: %s[%d] must be a string, got %T", field, i, item)
297
+ }
298
+ out = append(out, s)
299
+ }
300
+ return out, nil
288
301
  }
289
302
 
290
303
  // rejectUnknownFormatKeys surfaces typos in top-level format-block
291
304
  // keys at the boundary rather than silently ignoring them. The
292
305
  // key set mirrors `ITtscLintFormatConfig` exactly.
293
306
  func rejectUnknownFormatKeys(raw map[string]any) error {
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
307
+ allowed := map[string]struct{}{
308
+ "severity": {},
309
+ "semi": {},
310
+ "singleQuote": {},
311
+ "trailingComma": {},
312
+ "printWidth": {},
313
+ "tabWidth": {},
314
+ "useTabs": {},
315
+ "endOfLine": {},
316
+ "importOrder": {},
317
+ "importOrderSeparation": {},
318
+ "importOrderSortSpecifiers": {},
319
+ "importOrderCaseInsensitive": {},
320
+ "jsdoc": {},
321
+ }
322
+ for key := range raw {
323
+ if _, ok := allowed[key]; !ok {
324
+ return fmt.Errorf("@ttsc/lint: format unknown key %q; see ITtscLintFormatConfig for the allowed surface", key)
325
+ }
326
+ }
327
+ return nil
315
328
  }