browserslist 4.28.5 → 4.28.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/index.js +18 -11
  2. package/node.js +7 -5
  3. package/package.json +5 -5
  4. package/parse.js +94 -36
package/index.js CHANGED
@@ -399,8 +399,17 @@ function checkQueries(queries) {
399
399
  }
400
400
  }
401
401
 
402
- var cache = {}
403
- var parseCache = {}
402
+ var CACHE_MAX_ENTRIES = 500
403
+
404
+ function boundedCacheSet(map, key, value) {
405
+ if (map.size >= CACHE_MAX_ENTRIES) {
406
+ map.delete(map.keys().next().value)
407
+ }
408
+ map.set(key, value)
409
+ }
410
+
411
+ var cache = new Map()
412
+ var parseCache = new Map()
404
413
 
405
414
  function browserslist(queries, opts) {
406
415
  opts = prepareOpts(opts)
@@ -432,7 +441,7 @@ function browserslist(queries, opts) {
432
441
  }
433
442
 
434
443
  var cacheKey = JSON.stringify([queries, context])
435
- if (cache[cacheKey]) return cache[cacheKey]
444
+ if (cache.has(cacheKey)) return cache.get(cacheKey)
436
445
 
437
446
  var result = uniq(resolve(queries, context)).sort(function (name1, name2) {
438
447
  name1 = name1.split(' ')
@@ -449,17 +458,17 @@ function browserslist(queries, opts) {
449
458
  }
450
459
  })
451
460
  if (!env.env.BROWSERSLIST_DISABLE_CACHE) {
452
- cache[cacheKey] = result
461
+ boundedCacheSet(cache, cacheKey, result)
453
462
  }
454
463
  return result
455
464
  }
456
465
 
457
466
  function parseQueries(queries) {
458
467
  var cacheKey = JSON.stringify(queries)
459
- if (cacheKey in parseCache) return parseCache[cacheKey]
468
+ if (parseCache.has(cacheKey)) return parseCache.get(cacheKey)
460
469
  var result = parseWithoutCache(QUERIES, queries)
461
470
  if (!env.env.BROWSERSLIST_DISABLE_CACHE) {
462
- parseCache[cacheKey] = result
471
+ boundedCacheSet(parseCache, cacheKey, result)
463
472
  }
464
473
  return result
465
474
  }
@@ -823,7 +832,7 @@ var QUERIES = {
823
832
  // ...with downstream
824
833
  // ...including kaios
825
834
  regexp:
826
- /^baseline\s+(?:(\d+)|(newly|widely)\s+available(?:\s+on\s+(\d{4}-\d{2}-\d{2}))?)?(\s+with\s+downstream)?(\s+including\s+kaios)?$/i,
835
+ /^baseline\s+(?!\s)(?:(\d+)|(newly|widely)\s+(?!\s)available(?:\s+(?!\s)on\s+(?!\s)(\d{4}-\d{2}-\d{2}))?)?(\s+(?!\s)with\s+(?!\s)downstream)?(\s+(?!\s)including\s+(?!\s)kaios)?$/i,
827
836
  select: function (context, node) {
828
837
  var baselineVersions
829
838
  var includeDownstream = !!node.downstream
@@ -1058,10 +1067,8 @@ var QUERIES = {
1058
1067
  throw new BrowserslistError('Unknown version ' + to + ' of electron')
1059
1068
  }
1060
1069
  return Object.keys(e2c)
1061
- .filter(function (i) {
1062
- var parsed = parseFloat(i)
1063
- return parsed >= from && parsed <= to
1064
- })
1070
+ .filter(semverFilterLoose('>=', node.from))
1071
+ .filter(semverFilterLoose('<=', node.to))
1065
1072
  .map(function (i) {
1066
1073
  return 'chrome ' + e2c[i]
1067
1074
  })
package/node.js CHANGED
@@ -7,7 +7,8 @@ var BrowserslistError = require('./error')
7
7
 
8
8
  var IS_SECTION = /^\s*\[(.+)]\s*$/
9
9
  var CONFIG_PATTERN = /^browserslist-config-/
10
- var SCOPED_CONFIG__PATTERN = /@[^/]+(?:\/[^/]+)?\/browserslist-config(?:-|$|\/)/
10
+ var SCOPED_CONFIG__PATTERN =
11
+ /^@[^/]+(?:\/[^/]+)?\/browserslist-config(?:-|$|\/)/
11
12
  var FORMAT =
12
13
  'Browserslist config should be a string or an array ' +
13
14
  'of strings with browser queries'
@@ -221,12 +222,13 @@ function normalizeStats(data, stats) {
221
222
 
222
223
  if (typeof stats !== 'object') return undefined
223
224
 
224
- var normalized = {}
225
+ var normalized = Object.create(null)
225
226
  for (var i in stats) {
226
227
  var versions = Object.keys(stats[i])
227
- if (versions.length === 1 && data[i] && data[i].versions.length === 1) {
228
- var normal = data[i].versions[0]
229
- normalized[i] = {}
228
+ var known = Object.prototype.hasOwnProperty.call(data, i) && data[i]
229
+ if (versions.length === 1 && known && known.versions.length === 1) {
230
+ var normal = known.versions[0]
231
+ normalized[i] = Object.create(null)
230
232
  normalized[i][normal] = stats[i][versions[0]]
231
233
  } else {
232
234
  normalized[i] = stats[i]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "browserslist",
3
- "version": "4.28.5",
3
+ "version": "4.28.7",
4
4
  "description": "Share target browsers between different front-end tools, like Autoprefixer, Stylelint and babel-env-preset",
5
5
  "keywords": [
6
6
  "browsers",
@@ -33,10 +33,10 @@
33
33
  },
34
34
  "types": "./index.d.ts",
35
35
  "dependencies": {
36
- "baseline-browser-mapping": "^2.10.42",
37
- "caniuse-lite": "^1.0.30001800",
38
- "electron-to-chromium": "^1.5.387",
39
- "node-releases": "^2.0.50",
36
+ "baseline-browser-mapping": "^2.10.44",
37
+ "caniuse-lite": "^1.0.30001806",
38
+ "electron-to-chromium": "^1.5.393",
39
+ "node-releases": "^2.0.51",
40
40
  "update-browserslist-db": "^1.2.3"
41
41
  },
42
42
  "engines": {
package/parse.js CHANGED
@@ -1,21 +1,22 @@
1
- var AND_REGEXP = /^\s+and\s+(.*)/i
2
- var OR_REGEXP = /^(?:,\s*|\s+or\s+)(.*)/i
1
+ var SPACE = /\s/
3
2
 
4
3
  function flatten(array) {
5
4
  if (!Array.isArray(array)) return [array]
6
- return array.reduce(function (a, b) {
7
- return a.concat(flatten(b))
8
- }, [])
9
- }
10
-
11
- function find(string, predicate) {
12
- for (var max = string.length, n = 1; n <= max; n++) {
13
- var parsed = string.substr(-n, n)
14
- if (predicate(parsed, n, max)) {
15
- return string.slice(0, -n)
5
+ // Iterative flatten: `reduce`+`concat` copies the accumulator on every step,
6
+ // which is O(n²) once a query resolves to many nodes.
7
+ var result = []
8
+ var stack = [array]
9
+ while (stack.length) {
10
+ var item = stack.pop()
11
+ if (Array.isArray(item)) {
12
+ for (var i = item.length - 1; i >= 0; i--) {
13
+ stack.push(item[i])
14
+ }
15
+ } else {
16
+ result.push(item)
16
17
  }
17
18
  }
18
- return ''
19
+ return result
19
20
  }
20
21
 
21
22
  function matchQuery(all, query) {
@@ -41,27 +42,86 @@ function matchQuery(all, query) {
41
42
  return node
42
43
  }
43
44
 
44
- function matchBlock(all, string, qs) {
45
- var node
46
- return find(string, function (parsed, n, max) {
47
- if (AND_REGEXP.test(parsed)) {
48
- node = matchQuery(all, parsed.match(AND_REGEXP)[1])
49
- node.compose = 'and'
50
- qs.unshift(node)
51
- return true
52
- } else if (OR_REGEXP.test(parsed)) {
53
- node = matchQuery(all, parsed.match(OR_REGEXP)[1])
54
- node.compose = 'or'
55
- qs.unshift(node)
56
- return true
57
- } else if (n === max) {
58
- node = matchQuery(all, parsed.trim())
59
- node.compose = 'or'
60
- qs.unshift(node)
61
- return true
45
+ function pushClause(all, qs, text, compose) {
46
+ var node = matchQuery(all, text.trim())
47
+ node.compose = compose
48
+ qs.push(node)
49
+ }
50
+
51
+ // Splits a query block into clauses on the `\s+and\s+`, `\s+or\s+` and `,\s*`
52
+ // delimiters in a single left-to-right pass. The previous implementation grew
53
+ // a suffix one character at a time and re-tested anchored `\s+…` regexps at
54
+ // every length, which backtracks across whitespace runs and is O() — a
55
+ // small padded query could freeze the event loop for tens of seconds.
56
+ function parseBlock(all, block, qs) {
57
+ if (block.length === 0) return
58
+
59
+ var len = block.length
60
+ var clauseStart = 0
61
+ // Compose for the clause currently being read; the leftmost clause is `or`.
62
+ var compose = 'or'
63
+ var i = 0
64
+
65
+ while (i < len) {
66
+ var ch = block[i]
67
+
68
+ if (ch === ',') {
69
+ // `,\s*` delimiter. A delimiter at the very start (i === 0) has no left
70
+ // clause — the original never emits one there.
71
+ if (i !== 0) pushClause(all, qs, block.slice(clauseStart, i), compose)
72
+ i++
73
+ while (i < len && SPACE.test(block[i])) i++
74
+ compose = 'or'
75
+ clauseStart = i
76
+ continue
62
77
  }
63
- return false
64
- })
78
+
79
+ if (SPACE.test(ch)) {
80
+ // Possible `\s+and\s+` or `\s+or\s+`. Scan the whitespace run once
81
+ // (linear, no backtracking), then check the following keyword.
82
+ var q = i
83
+ while (q < len && SPACE.test(block[q])) q++
84
+
85
+ if (
86
+ q + 3 < len &&
87
+ (block[q] === 'a' || block[q] === 'A') &&
88
+ (block[q + 1] === 'n' || block[q + 1] === 'N') &&
89
+ (block[q + 2] === 'd' || block[q + 2] === 'D') &&
90
+ SPACE.test(block[q + 3])
91
+ ) {
92
+ // The leading `\s+` of `\s+and\s+` absorbs whitespace at the block
93
+ // start, so a delimiter at i === 0 has no left clause.
94
+ if (i !== 0) pushClause(all, qs, block.slice(clauseStart, i), compose)
95
+ var afterAnd = q + 3
96
+ while (afterAnd < len && SPACE.test(block[afterAnd])) afterAnd++
97
+ compose = 'and'
98
+ i = afterAnd
99
+ clauseStart = afterAnd
100
+ continue
101
+ } else if (
102
+ q + 2 < len &&
103
+ (block[q] === 'o' || block[q] === 'O') &&
104
+ (block[q + 1] === 'r' || block[q + 1] === 'R') &&
105
+ SPACE.test(block[q + 2])
106
+ ) {
107
+ if (i !== 0) pushClause(all, qs, block.slice(clauseStart, i), compose)
108
+ var afterOr = q + 2
109
+ while (afterOr < len && SPACE.test(block[afterOr])) afterOr++
110
+ compose = 'or'
111
+ i = afterOr
112
+ clauseStart = afterOr
113
+ continue
114
+ } else {
115
+ // Whitespace inside a clause; skip the run and keep reading.
116
+ i = q
117
+ continue
118
+ }
119
+ }
120
+
121
+ i++
122
+ }
123
+
124
+ pushClause(all, qs, block.slice(clauseStart), compose)
65
125
  }
66
126
 
67
127
  module.exports = function parse(all, queries) {
@@ -69,9 +129,7 @@ module.exports = function parse(all, queries) {
69
129
  return flatten(
70
130
  queries.map(function (block) {
71
131
  var qs = []
72
- do {
73
- block = matchBlock(all, block, qs)
74
- } while (block)
132
+ parseBlock(all, block, qs)
75
133
  return qs
76
134
  })
77
135
  )