Browse Source

fix: semver-compliant comparison of the prerelease chunk

tophf 5 years ago
parent
commit
2ea7f3f536
2 changed files with 75 additions and 6 deletions
  1. 30 6
      src/common/util.js
  2. 45 0
      test/common/index.test.js

+ 30 - 6
src/common/util.js

@@ -89,14 +89,38 @@ export function buffer2string(buf, offset = 0, length = 1e99) {
   return slices.join('');
   return slices.join('');
 }
 }
 
 
+const VERSION_RE = /^(.*?)-([-.0-9a-z]+)|$/i;
+const DIGITS_RE = /^\d+$/; // using regexp to avoid +'1e2' being parsed as 100
+
+/** @return -1 | 0 | 1 */
 export function compareVersion(ver1, ver2) {
 export function compareVersion(ver1, ver2) {
-  const parts1 = (ver1 || '').split('.');
-  const parts2 = (ver2 || '').split('.');
-  for (let i = 0; i < parts1.length || i < parts2.length; i += 1) {
-    const delta = (parseInt(parts1[i], 10) || 0) - (parseInt(parts2[i], 10) || 0);
-    if (delta) return delta < 0 ? -1 : 1;
+  const [, main1 = ver1 || '', pre1] = VERSION_RE.exec(ver1);
+  const [, main2 = ver2 || '', pre2] = VERSION_RE.exec(ver2);
+  const delta = compareVersionChunk(main1, main2)
+    || !pre1 - !pre2 // 1.2.3-pre-release is less than 1.2.3
+    || pre1 && compareVersionChunk(pre1, pre2, true); // if pre1 is present, pre2 is too
+  return delta < 0 ? -1 : +!!delta;
+}
+
+function compareVersionChunk(ver1, ver2, isSemverMode) {
+  const parts1 = ver1.split('.');
+  const parts2 = ver2.split('.');
+  const len1 = parts1.length;
+  const len2 = parts2.length;
+  const len = (isSemverMode ? Math.min : Math.max)(len1, len2);
+  let delta;
+  for (let i = 0; !delta && i < len; i += 1) {
+    const a = parts1[i];
+    const b = parts2[i];
+    if (isSemverMode) {
+      delta = DIGITS_RE.test(a) && DIGITS_RE.test(b)
+        ? a - b
+        : a > b || a < b && -1;
+    } else {
+      delta = (parseInt(a, 10) || 0) - (parseInt(b, 10) || 0);
+    }
   }
   }
-  return 0;
+  return delta || isSemverMode && (len1 - len2);
 }
 }
 
 
 const units = [
 const units = [

+ 45 - 0
test/common/index.test.js

@@ -24,6 +24,51 @@ test('compareVersion', (t) => {
   t.equal(compareVersion('1.2.1', '1.2'), 1);
   t.equal(compareVersion('1.2.1', '1.2'), 1);
   t.equal(compareVersion('1.1.9', '1.2'), -1);
   t.equal(compareVersion('1.1.9', '1.2'), -1);
   t.equal(compareVersion('1.10', '1.9'), 1);
   t.equal(compareVersion('1.10', '1.9'), 1);
+  t.deepEqual([
+    '1.2.3',
+    '1.2.3-alpha',
+    '1.0.0-x.7.z.92',
+    '1.0.0-alpha.1',
+    '1.0.0-alpha',
+    '4.11.6',
+    '4.2.0',
+    '1.5.19',
+    '1.5.5',
+    '4.1.3',
+    '2.3.1',
+    '10.5.5',
+    '11.3.0',
+    '1.0.0',
+    '1.0.0-rc.1',
+    '1.0.0-beta.11',
+    '1.0.0-beta',
+    '1.0.0-beta.2',
+    '1.0.0-alpha.beta+build',
+    '1.0.0-alpha.1',
+    '1.0.0-alpha',
+  ].sort(compareVersion), [
+    '1.0.0-alpha',
+    '1.0.0-alpha',
+    '1.0.0-alpha.1',
+    '1.0.0-alpha.1',
+    '1.0.0-alpha.beta+build',
+    '1.0.0-beta',
+    '1.0.0-beta.2',
+    '1.0.0-beta.11',
+    '1.0.0-rc.1',
+    '1.0.0-x.7.z.92',
+    '1.0.0',
+    '1.2.3-alpha',
+    '1.2.3',
+    '1.5.5',
+    '1.5.19',
+    '2.3.1',
+    '4.1.3',
+    '4.2.0',
+    '4.11.6',
+    '10.5.5',
+    '11.3.0',
+  ]);
   t.end();
   t.end();
 });
 });