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