Skip to content

Commit 47498de

Browse files
committed
fix: add identifier validation to inc()
1 parent 868d4bb commit 47498de

File tree

3 files changed

+99
-48
lines changed

3 files changed

+99
-48
lines changed

classes/semver.js

Lines changed: 63 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ class SemVer {
1010

1111
if (version instanceof SemVer) {
1212
if (version.loose === !!options.loose &&
13-
version.includePrerelease === !!options.includePrerelease) {
13+
version.includePrerelease === !!options.includePrerelease) {
1414
return version
1515
} else {
1616
version = version.version
@@ -176,35 +176,37 @@ class SemVer {
176176
// preminor will bump the version up to the next minor release, and immediately
177177
// down to pre-release. premajor and prepatch work the same way.
178178
inc (release, identifier, identifierBase) {
179+
this.#checkIdentifiers(release, identifier, identifierBase)
180+
179181
switch (release) {
180182
case 'premajor':
181183
this.prerelease.length = 0
182184
this.patch = 0
183185
this.minor = 0
184186
this.major++
185-
this.inc('pre', identifier, identifierBase)
187+
this.#pre(identifier, identifierBase)
186188
break
187189
case 'preminor':
188190
this.prerelease.length = 0
189191
this.patch = 0
190192
this.minor++
191-
this.inc('pre', identifier, identifierBase)
193+
this.#pre(identifier, identifierBase)
192194
break
193195
case 'prepatch':
194196
// If this is already a prerelease, it will bump to the next version
195197
// drop any prereleases that might already exist, since they are not
196198
// relevant at this point.
197199
this.prerelease.length = 0
198200
this.inc('patch', identifier, identifierBase)
199-
this.inc('pre', identifier, identifierBase)
201+
this.#pre(identifier, identifierBase)
200202
break
201203
// If the input is a non-prerelease version, this acts the same as
202204
// prepatch.
203205
case 'prerelease':
204206
if (this.prerelease.length === 0) {
205207
this.inc('patch', identifier, identifierBase)
206208
}
207-
this.inc('pre', identifier, identifierBase)
209+
this.#pre(identifier, identifierBase)
208210
break
209211

210212
case 'major':
@@ -244,50 +246,7 @@ class SemVer {
244246
}
245247
this.prerelease = []
246248
break
247-
// This probably shouldn't be used publicly.
248-
// 1.0.0 'pre' would become 1.0.0-0 which is the wrong direction.
249-
case 'pre': {
250-
const base = Number(identifierBase) ? 1 : 0
251-
252-
if (!identifier && identifierBase === false) {
253-
throw new Error('invalid increment argument: identifier is empty')
254-
}
255249

256-
if (this.prerelease.length === 0) {
257-
this.prerelease = [base]
258-
} else {
259-
let i = this.prerelease.length
260-
while (--i >= 0) {
261-
if (typeof this.prerelease[i] === 'number') {
262-
this.prerelease[i]++
263-
i = -2
264-
}
265-
}
266-
if (i === -1) {
267-
// didn't increment anything
268-
if (identifier === this.prerelease.join('.') && identifierBase === false) {
269-
throw new Error('invalid increment argument: identifier already exists')
270-
}
271-
this.prerelease.push(base)
272-
}
273-
}
274-
if (identifier) {
275-
// 1.2.0-beta.1 bumps to 1.2.0-beta.2,
276-
// 1.2.0-beta.fooblz or 1.2.0-beta bumps to 1.2.0-beta.0
277-
let prerelease = [identifier, base]
278-
if (identifierBase === false) {
279-
prerelease = [identifier]
280-
}
281-
if (compareIdentifiers(this.prerelease[0], identifier) === 0) {
282-
if (isNaN(this.prerelease[1])) {
283-
this.prerelease = prerelease
284-
}
285-
} else {
286-
this.prerelease = prerelease
287-
}
288-
}
289-
break
290-
}
291250
default:
292251
throw new Error(`invalid increment argument: ${release}`)
293252
}
@@ -297,6 +256,62 @@ class SemVer {
297256
}
298257
return this
299258
}
259+
260+
#checkIdentifiers (release, identifier, identifierBase) {
261+
if (release.startsWith('pre')) {
262+
if (!identifier && identifierBase === false) {
263+
throw new Error('invalid increment argument: identifier is empty')
264+
}
265+
266+
// Avoid an invalid semver results
267+
if (identifier) {
268+
const match = `-${identifier}`.match(this.options.loose ? re[t.PRERELEASELOOSE] : re[t.PRERELEASE])
269+
if (!match || match[1] !== identifier) {
270+
throw new Error(`invalid identifier: ${identifier}`)
271+
}
272+
}
273+
}
274+
}
275+
276+
// Keep 'pre' internal to avoid confusing results
277+
// inc('1.0.0', 'pre') would become 1.0.0-0 which is the wrong direction.
278+
#pre (identifier, identifierBase) {
279+
const base = Number(identifierBase) ? 1 : 0
280+
281+
if (this.prerelease.length === 0) {
282+
this.prerelease = [base]
283+
} else {
284+
let i = this.prerelease.length
285+
while (--i >= 0) {
286+
if (typeof this.prerelease[i] === 'number') {
287+
this.prerelease[i]++
288+
i = -2
289+
}
290+
}
291+
if (i === -1) {
292+
// didn't increment anything
293+
if (identifier === this.prerelease.join('.') && identifierBase === false) {
294+
throw new Error('invalid increment argument: identifier already exists')
295+
}
296+
this.prerelease.push(base)
297+
}
298+
}
299+
if (identifier) {
300+
// 1.2.0-beta.1 bumps to 1.2.0-beta.2,
301+
// 1.2.0-beta.fooblz or 1.2.0-beta bumps to 1.2.0-beta.0
302+
let prerelease = [identifier, base]
303+
if (identifierBase === false) {
304+
prerelease = [identifier]
305+
}
306+
if (compareIdentifiers(this.prerelease[0], identifier) === 0) {
307+
if (isNaN(this.prerelease[1])) {
308+
this.prerelease = prerelease
309+
}
310+
} else {
311+
this.prerelease = prerelease
312+
}
313+
}
314+
}
300315
}
301316

302317
module.exports = SemVer

test/classes/semver.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,39 @@ test('incrementing', t => {
106106
}))
107107
})
108108

109+
test('invalid increments', (t) => {
110+
// pre was used on internally and is not valid anymore
111+
t.throws(
112+
() => new SemVer('1.2.3').inc('pre'),
113+
Error('invalid increment argument: pre')
114+
)
115+
t.throws(
116+
() => new SemVer('1.2.3').inc('prerelease', '', false),
117+
Error('invalid increment argument: identifier is empty')
118+
)
119+
t.throws(
120+
() => new SemVer('1.2.3-dev').inc('prerelease', 'dev', false),
121+
Error('invalid increment argument: identifier already exists')
122+
)
123+
t.throws(
124+
() => new SemVer('1.2.3').inc('prerelease', 'invalid/preid'),
125+
Error('invalid identifier: invalid/preid')
126+
)
127+
128+
t.end()
129+
})
130+
131+
test('increment side-effects', (t) => {
132+
const v = new SemVer('1.0.0')
133+
try {
134+
v.inc('prerelease', 'hot/mess')
135+
} catch (er) {
136+
// ignore but check that the version has not changed
137+
}
138+
t.equal(v.toString(), '1.0.0')
139+
t.end()
140+
})
141+
109142
test('compare main vs pre', (t) => {
110143
const s = new SemVer('1.2.3')
111144
t.equal(s.compareMain('2.3.4'), -1)

test/fixtures/increments.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,4 +124,7 @@ module.exports = [
124124
['1.2.0-dev', 'prepatch', '1.2.1-dev', false, 'dev', false],
125125
['1.2.0', 'prerelease', null, false, '', false],
126126
['1.0.0-rc.1+build.4', 'prerelease', '1.0.0-rc.2', 'rc', false],
127+
['1.2.0', 'prerelease', null, false, 'invalid/preid'],
128+
['1.2.0', 'prerelease', null, false, 'invalid+build'],
129+
['1.2.0beta', 'prerelease', null, { loose: true }, 'invalid/preid'],
127130
]

0 commit comments

Comments
 (0)