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