@vltpkg/semver 1.0.0-rc.23 → 1.0.0-rc.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,75 @@
1
+ import { Version } from './version.ts';
2
+ /** all comparators are expressed in terms of these operators */
3
+ export type SimpleOperator = '' | '<' | '<=' | '>' | '>=';
4
+ /** operators that are expanded to simpler forms */
5
+ export type ComplexOperator = '^' | '~' | '~>';
6
+ /** comparator expressed as a [operator,version] tuple */
7
+ export type OVTuple = [SimpleOperator, Version];
8
+ /**
9
+ * The result of parsing a version value that might be either a full
10
+ * version like `1.2.3` or an X-Range like `1.2.x`
11
+ */
12
+ export type ParsedXRange = ParsedXMajor | ParsedXMinor | ParsedXPatch | ParsedXVersion;
13
+ /**
14
+ * a {@link ParsedXRange} that is just a `*`
15
+ */
16
+ export type ParsedXMajor = [];
17
+ /**
18
+ * a {@link ParsedXRange} that is just a major version
19
+ */
20
+ export type ParsedXMinor = [number];
21
+ /**
22
+ * a {@link ParsedXRange} that is just a major and minor version
23
+ */
24
+ export type ParsedXPatch = [number, number];
25
+ /**
26
+ * a {@link ParsedXRange} that is a full version
27
+ */
28
+ export type ParsedXVersion = [
29
+ M: number,
30
+ m: number,
31
+ p: number,
32
+ pr?: string | undefined,
33
+ b?: string | undefined
34
+ ];
35
+ /**
36
+ * Class used to parse the `||` separated portions
37
+ * of a range, and evaluate versions against it.
38
+ *
39
+ * This does most of the heavy lifting of range testing, and provides
40
+ * little affordance for improperly formatted strings. It should be
41
+ * considered an internal class, and usually not accessed directly.
42
+ */
43
+ export declare class Comparator {
44
+ #private;
45
+ /**
46
+ * does this range include prereleases, even when they do not
47
+ * match the tuple in the comparator?
48
+ */
49
+ includePrerelease: boolean;
50
+ /** raw string used to create this comparator */
51
+ raw: string;
52
+ /** tokens extracted from the raw string input */
53
+ tokens: string[];
54
+ /**
55
+ * Either the `any` comparator, the `none` comparator, or an operator
56
+ * and a {@link ParsedXRange}
57
+ */
58
+ tuples: (Comparator | OVTuple)[];
59
+ /** true if this comparator can not match anything */
60
+ isNone: boolean;
61
+ /**
62
+ * true if this comparator is a `'*'` type of range.
63
+ *
64
+ * Note that it still will not match versions with a prerelease value,
65
+ * unless the tuple in the version matches the tuple provided to the
66
+ * comparator, and the comparator version also has a prerelease value,
67
+ * unless `includePrerelease` is set.
68
+ */
69
+ isAny: boolean;
70
+ /** the canonical strict simplified parsed form of this constructor */
71
+ toString(): string;
72
+ constructor(comp: string, includePrerelease?: boolean);
73
+ /** return true if the version is a match for this comparator */
74
+ test(v: Version): boolean;
75
+ }
@@ -0,0 +1,513 @@
1
+ // TODO: it might be faster to not have Version objects in the
2
+ // comparator tuples, and instead just keep the parsed number arrays?
3
+ import { syntaxError } from '@vltpkg/error-cause';
4
+ import { fastSplit } from '@vltpkg/fast-split';
5
+ import { Version } from "./version.js";
6
+ const isOperator = (o) => !!o &&
7
+ (o === '>' ||
8
+ o === '<' ||
9
+ o === '>=' ||
10
+ o === '<=' ||
11
+ o === '' ||
12
+ o === '~' ||
13
+ o === '^' ||
14
+ o === '~>');
15
+ const preJunk = new Set('=v \t');
16
+ const invalidComp = (c, message) => syntaxError(`invalid comparator: '${c}' ${message}`, { found: c }, Comparator);
17
+ const assertNumber = (value, c, field) => {
18
+ const n = Number(value);
19
+ if (n !== n) {
20
+ throw invalidComp(c, `${field} must be numeric or 'x', got: '${value}'`);
21
+ }
22
+ return n;
23
+ };
24
+ const assertVersion = (v, comp) => {
25
+ if (!v) {
26
+ throw invalidComp(comp, 'no value provided for operator');
27
+ }
28
+ };
29
+ const assertMissing = (value, c, field) => {
30
+ if (value && !isX(value)) {
31
+ throw invalidComp(c, `cannot omit '${field}' and include subsequent fields`);
32
+ }
33
+ };
34
+ const MAJOR = 0;
35
+ const MINOR = 1;
36
+ const PATCH = 2;
37
+ const isX = (c) => !c || c === 'X' || c === 'x' || c === '*';
38
+ const isFullVersion = (parsed) => undefined !== parsed[PATCH];
39
+ const isXPatch = (parsed) => undefined !== parsed[MINOR] && undefined === parsed[PATCH];
40
+ const isXMinor = (parsed) => undefined !== parsed[MAJOR] && undefined === parsed[MINOR];
41
+ const isXMajor = (parsed) => undefined === parsed[MAJOR];
42
+ /**
43
+ * Class used to parse the `||` separated portions
44
+ * of a range, and evaluate versions against it.
45
+ *
46
+ * This does most of the heavy lifting of range testing, and provides
47
+ * little affordance for improperly formatted strings. It should be
48
+ * considered an internal class, and usually not accessed directly.
49
+ */
50
+ export class Comparator {
51
+ /**
52
+ * does this range include prereleases, even when they do not
53
+ * match the tuple in the comparator?
54
+ */
55
+ includePrerelease;
56
+ /** raw string used to create this comparator */
57
+ raw;
58
+ /** tokens extracted from the raw string input */
59
+ tokens;
60
+ /**
61
+ * Either the `any` comparator, the `none` comparator, or an operator
62
+ * and a {@link ParsedXRange}
63
+ */
64
+ tuples = [];
65
+ /** true if this comparator can not match anything */
66
+ isNone = false;
67
+ /**
68
+ * true if this comparator is a `'*'` type of range.
69
+ *
70
+ * Note that it still will not match versions with a prerelease value,
71
+ * unless the tuple in the version matches the tuple provided to the
72
+ * comparator, and the comparator version also has a prerelease value,
73
+ * unless `includePrerelease` is set.
74
+ */
75
+ isAny = false;
76
+ /** the canonical strict simplified parsed form of this constructor */
77
+ toString() {
78
+ return (this.isNone ? '<0.0.0-0'
79
+ : this.isAny ? '*'
80
+ : /* c8 ignore next */
81
+ this.tuples.map(c => (isAny(c) ? '*' : c.join(''))).join(' '));
82
+ }
83
+ constructor(comp, includePrerelease = false) {
84
+ this.includePrerelease = includePrerelease;
85
+ comp = comp.trim();
86
+ this.raw = comp;
87
+ let hyphen = false;
88
+ const rawComps = fastSplit(comp, ' ', -1, (part, parts, i) => {
89
+ if (part === '-') {
90
+ if (hyphen) {
91
+ throw invalidComp(comp, 'multiple hyphen ranges not allowed');
92
+ }
93
+ if (parts.length !== 1 || i === -1) {
94
+ throw invalidComp(comp, 'hyphen must be between two versions');
95
+ }
96
+ hyphen = true;
97
+ }
98
+ else if (hyphen && parts.length !== 2) {
99
+ throw invalidComp(comp, 'hyphen range must be alone');
100
+ }
101
+ });
102
+ // remove excess spaces, `> 1 2` => `>1 2`
103
+ const comps = [];
104
+ let followingOperator = false;
105
+ let l = 0;
106
+ for (const c of rawComps) {
107
+ if (c === '')
108
+ continue;
109
+ if (!followingOperator) {
110
+ followingOperator = isOperator(c);
111
+ comps.push(c);
112
+ l++;
113
+ continue;
114
+ }
115
+ // we know this is not undefined since followingOperator guards that
116
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
117
+ comps[l - 1] += c;
118
+ followingOperator = false;
119
+ }
120
+ // TS mistakenly thinks hyphen is always false here
121
+ if (hyphen) {
122
+ const [min, _, max] = comps;
123
+ /* c8 ignore start - defense in depth for TS, already guaranteed */
124
+ if (!min || !max) {
125
+ throw invalidComp(comp, 'hyphen must be between two versions');
126
+ }
127
+ /* c8 ignore stop */
128
+ this.#parseHyphenRange(min, max);
129
+ }
130
+ else if (!comps.length ||
131
+ (comps.length === 1 && isX(comps[0]))) {
132
+ this.tuples.push(this.#getComparatorAny());
133
+ }
134
+ else {
135
+ for (const c of comps) {
136
+ this.#parse(c);
137
+ if (this.isNone)
138
+ break;
139
+ }
140
+ }
141
+ this.tokens = comps;
142
+ this.isAny = true;
143
+ for (const c of this.tuples) {
144
+ if (Array.isArray(c) || !c.isAny) {
145
+ this.isAny = false;
146
+ break;
147
+ }
148
+ }
149
+ }
150
+ // inclusive min
151
+ #xInclusiveMin(raw) {
152
+ const z = this.includePrerelease ? '0' : undefined;
153
+ const [M, m = 0, p = 0, pr = z, build] = this.#parseX(raw);
154
+ return M === undefined ?
155
+ this.#getComparatorAny()
156
+ : ['>=', new Version(raw, M, m, p, pr, build)];
157
+ }
158
+ // exclusive min
159
+ #xExclusiveMin(raw) {
160
+ const parsed = this.#parseX(raw);
161
+ if (isFullVersion(parsed)) {
162
+ return ['>', new Version(raw, ...parsed)];
163
+ }
164
+ const z = this.includePrerelease ? '0' : undefined;
165
+ if (isXPatch(parsed)) {
166
+ // >1.2 => >=1.3.0
167
+ return [
168
+ '>=',
169
+ new Version(raw, parsed[MAJOR], parsed[MINOR] + 1, 0, z, undefined),
170
+ ];
171
+ }
172
+ if (isXMinor(parsed)) {
173
+ // >1 => >=2.0.0
174
+ return [
175
+ '>=',
176
+ new Version(raw, parsed[MAJOR] + 1, 0, 0, z, undefined),
177
+ ];
178
+ }
179
+ this.isNone = true;
180
+ this.tuples.length = 0;
181
+ return comparatorNone;
182
+ }
183
+ #xInclusiveMax(raw) {
184
+ const parsed = this.#parseX(raw);
185
+ return (isFullVersion(parsed) ? ['<=', new Version(raw, ...parsed)]
186
+ : isXPatch(parsed) ?
187
+ [
188
+ '<',
189
+ new Version(raw, parsed[MAJOR], parsed[MINOR] + 1, 0, '0', undefined),
190
+ ]
191
+ : isXMinor(parsed) ?
192
+ [
193
+ '<',
194
+ new Version(raw, parsed[MAJOR] + 1, 0, 0, '0', undefined),
195
+ ]
196
+ : this.#getComparatorAny());
197
+ }
198
+ #xExclusiveMax(raw) {
199
+ const z = this.includePrerelease ? '0' : undefined;
200
+ const [M = 0, m = 0, p = 0, pr = z, build] = this.#parseX(raw);
201
+ if (M === 0 && m === 0 && p === 0 && pr === '0') {
202
+ this.isNone = true;
203
+ this.tuples.length = 0;
204
+ return comparatorNone;
205
+ }
206
+ return ['<', new Version(raw, M, m, p, pr, build)];
207
+ }
208
+ #validXM(raw, m, p) {
209
+ assertMissing(m, raw, 'major');
210
+ assertMissing(p, raw, 'major');
211
+ if (m === '' || p === '') {
212
+ throw invalidComp(raw, `(Did you mean '*'?)`);
213
+ }
214
+ return [];
215
+ }
216
+ #validXm(raw, M, m, p) {
217
+ assertMissing(p, raw, 'major');
218
+ if (m === '' || p === '') {
219
+ throw invalidComp(raw, `(Did you mean '${M}'?)`);
220
+ }
221
+ return [assertNumber(M, raw, 'major')];
222
+ }
223
+ #validXp(raw, M, m, p) {
224
+ if (p === '') {
225
+ throw invalidComp(raw, `(Did you mean '${M}.${m}'?)`);
226
+ }
227
+ return [
228
+ assertNumber(M, raw, 'major'),
229
+ assertNumber(m, raw, 'minor'),
230
+ ];
231
+ }
232
+ #validTuple(raw, M, m, p) {
233
+ return [
234
+ assertNumber(M, raw, 'major'),
235
+ assertNumber(m, raw, 'minor'),
236
+ assertNumber(p, raw, 'patch'),
237
+ ];
238
+ }
239
+ #validXbuild(raw, M, m, p, pl) {
240
+ // build, no prerelease
241
+ const patch = p.substring(0, pl);
242
+ const build = p.substring(pl + 1);
243
+ if (!patch) {
244
+ throw invalidComp(raw, 'cannot specify build without patch');
245
+ }
246
+ if (!build) {
247
+ throw invalidComp(raw, `encountered '+', but no build value`);
248
+ }
249
+ return [
250
+ assertNumber(M, raw, 'major'),
251
+ assertNumber(m, raw, 'minor'),
252
+ assertNumber(patch, raw, 'patch'),
253
+ undefined,
254
+ build,
255
+ ];
256
+ }
257
+ #validXpr(raw, M, m, p, hy) {
258
+ {
259
+ // prerelease, no build
260
+ const patch = p.substring(0, hy);
261
+ const pr = p.substring(hy + 1);
262
+ if (!patch) {
263
+ throw invalidComp(raw, 'cannot specify prerelease without patch');
264
+ }
265
+ if (!pr) {
266
+ throw invalidComp(raw, `encountered '-', but no prerelease value`);
267
+ }
268
+ return [
269
+ assertNumber(M, raw, 'major'),
270
+ assertNumber(m, raw, 'minor'),
271
+ assertNumber(patch, raw, 'patch'),
272
+ pr,
273
+ undefined,
274
+ ];
275
+ }
276
+ }
277
+ #validXprbuild(raw, M, m, p, hy, pl) {
278
+ // both prerelease and build
279
+ const patch = p.substring(0, hy);
280
+ const pr = p.substring(hy + 1, pl);
281
+ const build = p.substring(pl + 1);
282
+ if (!patch) {
283
+ throw invalidComp(raw, 'cannot specify prerelease without patch');
284
+ }
285
+ if (!pr) {
286
+ throw invalidComp(raw, `encountered '-', but no prerelease value`);
287
+ }
288
+ if (!build) {
289
+ throw invalidComp(raw, `encountered '+', but no build value`);
290
+ }
291
+ return [
292
+ assertNumber(M, raw, 'major'),
293
+ assertNumber(m, raw, 'minor'),
294
+ assertNumber(patch, raw, 'patch'),
295
+ pr,
296
+ build,
297
+ ];
298
+ }
299
+ // pull the relevant values out of an X-range or version
300
+ // return the fields for creating a Version object.
301
+ // only call once operator is stripped off
302
+ #parseX(raw) {
303
+ let [M, m, p] = fastSplit(raw, '.', 3);
304
+ let prune = 0;
305
+ while (M && preJunk.has(M.charAt(prune)))
306
+ prune++;
307
+ if (M !== undefined && prune !== 0)
308
+ M = M.substring(prune);
309
+ // the `|| !M` is so TS knows we've handled undefined
310
+ if (!M || isX(M))
311
+ return this.#validXM(raw, m, p);
312
+ if (!m || isX(m))
313
+ return this.#validXm(raw, M, m, p);
314
+ if (!p || isX(p))
315
+ return this.#validXp(raw, M, m, p);
316
+ const hy = p.indexOf('-');
317
+ const pl = p.indexOf('+');
318
+ if (pl === -1 && hy === -1)
319
+ return this.#validTuple(raw, M, m, p);
320
+ if (pl === -1)
321
+ return this.#validXpr(raw, M, m, p, hy);
322
+ if (hy === -1)
323
+ return this.#validXbuild(raw, M, m, p, pl);
324
+ return this.#validXprbuild(raw, M, m, p, hy, pl);
325
+ }
326
+ #parseHyphenRange(min, max) {
327
+ const minv = this.#xInclusiveMin(min);
328
+ const maxv = this.#xInclusiveMax(max);
329
+ const minAny = isAny(minv);
330
+ const maxAny = isAny(maxv);
331
+ if (minAny && maxAny)
332
+ this.tuples.push(this.#getComparatorAny());
333
+ else if (minAny)
334
+ this.tuples.push(maxv);
335
+ else if (maxAny)
336
+ this.tuples.push(minv);
337
+ else
338
+ this.tuples.push(minv, maxv);
339
+ }
340
+ #parse(comp) {
341
+ const first = comp.charAt(0);
342
+ const first2 = comp.substring(0, 2);
343
+ const v1 = comp.substring(1);
344
+ const v2 = comp.substring(2);
345
+ switch (first2) {
346
+ case '~>':
347
+ assertVersion(v2, comp);
348
+ return this.#parseTilde(v2);
349
+ case '>=':
350
+ assertVersion(v2, comp);
351
+ return this.tuples.push(this.#xInclusiveMin(v2));
352
+ case '<=':
353
+ assertVersion(v2, comp);
354
+ return this.tuples.push(this.#xInclusiveMax(v2));
355
+ }
356
+ switch (first) {
357
+ case '~':
358
+ assertVersion(v1, comp);
359
+ return this.#parseTilde(v1);
360
+ case '^':
361
+ assertVersion(v1, comp);
362
+ return this.#parseCaret(v1);
363
+ case '>':
364
+ assertVersion(v1, comp);
365
+ return this.tuples.push(this.#xExclusiveMin(v1));
366
+ case '<':
367
+ assertVersion(v1, comp);
368
+ return this.tuples.push(this.#xExclusiveMax(v1));
369
+ }
370
+ return this.#parseEq(comp);
371
+ }
372
+ #parseTilde(comp) {
373
+ const parsed = this.#parseX(comp);
374
+ if (isXMajor(parsed)) {
375
+ this.tuples.push(this.#getComparatorAny());
376
+ return;
377
+ }
378
+ const z = this.includePrerelease ? '0' : undefined;
379
+ if (isXMinor(parsed)) {
380
+ const [M] = parsed;
381
+ this.tuples.push(['>=', new Version(comp, M, 0, 0, z, undefined)], ['<', new Version(comp, M + 1, 0, 0, '0', undefined)]);
382
+ return;
383
+ }
384
+ if (isXPatch(parsed)) {
385
+ const [M, m] = parsed;
386
+ const z = this.includePrerelease ? '0' : undefined;
387
+ this.tuples.push(['>=', new Version(comp, M, m, 0, z, undefined)], ['<', new Version(comp, M, m + 1, 0, '0', undefined)]);
388
+ return;
389
+ }
390
+ const [M, m, p, pr = z, build] = parsed;
391
+ this.tuples.push(['>=', new Version(comp, M, m, p, pr, build)], ['<', new Version(comp, M, m + 1, 0, '0', build)]);
392
+ }
393
+ #parseCaret(comp) {
394
+ const min = this.#xInclusiveMin(comp);
395
+ if (isAny(min)) {
396
+ this.tuples.push(min);
397
+ return;
398
+ }
399
+ const minv = min[1];
400
+ if (minv.major !== 0) {
401
+ this.tuples.push(min, [
402
+ '<',
403
+ new Version(comp, minv.major + 1, 0, 0, '0', undefined),
404
+ ]);
405
+ }
406
+ else if (minv.minor !== 0) {
407
+ this.tuples.push(min, [
408
+ '<',
409
+ new Version(comp, minv.major, minv.minor + 1, 0, '0', undefined),
410
+ ]);
411
+ }
412
+ else if (!minv.prerelease?.length) {
413
+ this.tuples.push(['', minv]);
414
+ }
415
+ else {
416
+ this.tuples.push(min, [
417
+ '<',
418
+ new Version(comp, minv.major, minv.minor, minv.patch + 1, '0', undefined),
419
+ ]);
420
+ }
421
+ }
422
+ #parseEq(comp) {
423
+ const parsed = this.#parseX(comp);
424
+ const z = this.includePrerelease ? '0' : undefined;
425
+ if (isFullVersion(parsed)) {
426
+ this.tuples.push(['', new Version(comp, ...parsed)]);
427
+ }
428
+ else if (isXMajor(parsed)) {
429
+ this.tuples.push(this.#getComparatorAny());
430
+ }
431
+ else if (isXMinor(parsed)) {
432
+ this.tuples.push([
433
+ '>=',
434
+ new Version(comp, parsed[MAJOR], 0, 0, z, undefined),
435
+ ]);
436
+ this.tuples.push([
437
+ '<',
438
+ new Version(comp, parsed[MAJOR] + 1, 0, 0, '0', undefined),
439
+ ]);
440
+ }
441
+ else if (isXPatch(parsed)) {
442
+ this.tuples.push([
443
+ '>=',
444
+ new Version(comp, parsed[MAJOR], parsed[MINOR], 0, z, undefined),
445
+ ], [
446
+ '<',
447
+ new Version(comp, parsed[MAJOR], parsed[MINOR] + 1, 0, '0', undefined),
448
+ ]);
449
+ }
450
+ }
451
+ /** return true if the version is a match for this comparator */
452
+ test(v) {
453
+ if (this.isNone)
454
+ return false;
455
+ const ip = this.includePrerelease;
456
+ const hasPR = !!v.prerelease?.length;
457
+ let prOK = ip || !hasPR;
458
+ for (const c of this.tuples) {
459
+ if (isAny(c)) {
460
+ continue;
461
+ }
462
+ const [op, cv] = c;
463
+ prOK ||= !!cv.prerelease?.length && v.tupleEquals(cv);
464
+ switch (op) {
465
+ case '':
466
+ if (!v.equals(cv))
467
+ return false;
468
+ continue;
469
+ case '>':
470
+ if (!v.greaterThan(cv))
471
+ return false;
472
+ continue;
473
+ case '>=':
474
+ if (!v.greaterThanEqual(cv))
475
+ return false;
476
+ continue;
477
+ case '<':
478
+ if (!v.lessThan(cv))
479
+ return false;
480
+ continue;
481
+ case '<=':
482
+ if (!v.lessThanEqual(cv))
483
+ return false;
484
+ continue;
485
+ }
486
+ }
487
+ // they all passed, so it can only fail for having a prerelease
488
+ // if we allow prereleases, or saw a matching tuple, that's ok.
489
+ return prOK;
490
+ }
491
+ #getComparatorAny() {
492
+ return this.includePrerelease ? comparatorAnyPR : comparatorAny;
493
+ }
494
+ }
495
+ const isAny = (c) => c === comparatorAny || c === comparatorAnyPR;
496
+ const comparatorAny = {
497
+ isAny: true,
498
+ toString: () => '*',
499
+ includePrerelease: false,
500
+ test: (v) => !v.prerelease?.length,
501
+ };
502
+ const comparatorAnyPR = {
503
+ isAny: true,
504
+ toString: () => '*',
505
+ includePrerelease: true,
506
+ test: (_) => true,
507
+ };
508
+ const comparatorNone = {
509
+ isNone: true,
510
+ toString: () => '<0.0.0-0',
511
+ includePrerelease: false,
512
+ test: (_) => false,
513
+ };