11/* @internal */
22namespace ts {
3- // Per https://semver.org/#spec-item-2:
4- //
3+ // https://semver.org/#spec-item-2
54 // > A normal version number MUST take the form X.Y.Z where X, Y, and Z are non-negative
65 // > integers, and MUST NOT contain leading zeroes. X is the major version, Y is the minor
76 // > version, and Z is the patch version. Each element MUST increase numerically.
87 //
98 // NOTE: We differ here in that we allow X and X.Y, with missing parts having the default
109 // 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;
1211
13- // Per https://semver.org/#spec-item-9:
14- //
12+ // https://semver.org/#spec-item-9
1513 // > A pre-release version MAY be denoted by appending a hyphen and a series of dot separated
1614 // > identifiers immediately following the patch version. Identifiers MUST comprise only ASCII
1715 // > alphanumerics and hyphen [0-9A-Za-z-]. Identifiers MUST NOT be empty. Numeric identifiers
1816 // > MUST NOT include leading zeroes.
1917 const prereleaseRegExp = / ^ (?: 0 | [ 1 - 9 ] \d * | [ a - z - ] [ a - z 0 - 9 - ] * ) (?: \. (?: 0 | [ 1 - 9 ] \d * | [ a - z - ] [ a - z 0 - 9 - ] * ) ) * $ / i;
2018
21- // Per https://semver.org/#spec-item-10:
22- //
19+ // https://semver.org/#spec-item-10
2320 // > Build metadata MAY be denoted by appending a plus sign and a series of dot separated
2421 // > identifiers immediately following the patch or pre-release version. Identifiers MUST
2522 // > comprise only ASCII alphanumerics and hyphen [0-9A-Za-z-]. Identifiers MUST NOT be empty.
2623 const buildRegExp = / ^ [ a - z 0 - 9 - ] + (?: \. [ a - z 0 - 9 - ] + ) * $ / i;
2724
28- // Per https://semver.org/#spec-item-9:
29- //
25+ // https://semver.org/#spec-item-9
3026 // > Numeric identifiers MUST NOT include leading zeroes.
3127 const numericIdentifierRegExp = / ^ ( 0 | [ 1 - 9 ] \d * ) $ / ;
3228
3329 /**
34- * Describes a precise semantic version number, per https://semver.org
30+ * Describes a precise semantic version number, https://semver.org
3531 */
3632 export class Version {
37- static readonly zero = new Version ( 0 ) ;
38-
3933 readonly major : number ;
4034 readonly minor : number ;
4135 readonly patch : number ;
4236 readonly prerelease : ReadonlyArray < string > ;
4337 readonly build : ReadonlyArray < string > ;
4438
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+
4647 Debug . assert ( major >= 0 , "Invalid argument: major" ) ;
4748 Debug . assert ( minor >= 0 , "Invalid argument: minor" ) ;
4849 Debug . assert ( patch >= 0 , "Invalid argument: patch" ) ;
@@ -51,104 +52,102 @@ namespace ts {
5152 this . major = major ;
5253 this . minor = minor ;
5354 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 ;
6057 }
6158
6259 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 ;
6562
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 ) ;
7065 }
7166
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
7569 // > Precedence is determined by the first difference when comparing each of these
7670 // > identifiers from left to right as follows: Major, minor, and patch versions are
7771 // > always compared numerically.
7872 //
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 [...]
8177 //
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 ) ;
9786 }
9887
9988 toString ( ) {
10089 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 ( "." ) } ` ;
10392 return result ;
10493 }
10594 }
10695
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
110114 // > When major, minor, and patch are equal, a pre-release version has lower precedence
111115 // > than a normal version.
112116 if ( left === right ) return Comparison . EqualTo ;
113117 if ( left . length === 0 ) return right . length === 0 ? Comparison . EqualTo : Comparison . GreaterThan ;
114118 if ( right . length === 0 ) return Comparison . LessThan ;
115119
116- // Per https://semver.org/#spec-item-11:
117- //
120+ // https://semver.org/#spec-item-11
118121 // > Precedence for two pre-release versions with the same major, minor, and patch version
119122 // > MUST be determined by comparing each dot separated identifier from left to right until
120- // > a difference is found
123+ // > a difference is found [...]
121124 const length = Math . min ( left . length , right . length ) ;
122125 for ( let i = 0 ; i < length ; i ++ ) {
123126 const leftIdentifier = left [ i ] ;
124127 const rightIdentifier = right [ i ] ;
125128 if ( leftIdentifier === rightIdentifier ) continue ;
126129
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 ) ;
129132 if ( leftIsNumeric || rightIsNumeric ) {
130- // Per https://semver.org/#spec-item-11:
131- //
133+ // https://semver.org/#spec-item-11
132134 // > Numeric identifiers always have lower precedence than non-numeric identifiers.
133135 if ( leftIsNumeric !== rightIsNumeric ) return leftIsNumeric ? Comparison . LessThan : Comparison . GreaterThan ;
134136
135- // Per https://semver.org/#spec-item-11:
136- //
137+ // https://semver.org/#spec-item-11
137138 // > identifiers consisting of only digits are compared numerically
138139 const result = compareValues ( + leftIdentifier , + rightIdentifier ) ;
139140 if ( result ) return result ;
140141 }
141142 else {
142- // Per https://semver.org/#spec-item-11:
143- //
143+ // https://semver.org/#spec-item-11
144144 // > identifiers with letters or hyphens are compared lexically in ASCII sort order.
145145 const result = compareStringsCaseSensitive ( leftIdentifier , rightIdentifier ) ;
146146 if ( result ) return result ;
147147 }
148148 }
149149
150- // Per https://semver.org/#spec-item-11:
151- //
150+ // https://semver.org/#spec-item-11
152151 // > A larger set of pre-release fields has a higher precedence than a smaller set, if all
153152 // > of the preceding identifiers are equal.
154153 return compareValues ( left . length , right . length ) ;
0 commit comments