1
1
/* @internal */
2
2
namespace ts {
3
- // Per https://semver.org/#spec-item-2:
4
- //
3
+ // https://semver.org/#spec-item-2
5
4
// > A normal version number MUST take the form X.Y.Z where X, Y, and Z are non-negative
6
5
// > integers, and MUST NOT contain leading zeroes. X is the major version, Y is the minor
7
6
// > version, and Z is the patch version. Each element MUST increase numerically.
8
7
//
9
8
// NOTE: We differ here in that we allow X and X.Y, with missing parts having the default
10
9
// value of `0`.
11
- const versionRegExp = / ^ ( 0 | [ 1 - 9 ] \d * ) (?: \. ( 0 | [ 1 - 9 ] \d * ) (?: \. ( 0 | [ 1 - 9 ] \d * ) (?: - ( [ a - z 0 - 9 - .] + ) ) ? (?: ( \+ [ a - z 0 - 9 - .] + ) ) ? ) ? ) ? $ / i;
10
+ const versionRegExp = / ^ ( 0 | [ 1 - 9 ] \d * ) (?: \. ( 0 | [ 1 - 9 ] \d * ) (?: \. ( 0 | [ 1 - 9 ] \d * ) (?: \ -( [ a - z 0 - 9 - .] + ) ) ? (?: \+ ( [ a - z 0 - 9 - .] + ) ) ? ) ? ) ? $ / i;
12
11
13
- // Per https://semver.org/#spec-item-9:
14
- //
12
+ // https://semver.org/#spec-item-9
15
13
// > A pre-release version MAY be denoted by appending a hyphen and a series of dot separated
16
14
// > identifiers immediately following the patch version. Identifiers MUST comprise only ASCII
17
15
// > alphanumerics and hyphen [0-9A-Za-z-]. Identifiers MUST NOT be empty. Numeric identifiers
18
16
// > MUST NOT include leading zeroes.
19
17
const prereleaseRegExp = / ^ (?: 0 | [ 1 - 9 ] \d * | [ a - z - ] [ a - z 0 - 9 - ] * ) (?: \. (?: 0 | [ 1 - 9 ] \d * | [ a - z - ] [ a - z 0 - 9 - ] * ) ) * $ / i;
20
18
21
- // Per https://semver.org/#spec-item-10:
22
- //
19
+ // https://semver.org/#spec-item-10
23
20
// > Build metadata MAY be denoted by appending a plus sign and a series of dot separated
24
21
// > identifiers immediately following the patch or pre-release version. Identifiers MUST
25
22
// > comprise only ASCII alphanumerics and hyphen [0-9A-Za-z-]. Identifiers MUST NOT be empty.
26
23
const buildRegExp = / ^ [ a - z 0 - 9 - ] + (?: \. [ a - z 0 - 9 - ] + ) * $ / i;
27
24
28
- // Per https://semver.org/#spec-item-9:
29
- //
25
+ // https://semver.org/#spec-item-9
30
26
// > Numeric identifiers MUST NOT include leading zeroes.
31
27
const numericIdentifierRegExp = / ^ ( 0 | [ 1 - 9 ] \d * ) $ / ;
32
28
33
29
/**
34
- * Describes a precise semantic version number, per https://semver.org
30
+ * Describes a precise semantic version number, https://semver.org
35
31
*/
36
32
export class Version {
37
- static readonly zero = new Version ( 0 ) ;
38
-
39
33
readonly major : number ;
40
34
readonly minor : number ;
41
35
readonly patch : number ;
42
36
readonly prerelease : ReadonlyArray < string > ;
43
37
readonly build : ReadonlyArray < string > ;
44
38
45
- constructor ( major : number , minor = 0 , patch = 0 , prerelease = "" , build = "" ) {
39
+ constructor ( text : string ) ;
40
+ constructor ( major : number , minor ?: number , patch ?: number , prerelease ?: string , build ?: string ) ;
41
+ constructor ( major : number | string , minor = 0 , patch = 0 , prerelease = "" , build = "" ) {
42
+ if ( typeof major === "string" ) {
43
+ const result = Debug . assertDefined ( tryParseComponents ( major ) , "Invalid version" ) ;
44
+ ( { major, minor, patch, prerelease, build } = result ) ;
45
+ }
46
+
46
47
Debug . assert ( major >= 0 , "Invalid argument: major" ) ;
47
48
Debug . assert ( minor >= 0 , "Invalid argument: minor" ) ;
48
49
Debug . assert ( patch >= 0 , "Invalid argument: patch" ) ;
@@ -51,104 +52,102 @@ namespace ts {
51
52
this . major = major ;
52
53
this . minor = minor ;
53
54
this . patch = patch ;
54
- this . prerelease = prerelease === "" ? emptyArray : prerelease . split ( "." ) ;
55
- this . build = build === "" ? emptyArray : build . split ( "." ) ;
56
- }
57
-
58
- static parse ( text : string ) {
59
- return Debug . assertDefined ( this . tryParse ( text ) ) ;
55
+ this . prerelease = prerelease ? prerelease . split ( "." ) : emptyArray ;
56
+ this . build = build ? build . split ( "." ) : emptyArray ;
60
57
}
61
58
62
59
static tryParse ( text : string ) {
63
- const match = versionRegExp . exec ( text ) ;
64
- if ( ! match ) return undefined ;
60
+ const result = tryParseComponents ( text ) ;
61
+ if ( ! result ) return undefined ;
65
62
66
- const [ , major , minor = 0 , patch = 0 , prerelease , build ] = match ;
67
- if ( prerelease && ! prereleaseRegExp . test ( prerelease ) ) return undefined ;
68
- if ( build && ! buildRegExp . test ( build ) ) return undefined ;
69
- return new Version ( + major , + minor , + patch , prerelease , build ) ;
63
+ const { major, minor, patch, prerelease, build } = result ;
64
+ return new Version ( major , minor , patch , prerelease , build ) ;
70
65
}
71
66
72
- static compare ( left : Version | undefined , right : Version | undefined , compareBuildMetadata ?: boolean ) {
73
- // Per https://semver.org/#spec-item-11:
74
- //
67
+ compareTo ( other : Version | undefined ) {
68
+ // https://semver.org/#spec-item-11
75
69
// > Precedence is determined by the first difference when comparing each of these
76
70
// > identifiers from left to right as follows: Major, minor, and patch versions are
77
71
// > always compared numerically.
78
72
//
79
- // > When major, minor, and patch are equal, a pre-release version has lower
80
- // > precedence than a normal version.
73
+ // https://semver.org/#spec-item-11
74
+ // > Precedence for two pre-release versions with the same major, minor, and patch version
75
+ // > MUST be determined by comparing each dot separated identifier from left to right until
76
+ // > a difference is found [...]
81
77
//
82
- // Per https://semver.org/#spec-item-10:
83
- //
84
- // > Build metadata SHOULD be ignored when determining version precedence.
85
- if ( left === right ) return Comparison . EqualTo ;
86
- if ( left === undefined ) return Comparison . LessThan ;
87
- if ( right === undefined ) return Comparison . GreaterThan ;
88
- return compareValues ( left . major , right . major )
89
- || compareValues ( left . minor , right . minor )
90
- || compareValues ( left . patch , right . patch )
91
- || compareVersionFragments ( left . prerelease , right . prerelease , /*compareNumericIdentifiers*/ true )
92
- || ( compareBuildMetadata ? compareVersionFragments ( left . build , right . build , /*compareNumericIdentifiers*/ false ) : Comparison . EqualTo ) ;
93
- }
94
-
95
- compareTo ( other : Version , compareBuildMetadata ?: boolean ) {
96
- return Version . compare ( this , other , compareBuildMetadata ) ;
78
+ // https://semver.org/#spec-item-11
79
+ // > Build metadata does not figure into precedence
80
+ if ( this === other ) return Comparison . EqualTo ;
81
+ if ( other === undefined ) return Comparison . GreaterThan ;
82
+ return compareValues ( this . major , other . major )
83
+ || compareValues ( this . minor , other . minor )
84
+ || compareValues ( this . patch , other . patch )
85
+ || comparePrerelaseIdentifiers ( this . prerelease , other . prerelease ) ;
97
86
}
98
87
99
88
toString ( ) {
100
89
let result = `${ this . major } .${ this . minor } .${ this . patch } ` ;
101
- if ( this . prerelease ) result += `-${ this . prerelease . join ( "." ) } ` ;
102
- if ( this . build ) result += `+${ this . build . join ( "." ) } ` ;
90
+ if ( some ( this . prerelease ) ) result += `-${ this . prerelease . join ( "." ) } ` ;
91
+ if ( some ( this . build ) ) result += `+${ this . build . join ( "." ) } ` ;
103
92
return result ;
104
93
}
105
94
}
106
95
107
- function compareVersionFragments ( left : ReadonlyArray < string > , right : ReadonlyArray < string > , compareNumericIdentifiers : boolean ) {
108
- // Per https://semver.org/#spec-item-11:
109
- //
96
+ function tryParseComponents ( text : string ) {
97
+ const match = versionRegExp . exec ( text ) ;
98
+ if ( ! match ) return undefined ;
99
+
100
+ const [ , major , minor = "0" , patch = "0" , prerelease = "" , build = "" ] = match ;
101
+ if ( prerelease && ! prereleaseRegExp . test ( prerelease ) ) return undefined ;
102
+ if ( build && ! buildRegExp . test ( build ) ) return undefined ;
103
+ return {
104
+ major : parseInt ( major , 10 ) ,
105
+ minor : parseInt ( minor , 10 ) ,
106
+ patch : parseInt ( patch , 10 ) ,
107
+ prerelease,
108
+ build
109
+ } ;
110
+ }
111
+
112
+ function comparePrerelaseIdentifiers ( left : ReadonlyArray < string > , right : ReadonlyArray < string > ) {
113
+ // https://semver.org/#spec-item-11
110
114
// > When major, minor, and patch are equal, a pre-release version has lower precedence
111
115
// > than a normal version.
112
116
if ( left === right ) return Comparison . EqualTo ;
113
117
if ( left . length === 0 ) return right . length === 0 ? Comparison . EqualTo : Comparison . GreaterThan ;
114
118
if ( right . length === 0 ) return Comparison . LessThan ;
115
119
116
- // Per https://semver.org/#spec-item-11:
117
- //
120
+ // https://semver.org/#spec-item-11
118
121
// > Precedence for two pre-release versions with the same major, minor, and patch version
119
122
// > MUST be determined by comparing each dot separated identifier from left to right until
120
- // > a difference is found
123
+ // > a difference is found [...]
121
124
const length = Math . min ( left . length , right . length ) ;
122
125
for ( let i = 0 ; i < length ; i ++ ) {
123
126
const leftIdentifier = left [ i ] ;
124
127
const rightIdentifier = right [ i ] ;
125
128
if ( leftIdentifier === rightIdentifier ) continue ;
126
129
127
- const leftIsNumeric = compareNumericIdentifiers && numericIdentifierRegExp . test ( leftIdentifier ) ;
128
- const rightIsNumeric = compareNumericIdentifiers && numericIdentifierRegExp . test ( rightIdentifier ) ;
130
+ const leftIsNumeric = numericIdentifierRegExp . test ( leftIdentifier ) ;
131
+ const rightIsNumeric = numericIdentifierRegExp . test ( rightIdentifier ) ;
129
132
if ( leftIsNumeric || rightIsNumeric ) {
130
- // Per https://semver.org/#spec-item-11:
131
- //
133
+ // https://semver.org/#spec-item-11
132
134
// > Numeric identifiers always have lower precedence than non-numeric identifiers.
133
135
if ( leftIsNumeric !== rightIsNumeric ) return leftIsNumeric ? Comparison . LessThan : Comparison . GreaterThan ;
134
136
135
- // Per https://semver.org/#spec-item-11:
136
- //
137
+ // https://semver.org/#spec-item-11
137
138
// > identifiers consisting of only digits are compared numerically
138
139
const result = compareValues ( + leftIdentifier , + rightIdentifier ) ;
139
140
if ( result ) return result ;
140
141
}
141
142
else {
142
- // Per https://semver.org/#spec-item-11:
143
- //
143
+ // https://semver.org/#spec-item-11
144
144
// > identifiers with letters or hyphens are compared lexically in ASCII sort order.
145
145
const result = compareStringsCaseSensitive ( leftIdentifier , rightIdentifier ) ;
146
146
if ( result ) return result ;
147
147
}
148
148
}
149
149
150
- // Per https://semver.org/#spec-item-11:
151
- //
150
+ // https://semver.org/#spec-item-11
152
151
// > A larger set of pre-release fields has a higher precedence than a smaller set, if all
153
152
// > of the preceding identifiers are equal.
154
153
return compareValues ( left . length , right . length ) ;
0 commit comments