@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
@@ -0,0 +1,1439 @@
1
+ // Go port of the browserslist 4.28.6 query engine and config discovery used
2
+ // by `unicorn/no-unnecessary-polyfills`.
3
+ //
4
+ // The port transliterates browserslist's `index.js`, `parse.js`, and
5
+ // `node.js` against the pinned dataset embedded in
6
+ // `polyfill_data_gen.json`, preserving JS numeric semantics (`parseInt` /
7
+ // `parseFloat` prefixes, NaN comparisons) and JS object-key iteration order
8
+ // wherever they leak into observable output. Fidelity notes:
9
+ //
10
+ // - Queries that require inputs a native host does not have are rejected
11
+ // with an error instead of resolving: `extends <pkg>` (executes JS),
12
+ // `supports <feature>` and regional `in XX` usage (caniuse feature and
13
+ // region tables are not embedded), `baseline ...`
14
+ // (baseline-browser-mapping is not embedded), and `current node` (there
15
+ // is no Node.js process). The calling rule treats every resolver error
16
+ // the way upstream's `try/catch` does: the rule stays silent.
17
+ // - Filesystem discovery (config files, custom stats) is re-read on every
18
+ // resolution instead of being process-cached, so long-lived hosts (LSP)
19
+ // always honor config edits. Content-derived results are still cached by
20
+ // the rule keyed on the resolved targets.
21
+ // - `oldDataWarning` and the `console.warn` on malformed package.json are
22
+ // not emitted; the dataset ships pinned with ttsc and warnings from a
23
+ // lint sidecar would only add noise.
24
+ package linthost
25
+
26
+ import (
27
+ "fmt"
28
+ "math"
29
+ "regexp"
30
+ "sort"
31
+ "strconv"
32
+ "strings"
33
+ "sync"
34
+ "time"
35
+ )
36
+
37
+ const browserslistYearMs = 365.259641 * 24 * 60 * 60 * 1000
38
+
39
+ // browserslistConfigError mirrors upstream's BrowserslistError: config-shape
40
+ // problems that `findConfigFile` re-throws while other parse failures are
41
+ // ignored.
42
+ type browserslistConfigError struct{ message string }
43
+
44
+ func (e *browserslistConfigError) Error() string { return e.message }
45
+
46
+ func newBrowserslistError(format string, args ...interface{}) error {
47
+ return &browserslistConfigError{message: fmt.Sprintf(format, args...)}
48
+ }
49
+
50
+ // browserslistBrowser is browserslist.data[name]: one agent's normalized
51
+ // version lists and release dates.
52
+ type browserslistBrowser struct {
53
+ name string
54
+ versions []string
55
+ released []string
56
+ releaseDate []polyfillReleaseDateEntry
57
+ }
58
+
59
+ // browserslistDataSet is the decoded artifact indexed the way the query
60
+ // engine consumes it.
61
+ type browserslistDataSet struct {
62
+ constants polyfillBrowserslistConstants
63
+ agentOrder []string
64
+ data map[string]*browserslistBrowser
65
+ usageOrder []string
66
+ usageGlobal map[string]float64
67
+ versionAliases map[string]map[string]string
68
+ nodeVersions []string
69
+ schedule map[string]polyfillNodeScheduleEntry
70
+ e2cOrder []string
71
+ e2c map[string]string
72
+ }
73
+
74
+ var (
75
+ browserslistDataOnce sync.Once
76
+ browserslistDataValue *browserslistDataSet
77
+ )
78
+
79
+ // browserslistData builds the runtime indexes from the embedded artifact
80
+ // once, mirroring browserslist's module-load initialization (version aliases
81
+ // from joined versions, `firefox esr` alias, global usage table).
82
+ func browserslistData() *browserslistDataSet {
83
+ browserslistDataOnce.Do(func() {
84
+ artifact := loadPolyfillData()
85
+ set := &browserslistDataSet{
86
+ constants: artifact.Browserslist,
87
+ data: map[string]*browserslistBrowser{},
88
+ usageGlobal: map[string]float64{},
89
+ versionAliases: map[string]map[string]string{},
90
+ nodeVersions: artifact.NodeVersions,
91
+ schedule: artifact.NodeSchedule,
92
+ e2c: map[string]string{},
93
+ }
94
+ for _, agent := range artifact.Agents {
95
+ set.agentOrder = append(set.agentOrder, agent.Name)
96
+ set.data[agent.Name] = &browserslistBrowser{
97
+ name: agent.Name,
98
+ versions: agent.Versions,
99
+ released: agent.Released,
100
+ releaseDate: agent.ReleaseDate,
101
+ }
102
+ for _, usage := range agent.Usage {
103
+ set.usageOrder = append(set.usageOrder, agent.Name+" "+usage.Version)
104
+ set.usageGlobal[agent.Name+" "+usage.Version] = usage.Usage
105
+ }
106
+ aliases := map[string]string{}
107
+ for _, full := range agent.Versions {
108
+ if !strings.Contains(full, "-") {
109
+ continue
110
+ }
111
+ for _, part := range strings.Split(full, "-") {
112
+ aliases[part] = full
113
+ }
114
+ }
115
+ set.versionAliases[agent.Name] = aliases
116
+ }
117
+ if set.versionAliases["firefox"] == nil {
118
+ set.versionAliases["firefox"] = map[string]string{}
119
+ }
120
+ set.versionAliases["firefox"]["esr"] = artifact.Browserslist.FirefoxEsr
121
+ for _, pair := range artifact.ElectronToChromium {
122
+ set.e2cOrder = append(set.e2cOrder, pair[0])
123
+ set.e2c[pair[0]] = pair[1]
124
+ }
125
+ browserslistDataValue = set
126
+ })
127
+ return browserslistDataValue
128
+ }
129
+
130
+ // browserslistOpts mirrors the `opts` object the rule passes to browserslist:
131
+ // a discovery path, an environment name, and (for tests) an injectable clock.
132
+ type browserslistOpts struct {
133
+ path string
134
+ env string
135
+ now func() time.Time
136
+ }
137
+
138
+ func (o browserslistOpts) clock() time.Time {
139
+ if o.now != nil {
140
+ return o.now()
141
+ }
142
+ return time.Now()
143
+ }
144
+
145
+ // browserslistContext is the per-resolution context: the discovery path (only
146
+ // when some parsed query needs it), custom usage stats, and the clock.
147
+ type browserslistContext struct {
148
+ path string
149
+ env string
150
+ now func() time.Time
151
+ customUsage map[string]float64
152
+ customOrder []string
153
+ customNull map[string]bool
154
+ data *browserslistDataSet
155
+ }
156
+
157
+ func (c *browserslistContext) clock() time.Time {
158
+ if c.now != nil {
159
+ return c.now()
160
+ }
161
+ return time.Now()
162
+ }
163
+
164
+ // browserslistQueryNode is one parsed query: parse.js's node object.
165
+ type browserslistQueryNode struct {
166
+ query string
167
+ not bool
168
+ compose string
169
+ typeIndex int
170
+ matches []string
171
+ }
172
+
173
+ // browserslistQueryDef is one QUERIES table entry.
174
+ type browserslistQueryDef struct {
175
+ name string
176
+ re *regexp.Regexp
177
+ needsPath bool
178
+ selectFn func(ctx *browserslistContext, node *browserslistQueryNode) ([]string, error)
179
+ }
180
+
181
+ var (
182
+ browserslistAndPattern = regexp.MustCompile(`(?i)^\s+and\s+(.*)`)
183
+ browserslistOrPattern = regexp.MustCompile(`(?i)^(?:,\s*|\s+or\s+)(.*)`)
184
+ )
185
+
186
+ // --- JS numeric semantics -------------------------------------------------
187
+
188
+ // jsParseInt mirrors JS parseInt(string): parse an optionally signed decimal
189
+ // integer prefix; NaN when no digits lead the (trimmed) string.
190
+ func jsParseInt(s string) float64 {
191
+ s = strings.TrimSpace(s)
192
+ i := 0
193
+ if i < len(s) && (s[i] == '+' || s[i] == '-') {
194
+ i++
195
+ }
196
+ start := i
197
+ for i < len(s) && s[i] >= '0' && s[i] <= '9' {
198
+ i++
199
+ }
200
+ if i == start {
201
+ return math.NaN()
202
+ }
203
+ value := 0.0
204
+ negative := s[0] == '-'
205
+ for _, ch := range s[start:i] {
206
+ value = value*10 + float64(ch-'0')
207
+ }
208
+ if negative {
209
+ return -value
210
+ }
211
+ return value
212
+ }
213
+
214
+ // jsParseFloat mirrors JS parseFloat(string): parse a decimal float prefix;
215
+ // NaN when no numeric prefix exists.
216
+ func jsParseFloat(s string) float64 {
217
+ s = strings.TrimSpace(s)
218
+ end := 0
219
+ seenDigit := false
220
+ seenDot := false
221
+ for end < len(s) {
222
+ ch := s[end]
223
+ if ch >= '0' && ch <= '9' {
224
+ seenDigit = true
225
+ } else if ch == '.' && !seenDot {
226
+ seenDot = true
227
+ } else if (ch == '+' || ch == '-') && end == 0 {
228
+ // sign
229
+ } else {
230
+ break
231
+ }
232
+ end++
233
+ }
234
+ if !seenDigit {
235
+ return math.NaN()
236
+ }
237
+ value, err := strconv.ParseFloat(strings.TrimSuffix(s[:end], "."), 64)
238
+ if err != nil {
239
+ return math.NaN()
240
+ }
241
+ return value
242
+ }
243
+
244
+ // jsCompare mirrors browserslist's compare(): NaN comparisons yield 0.
245
+ func jsCompare(a, b float64) int {
246
+ if a < b {
247
+ return -1
248
+ }
249
+ if a > b {
250
+ return 1
251
+ }
252
+ return 0
253
+ }
254
+
255
+ // browserslistGetMajor mirrors getMajor: parseInt of the first dot segment.
256
+ func browserslistGetMajor(version string) float64 {
257
+ return jsParseInt(strings.SplitN(version, ".", 2)[0])
258
+ }
259
+
260
+ // browserslistCompareSemver mirrors compareSemver over up-to-three parseInt'd
261
+ // segments with missing segments read as 0.
262
+ func browserslistCompareSemver(a, b []float64) int {
263
+ for i := 0; i < 3; i++ {
264
+ if c := jsCompare(browserslistSegment(a, i), browserslistSegment(b, i)); c != 0 {
265
+ return c
266
+ }
267
+ }
268
+ return 0
269
+ }
270
+
271
+ func browserslistSegment(parts []float64, index int) float64 {
272
+ if index < len(parts) {
273
+ return parts[index]
274
+ }
275
+ return 0
276
+ }
277
+
278
+ func browserslistSplitSemver(version string) []float64 {
279
+ segments := strings.Split(version, ".")
280
+ parts := make([]float64, len(segments))
281
+ for i, segment := range segments {
282
+ parts[i] = jsParseInt(segment)
283
+ }
284
+ return parts
285
+ }
286
+
287
+ // browserslistParseLatestFloat mirrors generateFilter's parseLatestFloat:
288
+ // the float after the dash of a joined version, else the whole version.
289
+ func browserslistParseLatestFloat(v string) float64 {
290
+ if _, after, found := strings.Cut(v, "-"); found && after != "" {
291
+ return jsParseFloat(after)
292
+ }
293
+ return jsParseFloat(v)
294
+ }
295
+
296
+ // browserslistFloatFilter mirrors generateFilter(sign, version).
297
+ func browserslistFloatFilter(sign, version string) func(string) bool {
298
+ switch sign {
299
+ case ">":
300
+ limit := browserslistParseLatestFloat(version)
301
+ return func(v string) bool { return browserslistParseLatestFloat(v) > limit }
302
+ case ">=":
303
+ limit := browserslistParseLatestFloat(version)
304
+ return func(v string) bool { return browserslistParseLatestFloat(v) >= limit }
305
+ case "<":
306
+ limit := jsParseFloat(version)
307
+ return func(v string) bool { return jsParseFloat(v) < limit }
308
+ default:
309
+ limit := jsParseFloat(version)
310
+ return func(v string) bool { return jsParseFloat(v) <= limit }
311
+ }
312
+ }
313
+
314
+ // browserslistSemverFilter mirrors generateSemverFilter(sign, version).
315
+ func browserslistSemverFilter(sign, version string) func(string) bool {
316
+ limit := browserslistSplitSemver(version)
317
+ for len(limit) < 3 {
318
+ limit = append(limit, 0)
319
+ }
320
+ for i := 1; i < 3; i++ {
321
+ if math.IsNaN(limit[i]) {
322
+ limit[i] = 0
323
+ }
324
+ }
325
+ switch sign {
326
+ case ">":
327
+ return func(v string) bool {
328
+ return browserslistCompareSemver(browserslistSplitSemver(v), limit) > 0
329
+ }
330
+ case ">=":
331
+ return func(v string) bool {
332
+ return browserslistCompareSemver(browserslistSplitSemver(v), limit) >= 0
333
+ }
334
+ case "<":
335
+ return func(v string) bool {
336
+ return browserslistCompareSemver(limit, browserslistSplitSemver(v)) > 0
337
+ }
338
+ default:
339
+ return func(v string) bool {
340
+ return browserslistCompareSemver(limit, browserslistSplitSemver(v)) >= 0
341
+ }
342
+ }
343
+ }
344
+
345
+ // browserslistSemverFilterLoose mirrors semverFilterLoose: npm-style ranges
346
+ // where a missing minor is a wildcard.
347
+ func browserslistSemverFilterLoose(operator, rangeVersion string) func(string) bool {
348
+ rangeParts := browserslistSplitSemver(rangeVersion)
349
+ minorWildcard := len(rangeParts) < 2
350
+ compareLoose := func(version []float64) int {
351
+ versionMajor := browserslistSegment(version, 0)
352
+ rangeMajor := browserslistSegment(rangeParts, 0)
353
+ if versionMajor != rangeMajor {
354
+ if versionMajor < rangeMajor {
355
+ return -1
356
+ }
357
+ return 1
358
+ }
359
+ if minorWildcard {
360
+ return 0
361
+ }
362
+ versionMinor := math.NaN()
363
+ if len(version) > 1 {
364
+ versionMinor = version[1]
365
+ }
366
+ rangeMinor := math.NaN()
367
+ if len(rangeParts) > 1 {
368
+ rangeMinor = rangeParts[1]
369
+ }
370
+ if versionMinor != rangeMinor {
371
+ if versionMinor < rangeMinor {
372
+ return -1
373
+ }
374
+ return 1
375
+ }
376
+ return 0
377
+ }
378
+ if operator == "<=" {
379
+ return func(v string) bool { return compareLoose(browserslistSplitSemver(v)) <= 0 }
380
+ }
381
+ return func(v string) bool { return compareLoose(browserslistSplitSemver(v)) >= 0 }
382
+ }
383
+
384
+ // --- data helpers ----------------------------------------------------------
385
+
386
+ // browserslistIsVersionsMatch mirrors isVersionsMatch: dotted prefix match.
387
+ func browserslistIsVersionsMatch(versionA, versionB string) bool {
388
+ return strings.HasPrefix(versionA+".", versionB+".")
389
+ }
390
+
391
+ // browserslistByName mirrors byName without the mobileToDesktop branches the
392
+ // rule never enables.
393
+ func browserslistByName(ctx *browserslistContext, name string) *browserslistBrowser {
394
+ name = strings.ToLower(name)
395
+ if alias, ok := ctx.data.constants.Aliases[name]; ok {
396
+ name = alias
397
+ }
398
+ return ctx.data.data[name]
399
+ }
400
+
401
+ func browserslistCheckName(ctx *browserslistContext, name string) (*browserslistBrowser, error) {
402
+ data := browserslistByName(ctx, name)
403
+ if data == nil {
404
+ return nil, newBrowserslistError("Unknown browser %s", name)
405
+ }
406
+ return data, nil
407
+ }
408
+
409
+ // browserslistNormalizeElectron mirrors normalizeElectron: three-part
410
+ // versions drop the patch segment.
411
+ func browserslistNormalizeElectron(version string) string {
412
+ parts := strings.Split(version, ".")
413
+ if len(parts) == 3 {
414
+ return strings.Join(parts[:2], ".")
415
+ }
416
+ return version
417
+ }
418
+
419
+ func browserslistResolveVersion(ctx *browserslistContext, data *browserslistBrowser, version string) string {
420
+ for _, v := range data.versions {
421
+ if v == version {
422
+ return version
423
+ }
424
+ }
425
+ if alias := ctx.data.versionAliases[data.name][version]; alias != "" {
426
+ return alias
427
+ }
428
+ return ""
429
+ }
430
+
431
+ func browserslistNormalizeVersion(ctx *browserslistContext, data *browserslistBrowser, version string) string {
432
+ if resolved := browserslistResolveVersion(ctx, data, version); resolved != "" {
433
+ return resolved
434
+ }
435
+ if len(data.versions) == 1 {
436
+ return data.versions[0]
437
+ }
438
+ return ""
439
+ }
440
+
441
+ func browserslistNameMapper(name string, versions []string) []string {
442
+ mapped := make([]string, len(versions))
443
+ for i, version := range versions {
444
+ mapped[i] = name + " " + version
445
+ }
446
+ return mapped
447
+ }
448
+
449
+ // browserslistGetMajorVersions mirrors getMajorVersions with JS falsiness:
450
+ // a missing, zero, or NaN minimum returns the whole released list.
451
+ func browserslistGetMajorVersions(released []string, number int) []string {
452
+ if len(released) == 0 {
453
+ return []string{}
454
+ }
455
+ var majors []float64
456
+ for _, version := range released {
457
+ major := browserslistGetMajor(version)
458
+ seen := false
459
+ for _, existing := range majors {
460
+ if existing == major { // NaN != NaN keeps NaN duplicates, like indexOf
461
+ seen = true
462
+ break
463
+ }
464
+ }
465
+ if !seen {
466
+ majors = append(majors, major)
467
+ }
468
+ }
469
+ index := len(majors) - number
470
+ if index < 0 {
471
+ return released
472
+ }
473
+ minimum := majors[index]
474
+ if minimum == 0 || math.IsNaN(minimum) {
475
+ return released
476
+ }
477
+ var selected []string
478
+ for i := len(released) - 1; i >= 0; i-- {
479
+ if minimum > browserslistGetMajor(released[i]) {
480
+ break
481
+ }
482
+ selected = append([]string{released[i]}, selected...)
483
+ }
484
+ return selected
485
+ }
486
+
487
+ // browserslistFilterJumps mirrors filterJumps for android / op_mob version
488
+ // jumps in caniuse data (mobileToDesktop is never set by the rule).
489
+ func browserslistFilterJumps(ctx *browserslistContext, list []string, name string, nVersions int) []string {
490
+ jump := 1
491
+ switch name {
492
+ case "android":
493
+ released := ctx.data.data["chrome"].released
494
+ index := -1
495
+ for i, version := range released {
496
+ if version == ctx.data.constants.AndroidEvergreenFirst {
497
+ index = i
498
+ break
499
+ }
500
+ }
501
+ jump = len(released) - index
502
+ case "op_mob":
503
+ released := ctx.data.data["op_mob"].released
504
+ latest := ""
505
+ if len(released) > 0 {
506
+ latest = released[len(released)-1]
507
+ }
508
+ jump = int(browserslistGetMajor(latest)) - ctx.data.constants.OpMobBlinkFirst + 1
509
+ default:
510
+ return list
511
+ }
512
+ if nVersions <= jump {
513
+ if len(list) == 0 {
514
+ return list
515
+ }
516
+ return list[len(list)-1:]
517
+ }
518
+ start := len(list) + jump - 1 - nVersions
519
+ if start < 0 {
520
+ start = 0
521
+ }
522
+ return list[start:]
523
+ }
524
+
525
+ // browserslistFilterByYear mirrors filterByYear over the ordered release
526
+ // dates of every agent.
527
+ func browserslistFilterByYear(ctx *browserslistContext, sinceMs float64) []string {
528
+ since := sinceMs / 1000
529
+ var selected []string
530
+ for _, name := range ctx.data.agentOrder {
531
+ data := browserslistByName(ctx, name)
532
+ if data == nil {
533
+ continue
534
+ }
535
+ for _, entry := range data.releaseDate {
536
+ if entry.Date >= since {
537
+ selected = append(selected, data.name+" "+entry.Version)
538
+ }
539
+ }
540
+ }
541
+ return selected
542
+ }
543
+
544
+ func browserslistUniq(list []string) []string {
545
+ seen := make(map[string]struct{}, len(list))
546
+ out := make([]string, 0, len(list))
547
+ for _, item := range list {
548
+ if _, dup := seen[item]; dup {
549
+ continue
550
+ }
551
+ seen[item] = struct{}{}
552
+ out = append(out, item)
553
+ }
554
+ return out
555
+ }
556
+
557
+ // --- parse.js port ----------------------------------------------------------
558
+
559
+ func browserslistParseQueries(queries []string) []*browserslistQueryNode {
560
+ var out []*browserslistQueryNode
561
+ for _, block := range queries {
562
+ var qs []*browserslistQueryNode
563
+ for {
564
+ block = browserslistMatchBlock(block, &qs)
565
+ if block == "" {
566
+ break
567
+ }
568
+ }
569
+ out = append(out, qs...)
570
+ }
571
+ return out
572
+ }
573
+
574
+ func browserslistMatchBlock(s string, qs *[]*browserslistQueryNode) string {
575
+ max := len(s)
576
+ for n := 1; n <= max; n++ {
577
+ parsed := s[max-n:]
578
+ if m := browserslistAndPattern.FindStringSubmatch(parsed); m != nil {
579
+ node := browserslistMatchQuery(m[1])
580
+ node.compose = "and"
581
+ *qs = append([]*browserslistQueryNode{node}, *qs...)
582
+ return s[:max-n]
583
+ }
584
+ if m := browserslistOrPattern.FindStringSubmatch(parsed); m != nil {
585
+ node := browserslistMatchQuery(m[1])
586
+ node.compose = "or"
587
+ *qs = append([]*browserslistQueryNode{node}, *qs...)
588
+ return s[:max-n]
589
+ }
590
+ if n == max {
591
+ node := browserslistMatchQuery(strings.TrimSpace(parsed))
592
+ node.compose = "or"
593
+ *qs = append([]*browserslistQueryNode{node}, *qs...)
594
+ return ""
595
+ }
596
+ }
597
+ return ""
598
+ }
599
+
600
+ func browserslistMatchQuery(query string) *browserslistQueryNode {
601
+ node := &browserslistQueryNode{query: query, typeIndex: -1}
602
+ stripped := query
603
+ if strings.HasPrefix(stripped, "not ") {
604
+ node.not = true
605
+ stripped = stripped[4:]
606
+ }
607
+ for i, def := range browserslistQueries {
608
+ if m := def.re.FindStringSubmatch(stripped); m != nil {
609
+ node.typeIndex = i
610
+ node.matches = m[1:]
611
+ return node
612
+ }
613
+ }
614
+ return node
615
+ }
616
+
617
+ // --- resolve ----------------------------------------------------------------
618
+
619
+ func browserslistResolveNodes(nodes []*browserslistQueryNode, ctx *browserslistContext) ([]string, error) {
620
+ result := []string{}
621
+ for index, node := range nodes {
622
+ if node.not && index == 0 {
623
+ return nil, newBrowserslistError(
624
+ "Write any browsers query (for instance, `defaults`) before `%s`", node.query)
625
+ }
626
+ var array []string
627
+ var err error
628
+ if node.typeIndex < 0 {
629
+ err = browserslistUnknownQueryError(ctx, node)
630
+ } else {
631
+ array, err = browserslistQueries[node.typeIndex].selectFn(ctx, node)
632
+ }
633
+ if err != nil {
634
+ return nil, err
635
+ }
636
+ for i, item := range array {
637
+ name, version, found := strings.Cut(item, " ")
638
+ if found && version == "0" {
639
+ data := browserslistByName(ctx, name)
640
+ if data == nil || len(data.versions) == 0 {
641
+ return nil, newBrowserslistError("Unknown browser %s", name)
642
+ }
643
+ array[i] = name + " " + data.versions[0]
644
+ }
645
+ }
646
+ if node.compose == "and" {
647
+ allowed := make(map[string]struct{}, len(array))
648
+ for _, item := range array {
649
+ allowed[item] = struct{}{}
650
+ }
651
+ filtered := result[:0]
652
+ for _, item := range result {
653
+ if _, in := allowed[item]; in != node.not {
654
+ filtered = append(filtered, item)
655
+ }
656
+ }
657
+ result = filtered
658
+ } else if node.not {
659
+ blocked := make(map[string]struct{}, len(array))
660
+ for _, item := range array {
661
+ blocked[item] = struct{}{}
662
+ }
663
+ filtered := result[:0]
664
+ for _, item := range result {
665
+ if _, in := blocked[item]; !in {
666
+ filtered = append(filtered, item)
667
+ }
668
+ }
669
+ result = filtered
670
+ } else {
671
+ result = append(result, array...)
672
+ }
673
+ }
674
+ return result, nil
675
+ }
676
+
677
+ // browserslistUnknownQueryError mirrors the `unknown` QUERIES entry plus the
678
+ // fallthrough for queries no regexp matched.
679
+ func browserslistUnknownQueryError(ctx *browserslistContext, node *browserslistQueryNode) error {
680
+ if browserslistByName(ctx, node.query) != nil {
681
+ return newBrowserslistError(
682
+ "Specify versions in Browserslist query for browser %s", node.query)
683
+ }
684
+ return newBrowserslistError(
685
+ "Unknown browser query `%s`. Maybe you are using old Browserslist or made typo in query.",
686
+ node.query)
687
+ }
688
+
689
+ // browserslistResolve resolves explicit queries the way `browserslist(queries,
690
+ // opts)` does; queries == nil resolves the discovered config or the defaults.
691
+ func browserslistResolve(queries []string, hasQueries bool, opts browserslistOpts) ([]string, error) {
692
+ data := browserslistData()
693
+ if !hasQueries {
694
+ config, found, err := browserslistLoadConfig(opts)
695
+ if err != nil {
696
+ return nil, err
697
+ }
698
+ if found {
699
+ queries = config
700
+ } else {
701
+ queries = data.constants.Defaults
702
+ }
703
+ }
704
+ nodes := browserslistParseQueries(queries)
705
+ ctx := &browserslistContext{env: opts.env, now: opts.now, data: data}
706
+ for _, node := range nodes {
707
+ if node.typeIndex >= 0 && browserslistQueries[node.typeIndex].needsPath {
708
+ ctx.path = opts.path
709
+ break
710
+ }
711
+ }
712
+ statsValues, statsOrder, statsNull, err := browserslistGetStat(opts)
713
+ if err != nil {
714
+ return nil, err
715
+ }
716
+ if statsValues != nil {
717
+ ctx.customUsage = statsValues
718
+ ctx.customOrder = statsOrder
719
+ ctx.customNull = statsNull
720
+ }
721
+ resolved, err := browserslistResolveNodes(nodes, ctx)
722
+ if err != nil {
723
+ return nil, err
724
+ }
725
+ result := browserslistUniq(resolved)
726
+ sort.SliceStable(result, func(i, j int) bool {
727
+ name1, version1, _ := strings.Cut(result[i], " ")
728
+ name2, version2, _ := strings.Cut(result[j], " ")
729
+ if name1 == name2 {
730
+ v1 := strings.SplitN(version1, "-", 2)[0]
731
+ v2 := strings.SplitN(version2, "-", 2)[0]
732
+ return browserslistCompareSemver(
733
+ browserslistSplitSemver(v2),
734
+ browserslistSplitSemver(v1),
735
+ ) < 0
736
+ }
737
+ return name1 < name2
738
+ })
739
+ return result, nil
740
+ }
741
+
742
+ // --- usage-based selects ----------------------------------------------------
743
+
744
+ func browserslistUsageSource(ctx *browserslistContext, custom bool) (map[string]float64, []string, map[string]bool, error) {
745
+ if custom {
746
+ if ctx.customUsage == nil {
747
+ return nil, nil, nil, newBrowserslistError("Custom usage statistics was not provided")
748
+ }
749
+ return ctx.customUsage, ctx.customOrder, ctx.customNull, nil
750
+ }
751
+ return ctx.data.usageGlobal, ctx.data.usageOrder, nil, nil
752
+ }
753
+
754
+ func browserslistPopularitySelect(custom bool) func(*browserslistContext, *browserslistQueryNode) ([]string, error) {
755
+ return func(ctx *browserslistContext, node *browserslistQueryNode) ([]string, error) {
756
+ sign := node.matches[0]
757
+ popularity := jsParseFloat(node.matches[1])
758
+ usage, order, nulls, err := browserslistUsageSource(ctx, custom)
759
+ if err != nil {
760
+ return nil, err
761
+ }
762
+ var result []string
763
+ for _, version := range order {
764
+ if custom && nulls[version] {
765
+ continue
766
+ }
767
+ percentage := usage[version]
768
+ keep := false
769
+ switch sign {
770
+ case ">":
771
+ keep = percentage > popularity
772
+ case "<":
773
+ keep = percentage < popularity
774
+ case "<=":
775
+ keep = percentage <= popularity
776
+ default:
777
+ keep = percentage >= popularity
778
+ }
779
+ if keep {
780
+ result = append(result, version)
781
+ }
782
+ }
783
+ return result, nil
784
+ }
785
+ }
786
+
787
+ var browserslistMyStatsPattern = regexp.MustCompile(`(?i)^my\s+stats$`)
788
+
789
+ func browserslistCoverSelect(ctx *browserslistContext, node *browserslistQueryNode) ([]string, error) {
790
+ coverage := jsParseFloat(node.matches[0])
791
+ usage := ctx.data.usageGlobal
792
+ order := ctx.data.usageOrder
793
+ var nulls map[string]bool
794
+ if len(node.matches) > 1 && node.matches[1] != "" {
795
+ place := node.matches[1]
796
+ if browserslistMyStatsPattern.MatchString(place) {
797
+ if ctx.customUsage == nil {
798
+ return nil, newBrowserslistError("Custom usage statistics was not provided")
799
+ }
800
+ usage, order, nulls = ctx.customUsage, ctx.customOrder, ctx.customNull
801
+ } else {
802
+ return nil, newBrowserslistError(
803
+ "Regional usage data is not supported by the ttsc browserslist port")
804
+ }
805
+ } else if len(node.matches) > 2 && node.matches[2] != "" {
806
+ return nil, newBrowserslistError(
807
+ "Package-provided usage statistics are not supported by the ttsc browserslist port")
808
+ }
809
+ versions := append([]string(nil), order...)
810
+ sort.SliceStable(versions, func(i, j int) bool {
811
+ if nulls != nil && (nulls[versions[i]] || nulls[versions[j]]) {
812
+ return false
813
+ }
814
+ return usage[versions[j]] < usage[versions[i]]
815
+ })
816
+ covered := 0.0
817
+ var result []string
818
+ for _, version := range versions {
819
+ if nulls == nil || !nulls[version] {
820
+ if usage[version] == 0 {
821
+ break
822
+ }
823
+ covered += usage[version]
824
+ }
825
+ result = append(result, version)
826
+ if covered >= coverage {
827
+ break
828
+ }
829
+ }
830
+ return result, nil
831
+ }
832
+
833
+ // --- node / electron selects -------------------------------------------------
834
+
835
+ func browserslistNodeQuery(ctx *browserslistContext, node *browserslistQueryNode) ([]string, error) {
836
+ version := node.matches[0]
837
+ var matched []string
838
+ for _, candidate := range ctx.data.nodeVersions {
839
+ if browserslistIsVersionsMatch(candidate, version) {
840
+ matched = append(matched, candidate)
841
+ }
842
+ }
843
+ if len(matched) == 0 {
844
+ return nil, newBrowserslistError("Unknown version %s of Node.js", version)
845
+ }
846
+ return []string{"node " + matched[len(matched)-1]}, nil
847
+ }
848
+
849
+ func browserslistSinceQuery(ctx *browserslistContext, node *browserslistQueryNode) ([]string, error) {
850
+ year := int(jsParseInt(node.matches[0]))
851
+ // Date.UTC treats two-digit years as 1900-based.
852
+ if year >= 0 && year <= 99 {
853
+ year += 1900
854
+ }
855
+ month := 1
856
+ if len(node.matches) > 1 && node.matches[1] != "" {
857
+ month = int(jsParseInt(node.matches[1]))
858
+ }
859
+ day := 1
860
+ if len(node.matches) > 2 && node.matches[2] != "" {
861
+ day = int(jsParseInt(node.matches[2]))
862
+ }
863
+ since := time.Date(year, time.Month(month), day, 0, 0, 0, 0, time.UTC)
864
+ return browserslistFilterByYear(ctx, float64(since.UnixMilli())), nil
865
+ }
866
+
867
+ // --- QUERIES table (exact upstream order) ------------------------------------
868
+
869
+ // browserslistQueries is assigned in init() because several selects
870
+ // (`defaults`, `dead`, `maintained node versions`) recursively parse queries,
871
+ // which would otherwise form a compile-time initialization cycle.
872
+ var browserslistQueries []browserslistQueryDef
873
+
874
+ func init() {
875
+ browserslistQueries = []browserslistQueryDef{
876
+ {
877
+ name: "last_major_versions",
878
+ re: regexp.MustCompile(`(?i)^last\s+(\d+)\s+major\s+versions?$`),
879
+ selectFn: func(ctx *browserslistContext, node *browserslistQueryNode) ([]string, error) {
880
+ count := int(jsParseInt(node.matches[0]))
881
+ var selected []string
882
+ for _, name := range ctx.data.agentOrder {
883
+ data := browserslistByName(ctx, name)
884
+ if data == nil {
885
+ continue
886
+ }
887
+ list := browserslistGetMajorVersions(data.released, count)
888
+ list = browserslistNameMapper(data.name, list)
889
+ list = browserslistFilterJumps(ctx, list, data.name, count)
890
+ selected = append(selected, list...)
891
+ }
892
+ return selected, nil
893
+ },
894
+ },
895
+ {
896
+ name: "last_versions",
897
+ re: regexp.MustCompile(`(?i)^last\s+(\d+)\s+versions?$`),
898
+ selectFn: func(ctx *browserslistContext, node *browserslistQueryNode) ([]string, error) {
899
+ count := int(jsParseInt(node.matches[0]))
900
+ var selected []string
901
+ for _, name := range ctx.data.agentOrder {
902
+ data := browserslistByName(ctx, name)
903
+ if data == nil {
904
+ continue
905
+ }
906
+ list := browserslistLastSlice(data.released, count)
907
+ list = browserslistNameMapper(data.name, list)
908
+ list = browserslistFilterJumps(ctx, list, data.name, count)
909
+ selected = append(selected, list...)
910
+ }
911
+ return selected, nil
912
+ },
913
+ },
914
+ {
915
+ name: "last_electron_major_versions",
916
+ re: regexp.MustCompile(`(?i)^last\s+(\d+)\s+electron\s+major\s+versions?$`),
917
+ selectFn: func(ctx *browserslistContext, node *browserslistQueryNode) ([]string, error) {
918
+ count := int(jsParseInt(node.matches[0]))
919
+ valid := browserslistGetMajorVersions(ctx.data.e2cOrder, count)
920
+ result := make([]string, len(valid))
921
+ for i, version := range valid {
922
+ result[i] = "chrome " + ctx.data.e2c[version]
923
+ }
924
+ return result, nil
925
+ },
926
+ },
927
+ {
928
+ name: "last_node_major_versions",
929
+ re: regexp.MustCompile(`(?i)^last\s+(\d+)\s+node\s+major\s+versions?$`),
930
+ selectFn: func(ctx *browserslistContext, node *browserslistQueryNode) ([]string, error) {
931
+ count := int(jsParseInt(node.matches[0]))
932
+ return browserslistNameMapper("node", browserslistGetMajorVersions(ctx.data.nodeVersions, count)), nil
933
+ },
934
+ },
935
+ {
936
+ name: "last_browser_major_versions",
937
+ re: regexp.MustCompile(`(?i)^last\s+(\d+)\s+(\w+)\s+major\s+versions?$`),
938
+ selectFn: func(ctx *browserslistContext, node *browserslistQueryNode) ([]string, error) {
939
+ count := int(jsParseInt(node.matches[0]))
940
+ data, err := browserslistCheckName(ctx, node.matches[1])
941
+ if err != nil {
942
+ return nil, err
943
+ }
944
+ list := browserslistGetMajorVersions(data.released, count)
945
+ mapped := browserslistNameMapper(data.name, list)
946
+ return browserslistFilterJumps(ctx, mapped, data.name, count), nil
947
+ },
948
+ },
949
+ {
950
+ name: "last_electron_versions",
951
+ re: regexp.MustCompile(`(?i)^last\s+(\d+)\s+electron\s+versions?$`),
952
+ selectFn: func(ctx *browserslistContext, node *browserslistQueryNode) ([]string, error) {
953
+ count := int(jsParseInt(node.matches[0]))
954
+ versions := browserslistLastSlice(ctx.data.e2cOrder, count)
955
+ result := make([]string, len(versions))
956
+ for i, version := range versions {
957
+ result[i] = "chrome " + ctx.data.e2c[version]
958
+ }
959
+ return result, nil
960
+ },
961
+ },
962
+ {
963
+ name: "last_node_versions",
964
+ re: regexp.MustCompile(`(?i)^last\s+(\d+)\s+node\s+versions?$`),
965
+ selectFn: func(ctx *browserslistContext, node *browserslistQueryNode) ([]string, error) {
966
+ count := int(jsParseInt(node.matches[0]))
967
+ return browserslistNameMapper("node", browserslistLastSlice(ctx.data.nodeVersions, count)), nil
968
+ },
969
+ },
970
+ {
971
+ name: "last_browser_versions",
972
+ re: regexp.MustCompile(`(?i)^last\s+(\d+)\s+(\w+)\s+versions?$`),
973
+ selectFn: func(ctx *browserslistContext, node *browserslistQueryNode) ([]string, error) {
974
+ count := int(jsParseInt(node.matches[0]))
975
+ data, err := browserslistCheckName(ctx, node.matches[1])
976
+ if err != nil {
977
+ return nil, err
978
+ }
979
+ list := browserslistLastSlice(data.released, count)
980
+ mapped := browserslistNameMapper(data.name, list)
981
+ return browserslistFilterJumps(ctx, mapped, data.name, count), nil
982
+ },
983
+ },
984
+ {
985
+ name: "unreleased_versions",
986
+ re: regexp.MustCompile(`(?i)^unreleased\s+versions$`),
987
+ selectFn: func(ctx *browserslistContext, node *browserslistQueryNode) ([]string, error) {
988
+ var selected []string
989
+ for _, name := range ctx.data.agentOrder {
990
+ data := browserslistByName(ctx, name)
991
+ if data == nil {
992
+ continue
993
+ }
994
+ for _, version := range data.versions {
995
+ if !browserslistContains(data.released, version) {
996
+ selected = append(selected, data.name+" "+version)
997
+ }
998
+ }
999
+ }
1000
+ return selected, nil
1001
+ },
1002
+ },
1003
+ {
1004
+ name: "unreleased_electron_versions",
1005
+ re: regexp.MustCompile(`(?i)^unreleased\s+electron\s+versions?$`),
1006
+ selectFn: func(ctx *browserslistContext, node *browserslistQueryNode) ([]string, error) {
1007
+ return nil, nil
1008
+ },
1009
+ },
1010
+ {
1011
+ name: "unreleased_browser_versions",
1012
+ re: regexp.MustCompile(`(?i)^unreleased\s+(\w+)\s+versions?$`),
1013
+ selectFn: func(ctx *browserslistContext, node *browserslistQueryNode) ([]string, error) {
1014
+ data, err := browserslistCheckName(ctx, node.matches[0])
1015
+ if err != nil {
1016
+ return nil, err
1017
+ }
1018
+ var selected []string
1019
+ for _, version := range data.versions {
1020
+ if !browserslistContains(data.released, version) {
1021
+ selected = append(selected, data.name+" "+version)
1022
+ }
1023
+ }
1024
+ return selected, nil
1025
+ },
1026
+ },
1027
+ {
1028
+ name: "last_years",
1029
+ re: regexp.MustCompile(`(?i)^last\s+((\d+\.)?\d+)\s+years?$`),
1030
+ selectFn: func(ctx *browserslistContext, node *browserslistQueryNode) ([]string, error) {
1031
+ years := jsParseFloat(node.matches[0])
1032
+ nowMs := float64(ctx.clock().UnixMilli())
1033
+ return browserslistFilterByYear(ctx, nowMs-browserslistYearMs*years), nil
1034
+ },
1035
+ },
1036
+ {
1037
+ name: "since_y",
1038
+ re: regexp.MustCompile(`(?i)^since (\d+)$`),
1039
+ selectFn: browserslistSinceQuery,
1040
+ },
1041
+ {
1042
+ name: "since_y_m",
1043
+ re: regexp.MustCompile(`(?i)^since (\d+)-(\d+)$`),
1044
+ selectFn: browserslistSinceQuery,
1045
+ },
1046
+ {
1047
+ name: "since_y_m_d",
1048
+ re: regexp.MustCompile(`(?i)^since (\d+)-(\d+)-(\d+)$`),
1049
+ selectFn: browserslistSinceQuery,
1050
+ },
1051
+ {
1052
+ name: "baseline",
1053
+ re: regexp.MustCompile(
1054
+ `(?i)^baseline\s+(?:(\d+)|(newly|widely)\s+available(?:\s+on\s+(\d{4}-\d{2}-\d{2}))?)?(\s+with\s+downstream)?(\s+including\s+kaios)?$`),
1055
+ selectFn: func(ctx *browserslistContext, node *browserslistQueryNode) ([]string, error) {
1056
+ return nil, newBrowserslistError(
1057
+ "`baseline` queries are not supported by the ttsc browserslist port")
1058
+ },
1059
+ },
1060
+ {
1061
+ name: "popularity",
1062
+ re: regexp.MustCompile(`^(>=?|<=?)\s*(\d+|\d+\.\d+|\.\d+)%$`),
1063
+ selectFn: browserslistPopularitySelect(false),
1064
+ },
1065
+ {
1066
+ name: "popularity_in_my_stats",
1067
+ re: regexp.MustCompile(`^(>=?|<=?)\s*(\d+|\d+\.\d+|\.\d+)%\s+in\s+my\s+stats$`),
1068
+ selectFn: browserslistPopularitySelect(true),
1069
+ },
1070
+ {
1071
+ name: "popularity_in_config_stats",
1072
+ re: regexp.MustCompile(`^(>=?|<=?)\s*(\d+|\d+\.\d+|\.\d+)%\s+in\s+(\S+)\s+stats$`),
1073
+ selectFn: func(ctx *browserslistContext, node *browserslistQueryNode) ([]string, error) {
1074
+ return nil, newBrowserslistError(
1075
+ "Package-provided usage statistics are not supported by the ttsc browserslist port")
1076
+ },
1077
+ },
1078
+ {
1079
+ name: "popularity_in_place",
1080
+ re: regexp.MustCompile(`^(>=?|<=?)\s*(\d+|\d+\.\d+|\.\d+)%\s+in\s+((alt-)?\w\w)$`),
1081
+ selectFn: func(ctx *browserslistContext, node *browserslistQueryNode) ([]string, error) {
1082
+ return nil, newBrowserslistError(
1083
+ "Regional usage data is not supported by the ttsc browserslist port")
1084
+ },
1085
+ },
1086
+ {
1087
+ name: "cover",
1088
+ re: regexp.MustCompile(`(?i)^cover\s+(\d+|\d+\.\d+|\.\d+)%$`),
1089
+ selectFn: func(ctx *browserslistContext, node *browserslistQueryNode) ([]string, error) {
1090
+ return browserslistCoverSelect(ctx, node)
1091
+ },
1092
+ },
1093
+ {
1094
+ name: "cover_in",
1095
+ re: regexp.MustCompile(`(?i)^cover\s+(\d+|\d+\.\d+|\.\d+)%\s+in\s+(my\s+stats|(alt-)?\w\w)$`),
1096
+ selectFn: func(ctx *browserslistContext, node *browserslistQueryNode) ([]string, error) {
1097
+ return browserslistCoverSelect(ctx, node)
1098
+ },
1099
+ },
1100
+ {
1101
+ name: "cover_config",
1102
+ re: regexp.MustCompile(`(?i)^cover\s+(\d+|\d+\.\d+|\.\d+)%\s+in\s+(\S+)\s+stats$`),
1103
+ selectFn: func(ctx *browserslistContext, node *browserslistQueryNode) ([]string, error) {
1104
+ return nil, newBrowserslistError(
1105
+ "Package-provided usage statistics are not supported by the ttsc browserslist port")
1106
+ },
1107
+ },
1108
+ {
1109
+ name: "supports",
1110
+ re: regexp.MustCompile(`^(?:(fully|partially)\s+)?supports\s+([\w-]+)$`),
1111
+ selectFn: func(ctx *browserslistContext, node *browserslistQueryNode) ([]string, error) {
1112
+ return nil, newBrowserslistError(
1113
+ "`supports` queries are not supported by the ttsc browserslist port")
1114
+ },
1115
+ },
1116
+ {
1117
+ name: "electron_range",
1118
+ re: regexp.MustCompile(`(?i)^electron\s+([\d.]+)\s*-\s*([\d.]+)$`),
1119
+ selectFn: func(ctx *browserslistContext, node *browserslistQueryNode) ([]string, error) {
1120
+ from := browserslistNormalizeElectron(node.matches[0])
1121
+ to := browserslistNormalizeElectron(node.matches[1])
1122
+ if _, ok := ctx.data.e2c[from]; !ok {
1123
+ return nil, newBrowserslistError("Unknown version %s of electron", node.matches[0])
1124
+ }
1125
+ if _, ok := ctx.data.e2c[to]; !ok {
1126
+ return nil, newBrowserslistError("Unknown version %s of electron", node.matches[1])
1127
+ }
1128
+ lower := browserslistSemverFilterLoose(">=", node.matches[0])
1129
+ upper := browserslistSemverFilterLoose("<=", node.matches[1])
1130
+ var result []string
1131
+ for _, version := range ctx.data.e2cOrder {
1132
+ if lower(version) && upper(version) {
1133
+ result = append(result, "chrome "+ctx.data.e2c[version])
1134
+ }
1135
+ }
1136
+ return result, nil
1137
+ },
1138
+ },
1139
+ {
1140
+ name: "node_range",
1141
+ re: regexp.MustCompile(`(?i)^node\s+([\d.]+)\s*-\s*([\d.]+)$`),
1142
+ selectFn: func(ctx *browserslistContext, node *browserslistQueryNode) ([]string, error) {
1143
+ lower := browserslistSemverFilterLoose(">=", node.matches[0])
1144
+ upper := browserslistSemverFilterLoose("<=", node.matches[1])
1145
+ var result []string
1146
+ for _, version := range ctx.data.nodeVersions {
1147
+ if lower(version) && upper(version) {
1148
+ result = append(result, "node "+version)
1149
+ }
1150
+ }
1151
+ return result, nil
1152
+ },
1153
+ },
1154
+ {
1155
+ name: "browser_range",
1156
+ re: regexp.MustCompile(`(?i)^(\w+)\s+([\d.]+)\s*-\s*([\d.]+)$`),
1157
+ selectFn: func(ctx *browserslistContext, node *browserslistQueryNode) ([]string, error) {
1158
+ data, err := browserslistCheckName(ctx, node.matches[0])
1159
+ if err != nil {
1160
+ return nil, err
1161
+ }
1162
+ fromVersion := browserslistNormalizeVersion(ctx, data, node.matches[1])
1163
+ if fromVersion == "" {
1164
+ fromVersion = node.matches[1]
1165
+ }
1166
+ toVersion := browserslistNormalizeVersion(ctx, data, node.matches[2])
1167
+ if toVersion == "" {
1168
+ toVersion = node.matches[2]
1169
+ }
1170
+ from := jsParseFloat(fromVersion)
1171
+ to := jsParseFloat(toVersion)
1172
+ var result []string
1173
+ for _, version := range data.released {
1174
+ parsed := jsParseFloat(version)
1175
+ if parsed >= from && parsed <= to {
1176
+ result = append(result, data.name+" "+version)
1177
+ }
1178
+ }
1179
+ return result, nil
1180
+ },
1181
+ },
1182
+ {
1183
+ name: "electron_ray",
1184
+ re: regexp.MustCompile(`(?i)^electron\s*(>=?|<=?)\s*([\d.]+)$`),
1185
+ selectFn: func(ctx *browserslistContext, node *browserslistQueryNode) ([]string, error) {
1186
+ version := browserslistNormalizeElectron(node.matches[1])
1187
+ filter := browserslistFloatFilter(node.matches[0], version)
1188
+ var result []string
1189
+ for _, candidate := range ctx.data.e2cOrder {
1190
+ if filter(candidate) {
1191
+ result = append(result, "chrome "+ctx.data.e2c[candidate])
1192
+ }
1193
+ }
1194
+ return result, nil
1195
+ },
1196
+ },
1197
+ {
1198
+ name: "node_ray",
1199
+ re: regexp.MustCompile(`(?i)^node\s*(>=?|<=?)\s*([\d.]+)$`),
1200
+ selectFn: func(ctx *browserslistContext, node *browserslistQueryNode) ([]string, error) {
1201
+ filter := browserslistSemverFilter(node.matches[0], node.matches[1])
1202
+ var result []string
1203
+ for _, version := range ctx.data.nodeVersions {
1204
+ if filter(version) {
1205
+ result = append(result, "node "+version)
1206
+ }
1207
+ }
1208
+ return result, nil
1209
+ },
1210
+ },
1211
+ {
1212
+ name: "browser_ray",
1213
+ re: regexp.MustCompile(`(?i)^(\w+)\s*(>=?|<=?)\s*([\d.]+|esr)$`),
1214
+ selectFn: func(ctx *browserslistContext, node *browserslistQueryNode) ([]string, error) {
1215
+ version := node.matches[2]
1216
+ data, err := browserslistCheckName(ctx, node.matches[0])
1217
+ if err != nil {
1218
+ return nil, err
1219
+ }
1220
+ if alias := ctx.data.versionAliases[data.name][strings.ToLower(version)]; alias != "" {
1221
+ version = alias
1222
+ }
1223
+ if !browserslistHasDigitOrDot(version) {
1224
+ return nil, newBrowserslistError("Unknown version %s of %s", version, node.matches[0])
1225
+ }
1226
+ filter := browserslistFloatFilter(node.matches[1], version)
1227
+ var result []string
1228
+ for _, candidate := range data.released {
1229
+ if filter(candidate) {
1230
+ result = append(result, data.name+" "+candidate)
1231
+ }
1232
+ }
1233
+ return result, nil
1234
+ },
1235
+ },
1236
+ {
1237
+ name: "firefox_esr",
1238
+ re: regexp.MustCompile(`(?i)^(firefox|ff|fx)\s+esr$`),
1239
+ selectFn: func(ctx *browserslistContext, node *browserslistQueryNode) ([]string, error) {
1240
+ return []string{"firefox " + ctx.data.constants.FirefoxEsr}, nil
1241
+ },
1242
+ },
1243
+ {
1244
+ name: "opera_mini_all",
1245
+ re: regexp.MustCompile(`(?i)(operamini|op_mini)\s+all`),
1246
+ selectFn: func(ctx *browserslistContext, node *browserslistQueryNode) ([]string, error) {
1247
+ return []string{"op_mini all"}, nil
1248
+ },
1249
+ },
1250
+ {
1251
+ name: "electron_version",
1252
+ re: regexp.MustCompile(`(?i)^electron\s+([\d.]+)$`),
1253
+ selectFn: func(ctx *browserslistContext, node *browserslistQueryNode) ([]string, error) {
1254
+ version := browserslistNormalizeElectron(node.matches[0])
1255
+ chrome, ok := ctx.data.e2c[version]
1256
+ if !ok {
1257
+ return nil, newBrowserslistError("Unknown version %s of electron", node.matches[0])
1258
+ }
1259
+ return []string{"chrome " + chrome}, nil
1260
+ },
1261
+ },
1262
+ {
1263
+ name: "node_major_version",
1264
+ re: regexp.MustCompile(`(?i)^node\s+(\d+)$`),
1265
+ selectFn: browserslistNodeQuery,
1266
+ },
1267
+ {
1268
+ name: "node_minor_version",
1269
+ re: regexp.MustCompile(`(?i)^node\s+(\d+\.\d+)$`),
1270
+ selectFn: browserslistNodeQuery,
1271
+ },
1272
+ {
1273
+ name: "node_patch_version",
1274
+ re: regexp.MustCompile(`(?i)^node\s+(\d+\.\d+\.\d+)$`),
1275
+ selectFn: browserslistNodeQuery,
1276
+ },
1277
+ {
1278
+ name: "current_node",
1279
+ re: regexp.MustCompile(`(?i)^current\s+node$`),
1280
+ selectFn: func(ctx *browserslistContext, node *browserslistQueryNode) ([]string, error) {
1281
+ return nil, newBrowserslistError(
1282
+ "`current node` is not supported by the ttsc browserslist port: the lint host does not run inside Node.js")
1283
+ },
1284
+ },
1285
+ {
1286
+ name: "maintained_node",
1287
+ re: regexp.MustCompile(`(?i)^maintained\s+node\s+versions$`),
1288
+ selectFn: func(ctx *browserslistContext, node *browserslistQueryNode) ([]string, error) {
1289
+ now := ctx.clock().UnixMilli()
1290
+ var queries []string
1291
+ for key, schedule := range ctx.data.schedule {
1292
+ start, startErr := browserslistParseScheduleDate(schedule.Start)
1293
+ end, endErr := browserslistParseScheduleDate(schedule.End)
1294
+ if startErr != nil || endErr != nil {
1295
+ continue
1296
+ }
1297
+ if now < end && now > start && browserslistIsEolReleased(ctx, key) {
1298
+ queries = append(queries, "node "+strings.TrimPrefix(key, "v"))
1299
+ }
1300
+ }
1301
+ return browserslistResolveNodes(browserslistParseQueries(queries), ctx)
1302
+ },
1303
+ },
1304
+ {
1305
+ name: "phantomjs_1_9",
1306
+ re: regexp.MustCompile(`(?i)^phantomjs\s+1.9$`),
1307
+ selectFn: func(ctx *browserslistContext, node *browserslistQueryNode) ([]string, error) {
1308
+ return []string{"safari 5"}, nil
1309
+ },
1310
+ },
1311
+ {
1312
+ name: "phantomjs_2_1",
1313
+ re: regexp.MustCompile(`(?i)^phantomjs\s+2.1$`),
1314
+ selectFn: func(ctx *browserslistContext, node *browserslistQueryNode) ([]string, error) {
1315
+ return []string{"safari 6"}, nil
1316
+ },
1317
+ },
1318
+ {
1319
+ name: "browser_version",
1320
+ re: regexp.MustCompile(`(?i)^(\w+)\s+(tp|[\d.]+)$`),
1321
+ selectFn: func(ctx *browserslistContext, node *browserslistQueryNode) ([]string, error) {
1322
+ version := node.matches[1]
1323
+ if strings.EqualFold(version, "tp") {
1324
+ version = "TP"
1325
+ }
1326
+ data, err := browserslistCheckName(ctx, node.matches[0])
1327
+ if err != nil {
1328
+ return nil, err
1329
+ }
1330
+ alias := browserslistNormalizeVersion(ctx, data, version)
1331
+ if alias != "" {
1332
+ version = alias
1333
+ } else {
1334
+ if !strings.Contains(version, ".") {
1335
+ alias = version + ".0"
1336
+ } else {
1337
+ alias = strings.TrimSuffix(version, ".0")
1338
+ }
1339
+ alias = browserslistNormalizeVersion(ctx, data, alias)
1340
+ if alias == "" {
1341
+ return nil, newBrowserslistError(
1342
+ "Unknown version %s of %s", version, node.matches[0])
1343
+ }
1344
+ version = alias
1345
+ }
1346
+ return []string{data.name + " " + version}, nil
1347
+ },
1348
+ },
1349
+ {
1350
+ name: "browserslist_config",
1351
+ re: regexp.MustCompile(`(?i)^browserslist config$`),
1352
+ needsPath: true,
1353
+ selectFn: func(ctx *browserslistContext, node *browserslistQueryNode) ([]string, error) {
1354
+ return browserslistResolve(nil, false, browserslistOpts{
1355
+ path: ctx.path,
1356
+ env: ctx.env,
1357
+ now: ctx.now,
1358
+ })
1359
+ },
1360
+ },
1361
+ {
1362
+ name: "extends",
1363
+ re: regexp.MustCompile(`(?i)^extends (.+)$`),
1364
+ needsPath: true,
1365
+ selectFn: func(ctx *browserslistContext, node *browserslistQueryNode) ([]string, error) {
1366
+ return nil, newBrowserslistError(
1367
+ "`extends` queries are not supported by the ttsc browserslist port: shareable configs are JavaScript modules")
1368
+ },
1369
+ },
1370
+ {
1371
+ name: "defaults",
1372
+ re: regexp.MustCompile(`(?i)^defaults$`),
1373
+ selectFn: func(ctx *browserslistContext, node *browserslistQueryNode) ([]string, error) {
1374
+ return browserslistResolveNodes(
1375
+ browserslistParseQueries(ctx.data.constants.Defaults), ctx)
1376
+ },
1377
+ },
1378
+ {
1379
+ name: "dead",
1380
+ re: regexp.MustCompile(`(?i)^dead$`),
1381
+ selectFn: func(ctx *browserslistContext, node *browserslistQueryNode) ([]string, error) {
1382
+ return browserslistResolveNodes(
1383
+ browserslistParseQueries(ctx.data.constants.Dead), ctx)
1384
+ },
1385
+ },
1386
+ {
1387
+ name: "unknown",
1388
+ re: regexp.MustCompile(`(?i)^(\w+)$`),
1389
+ selectFn: func(ctx *browserslistContext, node *browserslistQueryNode) ([]string, error) {
1390
+ return nil, browserslistUnknownQueryError(ctx, node)
1391
+ },
1392
+ },
1393
+ }
1394
+ }
1395
+
1396
+ func browserslistLastSlice(list []string, count int) []string {
1397
+ if count >= len(list) {
1398
+ return list
1399
+ }
1400
+ if count <= 0 {
1401
+ // JS slice(-0) returns the whole array; negative counts cannot occur
1402
+ // because the capture is \d+.
1403
+ return list
1404
+ }
1405
+ return list[len(list)-count:]
1406
+ }
1407
+
1408
+ func browserslistContains(list []string, item string) bool {
1409
+ for _, candidate := range list {
1410
+ if candidate == item {
1411
+ return true
1412
+ }
1413
+ }
1414
+ return false
1415
+ }
1416
+
1417
+ var browserslistDigitOrDotPattern = regexp.MustCompile(`[\d.]+`)
1418
+
1419
+ func browserslistHasDigitOrDot(version string) bool {
1420
+ return browserslistDigitOrDotPattern.MatchString(version)
1421
+ }
1422
+
1423
+ func browserslistParseScheduleDate(value string) (int64, error) {
1424
+ parsed, err := time.Parse("2006-01-02", value)
1425
+ if err != nil {
1426
+ return 0, err
1427
+ }
1428
+ return parsed.UnixMilli(), nil
1429
+ }
1430
+
1431
+ func browserslistIsEolReleased(ctx *browserslistContext, name string) bool {
1432
+ version := strings.TrimPrefix(name, "v")
1433
+ for _, candidate := range ctx.data.nodeVersions {
1434
+ if browserslistIsVersionsMatch(candidate, version) {
1435
+ return true
1436
+ }
1437
+ }
1438
+ return false
1439
+ }