Skip to content

Commit 9fb3ea1

Browse files
committed
feat: follow up semver v2.0.0-rc.2
1 parent 916678f commit 9fb3ea1

File tree

4 files changed

+103
-48
lines changed

4 files changed

+103
-48
lines changed

src/providers/code-lens/version.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@ import type { DependencyInfo, Extractor } from '#types/extractor'
22
import type { CodeLensProvider, TextDocument } from 'vscode'
33
import { internalCommands } from '#state'
44
import { getPackageInfo } from '#utils/api/package'
5-
import { getUpdateType } from '#utils/semver'
6-
import { formatVersion, isSupportedProtocol, parseVersion } from '#utils/version'
5+
import { formatVersion, getUpdateType, isSupportedProtocol, parseVersion } from '#utils/version'
76
import { debounce } from 'perfect-debounce'
87
import { CodeLens, EventEmitter } from 'vscode'
98

src/utils/semver.ts

Lines changed: 0 additions & 39 deletions
This file was deleted.

src/utils/version.ts

Lines changed: 61 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,52 @@ export function parseVersion(rawVersion: string): ParsedVersion | null {
4949
return { protocol, prefix, semver }
5050
}
5151

52+
/** related to: https://semver.org/spec/v2.0.0-rc.2.html */
53+
const CORE_VERSION_PATTERN = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)/
54+
type CoreVersion = [major: number, minor: number, patch: number]
55+
56+
function stripBuildMetadata(version: string): string {
57+
const idx = version.indexOf('+')
58+
return idx === -1 ? version : version.slice(0, idx)
59+
}
60+
61+
function parseCoreVersion(version: string): CoreVersion | null {
62+
const match = version.match(CORE_VERSION_PATTERN)
63+
if (!match)
64+
return null
65+
66+
return [Number(match[1]), Number(match[2]), Number(match[3])]
67+
}
68+
69+
export type UpdateType = 'major' | 'minor' | 'patch' | 'prerelease' | 'none'
70+
71+
export function getUpdateType(current: string, latest: string): UpdateType {
72+
current = stripBuildMetadata(current)
73+
latest = stripBuildMetadata(latest)
74+
75+
const cur = parseCoreVersion(current)
76+
const lat = parseCoreVersion(latest)
77+
78+
if (!cur || !lat)
79+
return 'none'
80+
81+
if (lat[0] !== cur[0])
82+
return lat[0] > cur[0] ? 'major' : 'none'
83+
84+
if (lat[1] !== cur[1])
85+
return lat[1] > cur[1] ? 'minor' : 'none'
86+
87+
if (lat[2] !== cur[2])
88+
return lat[2] > cur[2] ? 'patch' : 'none'
89+
90+
if (current !== latest && current.includes('-') && !latest.includes('-'))
91+
return 'prerelease'
92+
93+
return 'none'
94+
}
95+
5296
export function getPrereleaseId(version: string): string | null {
97+
version = stripBuildMetadata(version)
5398
const idx = version.indexOf('-')
5499
if (idx === -1)
55100
return null
@@ -75,8 +120,14 @@ function comparePrereleasePrecedence(a: string, b: string): number {
75120

76121
const numA = Number(partsA[i])
77122
const numB = Number(partsB[i])
78-
if (!Number.isNaN(numA) && !Number.isNaN(numB)) {
79-
return numA - numB
123+
const isNumA = !Number.isNaN(numA)
124+
const isNumB = !Number.isNaN(numB)
125+
126+
if (isNumA && isNumB) {
127+
if (numA !== numB)
128+
return numA - numB
129+
} else if (isNumA !== isNumB) {
130+
return isNumA ? -1 : 1
80131
} else if (partsA[i] !== partsB[i]) {
81132
return partsA[i] < partsB[i] ? -1 : 1
82133
}
@@ -86,10 +137,14 @@ function comparePrereleasePrecedence(a: string, b: string): number {
86137
}
87138

88139
export function lt(a: string, b: string): boolean {
89-
const [coreA, preA] = a.split('-', 2)
90-
const [coreB, preB] = b.split('-', 2)
91-
const partsA = coreA.split('.').map(Number)
92-
const partsB = coreB.split('.').map(Number)
140+
a = stripBuildMetadata(a)
141+
b = stripBuildMetadata(b)
142+
143+
const [coreA, preA] = a.split('-')
144+
const [coreB, preB] = b.split('-')
145+
146+
const partsA = parseCoreVersion(coreA)!
147+
const partsB = parseCoreVersion(coreB)!
93148
for (let i = 0; i < 3; i++) {
94149
const diff = (partsA[i] || 0) - (partsB[i] || 0)
95150
if (diff !== 0)

tests/utils/version.test.ts

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { describe, expect, it } from 'vitest'
2-
import { getPrereleaseId, lt, parseVersion } from '../../src/utils/version'
2+
import { getPrereleaseId, getUpdateType, lt, parseVersion } from '../../src/utils/version'
33

44
describe('parseVersion', () => {
55
it('should parse plain version', () => {
@@ -85,6 +85,11 @@ describe('getPrereleaseId', () => {
8585
it('should handle prerelease without dots', () => {
8686
expect(getPrereleaseId('1.0.0-canary')).toBe('canary')
8787
})
88+
89+
it('should ignore build metadata', () => {
90+
expect(getPrereleaseId('1.0.0-beta.1+build')).toBe('beta')
91+
expect(getPrereleaseId('1.0.0+build')).toBeNull()
92+
})
8893
})
8994

9095
describe('lt', () => {
@@ -126,4 +131,39 @@ describe('lt', () => {
126131
expect(lt('1.0.0-beta', '1.0.0-beta.1')).toBe(true)
127132
expect(lt('1.0.0-beta.1', '1.0.0-beta')).toBe(false)
128133
})
134+
135+
it('should ignore build metadata for precedence', () => {
136+
expect(lt('1.0.0+build1', '1.0.0+build2')).toBe(false)
137+
expect(lt('1.0.0-alpha+001', '1.0.0')).toBe(true)
138+
expect(lt('1.0.0-alpha+001', '1.0.0-alpha.1')).toBe(true)
139+
})
140+
141+
it('should rank numeric identifiers lower than non-numeric', () => {
142+
expect(lt('1.0.0-1', '1.0.0-alpha')).toBe(true)
143+
expect(lt('1.0.0-alpha', '1.0.0-1')).toBe(false)
144+
})
145+
146+
it('should follow rc.2 spec precedence example', () => {
147+
expect(lt('1.0.0-alpha', '1.0.0-alpha.1')).toBe(true)
148+
expect(lt('1.0.0-alpha.1', '1.0.0-beta.2')).toBe(true)
149+
expect(lt('1.0.0-beta.2', '1.0.0-beta.11')).toBe(true)
150+
expect(lt('1.0.0-beta.11', '1.0.0-rc.1')).toBe(true)
151+
expect(lt('1.0.0-rc.1', '1.0.0')).toBe(true)
152+
})
153+
154+
it('should handle prerelease identifiers containing hyphens', () => {
155+
expect(lt('1.0.0-alpha-beta', '1.0.0-alpha-gamma')).toBe(true)
156+
expect(lt('1.0.0-alpha-gamma', '1.0.0-alpha-beta')).toBe(false)
157+
})
158+
})
159+
160+
describe('getUpdateType', () => {
161+
it('should ignore build metadata', () => {
162+
expect(getUpdateType('1.0.0+build', '2.0.0+build')).toBe('major')
163+
expect(getUpdateType('1.0.0+build1', '1.0.0+build2')).toBe('none')
164+
})
165+
166+
it('should detect prerelease with build metadata', () => {
167+
expect(getUpdateType('1.0.0-alpha+001', '1.0.0')).toBe('prerelease')
168+
})
129169
})

0 commit comments

Comments
 (0)