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