UNPKG

33.8 kBJavaScriptView Raw
1var jsReleases = require('node-releases/data/processed/envs.json')
2var agents = require('caniuse-lite/dist/unpacker/agents').agents
3var jsEOL = require('node-releases/data/release-schedule/release-schedule.json')
4var path = require('path')
5var e2c = require('electron-to-chromium/versions')
6
7var BrowserslistError = require('./error')
8var env = require('./node') // Will load browser.js in webpack
9
10var YEAR = 365.259641 * 24 * 60 * 60 * 1000
11var ANDROID_EVERGREEN_FIRST = 37
12
13var QUERY_OR = 1
14var QUERY_AND = 2
15
16function isVersionsMatch (versionA, versionB) {
17 return (versionA + '.').indexOf(versionB + '.') === 0
18}
19
20function isEolReleased (name) {
21 var version = name.slice(1)
22 return jsReleases.some(function (i) {
23 return isVersionsMatch(i.version, version)
24 })
25}
26
27function normalize (versions) {
28 return versions.filter(function (version) {
29 return typeof version === 'string'
30 })
31}
32
33function normalizeElectron (version) {
34 var versionToUse = version
35 if (version.split('.').length === 3) {
36 versionToUse = version
37 .split('.')
38 .slice(0, -1)
39 .join('.')
40 }
41 return versionToUse
42}
43
44function nameMapper (name) {
45 return function mapName (version) {
46 return name + ' ' + version
47 }
48}
49
50function getMajor (version) {
51 return parseInt(version.split('.')[0])
52}
53
54function getMajorVersions (released, number) {
55 if (released.length === 0) return []
56 var majorVersions = uniq(released.map(getMajor))
57 var minimum = majorVersions[majorVersions.length - number]
58 if (!minimum) {
59 return released
60 }
61 var selected = []
62 for (var i = released.length - 1; i >= 0; i--) {
63 if (minimum > getMajor(released[i])) break
64 selected.unshift(released[i])
65 }
66 return selected
67}
68
69function uniq (array) {
70 var filtered = []
71 for (var i = 0; i < array.length; i++) {
72 if (filtered.indexOf(array[i]) === -1) filtered.push(array[i])
73 }
74 return filtered
75}
76
77// Helpers
78
79function fillUsage (result, name, data) {
80 for (var i in data) {
81 result[name + ' ' + i] = data[i]
82 }
83}
84
85function generateFilter (sign, version) {
86 version = parseFloat(version)
87 if (sign === '>') {
88 return function (v) {
89 return parseFloat(v) > version
90 }
91 } else if (sign === '>=') {
92 return function (v) {
93 return parseFloat(v) >= version
94 }
95 } else if (sign === '<') {
96 return function (v) {
97 return parseFloat(v) < version
98 }
99 } else {
100 return function (v) {
101 return parseFloat(v) <= version
102 }
103 }
104}
105
106function generateSemverFilter (sign, version) {
107 version = version.split('.').map(parseSimpleInt)
108 version[1] = version[1] || 0
109 version[2] = version[2] || 0
110 if (sign === '>') {
111 return function (v) {
112 v = v.split('.').map(parseSimpleInt)
113 return compareSemver(v, version) > 0
114 }
115 } else if (sign === '>=') {
116 return function (v) {
117 v = v.split('.').map(parseSimpleInt)
118 return compareSemver(v, version) >= 0
119 }
120 } else if (sign === '<') {
121 return function (v) {
122 v = v.split('.').map(parseSimpleInt)
123 return compareSemver(version, v) > 0
124 }
125 } else {
126 return function (v) {
127 v = v.split('.').map(parseSimpleInt)
128 return compareSemver(version, v) >= 0
129 }
130 }
131}
132
133function parseSimpleInt (x) {
134 return parseInt(x)
135}
136
137function compare (a, b) {
138 if (a < b) return -1
139 if (a > b) return +1
140 return 0
141}
142
143function compareSemver (a, b) {
144 return (
145 compare(parseInt(a[0]), parseInt(b[0])) ||
146 compare(parseInt(a[1] || '0'), parseInt(b[1] || '0')) ||
147 compare(parseInt(a[2] || '0'), parseInt(b[2] || '0'))
148 )
149}
150
151// this follows the npm-like semver behavior
152function semverFilterLoose (operator, range) {
153 range = range.split('.').map(parseSimpleInt)
154 if (typeof range[1] === 'undefined') {
155 range[1] = 'x'
156 }
157 // ignore any patch version because we only return minor versions
158 // range[2] = 'x'
159 switch (operator) {
160 case '<=':
161 return function (version) {
162 version = version.split('.').map(parseSimpleInt)
163 return compareSemverLoose(version, range) <= 0
164 }
165 default:
166 case '>=':
167 return function (version) {
168 version = version.split('.').map(parseSimpleInt)
169 return compareSemverLoose(version, range) >= 0
170 }
171 }
172}
173
174// this follows the npm-like semver behavior
175function compareSemverLoose (version, range) {
176 if (version[0] !== range[0]) {
177 return version[0] < range[0] ? -1 : +1
178 }
179 if (range[1] === 'x') {
180 return 0
181 }
182 if (version[1] !== range[1]) {
183 return version[1] < range[1] ? -1 : +1
184 }
185 return 0
186}
187
188function resolveVersion (data, version) {
189 if (data.versions.indexOf(version) !== -1) {
190 return version
191 } else if (browserslist.versionAliases[data.name][version]) {
192 return browserslist.versionAliases[data.name][version]
193 } else {
194 return false
195 }
196}
197
198function normalizeVersion (data, version) {
199 var resolved = resolveVersion(data, version)
200 if (resolved) {
201 return resolved
202 } else if (data.versions.length === 1) {
203 return data.versions[0]
204 } else {
205 return false
206 }
207}
208
209function filterByYear (since, context) {
210 since = since / 1000
211 return Object.keys(agents).reduce(function (selected, name) {
212 var data = byName(name, context)
213 if (!data) return selected
214 var versions = Object.keys(data.releaseDate).filter(function (v) {
215 return data.releaseDate[v] >= since
216 })
217 return selected.concat(versions.map(nameMapper(data.name)))
218 }, [])
219}
220
221function cloneData (data) {
222 return {
223 name: data.name,
224 versions: data.versions,
225 released: data.released,
226 releaseDate: data.releaseDate
227 }
228}
229
230function mapVersions (data, map) {
231 data.versions = data.versions.map(function (i) {
232 return map[i] || i
233 })
234 data.released = data.versions.map(function (i) {
235 return map[i] || i
236 })
237 var fixedDate = { }
238 for (var i in data.releaseDate) {
239 fixedDate[map[i] || i] = data.releaseDate[i]
240 }
241 data.releaseDate = fixedDate
242 return data
243}
244
245function byName (name, context) {
246 name = name.toLowerCase()
247 name = browserslist.aliases[name] || name
248 if (context.mobileToDesktop && browserslist.desktopNames[name]) {
249 var desktop = browserslist.data[browserslist.desktopNames[name]]
250 if (name === 'android') {
251 return normalizeAndroidData(cloneData(browserslist.data[name]), desktop)
252 } else {
253 var cloned = cloneData(desktop)
254 cloned.name = name
255 if (name === 'op_mob') {
256 cloned = mapVersions(cloned, { '10.0-10.1': '10' })
257 }
258 return cloned
259 }
260 }
261 return browserslist.data[name]
262}
263
264function normalizeAndroidVersions (androidVersions, chromeVersions) {
265 var firstEvergreen = ANDROID_EVERGREEN_FIRST
266 var last = chromeVersions[chromeVersions.length - 1]
267 return androidVersions
268 .filter(function (version) { return /^(?:[2-4]\.|[34]$)/.test(version) })
269 .concat(chromeVersions.slice(firstEvergreen - last - 1))
270}
271
272function normalizeAndroidData (android, chrome) {
273 android.released = normalizeAndroidVersions(android.released, chrome.released)
274 android.versions = normalizeAndroidVersions(android.versions, chrome.versions)
275 return android
276}
277
278function checkName (name, context) {
279 var data = byName(name, context)
280 if (!data) throw new BrowserslistError('Unknown browser ' + name)
281 return data
282}
283
284function unknownQuery (query) {
285 return new BrowserslistError(
286 'Unknown browser query `' + query + '`. ' +
287 'Maybe you are using old Browserslist or made typo in query.'
288 )
289}
290
291function filterAndroid (list, versions, context) {
292 if (context.mobileToDesktop) return list
293 var released = browserslist.data.android.released
294 var last = released[released.length - 1]
295 var diff = last - ANDROID_EVERGREEN_FIRST - versions
296 if (diff > 0) {
297 return list.slice(-1)
298 } else {
299 return list.slice(diff - 1)
300 }
301}
302
303/**
304 * Resolves queries into a browser list.
305 * @param {string|string[]} queries Queries to combine.
306 * Either an array of queries or a long string of queries.
307 * @param {object} [context] Optional arguments to
308 * the select function in `queries`.
309 * @returns {string[]} A list of browsers
310 */
311function resolve (queries, context) {
312 if (Array.isArray(queries)) {
313 queries = flatten(queries.map(parse))
314 } else {
315 queries = parse(queries)
316 }
317
318 return queries.reduce(function (result, query, index) {
319 var selection = query.queryString
320
321 var isExclude = selection.indexOf('not ') === 0
322 if (isExclude) {
323 if (index === 0) {
324 throw new BrowserslistError(
325 'Write any browsers query (for instance, `defaults`) ' +
326 'before `' + selection + '`')
327 }
328 selection = selection.slice(4)
329 }
330
331 for (var i = 0; i < QUERIES.length; i++) {
332 var type = QUERIES[i]
333 var match = selection.match(type.regexp)
334 if (match) {
335 var args = [context].concat(match.slice(1))
336 var array = type.select.apply(browserslist, args).map(function (j) {
337 var parts = j.split(' ')
338 if (parts[1] === '0') {
339 return parts[0] + ' ' + byName(parts[0], context).versions[0]
340 } else {
341 return j
342 }
343 })
344
345 switch (query.type) {
346 case QUERY_AND:
347 if (isExclude) {
348 return result.filter(function (j) {
349 return array.indexOf(j) === -1
350 })
351 } else {
352 return result.filter(function (j) {
353 return array.indexOf(j) !== -1
354 })
355 }
356 case QUERY_OR:
357 default:
358 if (isExclude) {
359 var filter = { }
360 array.forEach(function (j) {
361 filter[j] = true
362 })
363 return result.filter(function (j) {
364 return !filter[j]
365 })
366 }
367 return result.concat(array)
368 }
369 }
370 }
371
372 throw unknownQuery(selection)
373 }, [])
374}
375
376var cache = { }
377
378/**
379 * Return array of browsers by selection queries.
380 *
381 * @param {(string|string[])} [queries=browserslist.defaults] Browser queries.
382 * @param {object} [opts] Options.
383 * @param {string} [opts.path="."] Path to processed file.
384 * It will be used to find config files.
385 * @param {string} [opts.env="production"] Processing environment.
386 * It will be used to take right
387 * queries from config file.
388 * @param {string} [opts.config] Path to config file with queries.
389 * @param {object} [opts.stats] Custom browser usage statistics
390 * for "> 1% in my stats" query.
391 * @param {boolean} [opts.ignoreUnknownVersions=false] Do not throw on unknown
392 * version in direct query.
393 * @param {boolean} [opts.dangerousExtend] Disable security checks
394 * for extend query.
395 * @param {boolean} [opts.mobileToDesktop] Alias mobile browsers to the desktop
396 * version when Can I Use doesn't have
397 * data about the specified version.
398 * @returns {string[]} Array with browser names in Can I Use.
399 *
400 * @example
401 * browserslist('IE >= 10, IE 8') //=> ['ie 11', 'ie 10', 'ie 8']
402 */
403function browserslist (queries, opts) {
404 if (typeof opts === 'undefined') opts = { }
405
406 if (typeof opts.path === 'undefined') {
407 opts.path = path.resolve ? path.resolve('.') : '.'
408 }
409
410 if (typeof queries === 'undefined' || queries === null) {
411 var config = browserslist.loadConfig(opts)
412 if (config) {
413 queries = config
414 } else {
415 queries = browserslist.defaults
416 }
417 }
418
419 if (!(typeof queries === 'string' || Array.isArray(queries))) {
420 throw new BrowserslistError(
421 'Browser queries must be an array or string. Got ' + typeof queries + '.')
422 }
423
424 var context = {
425 ignoreUnknownVersions: opts.ignoreUnknownVersions,
426 dangerousExtend: opts.dangerousExtend,
427 mobileToDesktop: opts.mobileToDesktop,
428 path: opts.path,
429 env: opts.env
430 }
431
432 env.oldDataWarning(browserslist.data)
433 var stats = env.getStat(opts, browserslist.data)
434 if (stats) {
435 context.customUsage = { }
436 for (var browser in stats) {
437 fillUsage(context.customUsage, browser, stats[browser])
438 }
439 }
440
441 var cacheKey = JSON.stringify([queries, context])
442 if (cache[cacheKey]) return cache[cacheKey]
443
444 var result = uniq(resolve(queries, context)).sort(function (name1, name2) {
445 name1 = name1.split(' ')
446 name2 = name2.split(' ')
447 if (name1[0] === name2[0]) {
448 // assumptions on caniuse data
449 // 1) version ranges never overlaps
450 // 2) if version is not a range, it never contains `-`
451 var version1 = name1[1].split('-')[0]
452 var version2 = name2[1].split('-')[0]
453 return compareSemver(version2.split('.'), version1.split('.'))
454 } else {
455 return compare(name1[0], name2[0])
456 }
457 })
458 if (!process.env.BROWSERSLIST_DISABLE_CACHE) {
459 cache[cacheKey] = result
460 }
461 return result
462}
463
464function parse (queries) {
465 var qs = []
466 do {
467 queries = doMatch(queries, qs)
468 } while (queries)
469 return qs
470}
471
472function doMatch (string, qs) {
473 var or = /^(?:,\s*|\s+or\s+)(.*)/i
474 var and = /^\s+and\s+(.*)/i
475
476 return find(string, function (parsed, n, max) {
477 if (and.test(parsed)) {
478 qs.unshift({ type: QUERY_AND, queryString: parsed.match(and)[1] })
479 return true
480 } else if (or.test(parsed)) {
481 qs.unshift({ type: QUERY_OR, queryString: parsed.match(or)[1] })
482 return true
483 } else if (n === max) {
484 qs.unshift({ type: QUERY_OR, queryString: parsed.trim() })
485 return true
486 }
487 return false
488 })
489}
490
491function find (string, predicate) {
492 for (var n = 1, max = string.length; n <= max; n++) {
493 var parsed = string.substr(-n, n)
494 if (predicate(parsed, n, max)) {
495 return string.slice(0, -n)
496 }
497 }
498 return ''
499}
500
501function flatten (array) {
502 if (!Array.isArray(array)) return [array]
503 return array.reduce(function (a, b) {
504 return a.concat(flatten(b))
505 }, [])
506}
507
508// Will be filled by Can I Use data below
509browserslist.cache = { }
510browserslist.data = { }
511browserslist.usage = {
512 global: { },
513 custom: null
514}
515
516// Default browsers query
517browserslist.defaults = [
518 '> 0.5%',
519 'last 2 versions',
520 'Firefox ESR',
521 'not dead'
522]
523
524// Browser names aliases
525browserslist.aliases = {
526 fx: 'firefox',
527 ff: 'firefox',
528 ios: 'ios_saf',
529 explorer: 'ie',
530 blackberry: 'bb',
531 explorermobile: 'ie_mob',
532 operamini: 'op_mini',
533 operamobile: 'op_mob',
534 chromeandroid: 'and_chr',
535 firefoxandroid: 'and_ff',
536 ucandroid: 'and_uc',
537 qqandroid: 'and_qq'
538}
539
540// Can I Use only provides a few versions for some browsers (e.g. and_chr).
541// Fallback to a similar browser for unknown versions
542browserslist.desktopNames = {
543 and_chr: 'chrome',
544 and_ff: 'firefox',
545 ie_mob: 'ie',
546 op_mob: 'opera',
547 android: 'chrome' // has extra processing logic
548}
549
550// Aliases to work with joined versions like `ios_saf 7.0-7.1`
551browserslist.versionAliases = { }
552
553browserslist.clearCaches = env.clearCaches
554browserslist.parseConfig = env.parseConfig
555browserslist.readConfig = env.readConfig
556browserslist.findConfig = env.findConfig
557browserslist.loadConfig = env.loadConfig
558
559/**
560 * Return browsers market coverage.
561 *
562 * @param {string[]} browsers Browsers names in Can I Use.
563 * @param {string|object} [stats="global"] Which statistics should be used.
564 * Country code or custom statistics.
565 * Pass `"my stats"` to load statistics
566 * from Browserslist files.
567 *
568 * @return {number} Total market coverage for all selected browsers.
569 *
570 * @example
571 * browserslist.coverage(browserslist('> 1% in US'), 'US') //=> 83.1
572 */
573browserslist.coverage = function (browsers, stats) {
574 var data
575 if (typeof stats === 'undefined') {
576 data = browserslist.usage.global
577 } else if (stats === 'my stats') {
578 var opts = {}
579 opts.path = path.resolve ? path.resolve('.') : '.'
580 var customStats = env.getStat(opts)
581 if (!customStats) {
582 throw new BrowserslistError('Custom usage statistics was not provided')
583 }
584 data = {}
585 for (var browser in customStats) {
586 fillUsage(data, browser, customStats[browser])
587 }
588 } else if (typeof stats === 'string') {
589 if (stats.length > 2) {
590 stats = stats.toLowerCase()
591 } else {
592 stats = stats.toUpperCase()
593 }
594 env.loadCountry(browserslist.usage, stats, browserslist.data)
595 data = browserslist.usage[stats]
596 } else {
597 if ('dataByBrowser' in stats) {
598 stats = stats.dataByBrowser
599 }
600 data = { }
601 for (var name in stats) {
602 for (var version in stats[name]) {
603 data[name + ' ' + version] = stats[name][version]
604 }
605 }
606 }
607
608 return browsers.reduce(function (all, i) {
609 var usage = data[i]
610 if (usage === undefined) {
611 usage = data[i.replace(/ \S+$/, ' 0')]
612 }
613 return all + (usage || 0)
614 }, 0)
615}
616
617function nodeQuery (context, version) {
618 var nodeReleases = jsReleases.filter(function (i) {
619 return i.name === 'nodejs'
620 })
621 var matched = nodeReleases.filter(function (i) {
622 return isVersionsMatch(i.version, version)
623 })
624 if (matched.length === 0) {
625 if (context.ignoreUnknownVersions) {
626 return []
627 } else {
628 throw new BrowserslistError('Unknown version ' + version + ' of Node.js')
629 }
630 }
631 return ['node ' + matched[matched.length - 1].version]
632}
633
634function sinceQuery (context, year, month, date) {
635 year = parseInt(year)
636 month = parseInt(month || '01') - 1
637 date = parseInt(date || '01')
638 return filterByYear(Date.UTC(year, month, date, 0, 0, 0), context)
639}
640
641function coverQuery (context, coverage, statMode) {
642 coverage = parseFloat(coverage)
643 var usage = browserslist.usage.global
644 if (statMode) {
645 if (statMode.match(/^my\s+stats$/)) {
646 if (!context.customUsage) {
647 throw new BrowserslistError(
648 'Custom usage statistics was not provided'
649 )
650 }
651 usage = context.customUsage
652 } else {
653 var place
654 if (statMode.length === 2) {
655 place = statMode.toUpperCase()
656 } else {
657 place = statMode.toLowerCase()
658 }
659 env.loadCountry(browserslist.usage, place, browserslist.data)
660 usage = browserslist.usage[place]
661 }
662 }
663 var versions = Object.keys(usage).sort(function (a, b) {
664 return usage[b] - usage[a]
665 })
666 var coveraged = 0
667 var result = []
668 var version
669 for (var i = 0; i <= versions.length; i++) {
670 version = versions[i]
671 if (usage[version] === 0) break
672 coveraged += usage[version]
673 result.push(version)
674 if (coveraged >= coverage) break
675 }
676 return result
677}
678
679var QUERIES = [
680 {
681 regexp: /^last\s+(\d+)\s+major\s+versions?$/i,
682 select: function (context, versions) {
683 return Object.keys(agents).reduce(function (selected, name) {
684 var data = byName(name, context)
685 if (!data) return selected
686 var list = getMajorVersions(data.released, versions)
687 list = list.map(nameMapper(data.name))
688 if (data.name === 'android') {
689 list = filterAndroid(list, versions, context)
690 }
691 return selected.concat(list)
692 }, [])
693 }
694 },
695 {
696 regexp: /^last\s+(\d+)\s+versions?$/i,
697 select: function (context, versions) {
698 return Object.keys(agents).reduce(function (selected, name) {
699 var data = byName(name, context)
700 if (!data) return selected
701 var list = data.released.slice(-versions)
702 list = list.map(nameMapper(data.name))
703 if (data.name === 'android') {
704 list = filterAndroid(list, versions, context)
705 }
706 return selected.concat(list)
707 }, [])
708 }
709 },
710 {
711 regexp: /^last\s+(\d+)\s+electron\s+major\s+versions?$/i,
712 select: function (context, versions) {
713 var validVersions = getMajorVersions(Object.keys(e2c), versions)
714 return validVersions.map(function (i) {
715 return 'chrome ' + e2c[i]
716 })
717 }
718 },
719 {
720 regexp: /^last\s+(\d+)\s+(\w+)\s+major\s+versions?$/i,
721 select: function (context, versions, name) {
722 var data = checkName(name, context)
723 var validVersions = getMajorVersions(data.released, versions)
724 var list = validVersions.map(nameMapper(data.name))
725 if (data.name === 'android') {
726 list = filterAndroid(list, versions, context)
727 }
728 return list
729 }
730 },
731 {
732 regexp: /^last\s+(\d+)\s+electron\s+versions?$/i,
733 select: function (context, versions) {
734 return Object.keys(e2c)
735 .slice(-versions)
736 .map(function (i) {
737 return 'chrome ' + e2c[i]
738 })
739 }
740 },
741 {
742 regexp: /^last\s+(\d+)\s+(\w+)\s+versions?$/i,
743 select: function (context, versions, name) {
744 var data = checkName(name, context)
745 var list = data.released.slice(-versions).map(nameMapper(data.name))
746 if (data.name === 'android') {
747 list = filterAndroid(list, versions, context)
748 }
749 return list
750 }
751 },
752 {
753 regexp: /^unreleased\s+versions$/i,
754 select: function (context) {
755 return Object.keys(agents).reduce(function (selected, name) {
756 var data = byName(name, context)
757 if (!data) return selected
758 var list = data.versions.filter(function (v) {
759 return data.released.indexOf(v) === -1
760 })
761 list = list.map(nameMapper(data.name))
762 return selected.concat(list)
763 }, [])
764 }
765 },
766 {
767 regexp: /^unreleased\s+electron\s+versions?$/i,
768 select: function () {
769 return []
770 }
771 },
772 {
773 regexp: /^unreleased\s+(\w+)\s+versions?$/i,
774 select: function (context, name) {
775 var data = checkName(name, context)
776 return data.versions
777 .filter(function (v) {
778 return data.released.indexOf(v) === -1
779 })
780 .map(nameMapper(data.name))
781 }
782 },
783 {
784 regexp: /^last\s+(\d*.?\d+)\s+years?$/i,
785 select: function (context, years) {
786 return filterByYear(Date.now() - YEAR * years, context)
787 }
788 },
789 {
790 regexp: /^since (\d+)$/i,
791 select: sinceQuery
792 },
793 {
794 regexp: /^since (\d+)-(\d+)$/i,
795 select: sinceQuery
796 },
797 {
798 regexp: /^since (\d+)-(\d+)-(\d+)$/i,
799 select: sinceQuery
800 },
801 {
802 regexp: /^(>=?|<=?)\s*(\d+|\d+\.\d+|\.\d+)%$/,
803 select: function (context, sign, popularity) {
804 popularity = parseFloat(popularity)
805 var usage = browserslist.usage.global
806 return Object.keys(usage).reduce(function (result, version) {
807 if (sign === '>') {
808 if (usage[version] > popularity) {
809 result.push(version)
810 }
811 } else if (sign === '<') {
812 if (usage[version] < popularity) {
813 result.push(version)
814 }
815 } else if (sign === '<=') {
816 if (usage[version] <= popularity) {
817 result.push(version)
818 }
819 } else if (usage[version] >= popularity) {
820 result.push(version)
821 }
822 return result
823 }, [])
824 }
825 },
826 {
827 regexp: /^(>=?|<=?)\s*(\d+|\d+\.\d+|\.\d+)%\s+in\s+my\s+stats$/,
828 select: function (context, sign, popularity) {
829 popularity = parseFloat(popularity)
830 if (!context.customUsage) {
831 throw new BrowserslistError('Custom usage statistics was not provided')
832 }
833 var usage = context.customUsage
834 return Object.keys(usage).reduce(function (result, version) {
835 if (sign === '>') {
836 if (usage[version] > popularity) {
837 result.push(version)
838 }
839 } else if (sign === '<') {
840 if (usage[version] < popularity) {
841 result.push(version)
842 }
843 } else if (sign === '<=') {
844 if (usage[version] <= popularity) {
845 result.push(version)
846 }
847 } else if (usage[version] >= popularity) {
848 result.push(version)
849 }
850 return result
851 }, [])
852 }
853 },
854 {
855 regexp: /^(>=?|<=?)\s*(\d+|\d+\.\d+|\.\d+)%\s+in\s+(\S+)\s+stats$/,
856 select: function (context, sign, popularity, name) {
857 popularity = parseFloat(popularity)
858 var stats = env.loadStat(context, name, browserslist.data)
859 if (stats) {
860 context.customUsage = {}
861 for (var browser in stats) {
862 fillUsage(context.customUsage, browser, stats[browser])
863 }
864 }
865 if (!context.customUsage) {
866 throw new BrowserslistError('Custom usage statistics was not provided')
867 }
868 var usage = context.customUsage
869 return Object.keys(usage).reduce(function (result, version) {
870 if (sign === '>') {
871 if (usage[version] > popularity) {
872 result.push(version)
873 }
874 } else if (sign === '<') {
875 if (usage[version] < popularity) {
876 result.push(version)
877 }
878 } else if (sign === '<=') {
879 if (usage[version] <= popularity) {
880 result.push(version)
881 }
882 } else if (usage[version] >= popularity) {
883 result.push(version)
884 }
885 return result
886 }, [])
887 }
888 },
889 {
890 regexp: /^(>=?|<=?)\s*(\d+|\d+\.\d+|\.\d+)%\s+in\s+((alt-)?\w\w)$/,
891 select: function (context, sign, popularity, place) {
892 popularity = parseFloat(popularity)
893 if (place.length === 2) {
894 place = place.toUpperCase()
895 } else {
896 place = place.toLowerCase()
897 }
898 env.loadCountry(browserslist.usage, place, browserslist.data)
899 var usage = browserslist.usage[place]
900 return Object.keys(usage).reduce(function (result, version) {
901 if (sign === '>') {
902 if (usage[version] > popularity) {
903 result.push(version)
904 }
905 } else if (sign === '<') {
906 if (usage[version] < popularity) {
907 result.push(version)
908 }
909 } else if (sign === '<=') {
910 if (usage[version] <= popularity) {
911 result.push(version)
912 }
913 } else if (usage[version] >= popularity) {
914 result.push(version)
915 }
916 return result
917 }, [])
918 }
919 },
920 {
921 regexp: /^cover\s+(\d+|\d+\.\d+|\.\d+)%$/,
922 select: coverQuery
923 },
924 {
925 regexp: /^cover\s+(\d+|\d+\.\d+|\.\d+)%\s+in\s+(my\s+stats|(alt-)?\w\w)$/,
926 select: coverQuery
927 },
928 {
929 regexp: /^supports\s+([\w-]+)$/,
930 select: function (context, feature) {
931 env.loadFeature(browserslist.cache, feature)
932 var features = browserslist.cache[feature]
933 return Object.keys(features).reduce(function (result, version) {
934 var flags = features[version]
935 if (flags.indexOf('y') >= 0 || flags.indexOf('a') >= 0) {
936 result.push(version)
937 }
938 return result
939 }, [])
940 }
941 },
942 {
943 regexp: /^electron\s+([\d.]+)\s*-\s*([\d.]+)$/i,
944 select: function (context, from, to) {
945 var fromToUse = normalizeElectron(from)
946 var toToUse = normalizeElectron(to)
947 if (!e2c[fromToUse]) {
948 throw new BrowserslistError('Unknown version ' + from + ' of electron')
949 }
950 if (!e2c[toToUse]) {
951 throw new BrowserslistError('Unknown version ' + to + ' of electron')
952 }
953 from = parseFloat(from)
954 to = parseFloat(to)
955 return Object.keys(e2c)
956 .filter(function (i) {
957 var parsed = parseFloat(i)
958 return parsed >= from && parsed <= to
959 })
960 .map(function (i) {
961 return 'chrome ' + e2c[i]
962 })
963 }
964 },
965 {
966 regexp: /^node\s+([\d.]+)\s*-\s*([\d.]+)$/i,
967 select: function (context, from, to) {
968 var nodeVersions = jsReleases
969 .filter(function (i) {
970 return i.name === 'nodejs'
971 })
972 .map(function (i) {
973 return i.version
974 })
975 return nodeVersions
976 .filter(semverFilterLoose('>=', from))
977 .filter(semverFilterLoose('<=', to))
978 .map(function (v) {
979 return 'node ' + v
980 })
981 }
982 },
983 {
984 regexp: /^(\w+)\s+([\d.]+)\s*-\s*([\d.]+)$/i,
985 select: function (context, name, from, to) {
986 var data = checkName(name, context)
987 from = parseFloat(normalizeVersion(data, from) || from)
988 to = parseFloat(normalizeVersion(data, to) || to)
989 function filter (v) {
990 var parsed = parseFloat(v)
991 return parsed >= from && parsed <= to
992 }
993 return data.released.filter(filter).map(nameMapper(data.name))
994 }
995 },
996 {
997 regexp: /^electron\s*(>=?|<=?)\s*([\d.]+)$/i,
998 select: function (context, sign, version) {
999 var versionToUse = normalizeElectron(version)
1000 return Object.keys(e2c)
1001 .filter(generateFilter(sign, versionToUse))
1002 .map(function (i) {
1003 return 'chrome ' + e2c[i]
1004 })
1005 }
1006 },
1007 {
1008 regexp: /^node\s*(>=?|<=?)\s*([\d.]+)$/i,
1009 select: function (context, sign, version) {
1010 var nodeVersions = jsReleases
1011 .filter(function (i) {
1012 return i.name === 'nodejs'
1013 })
1014 .map(function (i) {
1015 return i.version
1016 })
1017 return nodeVersions
1018 .filter(generateSemverFilter(sign, version))
1019 .map(function (v) {
1020 return 'node ' + v
1021 })
1022 }
1023 },
1024 {
1025 regexp: /^(\w+)\s*(>=?|<=?)\s*([\d.]+)$/,
1026 select: function (context, name, sign, version) {
1027 var data = checkName(name, context)
1028 var alias = browserslist.versionAliases[data.name][version]
1029 if (alias) {
1030 version = alias
1031 }
1032 return data.released
1033 .filter(generateFilter(sign, version))
1034 .map(function (v) {
1035 return data.name + ' ' + v
1036 })
1037 }
1038 },
1039 {
1040 regexp: /^(firefox|ff|fx)\s+esr$/i,
1041 select: function () {
1042 return ['firefox 78', 'firefox 91']
1043 }
1044 },
1045 {
1046 regexp: /(operamini|op_mini)\s+all/i,
1047 select: function () {
1048 return ['op_mini all']
1049 }
1050 },
1051 {
1052 regexp: /^electron\s+([\d.]+)$/i,
1053 select: function (context, version) {
1054 var versionToUse = normalizeElectron(version)
1055 var chrome = e2c[versionToUse]
1056 if (!chrome) {
1057 throw new BrowserslistError(
1058 'Unknown version ' + version + ' of electron'
1059 )
1060 }
1061 return ['chrome ' + chrome]
1062 }
1063 },
1064 {
1065 regexp: /^node\s+(\d+)$/i,
1066 select: nodeQuery
1067 },
1068 {
1069 regexp: /^node\s+(\d+\.\d+)$/i,
1070 select: nodeQuery
1071 },
1072 {
1073 regexp: /^node\s+(\d+\.\d+\.\d+)$/i,
1074 select: nodeQuery
1075 },
1076 {
1077 regexp: /^current\s+node$/i,
1078 select: function (context) {
1079 return [env.currentNode(resolve, context)]
1080 }
1081 },
1082 {
1083 regexp: /^maintained\s+node\s+versions$/i,
1084 select: function (context) {
1085 var now = Date.now()
1086 var queries = Object.keys(jsEOL)
1087 .filter(function (key) {
1088 return (
1089 now < Date.parse(jsEOL[key].end) &&
1090 now > Date.parse(jsEOL[key].start) &&
1091 isEolReleased(key)
1092 )
1093 })
1094 .map(function (key) {
1095 return 'node ' + key.slice(1)
1096 })
1097 return resolve(queries, context)
1098 }
1099 },
1100 {
1101 regexp: /^phantomjs\s+1.9$/i,
1102 select: function () {
1103 return ['safari 5']
1104 }
1105 },
1106 {
1107 regexp: /^phantomjs\s+2.1$/i,
1108 select: function () {
1109 return ['safari 6']
1110 }
1111 },
1112 {
1113 regexp: /^(\w+)\s+(tp|[\d.]+)$/i,
1114 select: function (context, name, version) {
1115 if (/^tp$/i.test(version)) version = 'TP'
1116 var data = checkName(name, context)
1117 var alias = normalizeVersion(data, version)
1118 if (alias) {
1119 version = alias
1120 } else {
1121 if (version.indexOf('.') === -1) {
1122 alias = version + '.0'
1123 } else {
1124 alias = version.replace(/\.0$/, '')
1125 }
1126 alias = normalizeVersion(data, alias)
1127 if (alias) {
1128 version = alias
1129 } else if (context.ignoreUnknownVersions) {
1130 return []
1131 } else {
1132 throw new BrowserslistError(
1133 'Unknown version ' + version + ' of ' + name
1134 )
1135 }
1136 }
1137 return [data.name + ' ' + version]
1138 }
1139 },
1140 {
1141 regexp: /^browserslist config$/i,
1142 select: function (context) {
1143 return browserslist(undefined, context)
1144 }
1145 },
1146 {
1147 regexp: /^extends (.+)$/i,
1148 select: function (context, name) {
1149 return resolve(env.loadQueries(context, name), context)
1150 }
1151 },
1152 {
1153 regexp: /^defaults$/i,
1154 select: function (context) {
1155 return resolve(browserslist.defaults, context)
1156 }
1157 },
1158 {
1159 regexp: /^dead$/i,
1160 select: function (context) {
1161 var dead = [
1162 'ie <= 10',
1163 'ie_mob <= 11',
1164 'bb <= 10',
1165 'op_mob <= 12.1',
1166 'samsung 4'
1167 ]
1168 return resolve(dead, context)
1169 }
1170 },
1171 {
1172 regexp: /^(\w+)$/i,
1173 select: function (context, name) {
1174 if (byName(name, context)) {
1175 throw new BrowserslistError(
1176 'Specify versions in Browserslist query for browser ' + name
1177 )
1178 } else {
1179 throw unknownQuery(name)
1180 }
1181 }
1182 }
1183];
1184
1185// Get and convert Can I Use data
1186
1187(function () {
1188 for (var name in agents) {
1189 var browser = agents[name]
1190 browserslist.data[name] = {
1191 name: name,
1192 versions: normalize(agents[name].versions),
1193 released: normalize(agents[name].versions.slice(0, -3)),
1194 releaseDate: agents[name].release_date
1195 }
1196 fillUsage(browserslist.usage.global, name, browser.usage_global)
1197
1198 browserslist.versionAliases[name] = { }
1199 for (var i = 0; i < browser.versions.length; i++) {
1200 var full = browser.versions[i]
1201 if (!full) continue
1202
1203 if (full.indexOf('-') !== -1) {
1204 var interval = full.split('-')
1205 for (var j = 0; j < interval.length; j++) {
1206 browserslist.versionAliases[name][interval[j]] = full
1207 }
1208 }
1209 }
1210 }
1211
1212 browserslist.versionAliases.op_mob['59'] = '58'
1213}())
1214
1215module.exports = browserslist