@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
@@ -53,12 +53,18 @@ func printArrayLiteral(ctx *PrintContext, node *shimast.Node) (Doc, bool) {
53
53
  // suppresses. Hardcoding `true` would oscillate against Prettier on
54
54
  // every `none` project (the trailing-comma rule wouldn't insert one
55
55
  // and the printer would put one back).
56
+ //
57
+ // A destructuring assignment target ending in a rest
58
+ // (`[a, ...rest] = arr`) is the one exception: a trailing comma after
59
+ // its `AssignmentRestElement` is a syntax error, so the reflow must not
60
+ // emit one even under "all"/"es5". Matches the same suppression in
61
+ // `format/trailing-comma`; see isRestAssignmentTargetLiteral.
56
62
  return printList(ctx, listShape{
57
63
  OpenTok: "[",
58
64
  CloseTok: "]",
59
65
  Items: items,
60
66
  Space: false,
61
- AddComma: ctx.allowsEs5TrailingComma(),
67
+ AddComma: ctx.allowsEs5TrailingComma() && !isRestAssignmentTargetLiteral(node),
62
68
  Fill: isConciselyPrintedArray(arr.Elements.Nodes),
63
69
  ForceBreak: arrayShouldForceBreak(arr.Elements.Nodes),
64
70
  BlankBefore: blankBeforeItems(ctx.Source, arr.Elements.Nodes),
@@ -66,12 +66,18 @@ func printObjectLiteral(ctx *PrintContext, node *shimast.Node) (Doc, bool) {
66
66
  // trailing commas in ES5 so both "all" and "es5" keep them; only
67
67
  // "none" suppresses. Pairs with the call/array branches so the printer
68
68
  // never disagrees with the trailing-comma rule on the same setting.
69
+ //
70
+ // A destructuring assignment target ending in a rest
71
+ // (`({ a, ...rest } = obj)`) is the one exception: a trailing comma
72
+ // after its `AssignmentRestProperty` is a syntax error, so the reflow
73
+ // must not emit one even under "all"/"es5". Matches the same suppression
74
+ // in `format/trailing-comma`; see isRestAssignmentTargetLiteral.
69
75
  return printList(ctx, listShape{
70
76
  OpenTok: "{",
71
77
  CloseTok: "}",
72
78
  Items: items,
73
79
  Space: true,
74
- AddComma: ctx.allowsEs5TrailingComma(),
80
+ AddComma: ctx.allowsEs5TrailingComma() && !isRestAssignmentTargetLiteral(node),
75
81
  ForceBreak: forceBreak,
76
82
  BlankBefore: blankBeforeItems(ctx.Source, obj.Properties.Nodes),
77
83
  }), covered
@@ -0,0 +1,231 @@
1
+ package linthost
2
+
3
+ import (
4
+ "fmt"
5
+ "sort"
6
+ "sync"
7
+
8
+ shimast "github.com/microsoft/typescript-go/shim/ast"
9
+ shimchecker "github.com/microsoft/typescript-go/shim/checker"
10
+ publicrule "github.com/samchon/ttsc/packages/lint/rule"
11
+ )
12
+
13
+ type projectCycleResults struct {
14
+ byName map[string]projectCycleResult
15
+ }
16
+
17
+ type projectCycleResult struct {
18
+ status publicrule.ProjectRuleStatus
19
+ severity Severity
20
+ reporter *projectReporter
21
+ }
22
+
23
+ func (r *projectCycleResults) ProjectResult(name string) publicrule.ProjectRuleResult {
24
+ if r == nil {
25
+ return publicrule.ProjectRuleResult{Status: publicrule.ProjectRuleAbsent}
26
+ }
27
+ result, ok := r.byName[name]
28
+ if !ok {
29
+ return publicrule.ProjectRuleResult{Status: publicrule.ProjectRuleAbsent}
30
+ }
31
+ if result.reporter == nil {
32
+ return publicrule.ProjectRuleResult{Status: result.status}
33
+ }
34
+ return result.reporter.snapshot()
35
+ }
36
+
37
+ type projectCycle struct {
38
+ finalizeOnce sync.Once
39
+ findings []*Finding
40
+ results *projectCycleResults
41
+ }
42
+
43
+ type projectReporter struct {
44
+ mu sync.Mutex
45
+ active bool
46
+ failed bool
47
+ messages map[string]struct{}
48
+ state any
49
+ }
50
+
51
+ func (r *projectReporter) Fail() {
52
+ if r == nil {
53
+ return
54
+ }
55
+ r.mu.Lock()
56
+ defer r.mu.Unlock()
57
+ if r.active {
58
+ r.failed = true
59
+ }
60
+ }
61
+
62
+ func (r *projectReporter) Report(message string) {
63
+ if r == nil {
64
+ return
65
+ }
66
+ r.mu.Lock()
67
+ defer r.mu.Unlock()
68
+ if !r.active {
69
+ return
70
+ }
71
+ r.failed = true
72
+ if r.messages == nil {
73
+ r.messages = map[string]struct{}{}
74
+ }
75
+ r.messages[message] = struct{}{}
76
+ }
77
+
78
+ func (r *projectReporter) SetState(state any) {
79
+ if r == nil {
80
+ return
81
+ }
82
+ r.mu.Lock()
83
+ defer r.mu.Unlock()
84
+ if r.active {
85
+ r.state = state
86
+ }
87
+ }
88
+
89
+ func (r *projectReporter) snapshot() publicrule.ProjectRuleResult {
90
+ return r.snapshotLocked(false)
91
+ }
92
+
93
+ func (r *projectReporter) snapshotAndClose() publicrule.ProjectRuleResult {
94
+ return r.snapshotLocked(true)
95
+ }
96
+
97
+ func (r *projectReporter) snapshotLocked(close bool) publicrule.ProjectRuleResult {
98
+ if r == nil {
99
+ return publicrule.ProjectRuleResult{Status: publicrule.ProjectRuleAbsent}
100
+ }
101
+ r.mu.Lock()
102
+ defer r.mu.Unlock()
103
+ if close {
104
+ r.active = false
105
+ }
106
+ messages := make([]string, 0, len(r.messages))
107
+ for message := range r.messages {
108
+ messages = append(messages, message)
109
+ }
110
+ sort.Strings(messages)
111
+ status := publicrule.ProjectRulePassed
112
+ if r.failed {
113
+ status = publicrule.ProjectRuleFailed
114
+ }
115
+ findings := make([]publicrule.ProjectFinding, 0, len(messages))
116
+ for _, message := range messages {
117
+ findings = append(findings, publicrule.ProjectFinding{Message: message})
118
+ }
119
+ return publicrule.NewProjectRuleResult(status, r.state, findings, r)
120
+ }
121
+
122
+ func (c *projectCycle) finalize() []*Finding {
123
+ if c == nil || c.results == nil {
124
+ return nil
125
+ }
126
+ c.finalizeOnce.Do(func() {
127
+ names := make([]string, 0, len(c.results.byName))
128
+ for name := range c.results.byName {
129
+ names = append(names, name)
130
+ }
131
+ sort.Strings(names)
132
+ for _, name := range names {
133
+ entry := c.results.byName[name]
134
+ if entry.reporter == nil {
135
+ continue
136
+ }
137
+ result := entry.reporter.snapshotAndClose()
138
+ for _, finding := range result.Findings {
139
+ c.findings = append(c.findings, &Finding{
140
+ Rule: name,
141
+ Severity: entry.severity,
142
+ Message: finding.Message,
143
+ })
144
+ }
145
+ }
146
+ })
147
+ return append([]*Finding(nil), c.findings...)
148
+ }
149
+
150
+ func (e *Engine) evaluateProject(
151
+ identity publicrule.ProjectIdentity,
152
+ files []*shimast.SourceFile,
153
+ checker *shimchecker.Checker,
154
+ ) *projectCycle {
155
+ results := &projectCycleResults{byName: map[string]projectCycleResult{}}
156
+ cycle := &projectCycle{results: results}
157
+ names := allProjectRuleNames()
158
+ if len(names) == 0 {
159
+ return cycle
160
+ }
161
+ var sources []*shimast.SourceFile
162
+ sourcesResolved := false
163
+ for _, name := range names {
164
+ setting := e.projectSettings[name]
165
+ if !setting.Declared {
166
+ results.byName[name] = projectCycleResult{status: publicrule.ProjectRuleNotEvaluated}
167
+ continue
168
+ }
169
+ if setting.Severity == SeverityOff {
170
+ results.byName[name] = projectCycleResult{status: publicrule.ProjectRuleOff}
171
+ continue
172
+ }
173
+ adapter, exists := registeredProjectRules[name]
174
+ if !exists {
175
+ continue
176
+ }
177
+ if !sourcesResolved {
178
+ sources = e.projectSources(files)
179
+ sourcesResolved = true
180
+ }
181
+ reporter := &projectReporter{active: true}
182
+ context := publicrule.NewProjectContext(
183
+ identity,
184
+ sources,
185
+ checker,
186
+ publicrule.Severity(setting.Severity),
187
+ setting.Options,
188
+ reporter,
189
+ )
190
+ runProjectRuleCheck(adapter, context, reporter)
191
+ results.byName[name] = projectCycleResult{
192
+ severity: setting.Severity,
193
+ reporter: reporter,
194
+ }
195
+ }
196
+ return cycle
197
+ }
198
+
199
+ // projectSources applies only project-wide ignores to the source set exposed
200
+ // to project rules. File-scoped ignores merely refine their containing entry,
201
+ // while ResolveRules marks a source Ignored only for a global ignore entry.
202
+ func (e *Engine) projectSources(files []*shimast.SourceFile) []*shimast.SourceFile {
203
+ sources := make([]*shimast.SourceFile, 0, len(files))
204
+ for _, file := range files {
205
+ if file == nil {
206
+ continue
207
+ }
208
+ if e != nil && e.config != nil && e.config.ResolveRules(file.FileName()).Ignored {
209
+ continue
210
+ }
211
+ sources = append(sources, file)
212
+ }
213
+ return sources
214
+ }
215
+
216
+ func runProjectRuleCheck(
217
+ adapter projectRuleAdapter,
218
+ context *publicrule.ProjectContext,
219
+ reporter *projectReporter,
220
+ ) {
221
+ defer func() {
222
+ if recovered := recover(); recovered != nil {
223
+ reporter.Report(fmt.Sprintf(
224
+ "Project rule %q panicked while checking this Program: %v. Report this to the rule's author.",
225
+ adapter.name,
226
+ recovered,
227
+ ))
228
+ }
229
+ }()
230
+ adapter.inner.Check(context)
231
+ }
@@ -0,0 +1,78 @@
1
+ package linthost
2
+
3
+ import (
4
+ "fmt"
5
+ "os"
6
+ "sort"
7
+
8
+ publicrule "github.com/samchon/ttsc/packages/lint/rule"
9
+ )
10
+
11
+ // projectRuleAdapter caches contributor metadata and keeps project checks on a
12
+ // separate lifecycle from node rules.
13
+ type projectRuleAdapter struct {
14
+ inner publicrule.ProjectRule
15
+ name string
16
+ acceptsOptions bool
17
+ }
18
+
19
+ var registeredProjectRules = map[string]projectRuleAdapter{}
20
+
21
+ func registerProjectContributors() {
22
+ projects := publicrule.RegisteredProjects()
23
+ adapters := make([]projectRuleAdapter, 0, len(projects))
24
+ for _, project := range projects {
25
+ adapter, err := inspectProjectContributor(project)
26
+ if err != nil {
27
+ fmt.Fprintf(os.Stderr, "@ttsc/lint: %v; dropping project contributor entry\n", err)
28
+ continue
29
+ }
30
+ adapters = append(adapters, adapter)
31
+ }
32
+ sort.SliceStable(adapters, func(i, j int) bool { return adapters[i].name < adapters[j].name })
33
+ for _, adapter := range adapters {
34
+ if adapter.name == "" {
35
+ fmt.Fprintln(os.Stderr, "@ttsc/lint: contributor project rule with empty name ignored")
36
+ continue
37
+ }
38
+ if LookupRule(adapter.name) != nil {
39
+ fmt.Fprintf(os.Stderr, "@ttsc/lint: contributor project rule %q collides with a file rule; dropping project contributor entry\n", adapter.name)
40
+ continue
41
+ }
42
+ if _, exists := registeredProjectRules[adapter.name]; exists {
43
+ fmt.Fprintf(os.Stderr, "@ttsc/lint: contributor project rule %q collides with an existing project rule; dropping contributor entry\n", adapter.name)
44
+ continue
45
+ }
46
+ registeredProjectRules[adapter.name] = adapter
47
+ invalidateRuntimeRuleCodes()
48
+ }
49
+ }
50
+
51
+ func inspectProjectContributor(project publicrule.ProjectRule) (adapter projectRuleAdapter, err error) {
52
+ defer func() {
53
+ if recovered := recover(); recovered != nil {
54
+ err = fmt.Errorf("contributor project rule %T metadata panicked: %v", project, recovered)
55
+ }
56
+ }()
57
+ if project == nil {
58
+ return projectRuleAdapter{}, fmt.Errorf("nil contributor project rule")
59
+ }
60
+ adapter = projectRuleAdapter{
61
+ inner: project,
62
+ name: project.Name(),
63
+ acceptsOptions: true,
64
+ }
65
+ if optionsRule, ok := project.(publicrule.OptionsRule); ok {
66
+ adapter.acceptsOptions = optionsRule.AcceptsTtscLintOptions()
67
+ }
68
+ return adapter, nil
69
+ }
70
+
71
+ func allProjectRuleNames() []string {
72
+ names := make([]string, 0, len(registeredProjectRules))
73
+ for name := range registeredProjectRules {
74
+ names = append(names, name)
75
+ }
76
+ sort.Strings(names)
77
+ return names
78
+ }
@@ -0,0 +1,191 @@
1
+ // regex_clean.go ports the `clean-regexp` npm package (v1.0.0), the exact
2
+ // dependency the upstream unicorn/better-regex rule uses for its
3
+ // `new RegExp("pattern", "flags")` string-constructor branch. Unlike the
4
+ // literal branch (which runs the full regexp-tree optimizer in
5
+ // regex_tree_optimizer.go), upstream's constructor branch applies a fixed,
6
+ // ordered table of character-class shorthands via a global substring
7
+ // replace, gated by whether the regex carries the flags a replacement needs.
8
+ // Reproducing that table verbatim is what keeps the constructor branch's
9
+ // diagnostics and fixes identical to upstream; the full optimizer would
10
+ // rewrite strictly more and diverge.
11
+ //
12
+ // clean-regexp source: https://github.com/sindresorhus/clean-regexp
13
+ package linthost
14
+
15
+ import "strings"
16
+
17
+ type regexCleanMapping struct {
18
+ key string
19
+ value string
20
+ flags string
21
+ }
22
+
23
+ // regexCleanMappings mirrors clean-regexp's `lib/mappings` Map in declaration
24
+ // order. Entries without flags always apply; entries with `flags: "i"` apply
25
+ // only when the regex is case-insensitive.
26
+ var regexCleanMappings = []regexCleanMapping{
27
+ {key: "[0-9]", value: `\d`},
28
+ {key: "[^0-9]", value: `\D`},
29
+
30
+ // Word
31
+ {key: "[a-zA-Z0-9_]", value: `\w`},
32
+ {key: "[a-zA-Z_0-9]", value: `\w`},
33
+ {key: "[a-z0-9A-Z_]", value: `\w`},
34
+ {key: "[a-z0-9_A-Z]", value: `\w`},
35
+ {key: "[a-z_A-Z0-9]", value: `\w`},
36
+ {key: "[a-z_0-9A-Z]", value: `\w`},
37
+ {key: "[A-Za-z0-9_]", value: `\w`},
38
+ {key: "[A-Za-z_0-9]", value: `\w`},
39
+ {key: "[A-Z0-9a-z_]", value: `\w`},
40
+ {key: "[A-Z0-9_a-z]", value: `\w`},
41
+ {key: "[A-Z_a-z0-9]", value: `\w`},
42
+ {key: "[A-Z_0-9a-z]", value: `\w`},
43
+ {key: "[0-9a-zA-Z_]", value: `\w`},
44
+ {key: "[0-9a-z_A-Z]", value: `\w`},
45
+ {key: "[0-9A-Za-z_]", value: `\w`},
46
+ {key: "[0-9A-Z_a-z]", value: `\w`},
47
+ {key: "[0-9_a-zA-Z]", value: `\w`},
48
+ {key: "[0-9_A-Za-z]", value: `\w`},
49
+ {key: "[_a-zA-Z0-9]", value: `\w`},
50
+ {key: "[_a-z0-9A-Z]", value: `\w`},
51
+ {key: "[_A-Za-z0-9]", value: `\w`},
52
+ {key: "[_A-Z0-9a-z]", value: `\w`},
53
+ {key: "[_0-9a-zA-Z]", value: `\w`},
54
+ {key: "[_0-9A-Za-z]", value: `\w`},
55
+
56
+ // Word with digit
57
+ {key: `[a-zA-Z\d_]`, value: `\w`},
58
+ {key: `[a-zA-Z_\d]`, value: `\w`},
59
+ {key: `[a-z\dA-Z_]`, value: `\w`},
60
+ {key: `[a-z\d_A-Z]`, value: `\w`},
61
+ {key: `[a-z_A-Z\d]`, value: `\w`},
62
+ {key: `[a-z_\dA-Z]`, value: `\w`},
63
+ {key: `[A-Za-z\d_]`, value: `\w`},
64
+ {key: `[A-Za-z_\d]`, value: `\w`},
65
+ {key: `[A-Z\da-z_]`, value: `\w`},
66
+ {key: `[A-Z\d_a-z]`, value: `\w`},
67
+ {key: `[A-Z_a-z\d]`, value: `\w`},
68
+ {key: `[A-Z_\da-z]`, value: `\w`},
69
+ {key: `[\da-zA-Z_]`, value: `\w`},
70
+ {key: `[\da-z_A-Z]`, value: `\w`},
71
+ {key: `[\dA-Za-z_]`, value: `\w`},
72
+ {key: `[\dA-Z_a-z]`, value: `\w`},
73
+ {key: `[\d_a-zA-Z]`, value: `\w`},
74
+ {key: `[\d_A-Za-z]`, value: `\w`},
75
+ {key: `[_a-zA-Z\d]`, value: `\w`},
76
+ {key: `[_a-z\dA-Z]`, value: `\w`},
77
+ {key: `[_A-Za-z\d]`, value: `\w`},
78
+ {key: `[_A-Z\da-z]`, value: `\w`},
79
+ {key: `[_\da-zA-Z]`, value: `\w`},
80
+ {key: `[_\dA-Za-z]`, value: `\w`},
81
+
82
+ // Non-word
83
+ {key: "[^a-zA-Z0-9_]", value: `\W`},
84
+ {key: "[^a-zA-Z_0-9]", value: `\W`},
85
+ {key: "[^a-z0-9A-Z_]", value: `\W`},
86
+ {key: "[^a-z0-9_A-Z]", value: `\W`},
87
+ {key: "[^a-z_A-Z0-9]", value: `\W`},
88
+ {key: "[^a-z_0-9A-Z]", value: `\W`},
89
+ {key: "[^A-Za-z0-9_]", value: `\W`},
90
+ {key: "[^A-Za-z_0-9]", value: `\W`},
91
+ {key: "[^A-Z0-9a-z_]", value: `\W`},
92
+ {key: "[^A-Z0-9_a-z]", value: `\W`},
93
+ {key: "[^A-Z_a-z0-9]", value: `\W`},
94
+ {key: "[^A-Z_0-9a-z]", value: `\W`},
95
+ {key: "[^0-9a-zA-Z_]", value: `\W`},
96
+ {key: "[^0-9a-z_A-Z]", value: `\W`},
97
+ {key: "[^0-9A-Za-z_]", value: `\W`},
98
+ {key: "[^0-9A-Z_a-z]", value: `\W`},
99
+ {key: "[^0-9_a-zA-Z]", value: `\W`},
100
+ {key: "[^0-9_A-Za-z]", value: `\W`},
101
+ {key: "[^_a-zA-Z0-9]", value: `\W`},
102
+ {key: "[^_a-z0-9A-Z]", value: `\W`},
103
+ {key: "[^_A-Za-z0-9]", value: `\W`},
104
+ {key: "[^_A-Z0-9a-z]", value: `\W`},
105
+ {key: "[^_0-9a-zA-Z]", value: `\W`},
106
+ {key: "[^_0-9A-Za-z]", value: `\W`},
107
+
108
+ // Non-word with digit
109
+ {key: `[^a-zA-Z\d_]`, value: `\W`},
110
+ {key: `[^a-zA-Z_\d]`, value: `\W`},
111
+ {key: `[^a-z\dA-Z_]`, value: `\W`},
112
+ {key: `[^a-z\d_A-Z]`, value: `\W`},
113
+ {key: `[^a-z_A-Z\d]`, value: `\W`},
114
+ {key: `[^a-z_\dA-Z]`, value: `\W`},
115
+ {key: `[^A-Za-z\d_]`, value: `\W`},
116
+ {key: `[^A-Za-z_\d]`, value: `\W`},
117
+ {key: `[^A-Z\da-z_]`, value: `\W`},
118
+ {key: `[^A-Z\d_a-z]`, value: `\W`},
119
+ {key: `[^A-Z_a-z\d]`, value: `\W`},
120
+ {key: `[^A-Z_\da-z]`, value: `\W`},
121
+ {key: `[^\da-zA-Z_]`, value: `\W`},
122
+ {key: `[^\da-z_A-Z]`, value: `\W`},
123
+ {key: `[^\dA-Za-z_]`, value: `\W`},
124
+ {key: `[^\dA-Z_a-z]`, value: `\W`},
125
+ {key: `[^\d_a-zA-Z]`, value: `\W`},
126
+ {key: `[^\d_A-Za-z]`, value: `\W`},
127
+ {key: `[^_a-zA-Z\d]`, value: `\W`},
128
+ {key: `[^_a-z\dA-Z]`, value: `\W`},
129
+ {key: `[^_A-Za-z\d]`, value: `\W`},
130
+ {key: `[^_A-Z\da-z]`, value: `\W`},
131
+ {key: `[^_\da-zA-Z]`, value: `\W`},
132
+ {key: `[^_\dA-Za-z]`, value: `\W`},
133
+
134
+ // Word with case insensitivity
135
+ {key: "[a-z0-9_]", value: `\w`, flags: "i"},
136
+ {key: "[a-z_0-9]", value: `\w`, flags: "i"},
137
+ {key: "[0-9a-z_]", value: `\w`, flags: "i"},
138
+ {key: "[0-9_a-z]", value: `\w`, flags: "i"},
139
+ {key: "[_a-z0-9]", value: `\w`, flags: "i"},
140
+ {key: "[_0-9a-z]", value: `\w`, flags: "i"},
141
+ {key: "[^a-z0-9_]", value: `\W`, flags: "i"},
142
+
143
+ // Word with case insensitivity and digit
144
+ {key: `[a-z\d_]`, value: `\w`, flags: "i"},
145
+ {key: `[a-z_\d]`, value: `\w`, flags: "i"},
146
+ {key: `[\da-z_]`, value: `\w`, flags: "i"},
147
+ {key: `[\d_a-z]`, value: `\w`, flags: "i"},
148
+ {key: `[_a-z\d]`, value: `\w`, flags: "i"},
149
+ {key: `[_\da-z]`, value: `\w`, flags: "i"},
150
+
151
+ // Non-word with case insensitivity
152
+ {key: "[^a-z0-9_]", value: `\W`, flags: "i"},
153
+ {key: "[^a-z_0-9]", value: `\W`, flags: "i"},
154
+ {key: "[^0-9a-z_]", value: `\W`, flags: "i"},
155
+ {key: "[^0-9_a-z]", value: `\W`, flags: "i"},
156
+ {key: "[^_a-z0-9]", value: `\W`, flags: "i"},
157
+ {key: "[^_0-9a-z]", value: `\W`, flags: "i"},
158
+
159
+ // Non-word with case insensitivity and digit
160
+ {key: `[^a-z\d_]`, value: `\W`, flags: "i"},
161
+ {key: `[^a-z_\d]`, value: `\W`, flags: "i"},
162
+ {key: `[^\da-z_]`, value: `\W`, flags: "i"},
163
+ {key: `[^\d_a-z]`, value: `\W`, flags: "i"},
164
+ {key: `[^_a-z\d]`, value: `\W`, flags: "i"},
165
+ {key: `[^_\da-z]`, value: `\W`, flags: "i"},
166
+ }
167
+
168
+ // regexCleanRegexp applies clean-regexp's ordered substring replacements to a
169
+ // `new RegExp` pattern string, gated on `flags`. Mirrors clean-regexp's
170
+ // default export: for each mapping whose required flags are all present in the
171
+ // regex flags, globally replace the key substring with its shorthand.
172
+ func regexCleanRegexp(pattern, flags string) string {
173
+ for _, mapping := range regexCleanMappings {
174
+ if regexCleanHasFlags(flags, mapping.flags) {
175
+ pattern = strings.ReplaceAll(pattern, mapping.key, mapping.value)
176
+ }
177
+ }
178
+ return pattern
179
+ }
180
+
181
+ // regexCleanHasFlags reports whether every flag a replacement requires is
182
+ // present in the regex's flags (clean-regexp's `hasFlags`). An empty
183
+ // requirement is always satisfied.
184
+ func regexCleanHasFlags(regexFlags, replaceFlags string) bool {
185
+ for _, flag := range replaceFlags {
186
+ if !strings.ContainsRune(regexFlags, flag) {
187
+ return false
188
+ }
189
+ }
190
+ return true
191
+ }