@ttsc/lint 0.18.4 → 0.19.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (188) hide show
  1. package/README.md +125 -43
  2. package/go.mod +5 -4
  3. package/internal/rulecode/rulecode.go +88 -0
  4. package/lib/index.d.ts +10 -1
  5. package/lib/index.js +45 -17
  6. package/lib/index.js.map +1 -1
  7. package/lib/structures/ITtscLintConfig.d.ts +3 -0
  8. package/lib/structures/TtscLintRuleSetting.d.ts +3 -2
  9. package/lib/structures/format/ITtscLintFormat.d.ts +3 -2
  10. package/lib/structures/format/ITtscLintFormatSortImports.d.ts +20 -4
  11. package/lib/structures/rules/ITtscLintBoundariesRuleOptions.d.ts +80 -8
  12. package/lib/structures/rules/ITtscLintBoundariesRules.d.ts +6 -8
  13. package/lib/structures/rules/ITtscLintContributorRules.d.ts +18 -2
  14. package/lib/structures/rules/ITtscLintCoreRuleOptions.d.ts +347 -0
  15. package/lib/structures/rules/ITtscLintCoreRuleOptions.js +3 -0
  16. package/lib/structures/rules/ITtscLintCoreRuleOptions.js.map +1 -0
  17. package/lib/structures/rules/ITtscLintCoreRules.d.ts +101 -67
  18. package/lib/structures/rules/ITtscLintRegexpRules.d.ts +7 -2
  19. package/lib/structures/rules/ITtscLintRuleOptionsMap.d.ts +53 -7
  20. package/lib/structures/rules/ITtscLintRules.d.ts +6 -2
  21. package/lib/structures/rules/ITtscLintSolidRules.d.ts +0 -8
  22. package/lib/structures/rules/ITtscLintTypeScriptRuleOptions.d.ts +206 -0
  23. package/lib/structures/rules/ITtscLintTypeScriptRuleOptions.js +8 -0
  24. package/lib/structures/rules/ITtscLintTypeScriptRuleOptions.js.map +1 -0
  25. package/lib/structures/rules/ITtscLintTypeScriptRules.d.ts +55 -42
  26. package/lib/structures/rules/ITtscLintUnicornRuleOptions.d.ts +276 -0
  27. package/lib/structures/rules/ITtscLintUnicornRuleOptions.js +3 -0
  28. package/lib/structures/rules/ITtscLintUnicornRuleOptions.js.map +1 -0
  29. package/lib/structures/rules/ITtscLintUnicornRules.d.ts +44 -36
  30. package/lib/structures/rules/index.d.ts +3 -0
  31. package/lib/structures/rules/index.js +3 -0
  32. package/lib/structures/rules/index.js.map +1 -1
  33. package/linthost/ast_helpers.go +521 -23
  34. package/linthost/ast_selector_matcher.go +1628 -0
  35. package/linthost/ast_selector_parser.go +628 -0
  36. package/linthost/comment_scan.go +154 -0
  37. package/linthost/compile.go +129 -79
  38. package/linthost/config.go +352 -88
  39. package/linthost/config_format.go +15 -1
  40. package/linthost/contrib_adapter.go +47 -8
  41. package/linthost/declaration_rules.go +12 -11
  42. package/linthost/directives.go +9 -67
  43. package/linthost/dispatch.go +37 -3
  44. package/linthost/engine.go +368 -53
  45. package/linthost/escape_string.go +77 -0
  46. package/linthost/fix.go +133 -23
  47. package/linthost/flags_gen.go +18 -17
  48. package/linthost/format.go +73 -167
  49. package/linthost/format_editor_settings.go +134 -26
  50. package/linthost/grapheme_tables_gen.go +1920 -0
  51. package/linthost/host.go +159 -61
  52. package/linthost/literal_escape_scan.go +117 -0
  53. package/linthost/lsp.go +545 -98
  54. package/linthost/numeric_literal_precision.go +228 -0
  55. package/linthost/polyfill_browserslist.go +1439 -0
  56. package/linthost/polyfill_browserslist_config.go +603 -0
  57. package/linthost/polyfill_corejs.go +750 -0
  58. package/linthost/polyfill_data.go +113 -0
  59. package/linthost/polyfill_data_gen.json +39868 -0
  60. package/linthost/print_nodes_array.go +7 -1
  61. package/linthost/print_nodes_object.go +7 -1
  62. package/linthost/project_engine.go +231 -0
  63. package/linthost/project_rules.go +78 -0
  64. package/linthost/regex_clean.go +191 -0
  65. package/linthost/regex_tree.go +1544 -0
  66. package/linthost/regex_tree_optimizer.go +1685 -0
  67. package/linthost/regexp_cache.go +44 -0
  68. package/linthost/rule_codes.go +105 -0
  69. package/linthost/rule_codes.json +745 -0
  70. package/linthost/rules_ban_ts_comment.go +312 -0
  71. package/linthost/rules_boundaries.go +128 -43
  72. package/linthost/rules_boundaries_dependencies.go +1013 -0
  73. package/linthost/rules_core_extra.go +62 -92
  74. package/linthost/rules_cypress.go +1 -1
  75. package/linthost/rules_default_case.go +87 -11
  76. package/linthost/rules_dupes.go +38 -13
  77. package/linthost/rules_empty.go +188 -20
  78. package/linthost/rules_escape.go +62 -48
  79. package/linthost/rules_format_arrow_parens.go +19 -5
  80. package/linthost/rules_format_bracket_spacing.go +1 -1
  81. package/linthost/rules_format_clause_join.go +1 -1
  82. package/linthost/rules_format_declaration_header.go +31 -19
  83. package/linthost/rules_format_indent.go +1 -1
  84. package/linthost/rules_format_jsdoc.go +1 -1
  85. package/linthost/rules_format_orphan_semi.go +1 -1
  86. package/linthost/rules_format_parameter_properties.go +17 -7
  87. package/linthost/rules_format_print_width.go +1 -1
  88. package/linthost/rules_format_quote_props.go +1 -1
  89. package/linthost/rules_format_quotes.go +1 -1
  90. package/linthost/rules_format_semi.go +106 -79
  91. package/linthost/rules_format_sort_imports.go +79 -45
  92. package/linthost/rules_format_statement_split.go +1 -1
  93. package/linthost/rules_format_ternary_nullish_parens.go +1 -1
  94. package/linthost/rules_format_trailing_comma.go +64 -1
  95. package/linthost/rules_format_whitespace.go +1 -1
  96. package/linthost/rules_functional.go +21 -22
  97. package/linthost/rules_gap.go +80 -3
  98. package/linthost/rules_grouped_accessor_pairs.go +84 -25
  99. package/linthost/rules_guard_for_in.go +64 -147
  100. package/linthost/rules_jsx_a11y.go +232 -22
  101. package/linthost/rules_logic.go +84 -38
  102. package/linthost/rules_nextjs.go +105 -16
  103. package/linthost/rules_no_class_assign.go +70 -0
  104. package/linthost/rules_no_duplicate_imports.go +266 -0
  105. package/linthost/rules_no_else_return.go +156 -38
  106. package/linthost/rules_no_extend_native.go +113 -17
  107. package/linthost/rules_no_fallthrough.go +1013 -0
  108. package/linthost/rules_no_func_assign.go +70 -0
  109. package/linthost/rules_no_import_assign.go +278 -86
  110. package/linthost/rules_no_inner_declarations.go +186 -0
  111. package/linthost/rules_no_loop_func.go +438 -53
  112. package/linthost/rules_no_mixed_operators.go +208 -52
  113. package/linthost/rules_no_param_reassign.go +376 -61
  114. package/linthost/rules_no_promise_executor_return.go +156 -0
  115. package/linthost/rules_no_restricted_imports.go +1013 -57
  116. package/linthost/rules_no_restricted_syntax.go +190 -26
  117. package/linthost/rules_no_useless_constructor.go +6 -36
  118. package/linthost/rules_problems.go +154 -311
  119. package/linthost/rules_promise.go +2578 -109
  120. package/linthost/rules_react_extras.go +71 -45
  121. package/linthost/rules_react_hooks.go +18 -2
  122. package/linthost/rules_react_perf.go +1 -0
  123. package/linthost/rules_react_refresh.go +1 -1
  124. package/linthost/rules_regexp.go +230 -14
  125. package/linthost/rules_security.go +45 -19
  126. package/linthost/rules_self.go +21 -1
  127. package/linthost/rules_solid.go +13 -7
  128. package/linthost/rules_storybook.go +1 -1
  129. package/linthost/rules_suggestions.go +490 -84
  130. package/linthost/rules_testing_library.go +12 -1
  131. package/linthost/rules_ts.go +227 -54
  132. package/linthost/rules_ts_async.go +0 -131
  133. package/linthost/rules_ts_no_base_to_string.go +3 -4
  134. package/linthost/rules_ts_no_misused_promises.go +817 -0
  135. package/linthost/rules_ts_no_restricted_types.go +320 -34
  136. package/linthost/rules_ts_no_unnecessary_qualifier.go +132 -55
  137. package/linthost/rules_ts_no_unsafe_assignment.go +608 -60
  138. package/linthost/rules_ts_require_array_sort_compare.go +5 -5
  139. package/linthost/rules_ts_switch_exhaustiveness_check.go +556 -86
  140. package/linthost/rules_unicorn_better_regex.go +224 -16
  141. package/linthost/rules_unicorn_consistent_destructuring.go +1002 -7
  142. package/linthost/rules_unicorn_consistent_existence_index_check.go +208 -29
  143. package/linthost/rules_unicorn_consistent_function_scoping.go +1041 -8
  144. package/linthost/rules_unicorn_consistent_template_literal_escape.go +178 -7
  145. package/linthost/rules_unicorn_escape_case.go +30 -19
  146. package/linthost/rules_unicorn_filename_case.go +763 -16
  147. package/linthost/rules_unicorn_import_style.go +783 -17
  148. package/linthost/rules_unicorn_isolated_functions.go +934 -14
  149. package/linthost/rules_unicorn_no_hex_escape.go +26 -11
  150. package/linthost/rules_unicorn_no_typeof_undefined.go +234 -25
  151. package/linthost/rules_unicorn_no_unnecessary_polyfills.go +376 -21
  152. package/linthost/rules_unicorn_no_unused_properties.go +756 -19
  153. package/linthost/rules_unicorn_number_literal_case.go +58 -39
  154. package/linthost/rules_unicorn_prefer_at.go +7 -3
  155. package/linthost/rules_unicorn_prefer_number_properties.go +381 -48
  156. package/linthost/rules_unicorn_prefer_optional_catch_binding.go +24 -24
  157. package/linthost/rules_unicorn_prefer_simple_condition_first.go +597 -19
  158. package/linthost/rules_unicorn_prefer_string_raw.go +147 -16
  159. package/linthost/rules_unicorn_prevent_abbreviations.go +1963 -95
  160. package/linthost/rules_unicorn_string_content.go +611 -15
  161. package/linthost/rules_unicorn_switch_case_break_position.go +172 -15
  162. package/linthost/rules_unicorn_template_indent.go +923 -18
  163. package/linthost/rules_unicorn_text_encoding_identifier_case.go +202 -42
  164. package/linthost/rules_unicorn_throw_new_error.go +218 -26
  165. package/linthost/rules_var.go +829 -142
  166. package/linthost/string_length.go +222 -0
  167. package/package.json +4 -3
  168. package/rule/project.go +206 -0
  169. package/rule/rule.go +58 -1
  170. package/src/index.ts +58 -17
  171. package/src/structures/ITtscLintConfig.ts +3 -0
  172. package/src/structures/TtscLintRuleSetting.ts +3 -2
  173. package/src/structures/format/ITtscLintFormat.ts +3 -2
  174. package/src/structures/format/ITtscLintFormatSortImports.ts +21 -4
  175. package/src/structures/rules/ITtscLintBoundariesRuleOptions.ts +122 -8
  176. package/src/structures/rules/ITtscLintBoundariesRules.ts +6 -8
  177. package/src/structures/rules/ITtscLintContributorRules.ts +18 -2
  178. package/src/structures/rules/ITtscLintCoreRuleOptions.ts +465 -0
  179. package/src/structures/rules/ITtscLintCoreRules.ts +122 -67
  180. package/src/structures/rules/ITtscLintRegexpRules.ts +7 -2
  181. package/src/structures/rules/ITtscLintRuleOptionsMap.ts +88 -7
  182. package/src/structures/rules/ITtscLintRules.ts +7 -2
  183. package/src/structures/rules/ITtscLintSolidRules.ts +0 -9
  184. package/src/structures/rules/ITtscLintTypeScriptRuleOptions.ts +248 -0
  185. package/src/structures/rules/ITtscLintTypeScriptRules.ts +64 -42
  186. package/src/structures/rules/ITtscLintUnicornRuleOptions.ts +350 -0
  187. package/src/structures/rules/ITtscLintUnicornRules.ts +60 -36
  188. package/src/structures/rules/index.ts +3 -0
@@ -1,8 +1,10 @@
1
1
  package linthost
2
2
 
3
3
  import (
4
+ "bytes"
4
5
  "encoding/json"
5
6
  "errors"
7
+ "io"
6
8
  "os"
7
9
  "path/filepath"
8
10
  "strings"
@@ -38,10 +40,10 @@ func loadFormatRules(pluginsJSON, cwd, tsconfigPath string) (RuleResolver, error
38
40
  //
39
41
  // This is consulted only when lint.config.* configures no `format` block: a
40
42
  // configured format block always wins and never reaches this path. Within
41
- // settings.json a language-specific section matching `language` (e.g.
42
- // "[typescript]" or a combined "[javascript][typescript][json]") wins over the
43
- // top-level keys, mirroring VS Code's own resolution. Pass language="" to skip
44
- // language sections (e.g. the project-wide `ttsc format` CLI path).
43
+ // settings.json, matching combined language sections are applied in source
44
+ // order and an exact single-language section is applied last, mirroring VS
45
+ // Code's own resolution. Pass language="" to skip language sections (e.g. the
46
+ // project-wide `ttsc format` CLI path).
45
47
  //
46
48
  // Mapping (values use JSON types so they parse identically to a real `format`
47
49
  // block, where numbers decode as float64):
@@ -63,17 +65,22 @@ func editorFormatOverrides(startDir string, language string) map[string]any {
63
65
  if !ok {
64
66
  return out
65
67
  }
66
- // Top-level keys first, then language-specific sections override them.
67
- applyEditorSettings(out, settings)
68
+ // Top-level keys first, then matching combined language sections in source
69
+ // order. VS Code holds an exact single-language section aside and applies it
70
+ // last, regardless of where its full selector appears in settings.json.
71
+ applyEditorSettings(out, settings.values)
68
72
  if language != "" {
69
- for key, value := range settings {
70
- if !sectionMatchesLanguage(key, language) {
73
+ var exact map[string]any
74
+ for _, section := range settings.languageSections {
75
+ if len(section.identifiers) == 1 && section.identifiers[0] == language {
76
+ exact = section.values
71
77
  continue
72
78
  }
73
- if section, ok := value.(map[string]any); ok {
74
- applyEditorSettings(out, section)
79
+ if languageSectionContains(section.identifiers, language) {
80
+ applyEditorSettings(out, section.values)
75
81
  }
76
82
  }
83
+ applyEditorSettings(out, exact)
77
84
  }
78
85
  return out
79
86
  }
@@ -105,20 +112,41 @@ func applyEditorSettings(out map[string]any, settings map[string]any) {
105
112
  }
106
113
  }
107
114
 
108
- // sectionMatchesLanguage reports whether a settings.json key is a
109
- // language-specific section header (e.g. "[typescript]" or the combined
110
- // "[javascript][typescript][json]") that applies to language. An empty
111
- // language never matches.
112
- func sectionMatchesLanguage(key string, language string) bool {
113
- if language == "" {
114
- return false
115
+ // languageSectionIdentifiers parses a complete VS Code language selector such
116
+ // as "[typescript]" or "[javascript][typescript][json]". Each full selector is
117
+ // one override scope; its identifiers are used only to determine whether that
118
+ // scope applies to the requested language. Identifiers are trimmed and
119
+ // deduplicated, and empty normalized identifiers are discarded, before
120
+ // exact-versus-combined precedence is decided.
121
+ func languageSectionIdentifiers(key string) ([]string, bool) {
122
+ identifiers := []string{}
123
+ for len(key) != 0 {
124
+ if key[0] != '[' {
125
+ return nil, false
126
+ }
127
+ close := strings.IndexByte(key, ']')
128
+ if close <= 1 {
129
+ return nil, false
130
+ }
131
+ identifier := strings.TrimSpace(key[1:close])
132
+ if identifier == "" {
133
+ key = key[close+1:]
134
+ continue
135
+ }
136
+ if !languageSectionContains(identifiers, identifier) {
137
+ identifiers = append(identifiers, identifier)
138
+ }
139
+ key = key[close+1:]
115
140
  }
116
- if !strings.HasPrefix(key, "[") || !strings.HasSuffix(key, "]") {
141
+ return identifiers, len(identifiers) != 0
142
+ }
143
+
144
+ func languageSectionContains(identifiers []string, language string) bool {
145
+ if language == "" {
117
146
  return false
118
147
  }
119
- inner := strings.TrimSuffix(strings.TrimPrefix(key, "["), "]")
120
- for _, lang := range strings.Split(inner, "][") {
121
- if lang == language {
148
+ for _, identifier := range identifiers {
149
+ if identifier == language {
122
150
  return true
123
151
  }
124
152
  }
@@ -143,30 +171,110 @@ func vscodeLanguageID(filePath string) string {
143
171
  }
144
172
  }
145
173
 
174
+ type vscodeSettings struct {
175
+ values map[string]any
176
+ languageSections []vscodeLanguageSection
177
+ }
178
+
179
+ type vscodeLanguageSection struct {
180
+ identifiers []string
181
+ values map[string]any
182
+ }
183
+
146
184
  // loadNearestVSCodeSettings walks up from startDir looking for a
147
185
  // `.vscode/settings.json`, returning the first one found decoded as a JSONC
148
186
  // document. It returns ok=false when none exists or the file cannot be parsed,
149
187
  // so a broken settings file degrades to the format defaults instead of breaking
150
188
  // the formatter.
151
- func loadNearestVSCodeSettings(startDir string) (map[string]any, bool) {
189
+ func loadNearestVSCodeSettings(startDir string) (vscodeSettings, bool) {
152
190
  dir := startDir
153
191
  for {
154
192
  candidate := filepath.Join(dir, ".vscode", "settings.json")
155
193
  if data, err := os.ReadFile(candidate); err == nil {
156
- var parsed map[string]any
157
- if json.Unmarshal(stripJSONC(data), &parsed) == nil {
194
+ data = bytes.TrimPrefix(data, []byte{0xEF, 0xBB, 0xBF})
195
+ parsed, err := decodeVSCodeSettings(stripJSONC(data))
196
+ if err == nil {
158
197
  return parsed, true
159
198
  }
160
- return nil, false
199
+ return vscodeSettings{}, false
161
200
  }
162
201
  parent := filepath.Dir(dir)
163
202
  if parent == dir {
164
- return nil, false
203
+ return vscodeSettings{}, false
165
204
  }
166
205
  dir = parent
167
206
  }
168
207
  }
169
208
 
209
+ // decodeVSCodeSettings decodes the top-level JSON object without discarding
210
+ // declaration order. encoding/json maps are intentionally unordered, while VS
211
+ // Code merges matching combined language sections in the order they appear in
212
+ // settings.json and applies the exact single-language section afterwards. A
213
+ // repeated property keeps its first insertion position and final value, as it
214
+ // does in VS Code's parsed settings object.
215
+ func decodeVSCodeSettings(data []byte) (vscodeSettings, error) {
216
+ decoder := json.NewDecoder(bytes.NewReader(data))
217
+ token, err := decoder.Token()
218
+ if err != nil {
219
+ return vscodeSettings{}, err
220
+ }
221
+ if token != json.Delim('{') {
222
+ return vscodeSettings{}, errors.New("VS Code settings must be a JSON object")
223
+ }
224
+
225
+ orderedKeys := []string{}
226
+ values := map[string]any{}
227
+ for decoder.More() {
228
+ token, err := decoder.Token()
229
+ if err != nil {
230
+ return vscodeSettings{}, err
231
+ }
232
+ key, ok := token.(string)
233
+ if !ok {
234
+ return vscodeSettings{}, errors.New("VS Code setting key must be a string")
235
+ }
236
+ var value any
237
+ if err := decoder.Decode(&value); err != nil {
238
+ return vscodeSettings{}, err
239
+ }
240
+ if _, exists := values[key]; !exists {
241
+ orderedKeys = append(orderedKeys, key)
242
+ }
243
+ values[key] = value
244
+ }
245
+ token, err = decoder.Token()
246
+ if err != nil {
247
+ return vscodeSettings{}, err
248
+ }
249
+ if token != json.Delim('}') {
250
+ return vscodeSettings{}, errors.New("VS Code settings object is not closed")
251
+ }
252
+ if err := decoder.Decode(&struct{}{}); !errors.Is(err, io.EOF) {
253
+ if err != nil {
254
+ return vscodeSettings{}, err
255
+ }
256
+ return vscodeSettings{}, errors.New("VS Code settings contain trailing JSON")
257
+ }
258
+ settings := vscodeSettings{values: map[string]any{}}
259
+ for _, key := range orderedKeys {
260
+ value := values[key]
261
+ identifiers, isLanguageSection := languageSectionIdentifiers(key)
262
+ if !isLanguageSection {
263
+ settings.values[key] = value
264
+ continue
265
+ }
266
+ section, ok := value.(map[string]any)
267
+ if !ok {
268
+ continue
269
+ }
270
+ settings.languageSections = append(settings.languageSections, vscodeLanguageSection{
271
+ identifiers: identifiers,
272
+ values: section,
273
+ })
274
+ }
275
+ return settings, nil
276
+ }
277
+
170
278
  // stripJSONC removes `//` line comments, `/* */` block comments, and trailing
171
279
  // commas from a JSONC byte slice so encoding/json can parse VS Code's
172
280
  // settings.json. It is string-literal aware: comment markers and commas inside