Skip to content

Commit 865848f

Browse files
authoredSep 28, 2022
Fix <= and > comparisons when compared against prerelease versions (#50915)
* Fix <= and > comparisons when compared against prerelease versions * Improve coverage for semver
1 parent fbfe934 commit 865848f

File tree

2 files changed

+817
-118
lines changed

2 files changed

+817
-118
lines changed
 

‎src/compiler/semver.ts

+37-13
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,14 @@ namespace ts {
1515
// > alphanumerics and hyphen [0-9A-Za-z-]. Identifiers MUST NOT be empty. Numeric identifiers
1616
// > MUST NOT include leading zeroes.
1717
const prereleaseRegExp = /^(?:0|[1-9]\d*|[a-z-][a-z0-9-]*)(?:\.(?:0|[1-9]\d*|[a-z-][a-z0-9-]*))*$/i;
18+
const prereleasePartRegExp = /^(?:0|[1-9]\d*|[a-z-][a-z0-9-]*)$/i;
1819

1920
// https://semver.org/#spec-item-10
2021
// > Build metadata MAY be denoted by appending a plus sign and a series of dot separated
2122
// > identifiers immediately following the patch or pre-release version. Identifiers MUST
2223
// > comprise only ASCII alphanumerics and hyphen [0-9A-Za-z-]. Identifiers MUST NOT be empty.
2324
const buildRegExp = /^[a-z0-9-]+(?:\.[a-z0-9-]+)*$/i;
25+
const buildPartRegExp = /^[a-z0-9-]+$/i;
2426

2527
// https://semver.org/#spec-item-9
2628
// > Numeric identifiers MUST NOT include leading zeroes.
@@ -30,7 +32,7 @@ namespace ts {
3032
* Describes a precise semantic version number, https://semver.org
3133
*/
3234
export class Version {
33-
static readonly zero = new Version(0, 0, 0);
35+
static readonly zero = new Version(0, 0, 0, ["0"]);
3436

3537
readonly major: number;
3638
readonly minor: number;
@@ -39,8 +41,8 @@ namespace ts {
3941
readonly build: readonly string[];
4042

4143
constructor(text: string);
42-
constructor(major: number, minor?: number, patch?: number, prerelease?: string, build?: string);
43-
constructor(major: number | string, minor = 0, patch = 0, prerelease = "", build = "") {
44+
constructor(major: number, minor?: number, patch?: number, prerelease?: string | readonly string[], build?: string | readonly string[]);
45+
constructor(major: number | string, minor = 0, patch = 0, prerelease: string | readonly string[] = "", build: string | readonly string[] = "") {
4446
if (typeof major === "string") {
4547
const result = Debug.checkDefined(tryParseComponents(major), "Invalid version");
4648
({ major, minor, patch, prerelease, build } = result);
@@ -49,13 +51,18 @@ namespace ts {
4951
Debug.assert(major >= 0, "Invalid argument: major");
5052
Debug.assert(minor >= 0, "Invalid argument: minor");
5153
Debug.assert(patch >= 0, "Invalid argument: patch");
52-
Debug.assert(!prerelease || prereleaseRegExp.test(prerelease), "Invalid argument: prerelease");
53-
Debug.assert(!build || buildRegExp.test(build), "Invalid argument: build");
54+
55+
const prereleaseArray = prerelease ? isArray(prerelease) ? prerelease : prerelease.split(".") : emptyArray;
56+
const buildArray = build ? isArray(build) ? build : build.split(".") : emptyArray;
57+
58+
Debug.assert(every(prereleaseArray, s => prereleasePartRegExp.test(s)), "Invalid argument: prerelease");
59+
Debug.assert(every(buildArray, s => buildPartRegExp.test(s)), "Invalid argument: build");
60+
5461
this.major = major;
5562
this.minor = minor;
5663
this.patch = patch;
57-
this.prerelease = prerelease ? prerelease.split(".") : emptyArray;
58-
this.build = build ? build.split(".") : emptyArray;
64+
this.prerelease = prereleaseArray;
65+
this.build = buildArray;
5966
}
6067

6168
static tryParse(text: string) {
@@ -96,6 +103,17 @@ namespace ts {
96103
}
97104
}
98105

106+
with(fields: { major?: number, minor?: number, patch?: number, prerelease?: string | readonly string[], build?: string | readonly string[] }) {
107+
const {
108+
major = this.major,
109+
minor = this.minor,
110+
patch = this.patch,
111+
prerelease = this.prerelease,
112+
build = this.build
113+
} = fields;
114+
return new Version(major, minor, patch, prerelease, build);
115+
}
116+
99117
toString() {
100118
let result = `${this.major}.${this.minor}.${this.patch}`;
101119
if (some(this.prerelease)) result += `-${this.prerelease.join(".")}`;
@@ -184,6 +202,10 @@ namespace ts {
184202
return undefined;
185203
}
186204

205+
/**
206+
* Tests whether a version matches the range. This is equivalent to `satisfies(version, range, { includePrerelease: true })`.
207+
* in `node-semver`.
208+
*/
187209
test(version: Version | string) {
188210
if (typeof version === "string") version = new Version(version);
189211
return testDisjunction(version, this._alternatives);
@@ -311,20 +333,22 @@ namespace ts {
311333
break;
312334
case "<":
313335
case ">=":
314-
comparators.push(createComparator(operator, version));
336+
comparators.push(
337+
isWildcard(minor) || isWildcard(patch) ? createComparator(operator, version.with({ prerelease: "0" })) :
338+
createComparator(operator, version));
315339
break;
316340
case "<=":
317341
case ">":
318342
comparators.push(
319-
isWildcard(minor) ? createComparator(operator === "<=" ? "<" : ">=", version.increment("major")) :
320-
isWildcard(patch) ? createComparator(operator === "<=" ? "<" : ">=", version.increment("minor")) :
343+
isWildcard(minor) ? createComparator(operator === "<=" ? "<" : ">=", version.increment("major").with({ prerelease: "0" })) :
344+
isWildcard(patch) ? createComparator(operator === "<=" ? "<" : ">=", version.increment("minor").with({ prerelease: "0" })) :
321345
createComparator(operator, version));
322346
break;
323347
case "=":
324348
case undefined:
325349
if (isWildcard(minor) || isWildcard(patch)) {
326-
comparators.push(createComparator(">=", version));
327-
comparators.push(createComparator("<", version.increment(isWildcard(minor) ? "major" : "minor")));
350+
comparators.push(createComparator(">=", version.with({ prerelease: "0" })));
351+
comparators.push(createComparator("<", version.increment(isWildcard(minor) ? "major" : "minor").with({ prerelease: "0" })));
328352
}
329353
else {
330354
comparators.push(createComparator("=", version));
@@ -389,4 +413,4 @@ namespace ts {
389413
function formatComparator(comparator: Comparator) {
390414
return `${comparator.operator}${comparator.operand}`;
391415
}
392-
}
416+
}

0 commit comments

Comments
 (0)
Please sign in to comment.