subset.js 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. const Range = require('../classes/range.js')
  2. const Comparator = require('../classes/comparator.js')
  3. const { ANY } = Comparator
  4. const satisfies = require('../functions/satisfies.js')
  5. const compare = require('../functions/compare.js')
  6. // Complex range `r1 || r2 || ...` is a subset of `R1 || R2 || ...` iff:
  7. // - Every simple range `r1, r2, ...` is a null set, OR
  8. // - Every simple range `r1, r2, ...` which is not a null set is a subset of
  9. // some `R1, R2, ...`
  10. //
  11. // Simple range `c1 c2 ...` is a subset of simple range `C1 C2 ...` iff:
  12. // - If c is only the ANY comparator
  13. // - If C is only the ANY comparator, return true
  14. // - Else if in prerelease mode, return false
  15. // - else replace c with `[>=0.0.0]`
  16. // - If C is only the ANY comparator
  17. // - if in prerelease mode, return true
  18. // - else replace C with `[>=0.0.0]`
  19. // - Let EQ be the set of = comparators in c
  20. // - If EQ is more than one, return true (null set)
  21. // - Let GT be the highest > or >= comparator in c
  22. // - Let LT be the lowest < or <= comparator in c
  23. // - If GT and LT, and GT.semver > LT.semver, return true (null set)
  24. // - If any C is a = range, and GT or LT are set, return false
  25. // - If EQ
  26. // - If GT, and EQ does not satisfy GT, return true (null set)
  27. // - If LT, and EQ does not satisfy LT, return true (null set)
  28. // - If EQ satisfies every C, return true
  29. // - Else return false
  30. // - If GT
  31. // - If GT.semver is lower than any > or >= comp in C, return false
  32. // - If GT is >=, and GT.semver does not satisfy every C, return false
  33. // - If GT.semver has a prerelease, and not in prerelease mode
  34. // - If no C has a prerelease and the GT.semver tuple, return false
  35. // - If LT
  36. // - If LT.semver is greater than any < or <= comp in C, return false
  37. // - If LT is <=, and LT.semver does not satisfy every C, return false
  38. // - If GT.semver has a prerelease, and not in prerelease mode
  39. // - If no C has a prerelease and the LT.semver tuple, return false
  40. // - Else return true
  41. const subset = (sub, dom, options = {}) => {
  42. if (sub === dom) {
  43. return true
  44. }
  45. sub = new Range(sub, options)
  46. dom = new Range(dom, options)
  47. let sawNonNull = false
  48. OUTER: for (const simpleSub of sub.set) {
  49. for (const simpleDom of dom.set) {
  50. const isSub = simpleSubset(simpleSub, simpleDom, options)
  51. sawNonNull = sawNonNull || isSub !== null
  52. if (isSub) {
  53. continue OUTER
  54. }
  55. }
  56. // the null set is a subset of everything, but null simple ranges in
  57. // a complex range should be ignored. so if we saw a non-null range,
  58. // then we know this isn't a subset, but if EVERY simple range was null,
  59. // then it is a subset.
  60. if (sawNonNull) {
  61. return false
  62. }
  63. }
  64. return true
  65. }
  66. const simpleSubset = (sub, dom, options) => {
  67. if (sub === dom) {
  68. return true
  69. }
  70. if (sub.length === 1 && sub[0].semver === ANY) {
  71. if (dom.length === 1 && dom[0].semver === ANY) {
  72. return true
  73. } else if (options.includePrerelease) {
  74. sub = [new Comparator('>=0.0.0-0')]
  75. } else {
  76. sub = [new Comparator('>=0.0.0')]
  77. }
  78. }
  79. if (dom.length === 1 && dom[0].semver === ANY) {
  80. if (options.includePrerelease) {
  81. return true
  82. } else {
  83. dom = [new Comparator('>=0.0.0')]
  84. }
  85. }
  86. const eqSet = new Set()
  87. let gt, lt
  88. for (const c of sub) {
  89. if (c.operator === '>' || c.operator === '>=') {
  90. gt = higherGT(gt, c, options)
  91. } else if (c.operator === '<' || c.operator === '<=') {
  92. lt = lowerLT(lt, c, options)
  93. } else {
  94. eqSet.add(c.semver)
  95. }
  96. }
  97. if (eqSet.size > 1) {
  98. return null
  99. }
  100. let gtltComp
  101. if (gt && lt) {
  102. gtltComp = compare(gt.semver, lt.semver, options)
  103. if (gtltComp > 0) {
  104. return null
  105. } else if (gtltComp === 0 && (gt.operator !== '>=' || lt.operator !== '<=')) {
  106. return null
  107. }
  108. }
  109. // will iterate one or zero times
  110. for (const eq of eqSet) {
  111. if (gt && !satisfies(eq, String(gt), options)) {
  112. return null
  113. }
  114. if (lt && !satisfies(eq, String(lt), options)) {
  115. return null
  116. }
  117. for (const c of dom) {
  118. if (!satisfies(eq, String(c), options)) {
  119. return false
  120. }
  121. }
  122. return true
  123. }
  124. let higher, lower
  125. let hasDomLT, hasDomGT
  126. // if the subset has a prerelease, we need a comparator in the superset
  127. // with the same tuple and a prerelease, or it's not a subset
  128. let needDomLTPre = lt &&
  129. !options.includePrerelease &&
  130. lt.semver.prerelease.length ? lt.semver : false
  131. let needDomGTPre = gt &&
  132. !options.includePrerelease &&
  133. gt.semver.prerelease.length ? gt.semver : false
  134. // exception: <1.2.3-0 is the same as <1.2.3
  135. if (needDomLTPre && needDomLTPre.prerelease.length === 1 &&
  136. lt.operator === '<' && needDomLTPre.prerelease[0] === 0) {
  137. needDomLTPre = false
  138. }
  139. for (const c of dom) {
  140. hasDomGT = hasDomGT || c.operator === '>' || c.operator === '>='
  141. hasDomLT = hasDomLT || c.operator === '<' || c.operator === '<='
  142. if (gt) {
  143. if (needDomGTPre) {
  144. if (c.semver.prerelease && c.semver.prerelease.length &&
  145. c.semver.major === needDomGTPre.major &&
  146. c.semver.minor === needDomGTPre.minor &&
  147. c.semver.patch === needDomGTPre.patch) {
  148. needDomGTPre = false
  149. }
  150. }
  151. if (c.operator === '>' || c.operator === '>=') {
  152. higher = higherGT(gt, c, options)
  153. if (higher === c && higher !== gt) {
  154. return false
  155. }
  156. } else if (gt.operator === '>=' && !satisfies(gt.semver, String(c), options)) {
  157. return false
  158. }
  159. }
  160. if (lt) {
  161. if (needDomLTPre) {
  162. if (c.semver.prerelease && c.semver.prerelease.length &&
  163. c.semver.major === needDomLTPre.major &&
  164. c.semver.minor === needDomLTPre.minor &&
  165. c.semver.patch === needDomLTPre.patch) {
  166. needDomLTPre = false
  167. }
  168. }
  169. if (c.operator === '<' || c.operator === '<=') {
  170. lower = lowerLT(lt, c, options)
  171. if (lower === c && lower !== lt) {
  172. return false
  173. }
  174. } else if (lt.operator === '<=' && !satisfies(lt.semver, String(c), options)) {
  175. return false
  176. }
  177. }
  178. if (!c.operator && (lt || gt) && gtltComp !== 0) {
  179. return false
  180. }
  181. }
  182. // if there was a < or >, and nothing in the dom, then must be false
  183. // UNLESS it was limited by another range in the other direction.
  184. // Eg, >1.0.0 <1.0.1 is still a subset of <2.0.0
  185. if (gt && hasDomLT && !lt && gtltComp !== 0) {
  186. return false
  187. }
  188. if (lt && hasDomGT && !gt && gtltComp !== 0) {
  189. return false
  190. }
  191. // we needed a prerelease range in a specific tuple, but didn't get one
  192. // then this isn't a subset. eg >=1.2.3-pre is not a subset of >=1.0.0,
  193. // because it includes prereleases in the 1.2.3 tuple
  194. if (needDomGTPre || needDomLTPre) {
  195. return false
  196. }
  197. return true
  198. }
  199. // >=1.2.3 is lower than >1.2.3
  200. const higherGT = (a, b, options) => {
  201. if (!a) {
  202. return b
  203. }
  204. const comp = compare(a.semver, b.semver, options)
  205. return comp > 0 ? a
  206. : comp < 0 ? b
  207. : b.operator === '>' && a.operator === '>=' ? b
  208. : a
  209. }
  210. // <=1.2.3 is higher than <1.2.3
  211. const lowerLT = (a, b, options) => {
  212. if (!a) {
  213. return b
  214. }
  215. const comp = compare(a.semver, b.semver, options)
  216. return comp < 0 ? a
  217. : comp > 0 ? b
  218. : b.operator === '<' && a.operator === '<=' ? b
  219. : a
  220. }
  221. module.exports = subset