UNPKG

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