1 | // hoisted class for cyclic dependency
|
---|
2 | class Range {
|
---|
3 | constructor (range, options) {
|
---|
4 | options = parseOptions(options)
|
---|
5 |
|
---|
6 | if (range instanceof Range) {
|
---|
7 | if (
|
---|
8 | range.loose === !!options.loose &&
|
---|
9 | range.includePrerelease === !!options.includePrerelease
|
---|
10 | ) {
|
---|
11 | return range
|
---|
12 | } else {
|
---|
13 | return new Range(range.raw, options)
|
---|
14 | }
|
---|
15 | }
|
---|
16 |
|
---|
17 | if (range instanceof Comparator) {
|
---|
18 | // just put it in the set and return
|
---|
19 | this.raw = range.value
|
---|
20 | this.set = [[range]]
|
---|
21 | this.format()
|
---|
22 | return this
|
---|
23 | }
|
---|
24 |
|
---|
25 | this.options = options
|
---|
26 | this.loose = !!options.loose
|
---|
27 | this.includePrerelease = !!options.includePrerelease
|
---|
28 |
|
---|
29 | // First, split based on boolean or ||
|
---|
30 | this.raw = range
|
---|
31 | this.set = range
|
---|
32 | .split(/\s*\|\|\s*/)
|
---|
33 | // map the range to a 2d array of comparators
|
---|
34 | .map(range => this.parseRange(range.trim()))
|
---|
35 | // throw out any comparator lists that are empty
|
---|
36 | // this generally means that it was not a valid range, which is allowed
|
---|
37 | // in loose mode, but will still throw if the WHOLE range is invalid.
|
---|
38 | .filter(c => c.length)
|
---|
39 |
|
---|
40 | if (!this.set.length) {
|
---|
41 | throw new TypeError(`Invalid SemVer Range: ${range}`)
|
---|
42 | }
|
---|
43 |
|
---|
44 | // if we have any that are not the null set, throw out null sets.
|
---|
45 | if (this.set.length > 1) {
|
---|
46 | // keep the first one, in case they're all null sets
|
---|
47 | const first = this.set[0]
|
---|
48 | this.set = this.set.filter(c => !isNullSet(c[0]))
|
---|
49 | if (this.set.length === 0)
|
---|
50 | this.set = [first]
|
---|
51 | else if (this.set.length > 1) {
|
---|
52 | // if we have any that are *, then the range is just *
|
---|
53 | for (const c of this.set) {
|
---|
54 | if (c.length === 1 && isAny(c[0])) {
|
---|
55 | this.set = [c]
|
---|
56 | break
|
---|
57 | }
|
---|
58 | }
|
---|
59 | }
|
---|
60 | }
|
---|
61 |
|
---|
62 | this.format()
|
---|
63 | }
|
---|
64 |
|
---|
65 | format () {
|
---|
66 | this.range = this.set
|
---|
67 | .map((comps) => {
|
---|
68 | return comps.join(' ').trim()
|
---|
69 | })
|
---|
70 | .join('||')
|
---|
71 | .trim()
|
---|
72 | return this.range
|
---|
73 | }
|
---|
74 |
|
---|
75 | toString () {
|
---|
76 | return this.range
|
---|
77 | }
|
---|
78 |
|
---|
79 | parseRange (range) {
|
---|
80 | range = range.trim()
|
---|
81 |
|
---|
82 | // memoize range parsing for performance.
|
---|
83 | // this is a very hot path, and fully deterministic.
|
---|
84 | const memoOpts = Object.keys(this.options).join(',')
|
---|
85 | const memoKey = `parseRange:${memoOpts}:${range}`
|
---|
86 | const cached = cache.get(memoKey)
|
---|
87 | if (cached)
|
---|
88 | return cached
|
---|
89 |
|
---|
90 | const loose = this.options.loose
|
---|
91 | // `1.2.3 - 1.2.4` => `>=1.2.3 <=1.2.4`
|
---|
92 | const hr = loose ? re[t.HYPHENRANGELOOSE] : re[t.HYPHENRANGE]
|
---|
93 | range = range.replace(hr, hyphenReplace(this.options.includePrerelease))
|
---|
94 | debug('hyphen replace', range)
|
---|
95 | // `> 1.2.3 < 1.2.5` => `>1.2.3 <1.2.5`
|
---|
96 | range = range.replace(re[t.COMPARATORTRIM], comparatorTrimReplace)
|
---|
97 | debug('comparator trim', range, re[t.COMPARATORTRIM])
|
---|
98 |
|
---|
99 | // `~ 1.2.3` => `~1.2.3`
|
---|
100 | range = range.replace(re[t.TILDETRIM], tildeTrimReplace)
|
---|
101 |
|
---|
102 | // `^ 1.2.3` => `^1.2.3`
|
---|
103 | range = range.replace(re[t.CARETTRIM], caretTrimReplace)
|
---|
104 |
|
---|
105 | // normalize spaces
|
---|
106 | range = range.split(/\s+/).join(' ')
|
---|
107 |
|
---|
108 | // At this point, the range is completely trimmed and
|
---|
109 | // ready to be split into comparators.
|
---|
110 |
|
---|
111 | const compRe = loose ? re[t.COMPARATORLOOSE] : re[t.COMPARATOR]
|
---|
112 | const rangeList = range
|
---|
113 | .split(' ')
|
---|
114 | .map(comp => parseComparator(comp, this.options))
|
---|
115 | .join(' ')
|
---|
116 | .split(/\s+/)
|
---|
117 | // >=0.0.0 is equivalent to *
|
---|
118 | .map(comp => replaceGTE0(comp, this.options))
|
---|
119 | // in loose mode, throw out any that are not valid comparators
|
---|
120 | .filter(this.options.loose ? comp => !!comp.match(compRe) : () => true)
|
---|
121 | .map(comp => new Comparator(comp, this.options))
|
---|
122 |
|
---|
123 | // if any comparators are the null set, then replace with JUST null set
|
---|
124 | // if more than one comparator, remove any * comparators
|
---|
125 | // also, don't include the same comparator more than once
|
---|
126 | const l = rangeList.length
|
---|
127 | const rangeMap = new Map()
|
---|
128 | for (const comp of rangeList) {
|
---|
129 | if (isNullSet(comp))
|
---|
130 | return [comp]
|
---|
131 | rangeMap.set(comp.value, comp)
|
---|
132 | }
|
---|
133 | if (rangeMap.size > 1 && rangeMap.has(''))
|
---|
134 | rangeMap.delete('')
|
---|
135 |
|
---|
136 | const result = [...rangeMap.values()]
|
---|
137 | cache.set(memoKey, result)
|
---|
138 | return result
|
---|
139 | }
|
---|
140 |
|
---|
141 | intersects (range, options) {
|
---|
142 | if (!(range instanceof Range)) {
|
---|
143 | throw new TypeError('a Range is required')
|
---|
144 | }
|
---|
145 |
|
---|
146 | return this.set.some((thisComparators) => {
|
---|
147 | return (
|
---|
148 | isSatisfiable(thisComparators, options) &&
|
---|
149 | range.set.some((rangeComparators) => {
|
---|
150 | return (
|
---|
151 | isSatisfiable(rangeComparators, options) &&
|
---|
152 | thisComparators.every((thisComparator) => {
|
---|
153 | return rangeComparators.every((rangeComparator) => {
|
---|
154 | return thisComparator.intersects(rangeComparator, options)
|
---|
155 | })
|
---|
156 | })
|
---|
157 | )
|
---|
158 | })
|
---|
159 | )
|
---|
160 | })
|
---|
161 | }
|
---|
162 |
|
---|
163 | // if ANY of the sets match ALL of its comparators, then pass
|
---|
164 | test (version) {
|
---|
165 | if (!version) {
|
---|
166 | return false
|
---|
167 | }
|
---|
168 |
|
---|
169 | if (typeof version === 'string') {
|
---|
170 | try {
|
---|
171 | version = new SemVer(version, this.options)
|
---|
172 | } catch (er) {
|
---|
173 | return false
|
---|
174 | }
|
---|
175 | }
|
---|
176 |
|
---|
177 | for (let i = 0; i < this.set.length; i++) {
|
---|
178 | if (testSet(this.set[i], version, this.options)) {
|
---|
179 | return true
|
---|
180 | }
|
---|
181 | }
|
---|
182 | return false
|
---|
183 | }
|
---|
184 | }
|
---|
185 | module.exports = Range
|
---|
186 |
|
---|
187 | const LRU = require('lru-cache')
|
---|
188 | const cache = new LRU({ max: 1000 })
|
---|
189 |
|
---|
190 | const parseOptions = require('../internal/parse-options')
|
---|
191 | const Comparator = require('./comparator')
|
---|
192 | const debug = require('../internal/debug')
|
---|
193 | const SemVer = require('./semver')
|
---|
194 | const {
|
---|
195 | re,
|
---|
196 | t,
|
---|
197 | comparatorTrimReplace,
|
---|
198 | tildeTrimReplace,
|
---|
199 | caretTrimReplace
|
---|
200 | } = require('../internal/re')
|
---|
201 |
|
---|
202 | const isNullSet = c => c.value === '<0.0.0-0'
|
---|
203 | const isAny = c => c.value === ''
|
---|
204 |
|
---|
205 | // take a set of comparators and determine whether there
|
---|
206 | // exists a version which can satisfy it
|
---|
207 | const isSatisfiable = (comparators, options) => {
|
---|
208 | let result = true
|
---|
209 | const remainingComparators = comparators.slice()
|
---|
210 | let testComparator = remainingComparators.pop()
|
---|
211 |
|
---|
212 | while (result && remainingComparators.length) {
|
---|
213 | result = remainingComparators.every((otherComparator) => {
|
---|
214 | return testComparator.intersects(otherComparator, options)
|
---|
215 | })
|
---|
216 |
|
---|
217 | testComparator = remainingComparators.pop()
|
---|
218 | }
|
---|
219 |
|
---|
220 | return result
|
---|
221 | }
|
---|
222 |
|
---|
223 | // comprised of xranges, tildes, stars, and gtlt's at this point.
|
---|
224 | // already replaced the hyphen ranges
|
---|
225 | // turn into a set of JUST comparators.
|
---|
226 | const parseComparator = (comp, options) => {
|
---|
227 | debug('comp', comp, options)
|
---|
228 | comp = replaceCarets(comp, options)
|
---|
229 | debug('caret', comp)
|
---|
230 | comp = replaceTildes(comp, options)
|
---|
231 | debug('tildes', comp)
|
---|
232 | comp = replaceXRanges(comp, options)
|
---|
233 | debug('xrange', comp)
|
---|
234 | comp = replaceStars(comp, options)
|
---|
235 | debug('stars', comp)
|
---|
236 | return comp
|
---|
237 | }
|
---|
238 |
|
---|
239 | const isX = id => !id || id.toLowerCase() === 'x' || id === '*'
|
---|
240 |
|
---|
241 | // ~, ~> --> * (any, kinda silly)
|
---|
242 | // ~2, ~2.x, ~2.x.x, ~>2, ~>2.x ~>2.x.x --> >=2.0.0 <3.0.0-0
|
---|
243 | // ~2.0, ~2.0.x, ~>2.0, ~>2.0.x --> >=2.0.0 <2.1.0-0
|
---|
244 | // ~1.2, ~1.2.x, ~>1.2, ~>1.2.x --> >=1.2.0 <1.3.0-0
|
---|
245 | // ~1.2.3, ~>1.2.3 --> >=1.2.3 <1.3.0-0
|
---|
246 | // ~1.2.0, ~>1.2.0 --> >=1.2.0 <1.3.0-0
|
---|
247 | const replaceTildes = (comp, options) =>
|
---|
248 | comp.trim().split(/\s+/).map((comp) => {
|
---|
249 | return replaceTilde(comp, options)
|
---|
250 | }).join(' ')
|
---|
251 |
|
---|
252 | const replaceTilde = (comp, options) => {
|
---|
253 | const r = options.loose ? re[t.TILDELOOSE] : re[t.TILDE]
|
---|
254 | return comp.replace(r, (_, M, m, p, pr) => {
|
---|
255 | debug('tilde', comp, _, M, m, p, pr)
|
---|
256 | let ret
|
---|
257 |
|
---|
258 | if (isX(M)) {
|
---|
259 | ret = ''
|
---|
260 | } else if (isX(m)) {
|
---|
261 | ret = `>=${M}.0.0 <${+M + 1}.0.0-0`
|
---|
262 | } else if (isX(p)) {
|
---|
263 | // ~1.2 == >=1.2.0 <1.3.0-0
|
---|
264 | ret = `>=${M}.${m}.0 <${M}.${+m + 1}.0-0`
|
---|
265 | } else if (pr) {
|
---|
266 | debug('replaceTilde pr', pr)
|
---|
267 | ret = `>=${M}.${m}.${p}-${pr
|
---|
268 | } <${M}.${+m + 1}.0-0`
|
---|
269 | } else {
|
---|
270 | // ~1.2.3 == >=1.2.3 <1.3.0-0
|
---|
271 | ret = `>=${M}.${m}.${p
|
---|
272 | } <${M}.${+m + 1}.0-0`
|
---|
273 | }
|
---|
274 |
|
---|
275 | debug('tilde return', ret)
|
---|
276 | return ret
|
---|
277 | })
|
---|
278 | }
|
---|
279 |
|
---|
280 | // ^ --> * (any, kinda silly)
|
---|
281 | // ^2, ^2.x, ^2.x.x --> >=2.0.0 <3.0.0-0
|
---|
282 | // ^2.0, ^2.0.x --> >=2.0.0 <3.0.0-0
|
---|
283 | // ^1.2, ^1.2.x --> >=1.2.0 <2.0.0-0
|
---|
284 | // ^1.2.3 --> >=1.2.3 <2.0.0-0
|
---|
285 | // ^1.2.0 --> >=1.2.0 <2.0.0-0
|
---|
286 | const replaceCarets = (comp, options) =>
|
---|
287 | comp.trim().split(/\s+/).map((comp) => {
|
---|
288 | return replaceCaret(comp, options)
|
---|
289 | }).join(' ')
|
---|
290 |
|
---|
291 | const replaceCaret = (comp, options) => {
|
---|
292 | debug('caret', comp, options)
|
---|
293 | const r = options.loose ? re[t.CARETLOOSE] : re[t.CARET]
|
---|
294 | const z = options.includePrerelease ? '-0' : ''
|
---|
295 | return comp.replace(r, (_, M, m, p, pr) => {
|
---|
296 | debug('caret', comp, _, M, m, p, pr)
|
---|
297 | let ret
|
---|
298 |
|
---|
299 | if (isX(M)) {
|
---|
300 | ret = ''
|
---|
301 | } else if (isX(m)) {
|
---|
302 | ret = `>=${M}.0.0${z} <${+M + 1}.0.0-0`
|
---|
303 | } else if (isX(p)) {
|
---|
304 | if (M === '0') {
|
---|
305 | ret = `>=${M}.${m}.0${z} <${M}.${+m + 1}.0-0`
|
---|
306 | } else {
|
---|
307 | ret = `>=${M}.${m}.0${z} <${+M + 1}.0.0-0`
|
---|
308 | }
|
---|
309 | } else if (pr) {
|
---|
310 | debug('replaceCaret pr', pr)
|
---|
311 | if (M === '0') {
|
---|
312 | if (m === '0') {
|
---|
313 | ret = `>=${M}.${m}.${p}-${pr
|
---|
314 | } <${M}.${m}.${+p + 1}-0`
|
---|
315 | } else {
|
---|
316 | ret = `>=${M}.${m}.${p}-${pr
|
---|
317 | } <${M}.${+m + 1}.0-0`
|
---|
318 | }
|
---|
319 | } else {
|
---|
320 | ret = `>=${M}.${m}.${p}-${pr
|
---|
321 | } <${+M + 1}.0.0-0`
|
---|
322 | }
|
---|
323 | } else {
|
---|
324 | debug('no pr')
|
---|
325 | if (M === '0') {
|
---|
326 | if (m === '0') {
|
---|
327 | ret = `>=${M}.${m}.${p
|
---|
328 | }${z} <${M}.${m}.${+p + 1}-0`
|
---|
329 | } else {
|
---|
330 | ret = `>=${M}.${m}.${p
|
---|
331 | }${z} <${M}.${+m + 1}.0-0`
|
---|
332 | }
|
---|
333 | } else {
|
---|
334 | ret = `>=${M}.${m}.${p
|
---|
335 | } <${+M + 1}.0.0-0`
|
---|
336 | }
|
---|
337 | }
|
---|
338 |
|
---|
339 | debug('caret return', ret)
|
---|
340 | return ret
|
---|
341 | })
|
---|
342 | }
|
---|
343 |
|
---|
344 | const replaceXRanges = (comp, options) => {
|
---|
345 | debug('replaceXRanges', comp, options)
|
---|
346 | return comp.split(/\s+/).map((comp) => {
|
---|
347 | return replaceXRange(comp, options)
|
---|
348 | }).join(' ')
|
---|
349 | }
|
---|
350 |
|
---|
351 | const replaceXRange = (comp, options) => {
|
---|
352 | comp = comp.trim()
|
---|
353 | const r = options.loose ? re[t.XRANGELOOSE] : re[t.XRANGE]
|
---|
354 | return comp.replace(r, (ret, gtlt, M, m, p, pr) => {
|
---|
355 | debug('xRange', comp, ret, gtlt, M, m, p, pr)
|
---|
356 | const xM = isX(M)
|
---|
357 | const xm = xM || isX(m)
|
---|
358 | const xp = xm || isX(p)
|
---|
359 | const anyX = xp
|
---|
360 |
|
---|
361 | if (gtlt === '=' && anyX) {
|
---|
362 | gtlt = ''
|
---|
363 | }
|
---|
364 |
|
---|
365 | // if we're including prereleases in the match, then we need
|
---|
366 | // to fix this to -0, the lowest possible prerelease value
|
---|
367 | pr = options.includePrerelease ? '-0' : ''
|
---|
368 |
|
---|
369 | if (xM) {
|
---|
370 | if (gtlt === '>' || gtlt === '<') {
|
---|
371 | // nothing is allowed
|
---|
372 | ret = '<0.0.0-0'
|
---|
373 | } else {
|
---|
374 | // nothing is forbidden
|
---|
375 | ret = '*'
|
---|
376 | }
|
---|
377 | } else if (gtlt && anyX) {
|
---|
378 | // we know patch is an x, because we have any x at all.
|
---|
379 | // replace X with 0
|
---|
380 | if (xm) {
|
---|
381 | m = 0
|
---|
382 | }
|
---|
383 | p = 0
|
---|
384 |
|
---|
385 | if (gtlt === '>') {
|
---|
386 | // >1 => >=2.0.0
|
---|
387 | // >1.2 => >=1.3.0
|
---|
388 | gtlt = '>='
|
---|
389 | if (xm) {
|
---|
390 | M = +M + 1
|
---|
391 | m = 0
|
---|
392 | p = 0
|
---|
393 | } else {
|
---|
394 | m = +m + 1
|
---|
395 | p = 0
|
---|
396 | }
|
---|
397 | } else if (gtlt === '<=') {
|
---|
398 | // <=0.7.x is actually <0.8.0, since any 0.7.x should
|
---|
399 | // pass. Similarly, <=7.x is actually <8.0.0, etc.
|
---|
400 | gtlt = '<'
|
---|
401 | if (xm) {
|
---|
402 | M = +M + 1
|
---|
403 | } else {
|
---|
404 | m = +m + 1
|
---|
405 | }
|
---|
406 | }
|
---|
407 |
|
---|
408 | if (gtlt === '<')
|
---|
409 | pr = '-0'
|
---|
410 |
|
---|
411 | ret = `${gtlt + M}.${m}.${p}${pr}`
|
---|
412 | } else if (xm) {
|
---|
413 | ret = `>=${M}.0.0${pr} <${+M + 1}.0.0-0`
|
---|
414 | } else if (xp) {
|
---|
415 | ret = `>=${M}.${m}.0${pr
|
---|
416 | } <${M}.${+m + 1}.0-0`
|
---|
417 | }
|
---|
418 |
|
---|
419 | debug('xRange return', ret)
|
---|
420 |
|
---|
421 | return ret
|
---|
422 | })
|
---|
423 | }
|
---|
424 |
|
---|
425 | // Because * is AND-ed with everything else in the comparator,
|
---|
426 | // and '' means "any version", just remove the *s entirely.
|
---|
427 | const replaceStars = (comp, options) => {
|
---|
428 | debug('replaceStars', comp, options)
|
---|
429 | // Looseness is ignored here. star is always as loose as it gets!
|
---|
430 | return comp.trim().replace(re[t.STAR], '')
|
---|
431 | }
|
---|
432 |
|
---|
433 | const replaceGTE0 = (comp, options) => {
|
---|
434 | debug('replaceGTE0', comp, options)
|
---|
435 | return comp.trim()
|
---|
436 | .replace(re[options.includePrerelease ? t.GTE0PRE : t.GTE0], '')
|
---|
437 | }
|
---|
438 |
|
---|
439 | // This function is passed to string.replace(re[t.HYPHENRANGE])
|
---|
440 | // M, m, patch, prerelease, build
|
---|
441 | // 1.2 - 3.4.5 => >=1.2.0 <=3.4.5
|
---|
442 | // 1.2.3 - 3.4 => >=1.2.0 <3.5.0-0 Any 3.4.x will do
|
---|
443 | // 1.2 - 3.4 => >=1.2.0 <3.5.0-0
|
---|
444 | const hyphenReplace = incPr => ($0,
|
---|
445 | from, fM, fm, fp, fpr, fb,
|
---|
446 | to, tM, tm, tp, tpr, tb) => {
|
---|
447 | if (isX(fM)) {
|
---|
448 | from = ''
|
---|
449 | } else if (isX(fm)) {
|
---|
450 | from = `>=${fM}.0.0${incPr ? '-0' : ''}`
|
---|
451 | } else if (isX(fp)) {
|
---|
452 | from = `>=${fM}.${fm}.0${incPr ? '-0' : ''}`
|
---|
453 | } else if (fpr) {
|
---|
454 | from = `>=${from}`
|
---|
455 | } else {
|
---|
456 | from = `>=${from}${incPr ? '-0' : ''}`
|
---|
457 | }
|
---|
458 |
|
---|
459 | if (isX(tM)) {
|
---|
460 | to = ''
|
---|
461 | } else if (isX(tm)) {
|
---|
462 | to = `<${+tM + 1}.0.0-0`
|
---|
463 | } else if (isX(tp)) {
|
---|
464 | to = `<${tM}.${+tm + 1}.0-0`
|
---|
465 | } else if (tpr) {
|
---|
466 | to = `<=${tM}.${tm}.${tp}-${tpr}`
|
---|
467 | } else if (incPr) {
|
---|
468 | to = `<${tM}.${tm}.${+tp + 1}-0`
|
---|
469 | } else {
|
---|
470 | to = `<=${to}`
|
---|
471 | }
|
---|
472 |
|
---|
473 | return (`${from} ${to}`).trim()
|
---|
474 | }
|
---|
475 |
|
---|
476 | const testSet = (set, version, options) => {
|
---|
477 | for (let i = 0; i < set.length; i++) {
|
---|
478 | if (!set[i].test(version)) {
|
---|
479 | return false
|
---|
480 | }
|
---|
481 | }
|
---|
482 |
|
---|
483 | if (version.prerelease.length && !options.includePrerelease) {
|
---|
484 | // Find the set of versions that are allowed to have prereleases
|
---|
485 | // For example, ^1.2.3-pr.1 desugars to >=1.2.3-pr.1 <2.0.0
|
---|
486 | // That should allow `1.2.3-pr.2` to pass.
|
---|
487 | // However, `1.2.4-alpha.notready` should NOT be allowed,
|
---|
488 | // even though it's within the range set by the comparators.
|
---|
489 | for (let i = 0; i < set.length; i++) {
|
---|
490 | debug(set[i].semver)
|
---|
491 | if (set[i].semver === Comparator.ANY) {
|
---|
492 | continue
|
---|
493 | }
|
---|
494 |
|
---|
495 | if (set[i].semver.prerelease.length > 0) {
|
---|
496 | const allowed = set[i].semver
|
---|
497 | if (allowed.major === version.major &&
|
---|
498 | allowed.minor === version.minor &&
|
---|
499 | allowed.patch === version.patch) {
|
---|
500 | return true
|
---|
501 | }
|
---|
502 | }
|
---|
503 | }
|
---|
504 |
|
---|
505 | // Version has a -pre, but it's not one of the ones we like.
|
---|
506 | return false
|
---|
507 | }
|
---|
508 |
|
---|
509 | return true
|
---|
510 | }
|
---|