Skip to content

Commit

Permalink
version management structural updates for code sharing and performance
Browse files Browse the repository at this point in the history
  • Loading branch information
guybedford committed Oct 21, 2014
1 parent 7a67fd7 commit 5972ae4
Showing 1 changed file with 118 additions and 92 deletions.
210 changes: 118 additions & 92 deletions lib/semver.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,50 +20,49 @@ function toInt(num) {
return parseInt(num, 10);
}

var semverCompare = exports.compare = function(v1, v2) {
var semver1 = v1.match(semverRegEx);
var semver2 = v2.match(semverRegEx);

// not semvers - just sort string tags
if (!semver1 && !semver2)
return semver1 > semver2 ? 1 : -1;

// semver beats non-semver
if (!semver1)
return -1;
if (!semver2)
return 1;
function parseSemver(v) {
var semver = v.match(semverRegEx);
if (!semver)
return;
return {
major: toInt(semver[1]),
minor: toInt(semver[2]),
patch: toInt(semver[3]),
pre: semver[4] && semver[4].split('.')
};
}

var parts = ['major', 'minor', 'patch'];
function semverCompareParsed(v1, v2) {
// compare version numbers
for (var i = 1; i <= 3; i++) {
if (semver1[i] == semver2[i])
for (var i = 0; i < parts.length; i++) {
var part = parts[i];
var part1 = v1[part];
var part2 = v2[part];
if (part1 == part2)
continue;
// missing numbers take lower precedence
if (!semver1[i])
if (isNaN(part1))
return -1;
else if (!semver2[i])
if (isNaN(part2))
return 1;
return toInt(semver1[i]) > toInt(semver2[i]) ? 1 : -1;
return part1 > part2 ? 1 : -1;
}

if (semver1[4] === semver2[4])
if (!v1.pre && !v2.pre)
return 0;

// prerelease have lower order
if (!semver1[4])
if (!v1.pre)
return 1;
if (!semver2[4])
if (!v2.pre)
return -1;

// prerelease comparison
var prerelease1 = semver1[4].split('.');
var prerelease2 = semver2[4].split('.');
for (var i = 0, l = Math.min(prerelease1.length, prerelease2.length); i < l; i++) {
if (prerelease1[i] == prerelease2[i])
for (var i = 0, l = Math.min(v1.pre.length, v2.pre.length); i < l; i++) {
if (v1.pre[i] == v2.pre[i])
continue;

var isNum1 = prerelease1[i].match(numRegEx);
var isNum2 = prerelease2[i].match(numRegEx);
var isNum1 = v1.pre[i].match(numRegEx);
var isNum2 = v2.pre[i].match(numRegEx);

// numeric has lower precedence
if (isNum1 && !isNum2)
Expand All @@ -73,96 +72,123 @@ var semverCompare = exports.compare = function(v1, v2) {

// compare parts
if (isNum1 && isNum2)
return toInt(prerelease1[i]) > toInt(prerelease2[i]) ? 1 : -1;
return toInt(v1.pre[i]) > toInt(v2.pre[i]) ? 1 : -1;
else
return prerelease1[i] > prerelease2[i] ? 1 : -1;
return v1.pre[i] > v2.pre[i] ? 1 : -1;
}

if (prerelease1.length == prerelease2.length)
if (v1.pre.length == v2.pre.length)
return 0;

// more pre-release fields win if equal
return prerelease1.length > prerelease2.length ? 1 : -1;
return v1.pre.length > v2.pre.length ? 1 : -1;
}

exports.match = function match(range, version) {
// supported range types:
// 0.2, 1, ~1.2.3, ^1.2.3, ^0.4.3-alpha.1, *
var isSemver, isFuzzy;

if (range === version || range === '*')
return true;

((isSemver = range.substr(0, 1) == '^')
|| (isFuzzy = range.substr(0, 1) == '~')
) && (range = range.substr(1));
// match against a parsed range object
// saves operation repetition
// doesn't support tags
// if not semver or fuzzy, assume exact
/*
* semver - is this a semver range
* fuzzy - is this a fuzzy range
* version - the parsed version object
*/
function matchParsed(range, version) {
var rangeVersion = range.version;

// if the version is less than the range, it's not a match
if (semverCompare(range, version) == 1)
if (semverCompareParsed(rangeVersion, version) == 1)
return false;

// now we just have to check that the version isn't too high for the range
var rangeMatch = range.match(semverRegEx);
var versionMatch = version.match(semverRegEx);

if (!versionMatch || !rangeMatch)
return false;

var rangeMajor = toInt(rangeMatch[1]);
var rangeMinor = toInt(rangeMatch[2]);
var rangePatch = toInt(rangeMatch[3]);
var rangePre = rangeMatch[4];

var versionMajor = toInt(versionMatch[1]);
var versionMinor = toInt(versionMatch[2]);
var versionPatch = toInt(versionMatch[3]);
var versionPre = versionMatch[4];

if (isNaN(versionMinor) || isNaN(versionPatch))
if (isNaN(version.minor) || isNaN(version.patch))
return false;

// if the version has a prerelease, ensure the range version has a prerelease in it
// and that we match the range version up to the prerelease exactly
if (versionPre)
return !!(rangePre && rangeMajor == versionMajor && rangeMinor == versionMinor && rangePatch == versionPatch);

// 0, 0.1 behave like ~0, ~0.1
if (!isSemver && !isFuzzy && (isNaN(rangeMinor) || isNaN(rangePatch)))
isFuzzy = true;

// ~1, ~0 behave like ^1, ^0
if (isFuzzy && isNaN(rangeMinor)) {
isSemver = true;
isFuzzy = false;
}

// ^0.0 behaves like ~0.0
if (isSemver && !isNaN(rangeMinor) && isNaN(rangePatch)) {
isSemver = false;
isFuzzy = true;
}
if (version.pre)
return !!(rangeVersion.pre && rangeVersion.major == version.major && rangeVersion.minor == version.minor && rangeVersion.patch == version.patch);

// check semver range
if (isSemver) {
if (range.semver) {
// ^0
if (rangeMajor == 0 && isNaN(rangeMinor))
return versionMajor < 1;
if (rangeVersion.major == 0 && isNaN(rangeVersion.minor))
return version.major < 1;
// ^1..
else if (rangeMajor >= 1)
return rangeMajor == versionMajor;
else if (rangeVersion.major >= 1)
return rangeVersion.major == version.major;
// ^0.1, ^0.2
else if (rangeMinor >= 1)
return rangeMinor == versionMinor;
else if (rangeVersion.minor >= 1)
return rangeVersion.minor == version.minor;
// ^0.0.0
else
return (rangePatch || 0) == versionPatch;
return (rangeVersion.patch || 0) == version.patch;
}

// check fuzzy range
if (isFuzzy)
return versionMajor == rangeMajor && versionMinor < (rangeMinor || 0) + 1;
if (range.fuzzy)
return version.major == rangeVersion.major && version.minor < (rangeVersion.minor || 0) + 1;

// exact match
// eg 001.002.003 matches 1.2.3
return !!(!rangePre && rangeMajor == versionMajor && rangeMinor == versionMinor && rangePatch == versionPatch);
}
return !!(!rangeVersion.pre && rangeVersion.major == version.major && rangeVersion.minor == version.minor && rangeVersion.patch == version.patch);
}

function parseRange(range) {
var rangeObj = {};

((rangeObj.semver = range.substr(0, 1) == '^')
|| (rangeObj.fuzzy = range.substr(0, 1) == '~')
) && (range = range.substr(1));

var rangeVersion = rangeObj.version = parseSemver(range);

// 0, 0.1 behave like ~0, ~0.1
if (!rangeObj.fuzzy && !rangeObj.semver && (isNaN(rangeVersion.minor) || isNaN(rangeVersion.patch)))
rangeObj.fuzzy = true;

// ~1, ~0 behave like ^1, ^0
if (rangeObj.fuzzy && isNaN(rangeVersion.minor)) {
rangeObj.semver = true;
rangeObj.fuzzy = false;
}

// ^0.0 behaves like ~0.0
if (rangeObj.semver && !isNaN(rangeVersion.minor) && isNaN(rangeVersion.patch)) {
rangeObj.semver = false;
rangeObj.fuzzy = true;
}

return rangeObj;
}

exports.compare = function(v1, v2) {
var semver1 = parseSemver(v1);
var semver2 = parseSemver(v2);

// not semvers - just sort string tags
if (!semver1 && !semver2)
return v1 > v2 ? 1 : -1;

// semver beats non-semver
if (!semver1)
return -1;
if (!semver2)
return 1;

return semverCompareParsed(semver1, semver2);
}

exports.match = function match(range, version) {
// supported range types:
// 0.2, 1, ~1.2.3, ^1.2.3, ^0.4.3-alpha.1, *
if (range === version || range === '*')
return true;

var parsedVersion = parseSemver(version);

if (!parsedVersion)
return false;

return matchParsed(parseRange(range), parsedVersion);
}

0 comments on commit 5972ae4

Please sign in to comment.