• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1const ANY = Symbol('SemVer ANY')
2// hoisted class for cyclic dependency
3class Comparator {
4  static get ANY () {
5    return ANY
6  }
7
8  constructor (comp, options) {
9    options = parseOptions(options)
10
11    if (comp instanceof Comparator) {
12      if (comp.loose === !!options.loose) {
13        return comp
14      } else {
15        comp = comp.value
16      }
17    }
18
19    comp = comp.trim().split(/\s+/).join(' ')
20    debug('comparator', comp, options)
21    this.options = options
22    this.loose = !!options.loose
23    this.parse(comp)
24
25    if (this.semver === ANY) {
26      this.value = ''
27    } else {
28      this.value = this.operator + this.semver.version
29    }
30
31    debug('comp', this)
32  }
33
34  parse (comp) {
35    const r = this.options.loose ? re[t.COMPARATORLOOSE] : re[t.COMPARATOR]
36    const m = comp.match(r)
37
38    if (!m) {
39      throw new TypeError(`Invalid comparator: ${comp}`)
40    }
41
42    this.operator = m[1] !== undefined ? m[1] : ''
43    if (this.operator === '=') {
44      this.operator = ''
45    }
46
47    // if it literally is just '>' or '' then allow anything.
48    if (!m[2]) {
49      this.semver = ANY
50    } else {
51      this.semver = new SemVer(m[2], this.options.loose)
52    }
53  }
54
55  toString () {
56    return this.value
57  }
58
59  test (version) {
60    debug('Comparator.test', version, this.options.loose)
61
62    if (this.semver === ANY || version === ANY) {
63      return true
64    }
65
66    if (typeof version === 'string') {
67      try {
68        version = new SemVer(version, this.options)
69      } catch (er) {
70        return false
71      }
72    }
73
74    return cmp(version, this.operator, this.semver, this.options)
75  }
76
77  intersects (comp, options) {
78    if (!(comp instanceof Comparator)) {
79      throw new TypeError('a Comparator is required')
80    }
81
82    if (this.operator === '') {
83      if (this.value === '') {
84        return true
85      }
86      return new Range(comp.value, options).test(this.value)
87    } else if (comp.operator === '') {
88      if (comp.value === '') {
89        return true
90      }
91      return new Range(this.value, options).test(comp.semver)
92    }
93
94    options = parseOptions(options)
95
96    // Special cases where nothing can possibly be lower
97    if (options.includePrerelease &&
98      (this.value === '<0.0.0-0' || comp.value === '<0.0.0-0')) {
99      return false
100    }
101    if (!options.includePrerelease &&
102      (this.value.startsWith('<0.0.0') || comp.value.startsWith('<0.0.0'))) {
103      return false
104    }
105
106    // Same direction increasing (> or >=)
107    if (this.operator.startsWith('>') && comp.operator.startsWith('>')) {
108      return true
109    }
110    // Same direction decreasing (< or <=)
111    if (this.operator.startsWith('<') && comp.operator.startsWith('<')) {
112      return true
113    }
114    // same SemVer and both sides are inclusive (<= or >=)
115    if (
116      (this.semver.version === comp.semver.version) &&
117      this.operator.includes('=') && comp.operator.includes('=')) {
118      return true
119    }
120    // opposite directions less than
121    if (cmp(this.semver, '<', comp.semver, options) &&
122      this.operator.startsWith('>') && comp.operator.startsWith('<')) {
123      return true
124    }
125    // opposite directions greater than
126    if (cmp(this.semver, '>', comp.semver, options) &&
127      this.operator.startsWith('<') && comp.operator.startsWith('>')) {
128      return true
129    }
130    return false
131  }
132}
133
134module.exports = Comparator
135
136const parseOptions = require('../internal/parse-options')
137const { safeRe: re, t } = require('../internal/re')
138const cmp = require('../functions/cmp')
139const debug = require('../internal/debug')
140const SemVer = require('./semver')
141const Range = require('./range')
142