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