@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.
- package/README.md +125 -43
- package/go.mod +5 -4
- package/internal/rulecode/rulecode.go +88 -0
- package/lib/index.d.ts +10 -1
- package/lib/index.js +45 -17
- package/lib/index.js.map +1 -1
- package/lib/structures/ITtscLintConfig.d.ts +3 -0
- package/lib/structures/TtscLintRuleSetting.d.ts +3 -2
- package/lib/structures/format/ITtscLintFormat.d.ts +3 -2
- package/lib/structures/format/ITtscLintFormatSortImports.d.ts +20 -4
- package/lib/structures/rules/ITtscLintBoundariesRuleOptions.d.ts +80 -8
- package/lib/structures/rules/ITtscLintBoundariesRules.d.ts +6 -8
- package/lib/structures/rules/ITtscLintContributorRules.d.ts +18 -2
- package/lib/structures/rules/ITtscLintCoreRuleOptions.d.ts +347 -0
- package/lib/structures/rules/ITtscLintCoreRuleOptions.js +3 -0
- package/lib/structures/rules/ITtscLintCoreRuleOptions.js.map +1 -0
- package/lib/structures/rules/ITtscLintCoreRules.d.ts +101 -67
- package/lib/structures/rules/ITtscLintRegexpRules.d.ts +7 -2
- package/lib/structures/rules/ITtscLintRuleOptionsMap.d.ts +53 -7
- package/lib/structures/rules/ITtscLintRules.d.ts +6 -2
- package/lib/structures/rules/ITtscLintSolidRules.d.ts +0 -8
- package/lib/structures/rules/ITtscLintTypeScriptRuleOptions.d.ts +206 -0
- package/lib/structures/rules/ITtscLintTypeScriptRuleOptions.js +8 -0
- package/lib/structures/rules/ITtscLintTypeScriptRuleOptions.js.map +1 -0
- package/lib/structures/rules/ITtscLintTypeScriptRules.d.ts +55 -42
- package/lib/structures/rules/ITtscLintUnicornRuleOptions.d.ts +276 -0
- package/lib/structures/rules/ITtscLintUnicornRuleOptions.js +3 -0
- package/lib/structures/rules/ITtscLintUnicornRuleOptions.js.map +1 -0
- package/lib/structures/rules/ITtscLintUnicornRules.d.ts +44 -36
- package/lib/structures/rules/index.d.ts +3 -0
- package/lib/structures/rules/index.js +3 -0
- package/lib/structures/rules/index.js.map +1 -1
- package/linthost/ast_helpers.go +521 -23
- package/linthost/ast_selector_matcher.go +1628 -0
- package/linthost/ast_selector_parser.go +628 -0
- package/linthost/comment_scan.go +154 -0
- package/linthost/compile.go +129 -79
- package/linthost/config.go +352 -88
- package/linthost/config_format.go +15 -1
- package/linthost/contrib_adapter.go +47 -8
- package/linthost/declaration_rules.go +12 -11
- package/linthost/directives.go +9 -67
- package/linthost/dispatch.go +37 -3
- package/linthost/engine.go +368 -53
- package/linthost/escape_string.go +77 -0
- package/linthost/fix.go +133 -23
- package/linthost/flags_gen.go +18 -17
- package/linthost/format.go +73 -167
- package/linthost/format_editor_settings.go +134 -26
- package/linthost/grapheme_tables_gen.go +1920 -0
- package/linthost/host.go +159 -61
- package/linthost/literal_escape_scan.go +117 -0
- package/linthost/lsp.go +545 -98
- package/linthost/numeric_literal_precision.go +228 -0
- package/linthost/polyfill_browserslist.go +1439 -0
- package/linthost/polyfill_browserslist_config.go +603 -0
- package/linthost/polyfill_corejs.go +750 -0
- package/linthost/polyfill_data.go +113 -0
- package/linthost/polyfill_data_gen.json +39868 -0
- package/linthost/print_nodes_array.go +7 -1
- package/linthost/print_nodes_object.go +7 -1
- package/linthost/project_engine.go +231 -0
- package/linthost/project_rules.go +78 -0
- package/linthost/regex_clean.go +191 -0
- package/linthost/regex_tree.go +1544 -0
- package/linthost/regex_tree_optimizer.go +1685 -0
- package/linthost/regexp_cache.go +44 -0
- package/linthost/rule_codes.go +105 -0
- package/linthost/rule_codes.json +745 -0
- package/linthost/rules_ban_ts_comment.go +312 -0
- package/linthost/rules_boundaries.go +128 -43
- package/linthost/rules_boundaries_dependencies.go +1013 -0
- package/linthost/rules_core_extra.go +62 -92
- package/linthost/rules_cypress.go +1 -1
- package/linthost/rules_default_case.go +87 -11
- package/linthost/rules_dupes.go +38 -13
- package/linthost/rules_empty.go +188 -20
- package/linthost/rules_escape.go +62 -48
- package/linthost/rules_format_arrow_parens.go +19 -5
- package/linthost/rules_format_bracket_spacing.go +1 -1
- package/linthost/rules_format_clause_join.go +1 -1
- package/linthost/rules_format_declaration_header.go +31 -19
- package/linthost/rules_format_indent.go +1 -1
- package/linthost/rules_format_jsdoc.go +1 -1
- package/linthost/rules_format_orphan_semi.go +1 -1
- package/linthost/rules_format_parameter_properties.go +17 -7
- package/linthost/rules_format_print_width.go +1 -1
- package/linthost/rules_format_quote_props.go +1 -1
- package/linthost/rules_format_quotes.go +1 -1
- package/linthost/rules_format_semi.go +106 -79
- package/linthost/rules_format_sort_imports.go +79 -45
- package/linthost/rules_format_statement_split.go +1 -1
- package/linthost/rules_format_ternary_nullish_parens.go +1 -1
- package/linthost/rules_format_trailing_comma.go +64 -1
- package/linthost/rules_format_whitespace.go +1 -1
- package/linthost/rules_functional.go +21 -22
- package/linthost/rules_gap.go +80 -3
- package/linthost/rules_grouped_accessor_pairs.go +84 -25
- package/linthost/rules_guard_for_in.go +64 -147
- package/linthost/rules_jsx_a11y.go +232 -22
- package/linthost/rules_logic.go +84 -38
- package/linthost/rules_nextjs.go +105 -16
- package/linthost/rules_no_class_assign.go +70 -0
- package/linthost/rules_no_duplicate_imports.go +266 -0
- package/linthost/rules_no_else_return.go +156 -38
- package/linthost/rules_no_extend_native.go +113 -17
- package/linthost/rules_no_fallthrough.go +1013 -0
- package/linthost/rules_no_func_assign.go +70 -0
- package/linthost/rules_no_import_assign.go +278 -86
- package/linthost/rules_no_inner_declarations.go +186 -0
- package/linthost/rules_no_loop_func.go +438 -53
- package/linthost/rules_no_mixed_operators.go +208 -52
- package/linthost/rules_no_param_reassign.go +376 -61
- package/linthost/rules_no_promise_executor_return.go +156 -0
- package/linthost/rules_no_restricted_imports.go +1013 -57
- package/linthost/rules_no_restricted_syntax.go +190 -26
- package/linthost/rules_no_useless_constructor.go +6 -36
- package/linthost/rules_problems.go +154 -311
- package/linthost/rules_promise.go +2578 -109
- package/linthost/rules_react_extras.go +71 -45
- package/linthost/rules_react_hooks.go +18 -2
- package/linthost/rules_react_perf.go +1 -0
- package/linthost/rules_react_refresh.go +1 -1
- package/linthost/rules_regexp.go +230 -14
- package/linthost/rules_security.go +45 -19
- package/linthost/rules_self.go +21 -1
- package/linthost/rules_solid.go +13 -7
- package/linthost/rules_storybook.go +1 -1
- package/linthost/rules_suggestions.go +490 -84
- package/linthost/rules_testing_library.go +12 -1
- package/linthost/rules_ts.go +227 -54
- package/linthost/rules_ts_async.go +0 -131
- package/linthost/rules_ts_no_base_to_string.go +3 -4
- package/linthost/rules_ts_no_misused_promises.go +817 -0
- package/linthost/rules_ts_no_restricted_types.go +320 -34
- package/linthost/rules_ts_no_unnecessary_qualifier.go +132 -55
- package/linthost/rules_ts_no_unsafe_assignment.go +608 -60
- package/linthost/rules_ts_require_array_sort_compare.go +5 -5
- package/linthost/rules_ts_switch_exhaustiveness_check.go +556 -86
- package/linthost/rules_unicorn_better_regex.go +224 -16
- package/linthost/rules_unicorn_consistent_destructuring.go +1002 -7
- package/linthost/rules_unicorn_consistent_existence_index_check.go +208 -29
- package/linthost/rules_unicorn_consistent_function_scoping.go +1041 -8
- package/linthost/rules_unicorn_consistent_template_literal_escape.go +178 -7
- package/linthost/rules_unicorn_escape_case.go +30 -19
- package/linthost/rules_unicorn_filename_case.go +763 -16
- package/linthost/rules_unicorn_import_style.go +783 -17
- package/linthost/rules_unicorn_isolated_functions.go +934 -14
- package/linthost/rules_unicorn_no_hex_escape.go +26 -11
- package/linthost/rules_unicorn_no_typeof_undefined.go +234 -25
- package/linthost/rules_unicorn_no_unnecessary_polyfills.go +376 -21
- package/linthost/rules_unicorn_no_unused_properties.go +756 -19
- package/linthost/rules_unicorn_number_literal_case.go +58 -39
- package/linthost/rules_unicorn_prefer_at.go +7 -3
- package/linthost/rules_unicorn_prefer_number_properties.go +381 -48
- package/linthost/rules_unicorn_prefer_optional_catch_binding.go +24 -24
- package/linthost/rules_unicorn_prefer_simple_condition_first.go +597 -19
- package/linthost/rules_unicorn_prefer_string_raw.go +147 -16
- package/linthost/rules_unicorn_prevent_abbreviations.go +1963 -95
- package/linthost/rules_unicorn_string_content.go +611 -15
- package/linthost/rules_unicorn_switch_case_break_position.go +172 -15
- package/linthost/rules_unicorn_template_indent.go +923 -18
- package/linthost/rules_unicorn_text_encoding_identifier_case.go +202 -42
- package/linthost/rules_unicorn_throw_new_error.go +218 -26
- package/linthost/rules_var.go +829 -142
- package/linthost/string_length.go +222 -0
- package/package.json +4 -3
- package/rule/project.go +206 -0
- package/rule/rule.go +58 -1
- package/src/index.ts +58 -17
- package/src/structures/ITtscLintConfig.ts +3 -0
- package/src/structures/TtscLintRuleSetting.ts +3 -2
- package/src/structures/format/ITtscLintFormat.ts +3 -2
- package/src/structures/format/ITtscLintFormatSortImports.ts +21 -4
- package/src/structures/rules/ITtscLintBoundariesRuleOptions.ts +122 -8
- package/src/structures/rules/ITtscLintBoundariesRules.ts +6 -8
- package/src/structures/rules/ITtscLintContributorRules.ts +18 -2
- package/src/structures/rules/ITtscLintCoreRuleOptions.ts +465 -0
- package/src/structures/rules/ITtscLintCoreRules.ts +122 -67
- package/src/structures/rules/ITtscLintRegexpRules.ts +7 -2
- package/src/structures/rules/ITtscLintRuleOptionsMap.ts +88 -7
- package/src/structures/rules/ITtscLintRules.ts +7 -2
- package/src/structures/rules/ITtscLintSolidRules.ts +0 -9
- package/src/structures/rules/ITtscLintTypeScriptRuleOptions.ts +248 -0
- package/src/structures/rules/ITtscLintTypeScriptRules.ts +64 -42
- package/src/structures/rules/ITtscLintUnicornRuleOptions.ts +350 -0
- package/src/structures/rules/ITtscLintUnicornRules.ts +60 -36
- 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
|
+
}
|