Skip to content

Commit 1d37529

Browse files
committed
Cleaned up coerce behavior
- followed the convention for including a regex - ignore digit sequences longer than 16 characters - explain expected behavior more carefully in README - update tests to reflect expected behavior
1 parent e7092b4 commit 1d37529

File tree

3 files changed

+69
-17
lines changed

3 files changed

+69
-17
lines changed

README.md

+4
Original file line numberDiff line numberDiff line change
@@ -382,3 +382,7 @@ consumes all remaining characters which satisfy at least a partial semver
382382
Longer versions are simply truncated (`4.6.3.9.2-alpha2` becomes `4.6.3`).
383383
All surrounding text is simply ignored (`v3.4 replaces v3.3.1` becomes `3.4.0`).
384384
Only text which lacks digits will fail coercion (`version one` is not valid).
385+
The maximum length for any semver component considered for coercion is 16 characters;
386+
longer components will be ignored (`10000000000000000.4.7.4` becomes `4.7.4`).
387+
The maximum value for any semver component is `Integer.MAX_SAFE_INTEGER || (2**53 - 1)`;
388+
higher value components are invalid (`9999999999999999.4.7.4` is likely invalid).

semver.js

+17-10
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ exports.SEMVER_SPEC_VERSION = '2.0.0';
2121
var MAX_LENGTH = 256;
2222
var MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER || 9007199254740991;
2323

24+
// Max safe segment length for coercion.
25+
var MAX_SAFE_COMPONENT_LENGTH = 16;
26+
2427
// The actual regexps go on exports.re
2528
var re = exports.re = [];
2629
var src = exports.src = [];
@@ -156,6 +159,15 @@ src[XRANGE] = '^' + src[GTLT] + '\\s*' + src[XRANGEPLAIN] + '$';
156159
var XRANGELOOSE = R++;
157160
src[XRANGELOOSE] = '^' + src[GTLT] + '\\s*' + src[XRANGEPLAINLOOSE] + '$';
158161

162+
// Coercion.
163+
// Extract anything that could conceivably be a part of a valid semver
164+
var COERCE = R++;
165+
src[COERCE] = '(?:^|[^\\d])' +
166+
'(\\d{1,' + MAX_SAFE_COMPONENT_LENGTH + '})' +
167+
'(?:\\.(\\d{1,' + MAX_SAFE_COMPONENT_LENGTH + '}))?' +
168+
'(?:\\.(\\d{1,' + MAX_SAFE_COMPONENT_LENGTH + '}))?' +
169+
'(?:$|[^\\d])';
170+
159171
// Tilde ranges.
160172
// Meaning is "reasonably at or greater than"
161173
var LONETILDE = R++;
@@ -1298,20 +1310,15 @@ function intersects(r1, r2, loose) {
12981310
exports.coerce = coerce;
12991311
function coerce(version) {
13001312
if (version instanceof SemVer)
1301-
return version
1313+
return version;
13021314

13031315
if (typeof version !== 'string')
1304-
return null
1305-
1306-
if (version.length > MAX_LENGTH)
13071316
return null;
13081317

1309-
var match = version.match(/([vV]?\d+(?:[.]\d+){0,2})/)
1310-
if (match == null)
1311-
return null
1318+
var match = version.match(re[COERCE]);
13121319

1313-
var parts = match[0].split(/[.]/)
1314-
var semver = ['0', '0', '0'].map(function (ph, idx) { return parts[idx] || ph } ).join('.')
1320+
if (match == null)
1321+
return null;
13151322

1316-
return parse(semver)
1323+
return parse((match[1] || '0') + '.' + (match[2] || '0') + '.' + (match[3] || '0'));
13171324
}

test/coerce.js

+48-7
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,15 @@ var tap = require('tap');
22
var test = tap.test;
33
var semver = require('../semver.js');
44
var coerce = semver.coerce;
5+
var valid = semver.valid;
56

6-
test('\ncoerce tests', function(t) {
7-
var tooLong = '123' + '.1'.repeat(127);
8-
var justRight = '12' + '.1'.repeat(127);
7+
function r(text) {
8+
return function (count) {
9+
return text.repeat(count);
10+
};
11+
}
912

13+
test('\ncoerce tests', function(t) {
1014
// Expected to be null (cannot be coerced).
1115
[
1216
null,
@@ -15,13 +19,22 @@ test('\ncoerce tests', function(t) {
1519
'',
1620
'.',
1721
'version one',
18-
tooLong
22+
r('9')(16),
23+
r('1')(17),
24+
'a' + r('9')(16),
25+
'a' + r('1')(17),
26+
r('9')(16) + 'a',
27+
r('1')(17) + 'a',
28+
r('9')(16) + '.4.7.4',
29+
r('9')(16) + '.' + r('2')(16) + '.' + r('3')(16),
30+
r('1')(16) + '.' + r('9')(16) + '.' + r('3')(16),
31+
r('1')(16) + '.' + r('2')(16) + '.' + r('9')(16)
1932
].forEach(function (input) {
2033
var msg = 'coerce(' + input + ') should be null'
2134
t.same(coerce(input), null, msg)
2235
});
2336

24-
// Expected to be the same.
37+
// Expected to be the valid.
2538
[
2639
[semver.parse('1.2.3'), '1.2.3'],
2740
['.1', '1.0.0'],
@@ -49,6 +62,8 @@ test('\ncoerce tests', function(t) {
4962
['v1.2', '1.2.0'],
5063
['v1.2.3', '1.2.3'],
5164
['v1.2.3.4', '1.2.3'],
65+
[' 1', '1.0.0'],
66+
['1 ', '1.0.0'],
5267
['1 0', '1.0.0'],
5368
['1 1', '1.0.0'],
5469
['1.1 1', '1.1.0'],
@@ -65,13 +80,39 @@ test('\ncoerce tests', function(t) {
6580
['v2', '2.0.0'],
6681
['v3.4 replaces v3.3.1', '3.4.0'],
6782
['4.6.3.9.2-alpha2', '4.6.3'],
68-
[justRight, '12.1.1']
83+
[r('1')(17) + '.2', '2.0.0'],
84+
[r('1')(17) + '.2.3', '2.3.0'],
85+
['1.' + r('2')(17) + '.3', '1.0.0'],
86+
['1.2.' + r('3')(17), '1.2.0'],
87+
[r('1')(17) + '.2.3.4', '2.3.4'],
88+
['1.' + r('2')(17) + '.3.4', '1.0.0'],
89+
['1.2.' + r('3')(17) + '.4', '1.2.0'],
90+
[r('1')(17) + '.' + r('2')(16) + '.' + r('3')(16),
91+
r('2')(16) + '.' + r('3')(16) + '.0'],
92+
[r('1')(16) + '.' + r('2')(17) + '.' + r('3')(16),
93+
r('1')(16) + '.0.0'],
94+
[r('1')(16) + '.' + r('2')(16) + '.' + r('3')(17),
95+
r('1')(16) + '.' + r('2')(16) + '.0'],
96+
['11' + r('.1')(126), '11.1.1'],
97+
[r('1')(16), r('1')(16) + '.0.0'],
98+
['a' + r('1')(16), r('1')(16) + '.0.0'],
99+
[r('1')(16) + '.2.3.4', r('1')(16) + '.2.3'],
100+
['1.' + r('2')(16) + '.3.4', '1.' + r('2')(16) + '.3'],
101+
['1.2.' + r('3')(16) + '.4', '1.2.' + r('3')(16)],
102+
[r('1')(16) + '.' + r('2')(16) + '.' + r('3')(16),
103+
r('1')(16) + '.' + r('2')(16) + '.' + r('3')(16)],
104+
['1.2.3.' + r('4')(252) + '.5', '1.2.3'],
105+
['1.2.3.' + r('4')(1024), '1.2.3'],
106+
[r('1')(17) + '.4.7.4', '4.7.4']
69107
].forEach(function (tuple) {
70108
var input = tuple[0]
71109
var expected = tuple[1]
72110
var msg = 'coerce(' + input + ') should become ' + expected
73111
t.same((coerce(input) || {}).version, expected, msg)
74112
});
75113

114+
t.same(valid(coerce('42.6.7.9.3-alpha')), '42.6.7')
115+
t.same(valid(coerce('v2')), '2.0.0')
116+
76117
t.done();
77-
});
118+
});

0 commit comments

Comments
 (0)