locutus
Version:
Locutus other languages' standard libraries to JavaScript for fun and educational purposes
113 lines (112 loc) • 4.04 kB
JavaScript
export function version_compare(v1, v2, operator) {
// discuss at: https://locutus.io/php/version_compare/
// parity verified: PHP 8.3
// original by: Philippe Jausions (https://pear.php.net/user/jausions)
// original by: Aidan Lister (https://aidanlister.com/)
// reimplemented by: Kankrelune (https://www.webfaktory.info/)
// improved by: Brett Zamir (https://brett-zamir.me)
// improved by: Scott Baker
// improved by: Theriault (https://github.com/Theriault)
// example 1: version_compare('8.2.5rc', '8.2.5a')
// returns 1: 1
// example 2: version_compare('8.2.50', '8.2.52', '<')
// returns 2: true
// example 3: version_compare('5.3.0-dev', '5.3.0')
// returns 3: -1
// example 4: version_compare('4.1.0.52','4.01.0.51')
// returns 4: 1
// Important: compare must be initialized at 0.
let compare = 0;
// vm maps textual PHP versions to negatives so they're less than 0.
// PHP currently defines these as CASE-SENSITIVE. It is important to
// leave these as negatives so that they can come before numerical versions
// and as if no letters were there to begin with.
// (1alpha is < 1 and < 1.1 but > 1dev1)
// If a non-numerical value can't be mapped to this table, it receives
// -7 as its value.
const vm = {
dev: -6,
alpha: -5,
a: -5,
beta: -4,
b: -4,
RC: -3,
rc: -3,
'#': -2,
p: 1,
pl: 1,
};
// This function will be called to prepare each version argument.
// It replaces every _, -, and + with a dot.
// It surrounds any nonsequence of numbers/dots with dots.
// It replaces sequences of dots with a single dot.
// version_compare('4..0', '4.0') === 0
// Important: A string of 0 length needs to be converted into a value
// even less than an unexisting value in vm (-7), hence [-8].
// It's also important to not strip spaces because of this.
// version_compare('', ' ') === 1
const _prepVersion = function (v) {
let normalized = String(v).replace(/[_\-+]/g, '.');
normalized = normalized.replace(/([^.\d]+)/g, '.$1.').replace(/\.{2,}/g, '.');
return !normalized.length ? ['-8'] : normalized.split('.');
};
// This converts a version component to a number.
// Empty component becomes 0.
// Non-numerical component becomes a negative number.
// Numerical component becomes itself as an integer.
const _numVersion = function (v) {
if (!v) {
return 0;
}
return isNaN(Number(v)) ? (vm[v] ?? -7) : parseInt(v, 10);
};
const leftParts = _prepVersion(v1);
const rightParts = _prepVersion(v2);
const maxLength = Math.max(leftParts.length, rightParts.length);
for (let i = 0; i < maxLength; i++) {
if (leftParts[i] === rightParts[i]) {
continue;
}
const left = _numVersion(leftParts[i]);
const right = _numVersion(rightParts[i]);
if (left < right) {
compare = -1;
break;
}
else if (left > right) {
compare = 1;
break;
}
}
if (!operator) {
return compare;
}
// Important: operator is CASE-SENSITIVE.
// "No operator" seems to be treated as "<."
// Any other values seem to make the function return null.
switch (operator) {
case '>':
case 'gt':
return compare > 0;
case '>=':
case 'ge':
return compare >= 0;
case '<=':
case 'le':
return compare <= 0;
case '===':
case '=':
case 'eq':
return compare === 0;
case '<>':
case '!==':
case 'ne':
return compare !== 0;
case '':
case '<':
case 'lt':
return compare < 0;
default:
return null;
}
}