range.js 15 KB

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