PHP's version_compare in TypeScript

✓ Verified: PHP 8.3
Examples tested against actual runtime. CI re-verifies continuously. Only documented examples are tested.

How to use

Install via yarn add locutus and import: import { version_compare } from 'locutus/php/info/version_compare'.

Or with CommonJS: const { version_compare } = require('locutus/php/info/version_compare')

Use a bundler that supports tree-shaking so you only ship the functions you actually use. Vite, webpack, Rollup, and Parcel all handle this. For server-side use this is less of a concern.

Examples

These examples are extracted from test cases that automatically verify our functions against their native counterparts.

#codeexpected result
1version_compare('8.2.5rc', '8.2.5a')1
2version_compare('8.2.50', '8.2.52', '<')true
3version_compare('5.3.0-dev', '5.3.0')-1
4version_compare('4.1.0.52','4.01.0.51')1

Here's what our current TypeScript equivalent to PHP's version_compare looks like.

export function version_compare(v1: string, v2: string, operator?: string): number | boolean | null {
// 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: Record<string, number> = {
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: string): string[] {
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: string | undefined): number {
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
}
}

Improve this function

Locutus is a community effort following The McDonald's Theory: we ship first iterations, hoping others will improve them. If you see something that could be better, we'd love your contribution.

View on GitHub · Edit on GitHub · View Raw


« More PHP info functions


Star