source: node_modules/semver/classes/range.js

main
Last change on this file was 6c7cfa6, checked in by Klimentina Efremova <klimentina08642@…>, 9 days ago

Implemented Advaced database reports

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