1 // hoisted class for cyclic dependency
3 constructor (range, options) {
4 if (!options || typeof options !== 'object') {
7 includePrerelease: false
11 if (range instanceof Range) {
13 range.loose === !!options.loose &&
14 range.includePrerelease === !!options.includePrerelease
18 return new Range(range.raw, options)
22 if (range instanceof Comparator) {
23 // just put it in the set and return
24 this.raw = range.value
30 this.options = options
31 this.loose = !!options.loose
32 this.includePrerelease = !!options.includePrerelease
34 // First, split based on boolean or ||
38 // map the range to a 2d array of comparators
39 .map(range => this.parseRange(range.trim()))
40 // throw out any comparator lists that are empty
41 // this generally means that it was not a valid range, which is allowed
42 // in loose mode, but will still throw if the WHOLE range is invalid.
43 .filter(c => c.length)
45 if (!this.set.length) {
46 throw new TypeError(`Invalid SemVer Range: ${range}`)
55 return comps.join(' ').trim()
67 const loose = this.options.loose
69 // `1.2.3 - 1.2.4` => `>=1.2.3 <=1.2.4`
70 const hr = loose ? re[t.HYPHENRANGELOOSE] : re[t.HYPHENRANGE]
71 range = range.replace(hr, hyphenReplace(this.options.includePrerelease))
72 debug('hyphen replace', range)
73 // `> 1.2.3 < 1.2.5` => `>1.2.3 <1.2.5`
74 range = range.replace(re[t.COMPARATORTRIM], comparatorTrimReplace)
75 debug('comparator trim', range, re[t.COMPARATORTRIM])
77 // `~ 1.2.3` => `~1.2.3`
78 range = range.replace(re[t.TILDETRIM], tildeTrimReplace)
80 // `^ 1.2.3` => `^1.2.3`
81 range = range.replace(re[t.CARETTRIM], caretTrimReplace)
84 range = range.split(/\s+/).join(' ')
86 // At this point, the range is completely trimmed and
87 // ready to be split into comparators.
89 const compRe = loose ? re[t.COMPARATORLOOSE] : re[t.COMPARATOR]
92 .map(comp => parseComparator(comp, this.options))
95 .map(comp => replaceGTE0(comp, this.options))
96 // in loose mode, throw out any that are not valid comparators
97 .filter(this.options.loose ? comp => !!comp.match(compRe) : () => true)
98 .map(comp => new Comparator(comp, this.options))
101 intersects (range, options) {
102 if (!(range instanceof Range)) {
103 throw new TypeError('a Range is required')
106 return this.set.some((thisComparators) => {
108 isSatisfiable(thisComparators, options) &&
109 range.set.some((rangeComparators) => {
111 isSatisfiable(rangeComparators, options) &&
112 thisComparators.every((thisComparator) => {
113 return rangeComparators.every((rangeComparator) => {
114 return thisComparator.intersects(rangeComparator, options)
123 // if ANY of the sets match ALL of its comparators, then pass
129 if (typeof version === 'string') {
131 version = new SemVer(version, this.options)
137 for (let i = 0; i < this.set.length; i++) {
138 if (testSet(this.set[i], version, this.options)) {
145 module.exports = Range
147 const Comparator = require('./comparator')
148 const debug = require('../internal/debug')
149 const SemVer = require('./semver')
153 comparatorTrimReplace,
156 } = require('../internal/re')
158 // take a set of comparators and determine whether there
159 // exists a version which can satisfy it
160 const isSatisfiable = (comparators, options) => {
162 const remainingComparators = comparators.slice()
163 let testComparator = remainingComparators.pop()
165 while (result && remainingComparators.length) {
166 result = remainingComparators.every((otherComparator) => {
167 return testComparator.intersects(otherComparator, options)
170 testComparator = remainingComparators.pop()
176 // comprised of xranges, tildes, stars, and gtlt's at this point.
177 // already replaced the hyphen ranges
178 // turn into a set of JUST comparators.
179 const parseComparator = (comp, options) => {
180 debug('comp', comp, options)
181 comp = replaceCarets(comp, options)
183 comp = replaceTildes(comp, options)
184 debug('tildes', comp)
185 comp = replaceXRanges(comp, options)
186 debug('xrange', comp)
187 comp = replaceStars(comp, options)
192 const isX = id => !id || id.toLowerCase() === 'x' || id === '*'
194 // ~, ~> --> * (any, kinda silly)
195 // ~2, ~2.x, ~2.x.x, ~>2, ~>2.x ~>2.x.x --> >=2.0.0 <3.0.0-0
196 // ~2.0, ~2.0.x, ~>2.0, ~>2.0.x --> >=2.0.0 <2.1.0-0
197 // ~1.2, ~1.2.x, ~>1.2, ~>1.2.x --> >=1.2.0 <1.3.0-0
198 // ~1.2.3, ~>1.2.3 --> >=1.2.3 <1.3.0-0
199 // ~1.2.0, ~>1.2.0 --> >=1.2.0 <1.3.0-0
200 const replaceTildes = (comp, options) =>
201 comp.trim().split(/\s+/).map((comp) => {
202 return replaceTilde(comp, options)
205 const replaceTilde = (comp, options) => {
206 const r = options.loose ? re[t.TILDELOOSE] : re[t.TILDE]
207 return comp.replace(r, (_, M, m, p, pr) => {
208 debug('tilde', comp, _, M, m, p, pr)
214 ret = `>=${M}.0.0 <${+M + 1}.0.0-0`
216 // ~1.2 == >=1.2.0 <1.3.0-0
217 ret = `>=${M}.${m}.0 <${M}.${+m + 1}.0-0`
219 debug('replaceTilde pr', pr)
220 ret = `>=${M}.${m}.${p}-${pr
221 } <${M}.${+m + 1}.0-0`
223 // ~1.2.3 == >=1.2.3 <1.3.0-0
224 ret = `>=${M}.${m}.${p
225 } <${M}.${+m + 1}.0-0`
228 debug('tilde return', ret)
233 // ^ --> * (any, kinda silly)
234 // ^2, ^2.x, ^2.x.x --> >=2.0.0 <3.0.0-0
235 // ^2.0, ^2.0.x --> >=2.0.0 <3.0.0-0
236 // ^1.2, ^1.2.x --> >=1.2.0 <2.0.0-0
237 // ^1.2.3 --> >=1.2.3 <2.0.0-0
238 // ^1.2.0 --> >=1.2.0 <2.0.0-0
239 const replaceCarets = (comp, options) =>
240 comp.trim().split(/\s+/).map((comp) => {
241 return replaceCaret(comp, options)
244 const replaceCaret = (comp, options) => {
245 debug('caret', comp, options)
246 const r = options.loose ? re[t.CARETLOOSE] : re[t.CARET]
247 const z = options.includePrerelease ? '-0' : ''
248 return comp.replace(r, (_, M, m, p, pr) => {
249 debug('caret', comp, _, M, m, p, pr)
255 ret = `>=${M}.0.0${z} <${+M + 1}.0.0-0`
258 ret = `>=${M}.${m}.0${z} <${M}.${+m + 1}.0-0`
260 ret = `>=${M}.${m}.0${z} <${+M + 1}.0.0-0`
263 debug('replaceCaret pr', pr)
266 ret = `>=${M}.${m}.${p}-${pr
267 } <${M}.${m}.${+p + 1}-0`
269 ret = `>=${M}.${m}.${p}-${pr
270 } <${M}.${+m + 1}.0-0`
273 ret = `>=${M}.${m}.${p}-${pr
280 ret = `>=${M}.${m}.${p
281 }${z} <${M}.${m}.${+p + 1}-0`
283 ret = `>=${M}.${m}.${p
284 }${z} <${M}.${+m + 1}.0-0`
287 ret = `>=${M}.${m}.${p
292 debug('caret return', ret)
297 const replaceXRanges = (comp, options) => {
298 debug('replaceXRanges', comp, options)
299 return comp.split(/\s+/).map((comp) => {
300 return replaceXRange(comp, options)
304 const replaceXRange = (comp, options) => {
306 const r = options.loose ? re[t.XRANGELOOSE] : re[t.XRANGE]
307 return comp.replace(r, (ret, gtlt, M, m, p, pr) => {
308 debug('xRange', comp, ret, gtlt, M, m, p, pr)
310 const xm = xM || isX(m)
311 const xp = xm || isX(p)
314 if (gtlt === '=' && anyX) {
318 // if we're including prereleases in the match, then we need
319 // to fix this to -0, the lowest possible prerelease value
320 pr = options.includePrerelease ? '-0' : ''
323 if (gtlt === '>' || gtlt === '<') {
324 // nothing is allowed
327 // nothing is forbidden
330 } else if (gtlt && anyX) {
331 // we know patch is an x, because we have any x at all.
350 } else if (gtlt === '<=') {
351 // <=0.7.x is actually <0.8.0, since any 0.7.x should
352 // pass. Similarly, <=7.x is actually <8.0.0, etc.
364 ret = `${gtlt + M}.${m}.${p}${pr}`
366 ret = `>=${M}.0.0${pr} <${+M + 1}.0.0-0`
368 ret = `>=${M}.${m}.0${pr
369 } <${M}.${+m + 1}.0-0`
372 debug('xRange return', ret)
378 // Because * is AND-ed with everything else in the comparator,
379 // and '' means "any version", just remove the *s entirely.
380 const replaceStars = (comp, options) => {
381 debug('replaceStars', comp, options)
382 // Looseness is ignored here. star is always as loose as it gets!
383 return comp.trim().replace(re[t.STAR], '')
386 const replaceGTE0 = (comp, options) => {
387 debug('replaceGTE0', comp, options)
389 .replace(re[options.includePrerelease ? t.GTE0PRE : t.GTE0], '')
392 // This function is passed to string.replace(re[t.HYPHENRANGE])
393 // M, m, patch, prerelease, build
394 // 1.2 - 3.4.5 => >=1.2.0 <=3.4.5
395 // 1.2.3 - 3.4 => >=1.2.0 <3.5.0-0 Any 3.4.x will do
396 // 1.2 - 3.4 => >=1.2.0 <3.5.0-0
397 const hyphenReplace = incPr => ($0,
398 from, fM, fm, fp, fpr, fb,
399 to, tM, tm, tp, tpr, tb) => {
402 } else if (isX(fm)) {
403 from = `>=${fM}.0.0${incPr ? '-0' : ''}`
404 } else if (isX(fp)) {
405 from = `>=${fM}.${fm}.0${incPr ? '-0' : ''}`
409 from = `>=${from}${incPr ? '-0' : ''}`
414 } else if (isX(tm)) {
415 to = `<${+tM + 1}.0.0-0`
416 } else if (isX(tp)) {
417 to = `<${tM}.${+tm + 1}.0-0`
419 to = `<=${tM}.${tm}.${tp}-${tpr}`
421 to = `<${tM}.${tm}.${+tp + 1}-0`
426 return (`${from} ${to}`).trim()
429 const testSet = (set, version, options) => {
430 for (let i = 0; i < set.length; i++) {
431 if (!set[i].test(version)) {
436 if (version.prerelease.length && !options.includePrerelease) {
437 // Find the set of versions that are allowed to have prereleases
438 // For example, ^1.2.3-pr.1 desugars to >=1.2.3-pr.1 <2.0.0
439 // That should allow `1.2.3-pr.2` to pass.
440 // However, `1.2.4-alpha.notready` should NOT be allowed,
441 // even though it's within the range set by the comparators.
442 for (let i = 0; i < set.length; i++) {
444 if (set[i].semver === Comparator.ANY) {
448 if (set[i].semver.prerelease.length > 0) {
449 const allowed = set[i].semver
450 if (allowed.major === version.major &&
451 allowed.minor === version.minor &&
452 allowed.patch === version.patch) {
458 // Version has a -pre, but it's not one of the ones we like.