|
const SPACE_CHARACTERS = /\s+/g |
|
|
|
|
|
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) { |
|
|
|
this.raw = range.value |
|
this.set = [[range]] |
|
this.formatted = undefined |
|
return this |
|
} |
|
|
|
this.options = options |
|
this.loose = !!options.loose |
|
this.includePrerelease = !!options.includePrerelease |
|
|
|
|
|
|
|
|
|
this.raw = range.trim().replace(SPACE_CHARACTERS, ' ') |
|
|
|
|
|
this.set = this.raw |
|
.split('||') |
|
|
|
.map(r => this.parseRange(r.trim())) |
|
|
|
|
|
|
|
.filter(c => c.length) |
|
|
|
if (!this.set.length) { |
|
throw new TypeError(`Invalid SemVer Range: ${this.raw}`) |
|
} |
|
|
|
|
|
if (this.set.length > 1) { |
|
|
|
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) { |
|
|
|
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) { |
|
|
|
|
|
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 |
|
|
|
const hr = loose ? re[t.HYPHENRANGELOOSE] : re[t.HYPHENRANGE] |
|
range = range.replace(hr, hyphenReplace(this.options.includePrerelease)) |
|
debug('hyphen replace', range) |
|
|
|
|
|
range = range.replace(re[t.COMPARATORTRIM], comparatorTrimReplace) |
|
debug('comparator trim', range) |
|
|
|
|
|
range = range.replace(re[t.TILDETRIM], tildeTrimReplace) |
|
debug('tilde trim', range) |
|
|
|
|
|
range = range.replace(re[t.CARETTRIM], caretTrimReplace) |
|
debug('caret trim', range) |
|
|
|
|
|
|
|
|
|
let rangeList = range |
|
.split(' ') |
|
.map(comp => parseComparator(comp, this.options)) |
|
.join(' ') |
|
.split(/\s+/) |
|
|
|
.map(comp => replaceGTE0(comp, this.options)) |
|
|
|
if (loose) { |
|
|
|
rangeList = rangeList.filter(comp => { |
|
debug('loose invalid filter', comp, this.options) |
|
return !!comp.match(re[t.COMPARATORLOOSE]) |
|
}) |
|
} |
|
debug('range list', rangeList) |
|
|
|
|
|
|
|
|
|
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) |
|
}) |
|
}) |
|
) |
|
}) |
|
) |
|
}) |
|
} |
|
|
|
|
|
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 === '' |
|
|
|
|
|
|
|
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 |
|
} |
|
|
|
|
|
|
|
|
|
const parseComparator = (comp, options) => { |
|
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 === '*' |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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)) { |
|
|
|
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 { |
|
|
|
ret = `>=${M}.${m}.${p |
|
} <${M}.${+m + 1}.0-0` |
|
} |
|
|
|
debug('tilde return', ret) |
|
return ret |
|
}) |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 = '' |
|
} |
|
|
|
|
|
|
|
pr = options.includePrerelease ? '-0' : '' |
|
|
|
if (xM) { |
|
if (gtlt === '>' || gtlt === '<') { |
|
|
|
ret = '<0.0.0-0' |
|
} else { |
|
|
|
ret = '*' |
|
} |
|
} else if (gtlt && anyX) { |
|
|
|
|
|
if (xm) { |
|
m = 0 |
|
} |
|
p = 0 |
|
|
|
if (gtlt === '>') { |
|
|
|
|
|
gtlt = '>=' |
|
if (xm) { |
|
M = +M + 1 |
|
m = 0 |
|
p = 0 |
|
} else { |
|
m = +m + 1 |
|
p = 0 |
|
} |
|
} else if (gtlt === '<=') { |
|
|
|
|
|
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 |
|
}) |
|
} |
|
|
|
|
|
|
|
const replaceStars = (comp, options) => { |
|
debug('replaceStars', comp, options) |
|
|
|
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], '') |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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) { |
|
|
|
|
|
|
|
|
|
|
|
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 |
|
} |
|
} |
|
} |
|
|
|
|
|
return false |
|
} |
|
|
|
return true |
|
} |
|
|