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