Spaces:
Running
Running
| const SPACE_CHARACTERS = /\s+/g | |
| // hoisted class for cyclic dependency | |
| class Range { | |
| constructor (range, options) { | |
| options = parseOptions(options) | |
| if (range instanceof Range) { | |
| if ( | |
| range.loose === !!options.loose && | |
| range.includePrerelease === !!options.includePrerelease | |
| ) { | |
| return range | |
| } else { | |
| return new Range(range.raw, options) | |
| } | |
| } | |
| if (range instanceof Comparator) { | |
| // just put it in the set and return | |
| this.raw = range.value | |
| this.set = [[range]] | |
| this.formatted = undefined | |
| return this | |
| } | |
| this.options = options | |
| this.loose = !!options.loose | |
| this.includePrerelease = !!options.includePrerelease | |
| // First reduce all whitespace as much as possible so we do not have to rely | |
| // on potentially slow regexes like \s*. This is then stored and used for | |
| // future error messages as well. | |
| this.raw = range.trim().replace(SPACE_CHARACTERS, ' ') | |
| // First, split on || | |
| this.set = this.raw | |
| .split('||') | |
| // map the range to a 2d array of comparators | |
| .map(r => this.parseRange(r.trim())) | |
| // throw out any comparator lists that are empty | |
| // this generally means that it was not a valid range, which is allowed | |
| // in loose mode, but will still throw if the WHOLE range is invalid. | |
| .filter(c => c.length) | |
| if (!this.set.length) { | |
| throw new TypeError(`Invalid SemVer Range: ${this.raw}`) | |
| } | |
| // if we have any that are not the null set, throw out null sets. | |
| if (this.set.length > 1) { | |
| // keep the first one, in case they're all null sets | |
| const first = this.set[0] | |
| this.set = this.set.filter(c => !isNullSet(c[0])) | |
| if (this.set.length === 0) { | |
| this.set = [first] | |
| } else if (this.set.length > 1) { | |
| // if we have any that are *, then the range is just * | |
| for (const c of this.set) { | |
| if (c.length === 1 && isAny(c[0])) { | |
| this.set = [c] | |
| break | |
| } | |
| } | |
| } | |
| } | |
| this.formatted = undefined | |
| } | |
| get range () { | |
| if (this.formatted === undefined) { | |
| this.formatted = '' | |
| for (let i = 0; i < this.set.length; i++) { | |
| if (i > 0) { | |
| this.formatted += '||' | |
| } | |
| const comps = this.set[i] | |
| for (let k = 0; k < comps.length; k++) { | |
| if (k > 0) { | |
| this.formatted += ' ' | |
| } | |
| this.formatted += comps[k].toString().trim() | |
| } | |
| } | |
| } | |
| return this.formatted | |
| } | |
| format () { | |
| return this.range | |
| } | |
| toString () { | |
| return this.range | |
| } | |
| parseRange (range) { | |
| // memoize range parsing for performance. | |
| // this is a very hot path, and fully deterministic. | |
| const memoOpts = | |
| (this.options.includePrerelease && FLAG_INCLUDE_PRERELEASE) | | |
| (this.options.loose && FLAG_LOOSE) | |
| const memoKey = memoOpts + ':' + range | |
| const cached = cache.get(memoKey) | |
| if (cached) { | |
| return cached | |
| } | |
| const loose = this.options.loose | |
| // `1.2.3 - 1.2.4` => `>=1.2.3 <=1.2.4` | |
| const hr = loose ? re[t.HYPHENRANGELOOSE] : re[t.HYPHENRANGE] | |
| range = range.replace(hr, hyphenReplace(this.options.includePrerelease)) | |
| debug('hyphen replace', range) | |
| // `> 1.2.3 < 1.2.5` => `>1.2.3 <1.2.5` | |
| range = range.replace(re[t.COMPARATORTRIM], comparatorTrimReplace) | |
| debug('comparator trim', range) | |
| // `~ 1.2.3` => `~1.2.3` | |
| range = range.replace(re[t.TILDETRIM], tildeTrimReplace) | |
| debug('tilde trim', range) | |
| // `^ 1.2.3` => `^1.2.3` | |
| range = range.replace(re[t.CARETTRIM], caretTrimReplace) | |
| debug('caret trim', range) | |
| // At this point, the range is completely trimmed and | |
| // ready to be split into comparators. | |
| let rangeList = range | |
| .split(' ') | |
| .map(comp => parseComparator(comp, this.options)) | |
| .join(' ') | |
| .split(/\s+/) | |
| // >=0.0.0 is equivalent to * | |
| .map(comp => replaceGTE0(comp, this.options)) | |
| if (loose) { | |
| // in loose mode, throw out any that are not valid comparators | |
| rangeList = rangeList.filter(comp => { | |
| debug('loose invalid filter', comp, this.options) | |
| return !!comp.match(re[t.COMPARATORLOOSE]) | |
| }) | |
| } | |
| debug('range list', rangeList) | |
| // if any comparators are the null set, then replace with JUST null set | |
| // if more than one comparator, remove any * comparators | |
| // also, don't include the same comparator more than once | |
| const rangeMap = new Map() | |
| const comparators = rangeList.map(comp => new Comparator(comp, this.options)) | |
| for (const comp of comparators) { | |
| if (isNullSet(comp)) { | |
| return [comp] | |
| } | |
| rangeMap.set(comp.value, comp) | |
| } | |
| if (rangeMap.size > 1 && rangeMap.has('')) { | |
| rangeMap.delete('') | |
| } | |
| const result = [...rangeMap.values()] | |
| cache.set(memoKey, result) | |
| return result | |
| } | |
| intersects (range, options) { | |
| if (!(range instanceof Range)) { | |
| throw new TypeError('a Range is required') | |
| } | |
| return this.set.some((thisComparators) => { | |
| return ( | |
| isSatisfiable(thisComparators, options) && | |
| range.set.some((rangeComparators) => { | |
| return ( | |
| isSatisfiable(rangeComparators, options) && | |
| thisComparators.every((thisComparator) => { | |
| return rangeComparators.every((rangeComparator) => { | |
| return thisComparator.intersects(rangeComparator, options) | |
| }) | |
| }) | |
| ) | |
| }) | |
| ) | |
| }) | |
| } | |
| // if ANY of the sets match ALL of its comparators, then pass | |
| test (version) { | |
| if (!version) { | |
| return false | |
| } | |
| if (typeof version === 'string') { | |
| try { | |
| version = new SemVer(version, this.options) | |
| } catch (er) { | |
| return false | |
| } | |
| } | |
| for (let i = 0; i < this.set.length; i++) { | |
| if (testSet(this.set[i], version, this.options)) { | |
| return true | |
| } | |
| } | |
| return false | |
| } | |
| } | |
| module.exports = Range | |
| const LRU = require('../internal/lrucache') | |
| const cache = new LRU() | |
| const parseOptions = require('../internal/parse-options') | |
| const Comparator = require('./comparator') | |
| const debug = require('../internal/debug') | |
| const SemVer = require('./semver') | |
| const { | |
| safeRe: re, | |
| t, | |
| comparatorTrimReplace, | |
| tildeTrimReplace, | |
| caretTrimReplace, | |
| } = require('../internal/re') | |
| const { FLAG_INCLUDE_PRERELEASE, FLAG_LOOSE } = require('../internal/constants') | |
| const isNullSet = c => c.value === '<0.0.0-0' | |
| const isAny = c => c.value === '' | |
| // take a set of comparators and determine whether there | |
| // exists a version which can satisfy it | |
| const isSatisfiable = (comparators, options) => { | |
| let result = true | |
| const remainingComparators = comparators.slice() | |
| let testComparator = remainingComparators.pop() | |
| while (result && remainingComparators.length) { | |
| result = remainingComparators.every((otherComparator) => { | |
| return testComparator.intersects(otherComparator, options) | |
| }) | |
| testComparator = remainingComparators.pop() | |
| } | |
| return result | |
| } | |
| // comprised of xranges, tildes, stars, and gtlt's at this point. | |
| // already replaced the hyphen ranges | |
| // turn into a set of JUST comparators. | |
| const parseComparator = (comp, options) => { | |
| comp = comp.replace(re[t.BUILD], '') | |
| debug('comp', comp, options) | |
| comp = replaceCarets(comp, options) | |
| debug('caret', comp) | |
| comp = replaceTildes(comp, options) | |
| debug('tildes', comp) | |
| comp = replaceXRanges(comp, options) | |
| debug('xrange', comp) | |
| comp = replaceStars(comp, options) | |
| debug('stars', comp) | |
| return comp | |
| } | |
| const isX = id => !id || id.toLowerCase() === 'x' || id === '*' | |
| // ~, ~> --> * (any, kinda silly) | |
| // ~2, ~2.x, ~2.x.x, ~>2, ~>2.x ~>2.x.x --> >=2.0.0 <3.0.0-0 | |
| // ~2.0, ~2.0.x, ~>2.0, ~>2.0.x --> >=2.0.0 <2.1.0-0 | |
| // ~1.2, ~1.2.x, ~>1.2, ~>1.2.x --> >=1.2.0 <1.3.0-0 | |
| // ~1.2.3, ~>1.2.3 --> >=1.2.3 <1.3.0-0 | |
| // ~1.2.0, ~>1.2.0 --> >=1.2.0 <1.3.0-0 | |
| // ~0.0.1 --> >=0.0.1 <0.1.0-0 | |
| const replaceTildes = (comp, options) => { | |
| return comp | |
| .trim() | |
| .split(/\s+/) | |
| .map((c) => replaceTilde(c, options)) | |
| .join(' ') | |
| } | |
| const replaceTilde = (comp, options) => { | |
| const r = options.loose ? re[t.TILDELOOSE] : re[t.TILDE] | |
| return comp.replace(r, (_, M, m, p, pr) => { | |
| debug('tilde', comp, _, M, m, p, pr) | |
| let ret | |
| if (isX(M)) { | |
| ret = '' | |
| } else if (isX(m)) { | |
| ret = `>=${M}.0.0 <${+M + 1}.0.0-0` | |
| } else if (isX(p)) { | |
| // ~1.2 == >=1.2.0 <1.3.0-0 | |
| ret = `>=${M}.${m}.0 <${M}.${+m + 1}.0-0` | |
| } else if (pr) { | |
| debug('replaceTilde pr', pr) | |
| ret = `>=${M}.${m}.${p}-${pr | |
| } <${M}.${+m + 1}.0-0` | |
| } else { | |
| // ~1.2.3 == >=1.2.3 <1.3.0-0 | |
| ret = `>=${M}.${m}.${p | |
| } <${M}.${+m + 1}.0-0` | |
| } | |
| debug('tilde return', ret) | |
| return ret | |
| }) | |
| } | |
| // ^ --> * (any, kinda silly) | |
| // ^2, ^2.x, ^2.x.x --> >=2.0.0 <3.0.0-0 | |
| // ^2.0, ^2.0.x --> >=2.0.0 <3.0.0-0 | |
| // ^1.2, ^1.2.x --> >=1.2.0 <2.0.0-0 | |
| // ^1.2.3 --> >=1.2.3 <2.0.0-0 | |
| // ^1.2.0 --> >=1.2.0 <2.0.0-0 | |
| // ^0.0.1 --> >=0.0.1 <0.0.2-0 | |
| // ^0.1.0 --> >=0.1.0 <0.2.0-0 | |
| const replaceCarets = (comp, options) => { | |
| return comp | |
| .trim() | |
| .split(/\s+/) | |
| .map((c) => replaceCaret(c, options)) | |
| .join(' ') | |
| } | |
| const replaceCaret = (comp, options) => { | |
| debug('caret', comp, options) | |
| const r = options.loose ? re[t.CARETLOOSE] : re[t.CARET] | |
| const z = options.includePrerelease ? '-0' : '' | |
| return comp.replace(r, (_, M, m, p, pr) => { | |
| debug('caret', comp, _, M, m, p, pr) | |
| let ret | |
| if (isX(M)) { | |
| ret = '' | |
| } else if (isX(m)) { | |
| ret = `>=${M}.0.0${z} <${+M + 1}.0.0-0` | |
| } else if (isX(p)) { | |
| if (M === '0') { | |
| ret = `>=${M}.${m}.0${z} <${M}.${+m + 1}.0-0` | |
| } else { | |
| ret = `>=${M}.${m}.0${z} <${+M + 1}.0.0-0` | |
| } | |
| } else if (pr) { | |
| debug('replaceCaret pr', pr) | |
| if (M === '0') { | |
| if (m === '0') { | |
| ret = `>=${M}.${m}.${p}-${pr | |
| } <${M}.${m}.${+p + 1}-0` | |
| } else { | |
| ret = `>=${M}.${m}.${p}-${pr | |
| } <${M}.${+m + 1}.0-0` | |
| } | |
| } else { | |
| ret = `>=${M}.${m}.${p}-${pr | |
| } <${+M + 1}.0.0-0` | |
| } | |
| } else { | |
| debug('no pr') | |
| if (M === '0') { | |
| if (m === '0') { | |
| ret = `>=${M}.${m}.${p | |
| }${z} <${M}.${m}.${+p + 1}-0` | |
| } else { | |
| ret = `>=${M}.${m}.${p | |
| }${z} <${M}.${+m + 1}.0-0` | |
| } | |
| } else { | |
| ret = `>=${M}.${m}.${p | |
| } <${+M + 1}.0.0-0` | |
| } | |
| } | |
| debug('caret return', ret) | |
| return ret | |
| }) | |
| } | |
| const replaceXRanges = (comp, options) => { | |
| debug('replaceXRanges', comp, options) | |
| return comp | |
| .split(/\s+/) | |
| .map((c) => replaceXRange(c, options)) | |
| .join(' ') | |
| } | |
| const replaceXRange = (comp, options) => { | |
| comp = comp.trim() | |
| const r = options.loose ? re[t.XRANGELOOSE] : re[t.XRANGE] | |
| return comp.replace(r, (ret, gtlt, M, m, p, pr) => { | |
| debug('xRange', comp, ret, gtlt, M, m, p, pr) | |
| const xM = isX(M) | |
| const xm = xM || isX(m) | |
| const xp = xm || isX(p) | |
| const anyX = xp | |
| if (gtlt === '=' && anyX) { | |
| gtlt = '' | |
| } | |
| // if we're including prereleases in the match, then we need | |
| // to fix this to -0, the lowest possible prerelease value | |
| pr = options.includePrerelease ? '-0' : '' | |
| if (xM) { | |
| if (gtlt === '>' || gtlt === '<') { | |
| // nothing is allowed | |
| ret = '<0.0.0-0' | |
| } else { | |
| // nothing is forbidden | |
| ret = '*' | |
| } | |
| } else if (gtlt && anyX) { | |
| // we know patch is an x, because we have any x at all. | |
| // replace X with 0 | |
| if (xm) { | |
| m = 0 | |
| } | |
| p = 0 | |
| if (gtlt === '>') { | |
| // >1 => >=2.0.0 | |
| // >1.2 => >=1.3.0 | |
| gtlt = '>=' | |
| if (xm) { | |
| M = +M + 1 | |
| m = 0 | |
| p = 0 | |
| } else { | |
| m = +m + 1 | |
| p = 0 | |
| } | |
| } else if (gtlt === '<=') { | |
| // <=0.7.x is actually <0.8.0, since any 0.7.x should | |
| // pass. Similarly, <=7.x is actually <8.0.0, etc. | |
| gtlt = '<' | |
| if (xm) { | |
| M = +M + 1 | |
| } else { | |
| m = +m + 1 | |
| } | |
| } | |
| if (gtlt === '<') { | |
| pr = '-0' | |
| } | |
| ret = `${gtlt + M}.${m}.${p}${pr}` | |
| } else if (xm) { | |
| ret = `>=${M}.0.0${pr} <${+M + 1}.0.0-0` | |
| } else if (xp) { | |
| ret = `>=${M}.${m}.0${pr | |
| } <${M}.${+m + 1}.0-0` | |
| } | |
| debug('xRange return', ret) | |
| return ret | |
| }) | |
| } | |
| // Because * is AND-ed with everything else in the comparator, | |
| // and '' means "any version", just remove the *s entirely. | |
| const replaceStars = (comp, options) => { | |
| debug('replaceStars', comp, options) | |
| // Looseness is ignored here. star is always as loose as it gets! | |
| return comp | |
| .trim() | |
| .replace(re[t.STAR], '') | |
| } | |
| const replaceGTE0 = (comp, options) => { | |
| debug('replaceGTE0', comp, options) | |
| return comp | |
| .trim() | |
| .replace(re[options.includePrerelease ? t.GTE0PRE : t.GTE0], '') | |
| } | |
| // This function is passed to string.replace(re[t.HYPHENRANGE]) | |
| // M, m, patch, prerelease, build | |
| // 1.2 - 3.4.5 => >=1.2.0 <=3.4.5 | |
| // 1.2.3 - 3.4 => >=1.2.0 <3.5.0-0 Any 3.4.x will do | |
| // 1.2 - 3.4 => >=1.2.0 <3.5.0-0 | |
| // TODO build? | |
| const hyphenReplace = incPr => ($0, | |
| from, fM, fm, fp, fpr, fb, | |
| to, tM, tm, tp, tpr) => { | |
| if (isX(fM)) { | |
| from = '' | |
| } else if (isX(fm)) { | |
| from = `>=${fM}.0.0${incPr ? '-0' : ''}` | |
| } else if (isX(fp)) { | |
| from = `>=${fM}.${fm}.0${incPr ? '-0' : ''}` | |
| } else if (fpr) { | |
| from = `>=${from}` | |
| } else { | |
| from = `>=${from}${incPr ? '-0' : ''}` | |
| } | |
| if (isX(tM)) { | |
| to = '' | |
| } else if (isX(tm)) { | |
| to = `<${+tM + 1}.0.0-0` | |
| } else if (isX(tp)) { | |
| to = `<${tM}.${+tm + 1}.0-0` | |
| } else if (tpr) { | |
| to = `<=${tM}.${tm}.${tp}-${tpr}` | |
| } else if (incPr) { | |
| to = `<${tM}.${tm}.${+tp + 1}-0` | |
| } else { | |
| to = `<=${to}` | |
| } | |
| return `${from} ${to}`.trim() | |
| } | |
| const testSet = (set, version, options) => { | |
| for (let i = 0; i < set.length; i++) { | |
| if (!set[i].test(version)) { | |
| return false | |
| } | |
| } | |
| if (version.prerelease.length && !options.includePrerelease) { | |
| // Find the set of versions that are allowed to have prereleases | |
| // For example, ^1.2.3-pr.1 desugars to >=1.2.3-pr.1 <2.0.0 | |
| // That should allow `1.2.3-pr.2` to pass. | |
| // However, `1.2.4-alpha.notready` should NOT be allowed, | |
| // even though it's within the range set by the comparators. | |
| for (let i = 0; i < set.length; i++) { | |
| debug(set[i].semver) | |
| if (set[i].semver === Comparator.ANY) { | |
| continue | |
| } | |
| if (set[i].semver.prerelease.length > 0) { | |
| const allowed = set[i].semver | |
| if (allowed.major === version.major && | |
| allowed.minor === version.minor && | |
| allowed.patch === version.patch) { | |
| return true | |
| } | |
| } | |
| } | |
| // Version has a -pre, but it's not one of the ones we like. | |
| return false | |
| } | |
| return true | |
| } | |