@ttsc/lint 0.18.3 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (188) hide show
  1. package/README.md +95 -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 +129 -25
  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 +160 -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 +151 -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 +145 -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
@@ -0,0 +1,750 @@
1
+ // Go port of the core-js-compat 3.49.0 pieces `unicorn/no-unnecessary-
2
+ // polyfills` consumes — the targets parser, the sloppy SemVer comparator, and
3
+ // the unavailable-module computation — plus the upstream rule's polyfill
4
+ // pattern/token tables (built from the same pinned `data.json` key order and
5
+ // a port of change-case 5.4.4 `camelCase`). The generated fixture
6
+ // `test/testdata/polyfills/upstream-patterns.json` pins the table
7
+ // construction byte-for-byte against the real upstream packages, and
8
+ // `corejs-compat-cases.json` pins the compat computation.
9
+ package linthost
10
+
11
+ import (
12
+ "fmt"
13
+ "math"
14
+ "regexp"
15
+ "sort"
16
+ "strconv"
17
+ "strings"
18
+ "sync"
19
+ "time"
20
+ )
21
+
22
+ // --- change-case camelCase port ----------------------------------------------
23
+
24
+ var (
25
+ polyfillSplitLowerUpperPattern = regexp.MustCompile(`([\p{Ll}\d])(\p{Lu})`)
26
+ polyfillSplitUpperUpperPattern = regexp.MustCompile(`(\p{Lu})(\p{Lu}\p{Ll})`)
27
+ polyfillSplitStripPattern = regexp.MustCompile(`[^\p{L}\d]+`)
28
+ )
29
+
30
+ // polyfillChangeCaseSplit ports change-case's split(): mark case and
31
+ // separator boundaries with NUL, then split.
32
+ func polyfillChangeCaseSplit(value string) []string {
33
+ result := strings.TrimSpace(value)
34
+ result = polyfillSplitLowerUpperPattern.ReplaceAllString(result, "${1}\x00${2}")
35
+ result = polyfillSplitUpperUpperPattern.ReplaceAllString(result, "${1}\x00${2}")
36
+ result = polyfillSplitStripPattern.ReplaceAllString(result, "\x00")
37
+ result = strings.Trim(result, "\x00")
38
+ if result == "" {
39
+ return nil
40
+ }
41
+ return strings.Split(result, "\x00")
42
+ }
43
+
44
+ // polyfillCamelCase ports change-case's camelCase(), including the
45
+ // underscore it inserts before digit-initial words.
46
+ func polyfillCamelCase(input string) string {
47
+ words := polyfillChangeCaseSplit(input)
48
+ var builder strings.Builder
49
+ for index, word := range words {
50
+ if index == 0 {
51
+ builder.WriteString(strings.ToLower(word))
52
+ continue
53
+ }
54
+ runes := []rune(word)
55
+ if runes[0] >= '0' && runes[0] <= '9' {
56
+ builder.WriteByte('_')
57
+ builder.WriteRune(runes[0])
58
+ } else {
59
+ builder.WriteString(strings.ToUpper(string(runes[0])))
60
+ }
61
+ builder.WriteString(strings.ToLower(string(runes[1:])))
62
+ }
63
+ return builder.String()
64
+ }
65
+
66
+ // --- core-js-compat sloppy SemVer ---------------------------------------------
67
+
68
+ var polyfillSemverPattern = regexp.MustCompile(`(\d+)(?:\.(\d+))?(?:\.(\d+))?`)
69
+
70
+ // polyfillParseSemver mirrors core-js-compat's SemVer: the FIRST digit run
71
+ // anywhere in the string is the major version (">=18" parses as 18.0.0), and
72
+ // inputs without digits are invalid.
73
+ func polyfillParseSemver(input string) ([3]int, error) {
74
+ match := polyfillSemverPattern.FindStringSubmatch(input)
75
+ if match == nil {
76
+ return [3]int{}, fmt.Errorf("invalid version: %s", input)
77
+ }
78
+ var parsed [3]int
79
+ for i := 0; i < 3; i++ {
80
+ if match[i+1] == "" {
81
+ continue
82
+ }
83
+ value, err := strconv.Atoi(match[i+1])
84
+ if err != nil {
85
+ return [3]int{}, fmt.Errorf("invalid version: %s", input)
86
+ }
87
+ parsed[i] = value
88
+ }
89
+ return parsed, nil
90
+ }
91
+
92
+ // polyfillCompareSemver mirrors compare($a, operator, $b).
93
+ func polyfillCompareSemver(a, operator, b string) (bool, error) {
94
+ parsedA, err := polyfillParseSemver(a)
95
+ if err != nil {
96
+ return false, err
97
+ }
98
+ parsedB, err := polyfillParseSemver(b)
99
+ if err != nil {
100
+ return false, err
101
+ }
102
+ for i := 0; i < 3; i++ {
103
+ if parsedA[i] < parsedB[i] {
104
+ return operator == "<" || operator == "<=" || operator == "!=", nil
105
+ }
106
+ if parsedA[i] > parsedB[i] {
107
+ return operator == ">" || operator == ">=" || operator == "!=", nil
108
+ }
109
+ }
110
+ return operator == "==" || operator == "<=" || operator == ">=", nil
111
+ }
112
+
113
+ // --- targets parsing -----------------------------------------------------------
114
+
115
+ // polyfillTargetEntry is one parsed [engine, version] target.
116
+ type polyfillTargetEntry struct {
117
+ engine string
118
+ version string
119
+ }
120
+
121
+ // polyfillJSString mirrors String(value) over decoded ordered-JSON values.
122
+ func polyfillJSString(value interface{}) string {
123
+ switch typed := value.(type) {
124
+ case string:
125
+ return typed
126
+ case float64:
127
+ return polyfillJSNumberString(typed)
128
+ case bool:
129
+ if typed {
130
+ return "true"
131
+ }
132
+ return "false"
133
+ case nil:
134
+ return "null"
135
+ case []interface{}:
136
+ parts := make([]string, len(typed))
137
+ for i, element := range typed {
138
+ if element == nil {
139
+ parts[i] = ""
140
+ continue
141
+ }
142
+ parts[i] = polyfillJSString(element)
143
+ }
144
+ return strings.Join(parts, ",")
145
+ case *browserslistOrderedObject:
146
+ return "[object Object]"
147
+ default:
148
+ return fmt.Sprintf("%v", typed)
149
+ }
150
+ }
151
+
152
+ func polyfillJSNumberString(value float64) string {
153
+ if value == math.Trunc(value) && math.Abs(value) < 1e21 {
154
+ return strconv.FormatFloat(value, 'f', -1, 64)
155
+ }
156
+ return strconv.FormatFloat(value, 'g', -1, 64)
157
+ }
158
+
159
+ // polyfillParseTargets ports core-js-compat's targets-parser: normalize the
160
+ // targets input into deduplicated, alias-resolved, lowest-version [engine,
161
+ // version] pairs. `cwd` stands in for process.cwd() when a `browsers` query
162
+ // needs resolving, `now` feeds time-dependent browserslist queries.
163
+ func polyfillParseTargets(targets interface{}, cwd string, now func() time.Time) ([]polyfillTargetEntry, error) {
164
+ data := loadPolyfillData()
165
+
166
+ var browsers interface{}
167
+ var esmodules interface{}
168
+ var node interface{}
169
+ var rest [][2]interface{}
170
+
171
+ object, isObject := targets.(*browserslistOrderedObject)
172
+ if !isObject {
173
+ if _, isArray := targets.([]interface{}); !isArray {
174
+ if _, isString := targets.(string); !isString {
175
+ if targets == nil {
176
+ return nil, fmt.Errorf("targets must not be null")
177
+ }
178
+ // JS spreads non-object targets into {browsers: targets}; a
179
+ // non-string/array browsers value fails browserslist's query check.
180
+ return nil, fmt.Errorf("browser queries must be an array or string")
181
+ }
182
+ }
183
+ browsers = targets
184
+ } else {
185
+ lowered := &browserslistOrderedObject{values: map[string]interface{}{}}
186
+ for _, key := range object.keys {
187
+ lowerKey := strings.ToLower(key)
188
+ if _, exists := lowered.values[lowerKey]; !exists {
189
+ lowered.keys = append(lowered.keys, lowerKey)
190
+ }
191
+ lowered.values[lowerKey] = object.values[key]
192
+ }
193
+ for _, key := range lowered.keys {
194
+ switch key {
195
+ case "browsers":
196
+ browsers = lowered.values[key]
197
+ case "esmodules":
198
+ esmodules = lowered.values[key]
199
+ case "node":
200
+ node = lowered.values[key]
201
+ default:
202
+ rest = append(rest, [2]interface{}{key, lowered.values[key]})
203
+ }
204
+ }
205
+ }
206
+
207
+ var list [][2]interface{}
208
+ list = append(list, rest...)
209
+
210
+ esmodulesIntersect := false
211
+ esmodulesTrue := false
212
+ if esString, isString := esmodules.(string); isString && esString == "intersect" {
213
+ esmodulesIntersect = true
214
+ } else if polyfillJSTruthy(esmodules) {
215
+ esmodulesTrue = true
216
+ }
217
+
218
+ if polyfillJSTruthy(browsers) && !esmodulesTrue {
219
+ switch typed := browsers.(type) {
220
+ case string:
221
+ resolved, err := browserslistResolve([]string{typed}, true, browserslistOpts{path: cwd, now: now})
222
+ if err != nil {
223
+ return nil, err
224
+ }
225
+ for _, item := range resolved {
226
+ engine, version, _ := strings.Cut(item, " ")
227
+ list = append(list, [2]interface{}{engine, version})
228
+ }
229
+ case []interface{}:
230
+ queries := make([]string, len(typed))
231
+ for i, element := range typed {
232
+ query, isString := element.(string)
233
+ if !isString {
234
+ return nil, fmt.Errorf("browser queries must be strings")
235
+ }
236
+ queries[i] = query
237
+ }
238
+ resolved, err := browserslistResolve(queries, true, browserslistOpts{path: cwd, now: now})
239
+ if err != nil {
240
+ return nil, err
241
+ }
242
+ for _, item := range resolved {
243
+ engine, version, _ := strings.Cut(item, " ")
244
+ list = append(list, [2]interface{}{engine, version})
245
+ }
246
+ case *browserslistOrderedObject:
247
+ for _, key := range typed.keys {
248
+ list = append(list, [2]interface{}{key, typed.values[key]})
249
+ }
250
+ default:
251
+ return nil, fmt.Errorf("browser queries must be an array or string")
252
+ }
253
+ }
254
+
255
+ if esmodulesTrue {
256
+ for _, engine := range polyfillSortedKeys(data.EsModulesTargets) {
257
+ list = append(list, [2]interface{}{engine, data.EsModulesTargets[engine]})
258
+ }
259
+ }
260
+
261
+ if polyfillJSTruthy(node) {
262
+ if nodeString, isString := node.(string); isString && nodeString == "current" {
263
+ return nil, fmt.Errorf(
264
+ "`node: \"current\"` is not supported by the ttsc port: the lint host does not run inside Node.js")
265
+ }
266
+ list = append(list, [2]interface{}{"node", node})
267
+ }
268
+
269
+ type namedEntry struct {
270
+ engine string
271
+ version string
272
+ }
273
+ normalized := make([]namedEntry, 0, len(list))
274
+ for _, pair := range list {
275
+ engine := polyfillJSString(pair[0])
276
+ if alias, ok := data.Browserslist.Aliases[engine]; ok {
277
+ engine = alias
278
+ }
279
+ if alias, ok := data.CoreJs.Aliases[engine]; ok {
280
+ engine = alias
281
+ }
282
+ valid := false
283
+ for _, target := range data.CoreJs.ValidTargets {
284
+ if target == engine {
285
+ valid = true
286
+ break
287
+ }
288
+ }
289
+ if !valid {
290
+ continue
291
+ }
292
+ normalized = append(normalized, namedEntry{engine: engine, version: polyfillJSString(pair[1])})
293
+ }
294
+ sort.SliceStable(normalized, func(i, j int) bool {
295
+ return normalized[i].engine < normalized[j].engine
296
+ })
297
+
298
+ var order []string
299
+ reduced := map[string]string{}
300
+ for _, entry := range normalized {
301
+ current, exists := reduced[entry.engine]
302
+ if !exists {
303
+ order = append(order, entry.engine)
304
+ reduced[entry.engine] = entry.version
305
+ continue
306
+ }
307
+ lower, err := polyfillCompareSemver(entry.version, "<=", current)
308
+ if err != nil {
309
+ return nil, err
310
+ }
311
+ if lower {
312
+ reduced[entry.engine] = entry.version
313
+ }
314
+ }
315
+
316
+ result := make([]polyfillTargetEntry, 0, len(order))
317
+ if esmodulesIntersect {
318
+ for _, engine := range order {
319
+ moduleVersion, supported := data.EsModulesTargets[engine]
320
+ if !supported {
321
+ continue
322
+ }
323
+ version := reduced[engine]
324
+ higher, err := polyfillCompareSemver(moduleVersion, ">", version)
325
+ if err != nil {
326
+ return nil, err
327
+ }
328
+ if higher {
329
+ version = moduleVersion
330
+ }
331
+ result = append(result, polyfillTargetEntry{engine: engine, version: version})
332
+ }
333
+ return result, nil
334
+ }
335
+ for _, engine := range order {
336
+ result = append(result, polyfillTargetEntry{engine: engine, version: reduced[engine]})
337
+ }
338
+ return result, nil
339
+ }
340
+
341
+ // polyfillJSTruthy mirrors JS truthiness over decoded ordered-JSON values.
342
+ func polyfillJSTruthy(value interface{}) bool {
343
+ switch typed := value.(type) {
344
+ case nil:
345
+ return false
346
+ case bool:
347
+ return typed
348
+ case string:
349
+ return typed != ""
350
+ case float64:
351
+ return typed != 0 && !math.IsNaN(typed)
352
+ default:
353
+ return true
354
+ }
355
+ }
356
+
357
+ // polyfillSortedKeys returns map keys sorted lexicographically; the
358
+ // esmodules table is iterated through Object.entries whose keys are
359
+ // non-numeric and therefore insertion-ordered, but the emitted artifact is a
360
+ // JSON object, so a canonical order keeps the port deterministic. The pairs
361
+ // feed a full sort in the targets parser, so the choice is unobservable.
362
+ func polyfillSortedKeys(table map[string]string) []string {
363
+ keys := make([]string, 0, len(table))
364
+ for key := range table {
365
+ keys = append(keys, key)
366
+ }
367
+ sort.Strings(keys)
368
+ return keys
369
+ }
370
+
371
+ // --- unavailable module computation ---------------------------------------------
372
+
373
+ var (
374
+ polyfillFilteredModulesOnce sync.Once
375
+ polyfillFilteredModulesValue []string
376
+ )
377
+
378
+ // polyfillFilteredModules mirrors filterOutStabilizedProposals over the full
379
+ // module list: esnext.* entries with a stabilized es.* twin are dropped.
380
+ func polyfillFilteredModules() []string {
381
+ polyfillFilteredModulesOnce.Do(func() {
382
+ data := loadPolyfillData()
383
+ modules := make(map[string]struct{}, len(data.Modules))
384
+ for _, module := range data.Modules {
385
+ modules[module] = struct{}{}
386
+ }
387
+ for _, module := range data.Modules {
388
+ if !strings.HasPrefix(module, "esnext.") {
389
+ continue
390
+ }
391
+ if _, stabilized := modules["es."+strings.TrimPrefix(module, "esnext.")]; stabilized {
392
+ delete(modules, module)
393
+ }
394
+ }
395
+ filtered := make([]string, 0, len(modules))
396
+ for _, module := range data.Modules {
397
+ if _, kept := modules[module]; kept {
398
+ filtered = append(filtered, module)
399
+ }
400
+ }
401
+ polyfillFilteredModulesValue = filtered
402
+ })
403
+ return polyfillFilteredModulesValue
404
+ }
405
+
406
+ // polyfillCompatList mirrors `coreJsCompat({targets}).list`: the modules some
407
+ // target still requires, in data.json order.
408
+ func polyfillCompatList(targets []polyfillTargetEntry) ([]string, error) {
409
+ data := loadPolyfillData()
410
+ var list []string
411
+ for _, module := range polyfillFilteredModules() {
412
+ requirements := data.Compat[module]
413
+ required := false
414
+ for _, target := range targets {
415
+ requiredVersion, supported := requirements[target.engine]
416
+ if !supported {
417
+ required = true
418
+ continue
419
+ }
420
+ below, err := polyfillCompareSemver(target.version, "<", requiredVersion)
421
+ if err != nil {
422
+ return nil, err
423
+ }
424
+ if below {
425
+ required = true
426
+ }
427
+ }
428
+ if required {
429
+ list = append(list, module)
430
+ }
431
+ }
432
+ return list, nil
433
+ }
434
+
435
+ // polyfillUnavailableFeatureSet resolves and caches the unavailable-feature
436
+ // set for one targets value, mirroring the upstream rule's module-level
437
+ // `unavailableFeatureSetByTargets` cache (failed resolutions cache too). The
438
+ // key derives from the resolved targets content, so editing a Browserslist
439
+ // config or package.json always produces a fresh entry.
440
+ func polyfillUnavailableFeatureSet(cacheKey string, targets interface{}, cwd string, now func() time.Time) map[string]struct{} {
441
+ polyfillUnavailableCacheMutex.Lock()
442
+ cached, hit := polyfillUnavailableCache[cacheKey]
443
+ polyfillUnavailableCacheMutex.Unlock()
444
+ if hit {
445
+ return cached
446
+ }
447
+ var set map[string]struct{}
448
+ if entries, err := polyfillParseTargets(targets, cwd, now); err == nil {
449
+ if list, listErr := polyfillCompatList(entries); listErr == nil {
450
+ set = make(map[string]struct{}, len(list))
451
+ for _, module := range list {
452
+ set[module] = struct{}{}
453
+ }
454
+ }
455
+ }
456
+ polyfillUnavailableCacheMutex.Lock()
457
+ polyfillUnavailableCache[cacheKey] = set
458
+ polyfillUnavailableCacheMutex.Unlock()
459
+ return set
460
+ }
461
+
462
+ var (
463
+ polyfillUnavailableCacheMutex sync.Mutex
464
+ polyfillUnavailableCache = map[string]map[string]struct{}{}
465
+ )
466
+
467
+ // --- polyfill pattern / token tables ----------------------------------------------
468
+
469
+ // polyfillPattern is one feature's matching data from the upstream rule's
470
+ // module-level `polyfills` table.
471
+ type polyfillPattern struct {
472
+ feature string
473
+ segments int
474
+ patternSource string
475
+ pattern *regexp.Regexp
476
+ tokens []string
477
+ }
478
+
479
+ type polyfillPatternTables struct {
480
+ polyfills []*polyfillPattern
481
+ byToken map[string][]*polyfillPattern
482
+ tokensByFirstChar map[byte][]string
483
+ esConstructorTokens map[string]struct{}
484
+ }
485
+
486
+ var (
487
+ polyfillPatternOnce sync.Once
488
+ polyfillPatternTablesValue *polyfillPatternTables
489
+ )
490
+
491
+ // polyfillAdditionalModules mirrors additionalPolyfillModules.
492
+ var polyfillAdditionalModules = map[string][]string{
493
+ "es.promise.finally": {"p-finally"},
494
+ "es.object.set-prototype-of": {"setprototypeof"},
495
+ "es.string.code-point-at": {"code-point-at"},
496
+ }
497
+
498
+ // polyfillDirectFeatureCheck mirrors directFeatureCheckPolyfills.
499
+ var polyfillDirectFeatureCheck = map[string]struct{}{
500
+ "es6-symbol": {},
501
+ "promise-polyfill": {},
502
+ "es6-promise": {},
503
+ "weakmap-polyfill": {},
504
+ }
505
+
506
+ const (
507
+ polyfillPrefixesPattern = "(mdn-polyfills/|polyfill-)"
508
+ polyfillSuffixesPattern = "(-polyfill)"
509
+ polyfillDelimiterPattern = `(\.|-|\.prototype\.|/)?`
510
+ )
511
+
512
+ // polyfillGetFirstSegment mirrors getFirstSegment: the substring before the
513
+ // first `-`, `.`, or `/`.
514
+ func polyfillGetFirstSegment(value string) string {
515
+ if index := strings.IndexAny(value, "-./"); index >= 0 {
516
+ return value[:index]
517
+ }
518
+ return value
519
+ }
520
+
521
+ func polyfillAddToken(seen map[string]struct{}, tokens *[]string, value string) {
522
+ if value == "" {
523
+ return
524
+ }
525
+ add := func(token string) {
526
+ if _, dup := seen[token]; dup {
527
+ return
528
+ }
529
+ seen[token] = struct{}{}
530
+ *tokens = append(*tokens, token)
531
+ }
532
+ lowercase := strings.ToLower(value)
533
+ add(lowercase)
534
+ add(polyfillGetFirstSegment(lowercase))
535
+ camel := strings.ToLower(polyfillCamelCase(value))
536
+ add(camel)
537
+ add(polyfillGetFirstSegment(camel))
538
+ }
539
+
540
+ // polyfillPatterns builds the pattern/token tables from the embedded compat
541
+ // module list, transliterating the upstream rule's module-level setup.
542
+ func polyfillPatterns() *polyfillPatternTables {
543
+ polyfillPatternOnce.Do(func() {
544
+ data := loadPolyfillData()
545
+ tables := &polyfillPatternTables{
546
+ byToken: map[string][]*polyfillPattern{},
547
+ tokensByFirstChar: map[byte][]string{},
548
+ esConstructorTokens: map[string]struct{}{},
549
+ }
550
+ seenByFirstChar := map[byte]map[string]struct{}{}
551
+ for _, feature := range data.Modules {
552
+ parts := strings.Split(feature, ".")
553
+ rawEcmaVersion := parts[0]
554
+ rawConstructorName := ""
555
+ if len(parts) > 1 {
556
+ rawConstructorName = parts[1]
557
+ }
558
+ rawMethodName := ""
559
+ if len(parts) > 2 {
560
+ rawMethodName = parts[2]
561
+ }
562
+
563
+ ecmaVersion := rawEcmaVersion
564
+ if ecmaVersion == "es" {
565
+ ecmaVersion = `(es\d*)`
566
+ }
567
+ constructorName := "(" + rawConstructorName + "|" + polyfillCamelCase(rawConstructorName) + ")"
568
+ methodName := ""
569
+ if rawMethodName != "" {
570
+ methodName = "(" + rawMethodName + "|" + polyfillCamelCase(rawMethodName) + ")"
571
+ }
572
+ methodOrConstructor := methodName
573
+ if methodOrConstructor == "" {
574
+ methodOrConstructor = constructorName
575
+ }
576
+ additionalPattern := ""
577
+ if modules, ok := polyfillAdditionalModules[feature]; ok {
578
+ additionalPattern = "|(" + strings.Join(modules, "|") + ")"
579
+ }
580
+ var patternBuilder strings.Builder
581
+ patternBuilder.WriteString("^((" + polyfillPrefixesPattern + "?(")
582
+ if methodName != "" {
583
+ patternBuilder.WriteString("(" + ecmaVersion + polyfillDelimiterPattern + constructorName + polyfillDelimiterPattern + methodName + ")|")
584
+ patternBuilder.WriteString("(" + constructorName + polyfillDelimiterPattern + methodName + ")|")
585
+ }
586
+ patternBuilder.WriteString("(" + ecmaVersion + polyfillDelimiterPattern + constructorName + "))")
587
+ patternBuilder.WriteString(polyfillSuffixesPattern + "?)|")
588
+ patternBuilder.WriteString("(" + polyfillPrefixesPattern + methodOrConstructor + "|" + methodOrConstructor + polyfillSuffixesPattern + ")")
589
+ patternBuilder.WriteString(additionalPattern + ")$")
590
+ patternSource := patternBuilder.String()
591
+
592
+ seen := map[string]struct{}{}
593
+ var tokens []string
594
+ if rawEcmaVersion == "es" {
595
+ seen["es"] = struct{}{}
596
+ tokens = append(tokens, "es")
597
+ } else {
598
+ polyfillAddToken(seen, &tokens, rawEcmaVersion)
599
+ }
600
+ polyfillAddToken(seen, &tokens, rawConstructorName)
601
+ polyfillAddToken(seen, &tokens, rawMethodName)
602
+ for _, module := range polyfillAdditionalModules[feature] {
603
+ polyfillAddToken(seen, &tokens, module)
604
+ }
605
+
606
+ entry := &polyfillPattern{
607
+ feature: feature,
608
+ segments: len(parts),
609
+ patternSource: patternSource,
610
+ pattern: regexp.MustCompile("(?i)" + patternSource),
611
+ tokens: tokens,
612
+ }
613
+ tables.polyfills = append(tables.polyfills, entry)
614
+
615
+ if rawEcmaVersion == "es" {
616
+ tables.esConstructorTokens[strings.ToLower(rawConstructorName)] = struct{}{}
617
+ tables.esConstructorTokens[strings.ToLower(polyfillCamelCase(rawConstructorName))] = struct{}{}
618
+ }
619
+ for _, token := range tokens {
620
+ if token == "" {
621
+ continue
622
+ }
623
+ tables.byToken[token] = append(tables.byToken[token], entry)
624
+ firstChar := token[0]
625
+ if seenByFirstChar[firstChar] == nil {
626
+ seenByFirstChar[firstChar] = map[string]struct{}{}
627
+ }
628
+ if _, dup := seenByFirstChar[firstChar][token]; !dup {
629
+ seenByFirstChar[firstChar][token] = struct{}{}
630
+ tables.tokensByFirstChar[firstChar] = append(tables.tokensByFirstChar[firstChar], token)
631
+ }
632
+ }
633
+ }
634
+ polyfillPatternTablesValue = tables
635
+ })
636
+ return polyfillPatternTablesValue
637
+ }
638
+
639
+ // polyfillStripPrefix mirrors stripPolyfillPrefix.
640
+ func polyfillStripPrefix(value string) string {
641
+ if strings.HasPrefix(value, "polyfill-") {
642
+ return value[len("polyfill-"):]
643
+ }
644
+ if strings.HasPrefix(value, "mdn-polyfills/") {
645
+ return value[len("mdn-polyfills/"):]
646
+ }
647
+ return value
648
+ }
649
+
650
+ // polyfillHasEsConstructorPrefix mirrors hasEsConstructorPrefix.
651
+ func polyfillHasEsConstructorPrefix(tables *polyfillPatternTables, value string) bool {
652
+ for token := range tables.esConstructorTokens {
653
+ if strings.HasPrefix(value, token) {
654
+ return true
655
+ }
656
+ }
657
+ return false
658
+ }
659
+
660
+ // polyfillIsPotentialEsPrefix mirrors isPotentialEsPrefix.
661
+ func polyfillIsPotentialEsPrefix(tables *polyfillPatternTables, importedModule string) bool {
662
+ if !strings.HasPrefix(importedModule, "es") {
663
+ return false
664
+ }
665
+ index := 2
666
+ for index < len(importedModule) && importedModule[index] >= '0' && importedModule[index] <= '9' {
667
+ index++
668
+ }
669
+ if strings.HasPrefix(importedModule[index:], ".prototype.") {
670
+ index += len(".prototype.")
671
+ } else if index < len(importedModule) &&
672
+ (importedModule[index] == '.' || importedModule[index] == '-' || importedModule[index] == '/') {
673
+ index++
674
+ }
675
+ if index > len(importedModule) {
676
+ return false
677
+ }
678
+ return polyfillHasEsConstructorPrefix(tables, importedModule[index:])
679
+ }
680
+
681
+ // polyfillGetCandidates mirrors getPolyfillCandidates: the token prefilter
682
+ // that selects which patterns are worth testing, preserving upstream's
683
+ // candidate iteration order (it breaks best-match ties).
684
+ func polyfillGetCandidates(normalizedImportedModule string) []*polyfillPattern {
685
+ tables := polyfillPatterns()
686
+ stripped := polyfillStripPrefix(normalizedImportedModule)
687
+ if stripped == "" {
688
+ return nil
689
+ }
690
+ tokens, ok := tables.tokensByFirstChar[stripped[0]]
691
+ if !ok {
692
+ return nil
693
+ }
694
+ var candidates []*polyfillPattern
695
+ seen := map[*polyfillPattern]struct{}{}
696
+ add := func(entries []*polyfillPattern) {
697
+ for _, entry := range entries {
698
+ if _, dup := seen[entry]; dup {
699
+ continue
700
+ }
701
+ seen[entry] = struct{}{}
702
+ candidates = append(candidates, entry)
703
+ }
704
+ }
705
+ firstSegment := polyfillGetFirstSegment(stripped)
706
+ if firstSegment == stripped {
707
+ for _, token := range tokens {
708
+ if token == "es" {
709
+ if !polyfillIsPotentialEsPrefix(tables, stripped) {
710
+ continue
711
+ }
712
+ } else if !strings.HasPrefix(stripped, token) {
713
+ continue
714
+ }
715
+ add(tables.byToken[token])
716
+ }
717
+ } else {
718
+ for _, token := range tokens {
719
+ if token == "es" || !strings.HasPrefix(firstSegment, token) {
720
+ continue
721
+ }
722
+ add(tables.byToken[token])
723
+ }
724
+ }
725
+ if polyfillIsPotentialEsPrefix(tables, stripped) {
726
+ add(tables.byToken["es"])
727
+ }
728
+ if len(candidates) == 0 {
729
+ return nil
730
+ }
731
+ return candidates
732
+ }
733
+
734
+ // polyfillBestMatch mirrors getBestMatchingPolyfill: the least specific
735
+ // (fewest feature segments) candidate whose pattern matches, first-wins on
736
+ // ties.
737
+ func polyfillBestMatch(candidates []*polyfillPattern, importedModule string) *polyfillPattern {
738
+ var best *polyfillPattern
739
+ bestSegments := int(^uint(0) >> 1)
740
+ for _, candidate := range candidates {
741
+ if !candidate.pattern.MatchString(importedModule) {
742
+ continue
743
+ }
744
+ if candidate.segments < bestSegments {
745
+ best = candidate
746
+ bestSegments = candidate.segments
747
+ }
748
+ }
749
+ return best
750
+ }