@@ -77,27 +77,46 @@ export const EMPTY_VERSION: RawBasicVersionInfo = {
77
77
micro : undefined ,
78
78
} ,
79
79
} ;
80
+ Object . freeze ( EMPTY_VERSION ) ;
81
+
82
+ function copyStrict < T extends BasicVersionInfo > ( info : T ) : RawBasicVersionInfo {
83
+ const copied : RawBasicVersionInfo = {
84
+ major : info . major ,
85
+ minor : info . minor ,
86
+ micro : info . micro ,
87
+ } ;
88
+
89
+ const unnormalized = ( ( info as unknown ) as RawBasicVersionInfo ) . unnormalized ;
90
+ if ( unnormalized !== undefined ) {
91
+ copied . unnormalized = {
92
+ major : unnormalized . major ,
93
+ minor : unnormalized . minor ,
94
+ micro : unnormalized . micro ,
95
+ } ;
96
+ }
97
+
98
+ return copied ;
99
+ }
80
100
81
101
/**
82
102
* Make a copy and set all the properties properly.
83
103
*
84
- * Only the "basic" version info will be normalized. The caller
85
- * is responsible for any other properties beyond that.
104
+ * Only the "basic" version info will be set (and normalized).
105
+ * The caller is responsible for any other properties beyond that.
86
106
*/
87
107
export function normalizeBasicVersionInfo < T extends BasicVersionInfo > ( info : T | undefined ) : T {
88
108
if ( ! info ) {
89
109
return EMPTY_VERSION as T ;
90
110
}
91
- const norm : T = { ...info } ;
92
- const raw = ( norm as unknown ) as RawBasicVersionInfo ;
111
+ const norm = copyStrict ( info ) ;
93
112
// Do not normalize if it has already been normalized.
94
- if ( raw . unnormalized === undefined ) {
95
- raw . unnormalized = { } ;
96
- [ norm . major , raw . unnormalized . major ] = normalizeVersionPart ( norm . major ) ;
97
- [ norm . minor , raw . unnormalized . minor ] = normalizeVersionPart ( norm . minor ) ;
98
- [ norm . micro , raw . unnormalized . micro ] = normalizeVersionPart ( norm . micro ) ;
113
+ if ( norm . unnormalized === undefined ) {
114
+ norm . unnormalized = { } ;
115
+ [ norm . major , norm . unnormalized . major ] = normalizeVersionPart ( norm . major ) ;
116
+ [ norm . minor , norm . unnormalized . minor ] = normalizeVersionPart ( norm . minor ) ;
117
+ [ norm . micro , norm . unnormalized . micro ] = normalizeVersionPart ( norm . micro ) ;
99
118
}
100
- return norm ;
119
+ return norm as T ;
101
120
}
102
121
103
122
function validateVersionPart ( prop : string , part : number , unnormalized ?: ErrorMsg ) {
@@ -230,6 +249,55 @@ export function isVersionInfoEmpty<T extends BasicVersionInfo>(info: T): boolean
230
249
return info . major < 0 && info . minor < 0 && info . micro < 0 ;
231
250
}
232
251
252
+ /**
253
+ * Decide if two versions are the same or if one is "less".
254
+ *
255
+ * Note that a less-complete object that otherwise matches
256
+ * is considered "less".
257
+ *
258
+ * Additional checks for an otherwise "identical" version may be made
259
+ * through `compareExtra()`.
260
+ *
261
+ * @returns - the customary comparison indicator (e.g. -1 means left is "more")
262
+ * @returns - a string that indicates the property where they differ (if any)
263
+ */
264
+ export function compareVersions < T extends BasicVersionInfo , V extends BasicVersionInfo > (
265
+ // the versions to compare:
266
+ left : T ,
267
+ right : V ,
268
+ compareExtra ?: ( v1 : T , v2 : V ) => [ number , string ] ,
269
+ ) : [ number , string ] {
270
+ if ( left . major < right . major ) {
271
+ return [ 1 , 'major' ] ;
272
+ } else if ( left . major > right . major ) {
273
+ return [ - 1 , 'major' ] ;
274
+ } else if ( left . major === - 1 ) {
275
+ // Don't bother checking minor or micro.
276
+ return [ 0 , '' ] ;
277
+ }
278
+
279
+ if ( left . minor < right . minor ) {
280
+ return [ 1 , 'minor' ] ;
281
+ } else if ( left . minor > right . minor ) {
282
+ return [ - 1 , 'minor' ] ;
283
+ } else if ( left . minor === - 1 ) {
284
+ // Don't bother checking micro.
285
+ return [ 0 , '' ] ;
286
+ }
287
+
288
+ if ( left . micro < right . micro ) {
289
+ return [ 1 , 'micro' ] ;
290
+ } else if ( left . micro > right . micro ) {
291
+ return [ - 1 , 'micro' ] ;
292
+ }
293
+
294
+ if ( compareExtra !== undefined ) {
295
+ return compareExtra ( left , right ) ;
296
+ }
297
+
298
+ return [ 0 , '' ] ;
299
+ }
300
+
233
301
//===========================
234
302
// base version info
235
303
@@ -246,15 +314,12 @@ export type VersionInfo = BasicVersionInfo & {
246
314
* Make a copy and set all the properties properly.
247
315
*/
248
316
export function normalizeVersionInfo < T extends VersionInfo > ( info : T ) : T {
249
- const basic = normalizeBasicVersionInfo ( info ) ;
250
- if ( ! info ) {
251
- basic . raw = '' ;
252
- return basic ;
253
- }
254
- const norm = { ...info , ...basic } ;
317
+ const norm = normalizeBasicVersionInfo ( info ) ;
318
+ norm . raw = info . raw ;
255
319
if ( ! norm . raw ) {
256
320
norm . raw = '' ;
257
321
}
322
+ // Any string value of "raw" is considered normalized.
258
323
return norm ;
259
324
}
260
325
@@ -285,6 +350,52 @@ export function parseVersionInfo<T extends VersionInfo>(verStr: string): ParseRe
285
350
return result ;
286
351
}
287
352
353
+ /**
354
+ * Checks if major, minor, and micro match.
355
+ *
356
+ * Additional checks may be made through `compareExtra()`.
357
+ */
358
+ export function areIdenticalVersion < T extends BasicVersionInfo , V extends BasicVersionInfo > (
359
+ // the versions to compare:
360
+ left : T ,
361
+ right : V ,
362
+ compareExtra ?: ( v1 : T , v2 : V ) => [ number , string ] ,
363
+ ) : boolean {
364
+ const [ result ] = compareVersions ( left , right , compareExtra ) ;
365
+ return result === 0 ;
366
+ }
367
+
368
+ /**
369
+ * Checks if the versions are identical or one is more complete than other (and otherwise the same).
370
+ *
371
+ * At the least the major version must be set (non-negative).
372
+ */
373
+ export function areSimilarVersions < T extends BasicVersionInfo , V extends BasicVersionInfo > (
374
+ // the versions to compare:
375
+ left : T ,
376
+ right : V ,
377
+ compareExtra ?: ( v1 : T , v2 : V ) => [ number , string ] ,
378
+ ) : boolean {
379
+ const [ result , prop ] = compareVersions ( left , right , compareExtra ) ;
380
+ if ( result === 0 ) {
381
+ return true ;
382
+ }
383
+
384
+ if ( prop === 'major' ) {
385
+ // An empty version is never similar (except to another empty version).
386
+ return false ;
387
+ }
388
+
389
+ // tslint:disable:no-any
390
+ if ( result < 0 ) {
391
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
392
+ return ( ( right as unknown ) as any ) [ prop ] === - 1 ;
393
+ }
394
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
395
+ return ( ( left as unknown ) as any ) [ prop ] === - 1 ;
396
+ // tslint:enable:no-any
397
+ }
398
+
288
399
//===========================
289
400
// semver
290
401
0 commit comments