UNPKG

33.6 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 env: opts.env
429 }
430
431 env.oldDataWarning(browserslist.data)
432 var stats = env.getStat(opts, browserslist.data)
433 if (stats) {
434 context.customUsage = { }
435 for (var browser in stats) {
436 fillUsage(context.customUsage, browser, stats[browser])
437 }
438 }
439
440 var cacheKey = JSON.stringify([queries, context])
441 if (cache[cacheKey]) return cache[cacheKey]
442
443 var result = uniq(resolve(queries, context)).sort(function (name1, name2) {
444 name1 = name1.split(' ')
445 name2 = name2.split(' ')
446 if (name1[0] === name2[0]) {
447 // assumptions on caniuse data
448 // 1) version ranges never overlaps
449 // 2) if version is not a range, it never contains `-`
450 var version1 = name1[1].split('-')[0]
451 var version2 = name2[1].split('-')[0]
452 return compareSemver(version2.split('.'), version1.split('.'))
453 } else {
454 return compare(name1[0], name2[0])
455 }
456 })
457 if (!process.env.BROWSERSLIST_DISABLE_CACHE) {
458 cache[cacheKey] = result
459 }
460 return result
461}
462
463function parse (queries) {
464 var qs = []
465 do {
466 queries = doMatch(queries, qs)
467 } while (queries)
468 return qs
469}
470
471function doMatch (string, qs) {
472 var or = /^(?:,\s*|\s+or\s+)(.*)/i
473 var and = /^\s+and\s+(.*)/i
474
475 return find(string, function (parsed, n, max) {
476 if (and.test(parsed)) {
477 qs.unshift({ type: QUERY_AND, queryString: parsed.match(and)[1] })
478 return true
479 } else if (or.test(parsed)) {
480 qs.unshift({ type: QUERY_OR, queryString: parsed.match(or)[1] })
481 return true
482 } else if (n === max) {
483 qs.unshift({ type: QUERY_OR, queryString: parsed.trim() })
484 return true
485 }
486 return false
487 })
488}
489
490function find (string, predicate) {
491 for (var n = 1, max = string.length; n <= max; n++) {
492 var parsed = string.substr(-n, n)
493 if (predicate(parsed, n, max)) {
494 return string.slice(0, -n)
495 }
496 }
497 return ''
498}
499
500function flatten (array) {
501 if (!Array.isArray(array)) return [array]
502 return array.reduce(function (a, b) {
503 return a.concat(flatten(b))
504 }, [])
505}
506
507// Will be filled by Can I Use data below
508browserslist.cache = { }
509browserslist.data = { }
510browserslist.usage = {
511 global: { },
512 custom: null
513}
514
515// Default browsers query
516browserslist.defaults = [
517 '> 0.5%',
518 'last 2 versions',
519 'Firefox ESR',
520 'not dead'
521]
522
523// Browser names aliases
524browserslist.aliases = {
525 fx: 'firefox',
526 ff: 'firefox',
527 ios: 'ios_saf',
528 explorer: 'ie',
529 blackberry: 'bb',
530 explorermobile: 'ie_mob',
531 operamini: 'op_mini',
532 operamobile: 'op_mob',
533 chromeandroid: 'and_chr',
534 firefoxandroid: 'and_ff',
535 ucandroid: 'and_uc',
536 qqandroid: 'and_qq'
537}
538
539// Can I Use only provides a few versions for some browsers (e.g. and_chr).
540// Fallback to a similar browser for unknown versions
541browserslist.desktopNames = {
542 and_chr: 'chrome',
543 and_ff: 'firefox',
544 ie_mob: 'ie',
545 op_mob: 'opera',
546 android: 'chrome' // has extra processing logic
547}
548
549// Aliases to work with joined versions like `ios_saf 7.0-7.1`
550browserslist.versionAliases = { }
551
552browserslist.clearCaches = env.clearCaches
553browserslist.parseConfig = env.parseConfig
554browserslist.readConfig = env.readConfig
555browserslist.findConfig = env.findConfig
556browserslist.loadConfig = env.loadConfig
557
558/**
559 * Return browsers market coverage.
560 *
561 * @param {string[]} browsers Browsers names in Can I Use.
562 * @param {string|object} [stats="global"] Which statistics should be used.
563 * Country code or custom statistics.
564 * Pass `"my stats"` to load statistics
565 * from Browserslist files.
566 *
567 * @return {number} Total market coverage for all selected browsers.
568 *
569 * @example
570 * browserslist.coverage(browserslist('> 1% in US'), 'US') //=> 83.1
571 */
572browserslist.coverage = function (browsers, stats) {
573 var data
574 if (typeof stats === 'undefined') {
575 data = browserslist.usage.global
576 } else if (stats === 'my stats') {
577 var opts = {}
578 opts.path = path.resolve ? path.resolve('.') : '.'
579 var customStats = env.getStat(opts)
580 if (!customStats) {
581 throw new BrowserslistError('Custom usage statistics was not provided')
582 }
583 data = {}
584 for (var browser in customStats) {
585 fillUsage(data, browser, customStats[browser])
586 }
587 } else if (typeof stats === 'string') {
588 if (stats.length > 2) {
589 stats = stats.toLowerCase()
590 } else {
591 stats = stats.toUpperCase()
592 }
593 env.loadCountry(browserslist.usage, stats, browserslist.data)
594 data = browserslist.usage[stats]
595 } else {
596 if ('dataByBrowser' in stats) {
597 stats = stats.dataByBrowser
598 }
599 data = { }
600 for (var name in stats) {
601 for (var version in stats[name]) {
602 data[name + ' ' + version] = stats[name][version]
603 }
604 }
605 }
606
607 return browsers.reduce(function (all, i) {
608 var usage = data[i]
609 if (usage === undefined) {
610 usage = data[i.replace(/ \S+$/, ' 0')]
611 }
612 return all + (usage || 0)
613 }, 0)
614}
615
616var QUERIES = [
617 {
618 regexp: /^last\s+(\d+)\s+major\s+versions?$/i,
619 select: function (context, versions) {
620 return Object.keys(agents).reduce(function (selected, name) {
621 var data = byName(name, context)
622 if (!data) return selected
623 var list = getMajorVersions(data.released, versions)
624 list = list.map(nameMapper(data.name))
625 if (data.name === 'android') {
626 list = filterAndroid(list, versions, context)
627 }
628 return selected.concat(list)
629 }, [])
630 }
631 },
632 {
633 regexp: /^last\s+(\d+)\s+versions?$/i,
634 select: function (context, versions) {
635 return Object.keys(agents).reduce(function (selected, name) {
636 var data = byName(name, context)
637 if (!data) return selected
638 var list = data.released.slice(-versions)
639 list = list.map(nameMapper(data.name))
640 if (data.name === 'android') {
641 list = filterAndroid(list, versions, context)
642 }
643 return selected.concat(list)
644 }, [])
645 }
646 },
647 {
648 regexp: /^last\s+(\d+)\s+electron\s+major\s+versions?$/i,
649 select: function (context, versions) {
650 var validVersions = getMajorVersions(Object.keys(e2c), versions)
651 return validVersions.map(function (i) {
652 return 'chrome ' + e2c[i]
653 })
654 }
655 },
656 {
657 regexp: /^last\s+(\d+)\s+(\w+)\s+major\s+versions?$/i,
658 select: function (context, versions, name) {
659 var data = checkName(name, context)
660 var validVersions = getMajorVersions(data.released, versions)
661 var list = validVersions.map(nameMapper(data.name))
662 if (data.name === 'android') {
663 list = filterAndroid(list, versions, context)
664 }
665 return list
666 }
667 },
668 {
669 regexp: /^last\s+(\d+)\s+electron\s+versions?$/i,
670 select: function (context, versions) {
671 return Object.keys(e2c).slice(-versions).map(function (i) {
672 return 'chrome ' + e2c[i]
673 })
674 }
675 },
676 {
677 regexp: /^last\s+(\d+)\s+(\w+)\s+versions?$/i,
678 select: function (context, versions, name) {
679 var data = checkName(name, context)
680 var list = data.released.slice(-versions).map(nameMapper(data.name))
681 if (data.name === 'android') {
682 list = filterAndroid(list, versions, context)
683 }
684 return list
685 }
686 },
687 {
688 regexp: /^unreleased\s+versions$/i,
689 select: function (context) {
690 return Object.keys(agents).reduce(function (selected, name) {
691 var data = byName(name, context)
692 if (!data) return selected
693 var list = data.versions.filter(function (v) {
694 return data.released.indexOf(v) === -1
695 })
696 list = list.map(nameMapper(data.name))
697 return selected.concat(list)
698 }, [])
699 }
700 },
701 {
702 regexp: /^unreleased\s+electron\s+versions?$/i,
703 select: function () {
704 return []
705 }
706 },
707 {
708 regexp: /^unreleased\s+(\w+)\s+versions?$/i,
709 select: function (context, name) {
710 var data = checkName(name, context)
711 return data.versions.filter(function (v) {
712 return data.released.indexOf(v) === -1
713 }).map(nameMapper(data.name))
714 }
715 },
716 {
717 regexp: /^last\s+(\d*.?\d+)\s+years?$/i,
718 select: function (context, years) {
719 return filterByYear(Date.now() - YEAR * years, context)
720 }
721 },
722 {
723 regexp: /^since (\d+)(?:-(\d+))?(?:-(\d+))?$/i,
724 select: function (context, year, month, date) {
725 year = parseInt(year)
726 month = parseInt(month || '01') - 1
727 date = parseInt(date || '01')
728 return filterByYear(Date.UTC(year, month, date, 0, 0, 0), context)
729 }
730 },
731 {
732 regexp: /^(>=?|<=?)\s*(\d*\.?\d+)%$/,
733 select: function (context, sign, popularity) {
734 popularity = parseFloat(popularity)
735 var usage = browserslist.usage.global
736 return Object.keys(usage).reduce(function (result, version) {
737 if (sign === '>') {
738 if (usage[version] > popularity) {
739 result.push(version)
740 }
741 } else if (sign === '<') {
742 if (usage[version] < popularity) {
743 result.push(version)
744 }
745 } else if (sign === '<=') {
746 if (usage[version] <= popularity) {
747 result.push(version)
748 }
749 } else if (usage[version] >= popularity) {
750 result.push(version)
751 }
752 return result
753 }, [])
754 }
755 },
756 {
757 regexp: /^(>=?|<=?)\s*(\d*\.?\d+)%\s+in\s+my\s+stats$/,
758 select: function (context, sign, popularity) {
759 popularity = parseFloat(popularity)
760 if (!context.customUsage) {
761 throw new BrowserslistError('Custom usage statistics was not provided')
762 }
763 var usage = context.customUsage
764 return Object.keys(usage).reduce(function (result, version) {
765 if (sign === '>') {
766 if (usage[version] > popularity) {
767 result.push(version)
768 }
769 } else if (sign === '<') {
770 if (usage[version] < popularity) {
771 result.push(version)
772 }
773 } else if (sign === '<=') {
774 if (usage[version] <= popularity) {
775 result.push(version)
776 }
777 } else if (usage[version] >= popularity) {
778 result.push(version)
779 }
780 return result
781 }, [])
782 }
783 },
784 {
785 regexp: /^(>=?|<=?)\s*(\d*\.?\d+)%\s+in\s+(\S+)\s+stats$/,
786 select: function (context, sign, popularity, name) {
787 popularity = parseFloat(popularity)
788 var stats = env.loadStat(context, name, browserslist.data)
789 if (stats) {
790 context.customUsage = { }
791 for (var browser in stats) {
792 fillUsage(context.customUsage, browser, stats[browser])
793 }
794 }
795 if (!context.customUsage) {
796 throw new BrowserslistError('Custom usage statistics was not provided')
797 }
798 var usage = context.customUsage
799 return Object.keys(usage).reduce(function (result, version) {
800 if (sign === '>') {
801 if (usage[version] > popularity) {
802 result.push(version)
803 }
804 } else if (sign === '<') {
805 if (usage[version] < popularity) {
806 result.push(version)
807 }
808 } else if (sign === '<=') {
809 if (usage[version] <= popularity) {
810 result.push(version)
811 }
812 } else if (usage[version] >= popularity) {
813 result.push(version)
814 }
815 return result
816 }, [])
817 }
818 },
819 {
820 regexp: /^(>=?|<=?)\s*(\d*\.?\d+)%\s+in\s+((alt-)?\w\w)$/,
821 select: function (context, sign, popularity, place) {
822 popularity = parseFloat(popularity)
823 if (place.length === 2) {
824 place = place.toUpperCase()
825 } else {
826 place = place.toLowerCase()
827 }
828 env.loadCountry(browserslist.usage, place, browserslist.data)
829 var usage = browserslist.usage[place]
830 return Object.keys(usage).reduce(function (result, version) {
831 if (sign === '>') {
832 if (usage[version] > popularity) {
833 result.push(version)
834 }
835 } else 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 (usage[version] >= popularity) {
844 result.push(version)
845 }
846 return result
847 }, [])
848 }
849 },
850 {
851 regexp: /^cover\s+(\d*\.?\d+)%(\s+in\s+(my\s+stats|(alt-)?\w\w))?$/,
852 select: function (context, coverage, statMode) {
853 coverage = parseFloat(coverage)
854 var usage = browserslist.usage.global
855 if (statMode) {
856 if (statMode.match(/^\s+in\s+my\s+stats$/)) {
857 if (!context.customUsage) {
858 throw new BrowserslistError(
859 'Custom usage statistics was not provided'
860 )
861 }
862 usage = context.customUsage
863 } else {
864 var match = statMode.match(/\s+in\s+((alt-)?\w\w)/)
865 var place = match[1]
866 if (place.length === 2) {
867 place = place.toUpperCase()
868 } else {
869 place = place.toLowerCase()
870 }
871 env.loadCountry(browserslist.usage, place, browserslist.data)
872 usage = browserslist.usage[place]
873 }
874 }
875 var versions = Object.keys(usage).sort(function (a, b) {
876 return usage[b] - usage[a]
877 })
878 var coveraged = 0
879 var result = []
880 var version
881 for (var i = 0; i <= versions.length; i++) {
882 version = versions[i]
883 if (usage[version] === 0) break
884 coveraged += usage[version]
885 result.push(version)
886 if (coveraged >= coverage) break
887 }
888 return result
889 }
890 },
891 {
892 regexp: /^supports\s+([\w-]+)$/,
893 select: function (context, feature) {
894 env.loadFeature(browserslist.cache, feature)
895 var features = browserslist.cache[feature]
896 return Object.keys(features).reduce(function (result, version) {
897 var flags = features[version]
898 if (flags.indexOf('y') >= 0 || flags.indexOf('a') >= 0) {
899 result.push(version)
900 }
901 return result
902 }, [])
903 }
904 },
905 {
906 regexp: /^electron\s+([\d.]+)\s*-\s*([\d.]+)$/i,
907 select: function (context, from, to) {
908 var fromToUse = normalizeElectron(from)
909 var toToUse = normalizeElectron(to)
910 if (!e2c[fromToUse]) {
911 throw new BrowserslistError('Unknown version ' + from + ' of electron')
912 }
913 if (!e2c[toToUse]) {
914 throw new BrowserslistError('Unknown version ' + to + ' of electron')
915 }
916 from = parseFloat(from)
917 to = parseFloat(to)
918 return Object.keys(e2c).filter(function (i) {
919 var parsed = parseFloat(i)
920 return parsed >= from && parsed <= to
921 }).map(function (i) {
922 return 'chrome ' + e2c[i]
923 })
924 }
925 },
926 {
927 regexp: /^node\s+([\d.]+)\s*-\s*([\d.]+)$/i,
928 select: function (context, from, to) {
929 var nodeVersions = jsReleases.filter(function (i) {
930 return i.name === 'nodejs'
931 }).map(function (i) {
932 return i.version
933 })
934 var semverRegExp = /^(0|[1-9]\d*)(\.(0|[1-9]\d*)){0,2}$/
935 if (!semverRegExp.test(from)) {
936 throw new BrowserslistError(
937 'Unknown version ' + from + ' of Node.js')
938 }
939 if (!semverRegExp.test(to)) {
940 throw new BrowserslistError(
941 'Unknown version ' + to + ' of Node.js')
942 }
943 return nodeVersions
944 .filter(semverFilterLoose('>=', from))
945 .filter(semverFilterLoose('<=', to))
946 .map(function (v) {
947 return 'node ' + v
948 })
949 }
950 },
951 {
952 regexp: /^(\w+)\s+([\d.]+)\s*-\s*([\d.]+)$/i,
953 select: function (context, name, from, to) {
954 var data = checkName(name, context)
955 from = parseFloat(normalizeVersion(data, from) || from)
956 to = parseFloat(normalizeVersion(data, to) || to)
957 function filter (v) {
958 var parsed = parseFloat(v)
959 return parsed >= from && parsed <= to
960 }
961 return data.released.filter(filter).map(nameMapper(data.name))
962 }
963 },
964 {
965 regexp: /^electron\s*(>=?|<=?)\s*([\d.]+)$/i,
966 select: function (context, sign, version) {
967 var versionToUse = normalizeElectron(version)
968 return Object.keys(e2c)
969 .filter(generateFilter(sign, versionToUse))
970 .map(function (i) {
971 return 'chrome ' + e2c[i]
972 })
973 }
974 },
975 {
976 regexp: /^node\s*(>=?|<=?)\s*([\d.]+)$/i,
977 select: function (context, sign, version) {
978 var nodeVersions = jsReleases.filter(function (i) {
979 return i.name === 'nodejs'
980 }).map(function (i) {
981 return i.version
982 })
983 return nodeVersions
984 .filter(generateSemverFilter(sign, version))
985 .map(function (v) {
986 return 'node ' + v
987 })
988 }
989 },
990 {
991 regexp: /^(\w+)\s*(>=?|<=?)\s*([\d.]+)$/,
992 select: function (context, name, sign, version) {
993 var data = checkName(name, context)
994 var alias = browserslist.versionAliases[data.name][version]
995 if (alias) {
996 version = alias
997 }
998 return data.released
999 .filter(generateFilter(sign, version))
1000 .map(function (v) {
1001 return data.name + ' ' + v
1002 })
1003 }
1004 },
1005 {
1006 regexp: /^(firefox|ff|fx)\s+esr$/i,
1007 select: function () {
1008 return ['firefox 78']
1009 }
1010 },
1011 {
1012 regexp: /(operamini|op_mini)\s+all/i,
1013 select: function () {
1014 return ['op_mini all']
1015 }
1016 },
1017 {
1018 regexp: /^electron\s+([\d.]+)$/i,
1019 select: function (context, version) {
1020 var versionToUse = normalizeElectron(version)
1021 var chrome = e2c[versionToUse]
1022 if (!chrome) {
1023 throw new BrowserslistError(
1024 'Unknown version ' + version + ' of electron')
1025 }
1026 return ['chrome ' + chrome]
1027 }
1028 },
1029 {
1030 regexp: /^node\s+(\d+(\.\d+)?(\.\d+)?)$/i,
1031 select: function (context, version) {
1032 var nodeReleases = jsReleases.filter(function (i) {
1033 return i.name === 'nodejs'
1034 })
1035 var matched = nodeReleases.filter(function (i) {
1036 return isVersionsMatch(i.version, version)
1037 })
1038 if (matched.length === 0) {
1039 if (context.ignoreUnknownVersions) {
1040 return []
1041 } else {
1042 throw new BrowserslistError(
1043 'Unknown version ' + version + ' of Node.js')
1044 }
1045 }
1046 return ['node ' + matched[matched.length - 1].version]
1047 }
1048 },
1049 {
1050 regexp: /^current\s+node$/i,
1051 select: function (context) {
1052 return [env.currentNode(resolve, context)]
1053 }
1054 },
1055 {
1056 regexp: /^maintained\s+node\s+versions$/i,
1057 select: function (context) {
1058 var now = Date.now()
1059 var queries = Object.keys(jsEOL).filter(function (key) {
1060 return now < Date.parse(jsEOL[key].end) &&
1061 now > Date.parse(jsEOL[key].start) &&
1062 isEolReleased(key)
1063 }).map(function (key) {
1064 return 'node ' + key.slice(1)
1065 })
1066 return resolve(queries, context)
1067 }
1068 },
1069 {
1070 regexp: /^phantomjs\s+1.9$/i,
1071 select: function () {
1072 return ['safari 5']
1073 }
1074 },
1075 {
1076 regexp: /^phantomjs\s+2.1$/i,
1077 select: function () {
1078 return ['safari 6']
1079 }
1080 },
1081 {
1082 regexp: /^(\w+)\s+(tp|[\d.]+)$/i,
1083 select: function (context, name, version) {
1084 if (/^tp$/i.test(version)) version = 'TP'
1085 var data = checkName(name, context)
1086 var alias = normalizeVersion(data, version)
1087 if (alias) {
1088 version = alias
1089 } else {
1090 if (version.indexOf('.') === -1) {
1091 alias = version + '.0'
1092 } else {
1093 alias = version.replace(/\.0$/, '')
1094 }
1095 alias = normalizeVersion(data, alias)
1096 if (alias) {
1097 version = alias
1098 } else if (context.ignoreUnknownVersions) {
1099 return []
1100 } else {
1101 throw new BrowserslistError(
1102 'Unknown version ' + version + ' of ' + name)
1103 }
1104 }
1105 return [data.name + ' ' + version]
1106 }
1107 },
1108 {
1109 regexp: /^extends (.+)$/i,
1110 select: function (context, name) {
1111 return resolve(env.loadQueries(context, name), context)
1112 }
1113 },
1114 {
1115 regexp: /^defaults$/i,
1116 select: function (context) {
1117 return resolve(browserslist.defaults, context)
1118 }
1119 },
1120 {
1121 regexp: /^dead$/i,
1122 select: function (context) {
1123 var dead = [
1124 'ie <= 10',
1125 'ie_mob <= 11',
1126 'bb <= 10',
1127 'op_mob <= 12.1',
1128 'samsung 4'
1129 ]
1130 return resolve(dead, context)
1131 }
1132 },
1133 {
1134 regexp: /^(\w+)$/i,
1135 select: function (context, name) {
1136 if (byName(name, context)) {
1137 throw new BrowserslistError(
1138 'Specify versions in Browserslist query for browser ' + name)
1139 } else {
1140 throw unknownQuery(name)
1141 }
1142 }
1143 }
1144];
1145
1146// Get and convert Can I Use data
1147
1148(function () {
1149 for (var name in agents) {
1150 var browser = agents[name]
1151 browserslist.data[name] = {
1152 name: name,
1153 versions: normalize(agents[name].versions),
1154 released: normalize(agents[name].versions.slice(0, -3)),
1155 releaseDate: agents[name].release_date
1156 }
1157 fillUsage(browserslist.usage.global, name, browser.usage_global)
1158
1159 browserslist.versionAliases[name] = { }
1160 for (var i = 0; i < browser.versions.length; i++) {
1161 var full = browser.versions[i]
1162 if (!full) continue
1163
1164 if (full.indexOf('-') !== -1) {
1165 var interval = full.split('-')
1166 for (var j = 0; j < interval.length; j++) {
1167 browserslist.versionAliases[name][interval[j]] = full
1168 }
1169 }
1170 }
1171 }
1172
1173 browserslist.versionAliases.op_mob['59'] = '58'
1174}())
1175
1176module.exports = browserslist