source: node_modules/semver/classes/range.js

main
Last change on this file was d24f17c, checked in by Aleksandar Panovski <apano77@…>, 15 months ago

Initial commit

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