Skip to content

Commit

Permalink
Cleaned up coerce behavior
Browse files Browse the repository at this point in the history
- 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
  • Loading branch information
joeledwards committed Jan 13, 2018
1 parent e7092b4 commit 1d37529
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 17 deletions.
4 changes: 4 additions & 0 deletions README.md
Expand Up @@ -382,3 +382,7 @@ consumes all remaining characters which satisfy at least a partial semver
Longer versions are simply truncated (`4.6.3.9.2-alpha2` becomes `4.6.3`).
All surrounding text is simply ignored (`v3.4 replaces v3.3.1` becomes `3.4.0`).
Only text which lacks digits will fail coercion (`version one` is not valid).
The maximum length for any semver component considered for coercion is 16 characters;
longer components will be ignored (`10000000000000000.4.7.4` becomes `4.7.4`).
The maximum value for any semver component is `Integer.MAX_SAFE_INTEGER || (2**53 - 1)`;
higher value components are invalid (`9999999999999999.4.7.4` is likely invalid).
27 changes: 17 additions & 10 deletions semver.js
Expand Up @@ -21,6 +21,9 @@ exports.SEMVER_SPEC_VERSION = '2.0.0';
var MAX_LENGTH = 256;
var MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER || 9007199254740991;

// Max safe segment length for coercion.
var MAX_SAFE_COMPONENT_LENGTH = 16;

// The actual regexps go on exports.re
var re = exports.re = [];
var src = exports.src = [];
Expand Down Expand Up @@ -156,6 +159,15 @@ src[XRANGE] = '^' + src[GTLT] + '\\s*' + src[XRANGEPLAIN] + '$';
var XRANGELOOSE = R++;
src[XRANGELOOSE] = '^' + src[GTLT] + '\\s*' + src[XRANGEPLAINLOOSE] + '$';

// Coercion.
// Extract anything that could conceivably be a part of a valid semver
var COERCE = R++;
src[COERCE] = '(?:^|[^\\d])' +
'(\\d{1,' + MAX_SAFE_COMPONENT_LENGTH + '})' +
'(?:\\.(\\d{1,' + MAX_SAFE_COMPONENT_LENGTH + '}))?' +
'(?:\\.(\\d{1,' + MAX_SAFE_COMPONENT_LENGTH + '}))?' +
'(?:$|[^\\d])';

// Tilde ranges.
// Meaning is "reasonably at or greater than"
var LONETILDE = R++;
Expand Down Expand Up @@ -1298,20 +1310,15 @@ function intersects(r1, r2, loose) {
exports.coerce = coerce;
function coerce(version) {
if (version instanceof SemVer)
return version
return version;

if (typeof version !== 'string')
return null

if (version.length > MAX_LENGTH)
return null;

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

var parts = match[0].split(/[.]/)
var semver = ['0', '0', '0'].map(function (ph, idx) { return parts[idx] || ph } ).join('.')
if (match == null)
return null;

return parse(semver)
return parse((match[1] || '0') + '.' + (match[2] || '0') + '.' + (match[3] || '0'));
}
55 changes: 48 additions & 7 deletions test/coerce.js
Expand Up @@ -2,11 +2,15 @@ var tap = require('tap');
var test = tap.test;
var semver = require('../semver.js');
var coerce = semver.coerce;
var valid = semver.valid;

test('\ncoerce tests', function(t) {
var tooLong = '123' + '.1'.repeat(127);
var justRight = '12' + '.1'.repeat(127);
function r(text) {
return function (count) {
return text.repeat(count);
};
}

test('\ncoerce tests', function(t) {
// Expected to be null (cannot be coerced).
[
null,
Expand All @@ -15,13 +19,22 @@ test('\ncoerce tests', function(t) {
'',
'.',
'version one',
tooLong
r('9')(16),
r('1')(17),
'a' + r('9')(16),
'a' + r('1')(17),
r('9')(16) + 'a',
r('1')(17) + 'a',
r('9')(16) + '.4.7.4',
r('9')(16) + '.' + r('2')(16) + '.' + r('3')(16),
r('1')(16) + '.' + r('9')(16) + '.' + r('3')(16),
r('1')(16) + '.' + r('2')(16) + '.' + r('9')(16)
].forEach(function (input) {
var msg = 'coerce(' + input + ') should be null'
t.same(coerce(input), null, msg)
});

// Expected to be the same.
// Expected to be the valid.
[
[semver.parse('1.2.3'), '1.2.3'],
['.1', '1.0.0'],
Expand Down Expand Up @@ -49,6 +62,8 @@ test('\ncoerce tests', function(t) {
['v1.2', '1.2.0'],
['v1.2.3', '1.2.3'],
['v1.2.3.4', '1.2.3'],
[' 1', '1.0.0'],
['1 ', '1.0.0'],
['1 0', '1.0.0'],
['1 1', '1.0.0'],
['1.1 1', '1.1.0'],
Expand All @@ -65,13 +80,39 @@ test('\ncoerce tests', function(t) {
['v2', '2.0.0'],
['v3.4 replaces v3.3.1', '3.4.0'],
['4.6.3.9.2-alpha2', '4.6.3'],
[justRight, '12.1.1']
[r('1')(17) + '.2', '2.0.0'],
[r('1')(17) + '.2.3', '2.3.0'],
['1.' + r('2')(17) + '.3', '1.0.0'],
['1.2.' + r('3')(17), '1.2.0'],
[r('1')(17) + '.2.3.4', '2.3.4'],
['1.' + r('2')(17) + '.3.4', '1.0.0'],
['1.2.' + r('3')(17) + '.4', '1.2.0'],
[r('1')(17) + '.' + r('2')(16) + '.' + r('3')(16),
r('2')(16) + '.' + r('3')(16) + '.0'],
[r('1')(16) + '.' + r('2')(17) + '.' + r('3')(16),
r('1')(16) + '.0.0'],
[r('1')(16) + '.' + r('2')(16) + '.' + r('3')(17),
r('1')(16) + '.' + r('2')(16) + '.0'],
['11' + r('.1')(126), '11.1.1'],
[r('1')(16), r('1')(16) + '.0.0'],
['a' + r('1')(16), r('1')(16) + '.0.0'],
[r('1')(16) + '.2.3.4', r('1')(16) + '.2.3'],
['1.' + r('2')(16) + '.3.4', '1.' + r('2')(16) + '.3'],
['1.2.' + r('3')(16) + '.4', '1.2.' + r('3')(16)],
[r('1')(16) + '.' + r('2')(16) + '.' + r('3')(16),
r('1')(16) + '.' + r('2')(16) + '.' + r('3')(16)],
['1.2.3.' + r('4')(252) + '.5', '1.2.3'],
['1.2.3.' + r('4')(1024), '1.2.3'],
[r('1')(17) + '.4.7.4', '4.7.4']
].forEach(function (tuple) {
var input = tuple[0]
var expected = tuple[1]
var msg = 'coerce(' + input + ') should become ' + expected
t.same((coerce(input) || {}).version, expected, msg)
});

t.same(valid(coerce('42.6.7.9.3-alpha')), '42.6.7')
t.same(valid(coerce('v2')), '2.0.0')

t.done();
});
});

0 comments on commit 1d37529

Please sign in to comment.