From dd0d11d68fea2231fff41744162388fa5eacff16 Mon Sep 17 00:00:00 2001
From: Sergio Zharinov <zharinov@users.noreply.github.com>
Date: Thu, 16 Apr 2020 10:01:15 +0400
Subject: [PATCH] feat(ruby): Switch to @renovate/ruby-semver library (#5116)

---
 lib/versioning/ruby/index.spec.ts         |  9 +++
 lib/versioning/ruby/index.ts              |  2 +-
 lib/versioning/ruby/range.ts              | 19 +++--
 lib/versioning/ruby/strategies/bump.ts    |  2 +-
 lib/versioning/ruby/strategies/replace.ts |  2 +-
 lib/versioning/ruby/version.ts            | 99 +++++++++++++++--------
 package.json                              |  2 +-
 yarn.lock                                 | 12 ++-
 8 files changed, 91 insertions(+), 56 deletions(-)

diff --git a/lib/versioning/ruby/index.spec.ts b/lib/versioning/ruby/index.spec.ts
index 5cbe03cc43..3b18a1554e 100644
--- a/lib/versioning/ruby/index.spec.ts
+++ b/lib/versioning/ruby/index.spec.ts
@@ -278,6 +278,15 @@ describe('semverRuby', () => {
         false
       );
     });
+
+    it('returns null for garbage version input', () => {
+      expect(semverRuby.isLessThanRange('asdf', '> 1.2.2, ~> 2.0.0')).toBe(
+        null
+      );
+      expect(
+        semverRuby.isLessThanRange(null as string, '> 1.2.2, ~> 2.0.0')
+      ).toBe(null);
+    });
   });
 
   describe('.isValid', () => {
diff --git a/lib/versioning/ruby/index.ts b/lib/versioning/ruby/index.ts
index 334e27b46d..500270fd4e 100644
--- a/lib/versioning/ruby/index.ts
+++ b/lib/versioning/ruby/index.ts
@@ -5,7 +5,7 @@ import {
   satisfies,
   maxSatisfying,
   minSatisfying,
-} from '@snyk/ruby-semver';
+} from '@renovatebot/ruby-semver';
 import { VersioningApi, NewValueConfig } from '../common';
 import { logger } from '../../logger';
 import { parse as parseVersion } from './version';
diff --git a/lib/versioning/ruby/range.ts b/lib/versioning/ruby/range.ts
index be31b163aa..fd0f46e26b 100644
--- a/lib/versioning/ruby/range.ts
+++ b/lib/versioning/ruby/range.ts
@@ -1,5 +1,5 @@
-import { create } from '@snyk/ruby-semver/lib/ruby/gem-version';
-import { parse as _parse } from '@snyk/ruby-semver/lib/ruby/gem-requirement';
+import { create, Version } from '@renovatebot/ruby-semver/dist/ruby/version';
+import { parse as _parse } from '@renovatebot/ruby-semver/dist/ruby/requirement';
 import { logger } from '../../logger';
 import { EQUAL, NOT_EQUAL, GT, LT, GTE, LTE, PGTE } from './operator';
 
@@ -27,15 +27,14 @@ const parse = (range: string): Range => {
   };
 };
 
-interface GemVersion {
-  release(): GemVersion;
-  compare(ver: GemVersion): number;
-  bump(): GemVersion;
-}
-type GemRequirement = [string, GemVersion];
+type GemRequirement = [string, Version];
 
-const ltr = (version: string, range: string): boolean => {
-  const gemVersion: GemVersion = create(version);
+const ltr = (version: string, range: string): boolean | null => {
+  const gemVersion = create(version);
+  if (!gemVersion) {
+    logger.warn(`Invalid ruby version '${version}'`);
+    return null;
+  }
   const requirements: GemRequirement[] = range.split(',').map(_parse);
 
   const results = requirements.map(([operator, ver]) => {
diff --git a/lib/versioning/ruby/strategies/bump.ts b/lib/versioning/ruby/strategies/bump.ts
index acbac49726..a66d6ffe36 100644
--- a/lib/versioning/ruby/strategies/bump.ts
+++ b/lib/versioning/ruby/strategies/bump.ts
@@ -1,4 +1,4 @@
-import { gte, lte } from '@snyk/ruby-semver';
+import { gte, lte } from '@renovatebot/ruby-semver';
 import { logger } from '../../../logger';
 import { EQUAL, NOT_EQUAL, GT, LT, GTE, LTE, PGTE } from '../operator';
 import { floor, increment, decrement } from '../version';
diff --git a/lib/versioning/ruby/strategies/replace.ts b/lib/versioning/ruby/strategies/replace.ts
index 632e32ba3b..4ff52e7562 100644
--- a/lib/versioning/ruby/strategies/replace.ts
+++ b/lib/versioning/ruby/strategies/replace.ts
@@ -1,4 +1,4 @@
-import { satisfies } from '@snyk/ruby-semver';
+import { satisfies } from '@renovatebot/ruby-semver';
 import bump from './bump';
 import { logger } from '../../../logger';
 
diff --git a/lib/versioning/ruby/version.ts b/lib/versioning/ruby/version.ts
index a6914561f5..7059b5b922 100644
--- a/lib/versioning/ruby/version.ts
+++ b/lib/versioning/ruby/version.ts
@@ -1,12 +1,24 @@
 import last from 'lodash/last';
-import { create } from '@snyk/ruby-semver/lib/ruby/gem-version';
-import { diff, major, minor, patch, prerelease } from '@snyk/ruby-semver';
+import {
+  create,
+  SegmentElement,
+} from '@renovatebot/ruby-semver/dist/ruby/version';
+import { eq, major, minor, patch, prerelease } from '@renovatebot/ruby-semver';
 
 interface RubyVersion {
   major: number;
   minor: number;
   patch: number;
-  prerelease: string[];
+  prerelease: string[] | null;
+}
+
+function releaseSegments(version: string): SegmentElement[] {
+  const v = create(version);
+  if (v) {
+    return v.release().getSegments();
+  }
+  /* istanbul ignore next */
+  return [];
 }
 
 const parse = (version: string): RubyVersion => ({
@@ -19,13 +31,14 @@ const parse = (version: string): RubyVersion => ({
 const adapt = (left: string, right: string): string =>
   left.split('.').slice(0, right.split('.').length).join('.');
 
-const floor = (version: string): string =>
-  [...create(version).release().getSegments().slice(0, -1), 0].join('.');
+const floor = (version: string): string => {
+  return [...releaseSegments(version).slice(0, -1), 0].join('.');
+};
 
 // istanbul ignore next
 const incrementLastSegment = (version: string): string => {
-  const segments = create(version).release().getSegments();
-  const nextLast = parseInt(last(segments), 10) + 1;
+  const segments = releaseSegments(version);
+  const nextLast = parseInt(last(segments) as string, 10) + 1;
 
   return [...segments.slice(0, -1), nextLast].join('.');
 };
@@ -48,24 +61,29 @@ const incrementPatch = (ptch: number, pre: string[]): number =>
 
 // istanbul ignore next
 const increment = (from: string, to: string): string => {
-  const { major: maj, minor: min, patch: ptch, prerelease: pre } = parse(from);
+  const parsed = parse(from);
+  const { major: maj, prerelease: pre } = parsed;
+  let { minor: min, patch: ptch } = parsed;
+  min = min || 0;
+  ptch = ptch || 0;
 
   let nextVersion: string;
-  switch (diff(from, adapt(to, from))) {
-    case 'major':
-      nextVersion = [incrementMajor(maj, min, ptch, pre || []), 0, 0].join('.');
-      break;
-    case 'minor':
-      nextVersion = [maj, incrementMinor(min, ptch, pre || []), 0].join('.');
-      break;
-    case 'patch':
-      nextVersion = [maj, min, incrementPatch(ptch, pre || [])].join('.');
-      break;
-    case 'prerelease':
-      nextVersion = [maj, min, ptch].join('.');
-      break;
-    default:
-      return incrementLastSegment(from);
+  const adapted = adapt(to, from);
+  if (eq(from, adapted)) {
+    return incrementLastSegment(from);
+  }
+
+  const isStable = (x: string): boolean => /^[0-9.-/]+$/.test(x);
+  if (major(from) !== major(adapted)) {
+    nextVersion = [incrementMajor(maj, min, ptch, pre || []), 0, 0].join('.');
+  } else if (minor(from) !== minor(adapted)) {
+    nextVersion = [maj, incrementMinor(min, ptch, pre || []), 0].join('.');
+  } else if (patch(from) !== patch(adapted)) {
+    nextVersion = [maj, min, incrementPatch(ptch, pre || [])].join('.');
+  } else if (isStable(from) && isStable(adapted)) {
+    nextVersion = [maj, min, incrementPatch(ptch, pre || [])].join('.');
+  } else {
+    nextVersion = [maj, min, ptch].join('.');
   }
 
   return increment(nextVersion, to);
@@ -73,20 +91,31 @@ const increment = (from: string, to: string): string => {
 
 // istanbul ignore next
 const decrement = (version: string): string => {
-  const segments = create(version).release().getSegments();
+  const segments = releaseSegments(version);
   const nextSegments = segments
     .reverse()
-    .reduce((accumulator: number[], segment: number, index: number) => {
-      if (index === 0) {
-        return [segment - 1];
-      }
-
-      if (accumulator[index - 1] === -1) {
-        return [...accumulator.slice(0, index - 1), 0, segment - 1];
-      }
-
-      return [...accumulator, segment];
-    }, []);
+    .reduce(
+      (
+        accumulator: number[],
+        segment: SegmentElement,
+        index: number
+      ): number[] => {
+        if (index === 0) {
+          return [(segment as number) - 1];
+        }
+
+        if (accumulator[index - 1] === -1) {
+          return [
+            ...accumulator.slice(0, index - 1),
+            0,
+            (segment as number) - 1,
+          ];
+        }
+
+        return [...accumulator, segment as number];
+      },
+      []
+    );
 
   return nextSegments.reverse().join('.');
 };
diff --git a/package.json b/package.json
index c3c5935396..7f5c367ead 100644
--- a/package.json
+++ b/package.json
@@ -110,8 +110,8 @@
   },
   "dependencies": {
     "@renovate/pep440": "0.4.1",
+    "@renovatebot/ruby-semver": "0.1.3",
     "@sindresorhus/is": "2.1.0",
-    "@snyk/ruby-semver": "2.1.0",
     "@yarnpkg/lockfile": "1.1.0",
     "aws-sdk": "2.656.0",
     "azure-devops-node-api": "10.1.1",
diff --git a/yarn.lock b/yarn.lock
index 86bcd18db4..bf6d70bfdf 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1163,6 +1163,11 @@
   dependencies:
     xregexp "4.2.0"
 
+"@renovatebot/ruby-semver@0.1.3":
+  version "0.1.3"
+  resolved "https://registry.yarnpkg.com/@renovatebot/ruby-semver/-/ruby-semver-0.1.3.tgz#dfa43139d5a465dddec9e0e712d9d359bf7ea167"
+  integrity sha512-tGd5CMTCrFrmJhGxIhLP2Imv5bkzfZQY/6gfV+T6TOhMfca9hRBA7hj/xAexSDLAFN5QmEjGPK0SFusVUPPHIw==
+
 "@semantic-release/commit-analyzer@^8.0.0":
   version "8.0.1"
   resolved "https://registry.yarnpkg.com/@semantic-release/commit-analyzer/-/commit-analyzer-8.0.1.tgz#5d2a37cd5a3312da0e3ac05b1ca348bf60b90bca"
@@ -1296,13 +1301,6 @@
   resolved "https://registry.yarnpkg.com/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz#8da5c6530915653f3a1f38fd5f101d8c3f8079c5"
   integrity sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==
 
-"@snyk/ruby-semver@2.1.0":
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/@snyk/ruby-semver/-/ruby-semver-2.1.0.tgz#d76cc02fc20860c224da2646fe9cc1bb0b0d1d15"
-  integrity sha512-u8ez8kWyqge+N+FxRDx/uPBmcHzY7BMfODvzEVeoTOeoD0CHPymEaVlkEKA8ZHtxzXjUzPIl2I8f2siZEzLjYg==
-  dependencies:
-    lodash "^4.17.14"
-
 "@szmarczak/http-timer@^1.1.2":
   version "1.1.2"
   resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-1.1.2.tgz#b1665e2c461a2cd92f4c1bbf50d5454de0d4b421"
-- 
GitLab