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