@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,25 +1,233 @@
1
- // unicorn/better-regex: the upstream rule rewrites regex literals into
2
- // the shortest equivalent form (e.g. `[0-9]` -> `\d`, `[^\W\D]` -> `\d`,
3
- // dedupes character classes, collapses redundant quantifiers). That
4
- // rewrite requires a full regex parser with character-class semantics —
5
- // far beyond the AST-only scope of this slice so the rule is
6
- // registered as a no-op stub to keep `TestTypedKeysMatchRegisteredRules`
7
- // honest with the typed `ITtscLintUnicornRules` surface.
1
+ // unicorn/better-regex: rewrite regex literals into their shortest
2
+ // equivalent form (`[0-9]` -> `\d`, dedupe/sort character classes, collapse
3
+ // redundant quantifiers). Two branches mirror the upstream rule:
4
+ //
5
+ // - Regex literals (`/[0-9]/`) run the full regexp-tree optimizer port in
6
+ // regex_tree.go + regex_tree_optimizer.go, then report + autofix when the
7
+ // optimized literal differs. Literals carrying the `u` or `v` flag are
8
+ // skipped: regexp-tree does not handle Unicode/Unicode-sets mode well, so
9
+ // upstream (and this port) leave them untouched.
10
+ // - `new RegExp("pattern", "flags")` string constructors run the
11
+ // clean-regexp table port in regex_clean.go and rewrite the string
12
+ // argument in place. Regex-literal arguments (`new RegExp(/[0-9]/)`) are
13
+ // handled by the literal branch on the inner node, not here.
14
+ //
15
+ // The one option, `sortCharacterClasses` (default true), maps to blacklisting
16
+ // regexp-tree's `charClassClassrangesMerge` transform when set to false.
8
17
  //
9
- // Replace this stub with a real implementation when a regex AST helper
10
- // lands; the matching fixture under
11
- // `tests/test-lint/src/cases/unicorn-better-regex.ts` carries the
12
- // `@ttsc-corpus-skip` directive so the corpus test suite walks past it.
13
18
  // https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/better-regex.md
14
19
  package linthost
15
20
 
16
- import shimast "github.com/microsoft/typescript-go/shim/ast"
21
+ import (
22
+ "encoding/json"
23
+ "fmt"
24
+
25
+ shimast "github.com/microsoft/typescript-go/shim/ast"
26
+ shimscanner "github.com/microsoft/typescript-go/shim/scanner"
27
+ )
28
+
29
+ type unicornBetterRegex struct{ optionsRule }
30
+
31
+ // unicornBetterRegexOptions is the rule's public option object. A nil
32
+ // `SortCharacterClasses` keeps the upstream default (sort/merge enabled).
33
+ type unicornBetterRegexOptions struct {
34
+ SortCharacterClasses *bool `json:"sortCharacterClasses"`
35
+ }
36
+
37
+ func (unicornBetterRegex) Name() string { return "unicorn/better-regex" }
38
+ func (unicornBetterRegex) Visits() []shimast.Kind {
39
+ return []shimast.Kind{shimast.KindRegularExpressionLiteral, shimast.KindNewExpression}
40
+ }
41
+
42
+ func (unicornBetterRegex) ValidateOptions(raw json.RawMessage) error {
43
+ return validateUnicornBetterRegexOptions(raw)
44
+ }
45
+
46
+ func (unicornBetterRegex) Check(ctx *Context, node *shimast.Node) {
47
+ if ctx == nil || node == nil {
48
+ return
49
+ }
50
+ switch node.Kind {
51
+ case shimast.KindRegularExpressionLiteral:
52
+ unicornBetterRegexCheckLiteral(ctx, node)
53
+ case shimast.KindNewExpression:
54
+ unicornBetterRegexCheckConstructor(ctx, node)
55
+ }
56
+ }
57
+
58
+ // unicornBetterRegexBlacklist decodes the `sortCharacterClasses` option and
59
+ // returns the regexp-tree transform blacklist it implies. Only an explicit
60
+ // `false` disables the class range merge/sort transform.
61
+ func unicornBetterRegexBlacklist(ctx *Context) map[string]bool {
62
+ var options unicornBetterRegexOptions
63
+ _ = ctx.DecodeOptions(&options)
64
+ if options.SortCharacterClasses != nil && !*options.SortCharacterClasses {
65
+ return map[string]bool{"charClassClassrangesMerge": true}
66
+ }
67
+ return nil
68
+ }
69
+
70
+ // validateUnicornBetterRegexOptions enforces the upstream schema: an optional
71
+ // object with a single boolean `sortCharacterClasses` and no other keys.
72
+ func validateUnicornBetterRegexOptions(raw json.RawMessage) error {
73
+ if len(raw) == 0 {
74
+ return nil
75
+ }
76
+ var fields map[string]json.RawMessage
77
+ if err := json.Unmarshal(raw, &fields); err != nil {
78
+ return fmt.Errorf("unicorn/better-regex options must be an object: %w", err)
79
+ }
80
+ for key, value := range fields {
81
+ if key != "sortCharacterClasses" {
82
+ return fmt.Errorf("unicorn/better-regex: unknown option %q", key)
83
+ }
84
+ var flag bool
85
+ if err := json.Unmarshal(value, &flag); err != nil {
86
+ return fmt.Errorf("unicorn/better-regex: sortCharacterClasses must be a boolean")
87
+ }
88
+ }
89
+ return nil
90
+ }
91
+
92
+ // unicornBetterRegexCheckLiteral optimizes a regex literal and reports when the
93
+ // canonical form differs. The autofix replaces the whole literal token, except
94
+ // when the literal is the object of a non-optional `.source` / `.toString`
95
+ // member access — rewriting there could change a serialized-source consumer's
96
+ // expectations, so upstream reports without a fix.
97
+ func unicornBetterRegexCheckLiteral(ctx *Context, node *shimast.Node) {
98
+ source := ctx.File.Text()
99
+ start := shimscanner.SkipTrivia(source, node.Pos())
100
+ end := node.End()
101
+ if start < 0 || end > len(source) || start >= end {
102
+ return
103
+ }
104
+ original := source[start:end]
105
+ // regexp-tree mishandles `u` / `v` mode, so upstream skips those flags.
106
+ flags := unicornBetterRegexLiteralFlags(original)
107
+ if containsAnyByte(flags, "uv") {
108
+ return
109
+ }
110
+ optimized, err := regexOptimizeLiteral(original, unicornBetterRegexBlacklist(ctx))
111
+ if err != nil {
112
+ // A literal TypeScript already tokenized should parse, but this port
113
+ // covers a subset of regexp-tree; on any parse gap, decline rather than
114
+ // emit a wrong diagnostic or fix.
115
+ return
116
+ }
117
+ if optimized == original {
118
+ return
119
+ }
120
+ message := fmt.Sprintf("%s can be optimized to %s.", original, optimized)
121
+ if unicornBetterRegexIsSourceOrToString(node) {
122
+ ctx.Report(node, message)
123
+ return
124
+ }
125
+ ctx.ReportFix(node, message, TextEdit{Pos: start, End: end, Text: optimized})
126
+ }
127
+
128
+ // unicornBetterRegexCheckConstructor rewrites a `new RegExp("pattern", "flags")`
129
+ // string argument via the clean-regexp table. Only a bare `new RegExp(...)`
130
+ // with a string-literal first argument qualifies; a regex-literal argument is
131
+ // left to the literal branch, and non-`RegExp` / non-string forms are ignored.
132
+ //
133
+ // clean-regexp works on the literal's cooked value, so the rewritten pattern is
134
+ // re-escaped through the shared escapeString port before it goes back into the
135
+ // source, keeping the original quote character. Emitting the cooked bytes
136
+ // directly would break the literal the moment the pattern carried a line
137
+ // terminator.
138
+ func unicornBetterRegexCheckConstructor(ctx *Context, node *shimast.Node) {
139
+ newExpr := node.AsNewExpression()
140
+ if newExpr == nil || identifierText(newExpr.Expression) != "RegExp" {
141
+ return
142
+ }
143
+ if newExpr.Arguments == nil || len(newExpr.Arguments.Nodes) < 1 {
144
+ return
145
+ }
146
+ patternNode := newExpr.Arguments.Nodes[0]
147
+ if patternNode == nil || patternNode.Kind != shimast.KindStringLiteral {
148
+ return
149
+ }
150
+ oldPattern := stringLiteralText(patternNode)
151
+ flags := ""
152
+ if len(newExpr.Arguments.Nodes) >= 2 {
153
+ if flagsNode := newExpr.Arguments.Nodes[1]; flagsNode != nil && flagsNode.Kind == shimast.KindStringLiteral {
154
+ flags = stringLiteralText(flagsNode)
155
+ }
156
+ }
157
+ newPattern := regexCleanRegexp(oldPattern, flags)
158
+ if oldPattern == newPattern {
159
+ return
160
+ }
161
+ source := ctx.File.Text()
162
+ patternStart := shimscanner.SkipTrivia(source, patternNode.Pos())
163
+ patternEnd := patternNode.End()
164
+ if patternStart < 0 || patternEnd > len(source) || patternStart >= patternEnd {
165
+ return
166
+ }
167
+ quote := source[patternStart]
168
+ replacement := escapeString(newPattern, quote)
169
+ ctx.ReportFix(
170
+ node,
171
+ fmt.Sprintf("%s can be optimized to %s.", oldPattern, newPattern),
172
+ TextEdit{Pos: patternStart, End: patternEnd, Text: replacement},
173
+ )
174
+ }
175
+
176
+ // unicornBetterRegexLiteralFlags returns the flag suffix of a `/pattern/flags`
177
+ // literal by locating the closing delimiter outside a character class, the
178
+ // same scan the literal parser uses.
179
+ func unicornBetterRegexLiteralFlags(literal string) string {
180
+ runes := []rune(literal)
181
+ if len(runes) < 2 || runes[0] != '/' {
182
+ return ""
183
+ }
184
+ inClass := false
185
+ for index := 1; index < len(runes); index++ {
186
+ switch runes[index] {
187
+ case '\\':
188
+ index++
189
+ case '[':
190
+ inClass = true
191
+ case ']':
192
+ inClass = false
193
+ case '/':
194
+ if !inClass {
195
+ return string(runes[index+1:])
196
+ }
197
+ }
198
+ }
199
+ return ""
200
+ }
17
201
 
18
- type unicornBetterRegex struct{}
202
+ // containsAnyByte reports whether s contains any byte listed in set.
203
+ func containsAnyByte(s, set string) bool {
204
+ for index := 0; index < len(s); index++ {
205
+ for j := 0; j < len(set); j++ {
206
+ if s[index] == set[j] {
207
+ return true
208
+ }
209
+ }
210
+ }
211
+ return false
212
+ }
19
213
 
20
- func (unicornBetterRegex) Name() string { return "unicorn/better-regex" }
21
- func (unicornBetterRegex) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindSourceFile} }
22
- func (unicornBetterRegex) Check(_ *Context, _ *shimast.Node) {
214
+ // unicornBetterRegexIsSourceOrToString reports whether the regex literal is the
215
+ // object of a non-optional, non-computed `.source` or `.toString` member
216
+ // access, the one shape upstream reports without attaching a fix.
217
+ func unicornBetterRegexIsSourceOrToString(node *shimast.Node) bool {
218
+ parent := node.Parent
219
+ if parent == nil || parent.Kind != shimast.KindPropertyAccessExpression {
220
+ return false
221
+ }
222
+ access := parent.AsPropertyAccessExpression()
223
+ if access == nil || access.Expression != node || access.QuestionDotToken != nil {
224
+ return false
225
+ }
226
+ switch identifierText(access.Name()) {
227
+ case "source", "toString":
228
+ return true
229
+ }
230
+ return false
23
231
  }
24
232
 
25
233
  func init() {