123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672
  1. var path = require('path')
  2. var e2c = require('electron-to-chromium/versions')
  3. var agents = require('caniuse-lite/dist/unpacker/agents').agents
  4. var region = require('caniuse-lite/dist/unpacker/region').default
  5. var BrowserslistError = require('./error')
  6. var env = require('./node') // Will load browser.js in webpack
  7. var FLOAT_RANGE = /^\d+(\.\d+)?(-\d+(\.\d+)?)*$/
  8. function normalize (versions) {
  9. return versions.filter(function (version) {
  10. return typeof version === 'string'
  11. })
  12. }
  13. function nameMapper (name) {
  14. return function mapName (version) {
  15. return name + ' ' + version
  16. }
  17. }
  18. function getMajor (version) {
  19. return parseInt(version.split('.')[0])
  20. }
  21. function getMajorVersions (released, number) {
  22. if (released.length === 0) return []
  23. var minimum = getMajor(released[released.length - 1]) - parseInt(number) + 1
  24. var selected = []
  25. for (var i = released.length - 1; i >= 0; i--) {
  26. if (minimum > getMajor(released[i])) break
  27. selected.unshift(released[i])
  28. }
  29. return selected
  30. }
  31. function uniq (array) {
  32. var filtered = []
  33. for (var i = 0; i < array.length; i++) {
  34. if (filtered.indexOf(array[i]) === -1) filtered.push(array[i])
  35. }
  36. return filtered
  37. }
  38. // Helpers
  39. function fillUsage (result, name, data) {
  40. for (var i in data) {
  41. result[name + ' ' + i] = data[i]
  42. }
  43. }
  44. function generateFilter (sign, version) {
  45. version = parseFloat(version)
  46. if (sign === '>') {
  47. return function (v) {
  48. return parseFloat(v) > version
  49. }
  50. } else if (sign === '>=') {
  51. return function (v) {
  52. return parseFloat(v) >= version
  53. }
  54. } else if (sign === '<') {
  55. return function (v) {
  56. return parseFloat(v) < version
  57. }
  58. } else {
  59. return function (v) {
  60. return parseFloat(v) <= version
  61. }
  62. }
  63. }
  64. function compareStrings (a, b) {
  65. if (a < b) return -1
  66. if (a > b) return +1
  67. return 0
  68. }
  69. function normalizeVersion (data, version) {
  70. if (data.versions.indexOf(version) !== -1) {
  71. return version
  72. } else if (browserslist.versionAliases[data.name][version]) {
  73. return browserslist.versionAliases[data.name][version]
  74. } else if (data.versions.length === 1) {
  75. return data.versions[0]
  76. } else {
  77. return false
  78. }
  79. }
  80. function loadCountryStatistics (country) {
  81. country = country.replace(/[^\w-]/g, '')
  82. if (!browserslist.usage[country]) {
  83. var usage = { }
  84. // eslint-disable-next-line security/detect-non-literal-require
  85. var compressed = require('caniuse-lite/data/regions/' + country + '.js')
  86. var data = region(compressed)
  87. for (var i in data) {
  88. fillUsage(usage, i, data[i])
  89. }
  90. browserslist.usage[country] = usage
  91. }
  92. }
  93. function filterByYear (since) {
  94. return Object.keys(agents).reduce(function (selected, name) {
  95. var data = byName(name)
  96. if (!data) return selected
  97. var versions = Object.keys(data.releaseDate).filter(function (v) {
  98. return data.releaseDate[v] >= since
  99. })
  100. return selected.concat(versions.map(nameMapper(data.name)))
  101. }, [])
  102. }
  103. function byName (name) {
  104. name = name.toLowerCase()
  105. name = browserslist.aliases[name] || name
  106. return browserslist.data[name]
  107. }
  108. function checkName (name) {
  109. var data = byName(name)
  110. if (!data) throw new BrowserslistError('Unknown browser ' + name)
  111. return data
  112. }
  113. function resolve (queries, context) {
  114. return queries.reduce(function (result, selection, index) {
  115. selection = selection.trim()
  116. if (selection === '') return result
  117. var isExclude = selection.indexOf('not ') === 0
  118. if (isExclude) {
  119. if (index === 0) {
  120. throw new BrowserslistError(
  121. 'Write any browsers query (for instance, `defaults`) ' +
  122. 'before `' + selection + '`')
  123. }
  124. selection = selection.slice(4)
  125. }
  126. for (var i = 0; i < QUERIES.length; i++) {
  127. var type = QUERIES[i]
  128. var match = selection.match(type.regexp)
  129. if (match) {
  130. var args = [context].concat(match.slice(1))
  131. var array = type.select.apply(browserslist, args)
  132. if (isExclude) {
  133. array = array.concat(array.map(function (j) {
  134. return j.replace(/\s\d+/, ' 0')
  135. }))
  136. return result.filter(function (j) {
  137. return array.indexOf(j) === -1
  138. })
  139. }
  140. return result.concat(array)
  141. }
  142. }
  143. throw new BrowserslistError('Unknown browser query `' + selection + '`')
  144. }, [])
  145. }
  146. /**
  147. * Return array of browsers by selection queries.
  148. *
  149. * @param {(string|string[])} [queries=browserslist.defaults] Browser queries.
  150. * @param {object} opts Options.
  151. * @param {string} [opts.path="."] Path to processed file.
  152. * It will be used to find config files.
  153. * @param {string} [opts.env="development"] Processing environment.
  154. * It will be used to take right
  155. * queries from config file.
  156. * @param {string} [opts.config] Path to config file with queries.
  157. * @param {object} [opts.stats] Custom browser usage statistics
  158. * for "> 1% in my stats" query.
  159. * @return {string[]} Array with browser names in Can I Use.
  160. *
  161. * @example
  162. * browserslist('IE >= 10, IE 8') //=> ['ie 11', 'ie 10', 'ie 8']
  163. */
  164. function browserslist (queries, opts) {
  165. if (typeof opts === 'undefined') opts = { }
  166. if (!opts.hasOwnProperty('path')) {
  167. opts.path = path.resolve ? path.resolve('.') : '.'
  168. }
  169. if (typeof queries === 'undefined' || queries === null) {
  170. var config = env.loadConfig(opts)
  171. if (config) {
  172. queries = config
  173. } else {
  174. queries = browserslist.defaults
  175. }
  176. }
  177. if (typeof queries === 'string') {
  178. queries = queries.split(/,\s*/)
  179. }
  180. if (!Array.isArray(queries)) {
  181. throw new BrowserslistError(
  182. 'Browser queries must be an array. Got ' + typeof queries + '.')
  183. }
  184. var context = { dangerousExtend: opts.dangerousExtend }
  185. var stats = env.getStat(opts)
  186. if (stats) {
  187. if ('dataByBrowser' in stats) {
  188. stats = stats.dataByBrowser
  189. }
  190. context.customUsage = { }
  191. for (var browser in stats) {
  192. fillUsage(context.customUsage, browser, stats[browser])
  193. }
  194. }
  195. var result = resolve(queries, context).map(function (i) {
  196. var parts = i.split(' ')
  197. var name = parts[0]
  198. var version = parts[1]
  199. if (version === '0') {
  200. return name + ' ' + byName(name).versions[0]
  201. } else {
  202. return i
  203. }
  204. }).sort(function (name1, name2) {
  205. name1 = name1.split(' ')
  206. name2 = name2.split(' ')
  207. if (name1[0] === name2[0]) {
  208. if (FLOAT_RANGE.test(name1[1]) && FLOAT_RANGE.test(name2[1])) {
  209. return parseFloat(name2[1]) - parseFloat(name1[1])
  210. } else {
  211. return compareStrings(name2[1], name1[1])
  212. }
  213. } else {
  214. return compareStrings(name1[0], name2[0])
  215. }
  216. })
  217. return uniq(result)
  218. }
  219. // Will be filled by Can I Use data below
  220. browserslist.data = { }
  221. browserslist.usage = {
  222. global: { },
  223. custom: null
  224. }
  225. // Default browsers query
  226. browserslist.defaults = [
  227. '> 1%',
  228. 'last 2 versions',
  229. 'Firefox ESR'
  230. ]
  231. // Browser names aliases
  232. browserslist.aliases = {
  233. fx: 'firefox',
  234. ff: 'firefox',
  235. ios: 'ios_saf',
  236. explorer: 'ie',
  237. blackberry: 'bb',
  238. explorermobile: 'ie_mob',
  239. operamini: 'op_mini',
  240. operamobile: 'op_mob',
  241. chromeandroid: 'and_chr',
  242. firefoxandroid: 'and_ff',
  243. ucandroid: 'and_uc',
  244. qqandroid: 'and_qq'
  245. }
  246. // Aliases to work with joined versions like `ios_saf 7.0-7.1`
  247. browserslist.versionAliases = { }
  248. browserslist.clearCaches = env.clearCaches
  249. browserslist.parseConfig = env.parseConfig
  250. browserslist.readConfig = env.readConfig
  251. browserslist.findConfig = env.findConfig
  252. /**
  253. * Return browsers market coverage.
  254. *
  255. * @param {string[]} browsers Browsers names in Can I Use.
  256. * @param {string} [country="global"] Which country statistics should be used.
  257. *
  258. * @return {number} Total market coverage for all selected browsers.
  259. *
  260. * @example
  261. * browserslist.coverage(browserslist('> 1% in US'), 'US') //=> 83.1
  262. */
  263. browserslist.coverage = function (browsers, country) {
  264. if (country && country !== 'global') {
  265. if (country.length > 2) {
  266. country = country.toLowerCase()
  267. } else {
  268. country = country.toUpperCase()
  269. }
  270. loadCountryStatistics(country)
  271. } else {
  272. country = 'global'
  273. }
  274. return browsers.reduce(function (all, i) {
  275. var usage = browserslist.usage[country][i]
  276. if (usage === undefined) {
  277. usage = browserslist.usage[country][i.replace(/ [\d.]+$/, ' 0')]
  278. }
  279. return all + (usage || 0)
  280. }, 0)
  281. }
  282. var QUERIES = [
  283. {
  284. regexp: /^last\s+(\d+)\s+major versions?$/i,
  285. select: function (context, versions) {
  286. return Object.keys(agents).reduce(function (selected, name) {
  287. var data = byName(name)
  288. if (!data) return selected
  289. var array = getMajorVersions(data.released, versions)
  290. array = array.map(nameMapper(data.name))
  291. return selected.concat(array)
  292. }, [])
  293. }
  294. },
  295. {
  296. regexp: /^last\s+(\d+)\s+versions?$/i,
  297. select: function (context, versions) {
  298. return Object.keys(agents).reduce(function (selected, name) {
  299. var data = byName(name)
  300. if (!data) return selected
  301. var array = data.released.slice(-versions)
  302. array = array.map(nameMapper(data.name))
  303. return selected.concat(array)
  304. }, [])
  305. }
  306. },
  307. {
  308. regexp: /^last\s+(\d+)\s+electron\s+major versions?$/i,
  309. select: function (context, versions) {
  310. var validVersions = getMajorVersions(Object.keys(e2c).reverse(), versions)
  311. return validVersions.map(function (i) {
  312. return 'chrome ' + e2c[i]
  313. })
  314. }
  315. },
  316. {
  317. regexp: /^last\s+(\d+)\s+(\w+)\s+major versions?$/i,
  318. select: function (context, versions, name) {
  319. var data = checkName(name)
  320. var validVersions = getMajorVersions(data.released, versions)
  321. return validVersions.map(nameMapper(data.name))
  322. }
  323. },
  324. {
  325. regexp: /^last\s+(\d+)\s+electron\s+versions?$/i,
  326. select: function (context, versions) {
  327. return Object.keys(e2c).reverse().slice(-versions).map(function (i) {
  328. return 'chrome ' + e2c[i]
  329. })
  330. }
  331. },
  332. {
  333. regexp: /^last\s+(\d+)\s+(\w+)\s+versions?$/i,
  334. select: function (context, versions, name) {
  335. var data = checkName(name)
  336. return data.released.slice(-versions).map(nameMapper(data.name))
  337. }
  338. },
  339. {
  340. regexp: /^unreleased\s+versions$/i,
  341. select: function () {
  342. return Object.keys(agents).reduce(function (selected, name) {
  343. var data = byName(name)
  344. if (!data) return selected
  345. var array = data.versions.filter(function (v) {
  346. return data.released.indexOf(v) === -1
  347. })
  348. array = array.map(nameMapper(data.name))
  349. return selected.concat(array)
  350. }, [])
  351. }
  352. },
  353. {
  354. regexp: /^unreleased\s+electron\s+versions?$/i,
  355. select: function () {
  356. return []
  357. }
  358. },
  359. {
  360. regexp: /^unreleased\s+(\w+)\s+versions?$/i,
  361. select: function (context, name) {
  362. var data = checkName(name)
  363. return data.versions.filter(function (v) {
  364. return data.released.indexOf(v) === -1
  365. }).map(nameMapper(data.name))
  366. }
  367. },
  368. {
  369. regexp: /^last\s+(\d+)\s+years?$/i,
  370. select: function (context, years) {
  371. var date = new Date()
  372. var since = date.setFullYear(date.getFullYear() - years) / 1000
  373. return filterByYear(since)
  374. }
  375. },
  376. {
  377. regexp: /^since (\d+)(?:-(\d+))?(?:-(\d+))?$/i,
  378. select: function (context, year, month, date) {
  379. year = parseInt(year)
  380. month = parseInt(month || '01') - 1
  381. date = parseInt(date || '01')
  382. var since = Date.UTC(year, month, date, 0, 0, 0) / 1000
  383. return filterByYear(since)
  384. }
  385. },
  386. {
  387. regexp: /^(>=?|<=?)\s*(\d*\.?\d+)%$/,
  388. select: function (context, sign, popularity) {
  389. popularity = parseFloat(popularity)
  390. var usage = browserslist.usage.global
  391. return Object.keys(usage).reduce(function (result, version) {
  392. if (sign === '>') {
  393. if (usage[version] > popularity) {
  394. result.push(version)
  395. }
  396. } else if (sign === '<') {
  397. if (usage[version] < popularity) {
  398. result.push(version)
  399. }
  400. } else if (sign === '<=') {
  401. if (usage[version] <= popularity) {
  402. result.push(version)
  403. }
  404. } else if (usage[version] >= popularity) {
  405. result.push(version)
  406. }
  407. return result
  408. }, [])
  409. }
  410. },
  411. {
  412. regexp: /^(>=?|<=?)\s*(\d*\.?\d+)%\s+in\s+my\s+stats$/,
  413. select: function (context, sign, popularity) {
  414. popularity = parseFloat(popularity)
  415. if (!context.customUsage) {
  416. throw new BrowserslistError('Custom usage statistics was not provided')
  417. }
  418. var usage = context.customUsage
  419. return Object.keys(usage).reduce(function (result, version) {
  420. if (sign === '>') {
  421. if (usage[version] > popularity) {
  422. result.push(version)
  423. }
  424. } else if (sign === '<') {
  425. if (usage[version] < popularity) {
  426. result.push(version)
  427. }
  428. } else if (sign === '<=') {
  429. if (usage[version] <= popularity) {
  430. result.push(version)
  431. }
  432. } else if (usage[version] >= popularity) {
  433. result.push(version)
  434. }
  435. return result
  436. }, [])
  437. }
  438. },
  439. {
  440. regexp: /^(>=?|<=?)\s*(\d*\.?\d+)%\s+in\s+((alt-)?\w\w)$/,
  441. select: function (context, sign, popularity, place) {
  442. popularity = parseFloat(popularity)
  443. if (place.length === 2) {
  444. place = place.toUpperCase()
  445. } else {
  446. place = place.toLowerCase()
  447. }
  448. loadCountryStatistics(place)
  449. var usage = browserslist.usage[place]
  450. return Object.keys(usage).reduce(function (result, version) {
  451. if (sign === '>') {
  452. if (usage[version] > popularity) {
  453. result.push(version)
  454. }
  455. } else if (sign === '<') {
  456. if (usage[version] < popularity) {
  457. result.push(version)
  458. }
  459. } else if (sign === '<=') {
  460. if (usage[version] <= popularity) {
  461. result.push(version)
  462. }
  463. } else if (usage[version] >= popularity) {
  464. result.push(version)
  465. }
  466. return result
  467. }, [])
  468. }
  469. },
  470. {
  471. regexp: /^electron\s+([\d.]+)\s*-\s*([\d.]+)$/i,
  472. select: function (context, from, to) {
  473. if (!e2c[from]) {
  474. throw new BrowserslistError('Unknown version ' + from + ' of electron')
  475. }
  476. if (!e2c[to]) {
  477. throw new BrowserslistError('Unknown version ' + to + ' of electron')
  478. }
  479. from = parseFloat(from)
  480. to = parseFloat(to)
  481. return Object.keys(e2c).filter(function (i) {
  482. var parsed = parseFloat(i)
  483. return parsed >= from && parsed <= to
  484. }).map(function (i) {
  485. return 'chrome ' + e2c[i]
  486. })
  487. }
  488. },
  489. {
  490. regexp: /^(\w+)\s+([\d.]+)\s*-\s*([\d.]+)$/i,
  491. select: function (context, name, from, to) {
  492. var data = checkName(name)
  493. from = parseFloat(normalizeVersion(data, from) || from)
  494. to = parseFloat(normalizeVersion(data, to) || to)
  495. function filter (v) {
  496. var parsed = parseFloat(v)
  497. return parsed >= from && parsed <= to
  498. }
  499. return data.released.filter(filter).map(nameMapper(data.name))
  500. }
  501. },
  502. {
  503. regexp: /^electron\s*(>=?|<=?)\s*([\d.]+)$/i,
  504. select: function (context, sign, version) {
  505. return Object.keys(e2c)
  506. .filter(generateFilter(sign, version))
  507. .map(function (i) {
  508. return 'chrome ' + e2c[i]
  509. })
  510. }
  511. },
  512. {
  513. regexp: /^(\w+)\s*(>=?|<=?)\s*([\d.]+)$/,
  514. select: function (context, name, sign, version) {
  515. var data = checkName(name)
  516. var alias = browserslist.versionAliases[data.name][version]
  517. if (alias) {
  518. version = alias
  519. }
  520. return data.released
  521. .filter(generateFilter(sign, version))
  522. .map(function (v) {
  523. return data.name + ' ' + v
  524. })
  525. }
  526. },
  527. {
  528. regexp: /^(firefox|ff|fx)\s+esr$/i,
  529. select: function () {
  530. return ['firefox 52']
  531. }
  532. },
  533. {
  534. regexp: /(operamini|op_mini)\s+all/i,
  535. select: function () {
  536. return ['op_mini all']
  537. }
  538. },
  539. {
  540. regexp: /^electron\s+([\d.]+)$/i,
  541. select: function (context, version) {
  542. var chrome = e2c[version]
  543. if (!chrome) {
  544. throw new BrowserslistError(
  545. 'Unknown version ' + version + ' of electron')
  546. }
  547. return ['chrome ' + chrome]
  548. }
  549. },
  550. {
  551. regexp: /^(\w+)\s+(tp|[\d.]+)$/i,
  552. select: function (context, name, version) {
  553. if (/^tp$/i.test(version)) version = 'TP'
  554. var data = checkName(name)
  555. var alias = normalizeVersion(data, version)
  556. if (alias) {
  557. version = alias
  558. } else {
  559. if (version.indexOf('.') === -1) {
  560. alias = version + '.0'
  561. } else if (/\.0$/.test(version)) {
  562. alias = version.replace(/\.0$/, '')
  563. }
  564. alias = normalizeVersion(data, alias)
  565. if (alias) {
  566. version = alias
  567. } else {
  568. throw new BrowserslistError(
  569. 'Unknown version ' + version + ' of ' + name)
  570. }
  571. }
  572. return [data.name + ' ' + version]
  573. }
  574. },
  575. {
  576. regexp: /^extends (.+)$/i,
  577. select: function (context, name) {
  578. return resolve(env.loadQueries(context, name), context)
  579. }
  580. },
  581. {
  582. regexp: /^defaults$/i,
  583. select: function () {
  584. return browserslist(browserslist.defaults)
  585. }
  586. }
  587. ];
  588. // Get and convert Can I Use data
  589. (function () {
  590. for (var name in agents) {
  591. var browser = agents[name]
  592. browserslist.data[name] = {
  593. name: name,
  594. versions: normalize(agents[name].versions),
  595. released: normalize(agents[name].versions.slice(0, -3)),
  596. releaseDate: agents[name].release_date
  597. }
  598. fillUsage(browserslist.usage.global, name, browser.usage_global)
  599. browserslist.versionAliases[name] = { }
  600. for (var i = 0; i < browser.versions.length; i++) {
  601. var full = browser.versions[i]
  602. if (!full) continue
  603. if (full.indexOf('-') !== -1) {
  604. var interval = full.split('-')
  605. for (var j = 0; j < interval.length; j++) {
  606. browserslist.versionAliases[name][interval[j]] = full
  607. }
  608. }
  609. }
  610. }
  611. }())
  612. module.exports = browserslist