Skip to content

Commit a7ab13a

Browse files
madtisamadtisawraithgar
authoredJan 31, 2024
feat: preserve pre-release and build parts of a version on coerce (#671)
# What / Why Introduces the new coerce option `includePrerelease`, if set, allowing to preserve pre-release and build parts of a version. ## References Fixes #592 Fixes #357 --------- Co-authored-by: madtisa <temp-email-for-oauth-madtisa@gitlab.localhost> Co-authored-by: Gar <wraithgar@github.com>
1 parent 816c7b2 commit a7ab13a

File tree

4 files changed

+60
-9
lines changed

4 files changed

+60
-9
lines changed
 

‎README.md

+4
Original file line numberDiff line numberDiff line change
@@ -529,6 +529,10 @@ tuple. For example, `1.2.3.4` will return `2.3.4` in rtl mode, not
529529
`4.0.0`. `1.2.3/4` will return `4.0.0`, because the `4` is not a part of
530530
any other overlapping SemVer tuple.
531531

532+
If the `options.includePrerelease` flag is set, then the `coerce` result will contain
533+
prerelease and build parts of a version. For example, `1.2.3.4-rc.1+rev.2`
534+
will preserve prerelease `rc.1` and build `rev.2` in the result.
535+
532536
### Clean
533537

534538
* `clean(version)`: Clean a string to be a valid semver if possible

‎functions/coerce.js

+13-5
Original file line numberDiff line numberDiff line change
@@ -19,34 +19,42 @@ const coerce = (version, options) => {
1919

2020
let match = null
2121
if (!options.rtl) {
22-
match = version.match(re[t.COERCE])
22+
match = version.match(options.includePrerelease ? re[t.COERCEFULL] : re[t.COERCE])
2323
} else {
2424
// Find the right-most coercible string that does not share
2525
// a terminus with a more left-ward coercible string.
2626
// Eg, '1.2.3.4' wants to coerce '2.3.4', not '3.4' or '4'
27+
// With includePrerelease option set, '1.2.3.4-rc' wants to coerce '2.3.4-rc', not '2.3.4'
2728
//
2829
// Walk through the string checking with a /g regexp
2930
// Manually set the index so as to pick up overlapping matches.
3031
// Stop when we get a match that ends at the string end, since no
3132
// coercible string can be more right-ward without the same terminus.
33+
const coerceRtlRegex = options.includePrerelease ? re[t.COERCERTLFULL] : re[t.COERCERTL]
3234
let next
33-
while ((next = re[t.COERCERTL].exec(version)) &&
35+
while ((next = coerceRtlRegex.exec(version)) &&
3436
(!match || match.index + match[0].length !== version.length)
3537
) {
3638
if (!match ||
3739
next.index + next[0].length !== match.index + match[0].length) {
3840
match = next
3941
}
40-
re[t.COERCERTL].lastIndex = next.index + next[1].length + next[2].length
42+
coerceRtlRegex.lastIndex = next.index + next[1].length + next[2].length
4143
}
4244
// leave it in a clean state
43-
re[t.COERCERTL].lastIndex = -1
45+
coerceRtlRegex.lastIndex = -1
4446
}
4547

4648
if (match === null) {
4749
return null
4850
}
4951

50-
return parse(`${match[2]}.${match[3] || '0'}.${match[4] || '0'}`, options)
52+
const major = match[2]
53+
const minor = match[3] || '0'
54+
const patch = match[4] || '0'
55+
const prerelease = options.includePrerelease && match[5] ? `-${match[5]}` : ''
56+
const build = options.includePrerelease && match[6] ? `+${match[6]}` : ''
57+
58+
return parse(`${major}.${minor}.${patch}${prerelease}${build}`, options)
5159
}
5260
module.exports = coerce

‎internal/re.js

+7-2
Original file line numberDiff line numberDiff line change
@@ -154,12 +154,17 @@ createToken('XRANGELOOSE', `^${src[t.GTLT]}\\s*${src[t.XRANGEPLAINLOOSE]}$`)
154154

155155
// Coercion.
156156
// Extract anything that could conceivably be a part of a valid semver
157-
createToken('COERCE', `${'(^|[^\\d])' +
157+
createToken('COERCEPLAIN', `${'(^|[^\\d])' +
158158
'(\\d{1,'}${MAX_SAFE_COMPONENT_LENGTH}})` +
159159
`(?:\\.(\\d{1,${MAX_SAFE_COMPONENT_LENGTH}}))?` +
160-
`(?:\\.(\\d{1,${MAX_SAFE_COMPONENT_LENGTH}}))?` +
160+
`(?:\\.(\\d{1,${MAX_SAFE_COMPONENT_LENGTH}}))?`)
161+
createToken('COERCE', `${src[t.COERCEPLAIN]}(?:$|[^\\d])`)
162+
createToken('COERCEFULL', src[t.COERCEPLAIN] +
163+
`(?:${src[t.PRERELEASE]})?` +
164+
`(?:${src[t.BUILD]})?` +
161165
`(?:$|[^\\d])`)
162166
createToken('COERCERTL', src[t.COERCE], true)
167+
createToken('COERCERTLFULL', src[t.COERCEFULL], true)
163168

164169
// Tilde ranges.
165170
// Meaning is "reasonably at or greater than"

‎test/functions/coerce.js

+36-2
Original file line numberDiff line numberDiff line change
@@ -110,13 +110,47 @@ test('coerce tests', (t) => {
110110
['1.2.3/6', '6.0.0', { rtl: true }],
111111
['1.2.3.4', '2.3.4', { rtl: true }],
112112
['1.2.3.4xyz', '2.3.4', { rtl: true }],
113+
114+
['1-rc.5', '1.0.0-rc.5', { includePrerelease: true }],
115+
['1.2-rc.5', '1.2.0-rc.5', { includePrerelease: true }],
116+
['1.2.3-rc.5', '1.2.3-rc.5', { includePrerelease: true }],
117+
['1.2.3-rc.5/a', '1.2.3-rc.5', { includePrerelease: true }],
118+
['1.2.3.4-rc.5', '1.2.3', { includePrerelease: true }],
119+
['1.2.3.4+rev.6', '1.2.3', { includePrerelease: true }],
120+
121+
['1+rev.6', '1.0.0+rev.6', { includePrerelease: true }],
122+
['1.2+rev.6', '1.2.0+rev.6', { includePrerelease: true }],
123+
['1.2.3+rev.6', '1.2.3+rev.6', { includePrerelease: true }],
124+
['1.2.3+rev.6/a', '1.2.3+rev.6', { includePrerelease: true }],
125+
['1.2.3.4-rc.5', '1.2.3', { includePrerelease: true }],
126+
['1.2.3.4+rev.6', '1.2.3', { includePrerelease: true }],
127+
128+
['1-rc.5+rev.6', '1.0.0-rc.5+rev.6', { includePrerelease: true }],
129+
['1.2-rc.5+rev.6', '1.2.0-rc.5+rev.6', { includePrerelease: true }],
130+
['1.2.3-rc.5+rev.6', '1.2.3-rc.5+rev.6', { includePrerelease: true }],
131+
['1.2.3-rc.5+rev.6/a', '1.2.3-rc.5+rev.6', { includePrerelease: true }],
132+
133+
['1.2-rc.5+rev.6', '1.2.0-rc.5+rev.6', { rtl: true, includePrerelease: true }],
134+
['1.2.3-rc.5+rev.6', '1.2.3-rc.5+rev.6', { rtl: true, includePrerelease: true }],
135+
['1.2.3.4-rc.5+rev.6', '2.3.4-rc.5+rev.6', { rtl: true, includePrerelease: true }],
136+
['1.2.3.4-rc.5', '2.3.4-rc.5', { rtl: true, includePrerelease: true }],
137+
['1.2.3.4+rev.6', '2.3.4+rev.6', { rtl: true, includePrerelease: true }],
138+
['1.2.3.4-rc.5+rev.6/7', '7.0.0', { rtl: true, includePrerelease: true }],
139+
['1.2.3.4-rc/7.5+rev.6', '7.5.0+rev.6', { rtl: true, includePrerelease: true }],
140+
['1.2.3.4/7-rc.5+rev.6', '7.0.0-rc.5+rev.6', { rtl: true, includePrerelease: true }],
113141
]
114142
coerceToValid.forEach(([input, expected, options]) => {
115-
const msg = `coerce(${input}) should become ${expected}`
116-
t.same((coerce(input, options) || {}).version, expected, msg)
143+
const coerceExpression = `coerce(${input}, ${JSON.stringify(options)})`
144+
const coercedVersion = coerce(input, options) || {}
145+
const expectedVersion = parse(expected)
146+
t.equal(expectedVersion.compare(coercedVersion), 0,
147+
`${coerceExpression} should be equal to ${expectedVersion}`)
148+
t.equal(expectedVersion.compareBuild(coercedVersion), 0,
149+
`${coerceExpression} build should be equal to ${expectedVersion}`)
117150
})
118151

119152
t.same(valid(coerce('42.6.7.9.3-alpha')), '42.6.7')
153+
t.same(valid(coerce('42.6.7-alpha+rev.1', { includePrerelease: true })), '42.6.7-alpha')
120154
t.same(valid(coerce('v2')), '2.0.0')
121155

122156
t.end()

0 commit comments

Comments
 (0)
Please sign in to comment.