From c64fe52a7e2a00af1682e72fd4b05768cbeccd58 Mon Sep 17 00:00:00 2001
From: Sergio Zharinov <zharinov@users.noreply.github.com>
Date: Mon, 21 Sep 2020 23:30:09 +0400
Subject: [PATCH] fix(bundler): More flexible parsing for gem lines (#7321)

Co-authored-by: Rhys Arkins <rhys@arkins.net>
---
 .../bundler/__fixtures__/Gemfile.sourceBlock  |  5 +++
 .../Gemfile.sourceBlockWithNewLines           |  2 +-
 .../__snapshots__/extract.spec.ts.snap        | 33 ++++++++++++++++++-
 lib/manager/bundler/extract.spec.ts           |  9 +++++
 lib/manager/bundler/extract.ts                | 31 +++++------------
 5 files changed, 55 insertions(+), 25 deletions(-)
 create mode 100644 lib/manager/bundler/__fixtures__/Gemfile.sourceBlock

diff --git a/lib/manager/bundler/__fixtures__/Gemfile.sourceBlock b/lib/manager/bundler/__fixtures__/Gemfile.sourceBlock
new file mode 100644
index 0000000000..beeea799e4
--- /dev/null
+++ b/lib/manager/bundler/__fixtures__/Gemfile.sourceBlock
@@ -0,0 +1,5 @@
+source 'https://hub.tech.my.domain.de/artifactory/api/gems/my-gems-prod-local/' do
+    gem 'sfn_my_dep1', "~> 1"
+    gem 'sfn_my_dep2', "~> 1"
+
+end
diff --git a/lib/manager/bundler/__fixtures__/Gemfile.sourceBlockWithNewLines b/lib/manager/bundler/__fixtures__/Gemfile.sourceBlockWithNewLines
index 879438f90c..b2e052aac6 100644
--- a/lib/manager/bundler/__fixtures__/Gemfile.sourceBlockWithNewLines
+++ b/lib/manager/bundler/__fixtures__/Gemfile.sourceBlockWithNewLines
@@ -4,4 +4,4 @@ source 'https://rubygems.org' do
   gem 'rubocop'
 
   gem 'brakeman'
-end
\ No newline at end of file
+end
diff --git a/lib/manager/bundler/__snapshots__/extract.spec.ts.snap b/lib/manager/bundler/__snapshots__/extract.spec.ts.snap
index 50ae40eb87..dff3967352 100644
--- a/lib/manager/bundler/__snapshots__/extract.spec.ts.snap
+++ b/lib/manager/bundler/__snapshots__/extract.spec.ts.snap
@@ -2491,12 +2491,13 @@ Object {
       },
     },
     Object {
+      "currentValue": "~> 0.0.2",
+      "datasource": "rubygems",
       "depName": "omniauth-ultraauth",
       "lockedVersion": "0.0.2",
       "managerData": Object {
         "lineNumber": 46,
       },
-      "skipReason": "no-version",
     },
     Object {
       "currentValue": "~> 1.0.5",
@@ -4689,6 +4690,36 @@ Object {
 }
 `;
 
+exports[`lib/manager/bundler/extract parse source blocks in Gemfile 1`] = `
+Object {
+  "deps": Array [
+    Object {
+      "currentValue": "~> 1",
+      "datasource": "rubygems",
+      "depName": "sfn_my_dep1",
+      "managerData": Object {
+        "lineNumber": 1,
+      },
+      "registryUrls": Array [
+        "https://hub.tech.my.domain.de/artifactory/api/gems/my-gems-prod-local/",
+      ],
+    },
+    Object {
+      "currentValue": "~> 1",
+      "datasource": "rubygems",
+      "depName": "sfn_my_dep2",
+      "managerData": Object {
+        "lineNumber": 2,
+      },
+      "registryUrls": Array [
+        "https://hub.tech.my.domain.de/artifactory/api/gems/my-gems-prod-local/",
+      ],
+    },
+  ],
+  "registryUrls": Array [],
+}
+`;
+
 exports[`lib/manager/bundler/extract parse source blocks with spaces in Gemfile 1`] = `
 Object {
   "compatibility": Object {
diff --git a/lib/manager/bundler/extract.spec.ts b/lib/manager/bundler/extract.spec.ts
index f7585134f5..132f03dabb 100644
--- a/lib/manager/bundler/extract.spec.ts
+++ b/lib/manager/bundler/extract.spec.ts
@@ -51,6 +51,10 @@ const gitlabFossGemfile = readFileSync(
   'lib/manager/bundler/__fixtures__/Gemfile.gitlab-foss',
   'utf8'
 );
+const sourceBlockGemfile = readFileSync(
+  'lib/manager/bundler/__fixtures__/Gemfile.sourceBlock',
+  'utf8'
+);
 const sourceBlockWithNewLinesGemfileLock = readFileSync(
   'lib/manager/bundler/__fixtures__/Gemfile.sourceBlockWithNewLines.lock',
   'utf8'
@@ -157,6 +161,11 @@ describe('lib/manager/bundler/extract', () => {
     validateGems(gitlabFossGemfile, res);
   });
 
+  it('parse source blocks in Gemfile', async () => {
+    fs.readLocalFile.mockResolvedValueOnce(sourceBlockGemfile);
+    const res = await extractPackageFile(sourceBlockGemfile, 'Gemfile');
+    expect(res).toMatchSnapshot();
+  });
   it('parse source blocks with spaces in Gemfile', async () => {
     fs.readLocalFile.mockResolvedValueOnce(sourceBlockWithNewLinesGemfileLock);
     const res = await extractPackageFile(
diff --git a/lib/manager/bundler/extract.ts b/lib/manager/bundler/extract.ts
index e59c67c6ce..4f5115b315 100644
--- a/lib/manager/bundler/extract.ts
+++ b/lib/manager/bundler/extract.ts
@@ -38,33 +38,18 @@ export async function extractPackageFile(
     if (rubyMatch) {
       res.compatibility = { ruby: rubyMatch[1] };
     }
-    let gemMatch: RegExpMatchArray;
-    let gemDelimiter: string;
-    for (const delimiter of delimiters) {
-      const gemMatchRegex = `^gem ${delimiter}([^${delimiter}]+)${delimiter}(,\\s+${delimiter}([^${delimiter}]+)${delimiter}){0,2}`;
-      if (regEx(gemMatchRegex).test(line)) {
-        gemDelimiter = delimiter;
-        gemMatch = gemMatch || regEx(gemMatchRegex).exec(line);
-      }
-    }
+    const gemMatchRegex = /^\s*gem\s+(['"])(?<depName>[^'"]+)\1(\s*,\s*(?<currentValue>(['"])[^'"]+\5(\s*,\s*\5[^'"]+\5)?))?/;
+    const gemMatch = gemMatchRegex.exec(line);
     if (gemMatch) {
       const dep: PackageDependency = {
-        depName: gemMatch[1],
+        depName: gemMatch.groups.depName,
         managerData: { lineNumber },
       };
-      if (gemMatch[3]) {
-        let currentValue = gemMatch[0]
-          .substring(`gem ${gemDelimiter}${dep.depName}${gemDelimiter},`.length)
-          .trim();
-        // strip quotes unless it's a complex constraint
-        if (
-          currentValue.startsWith(gemDelimiter) &&
-          currentValue.endsWith(gemDelimiter) &&
-          currentValue.split(gemDelimiter).length === 3
-        ) {
-          currentValue = currentValue.slice(1, -1);
-        }
-        dep.currentValue = currentValue;
+      if (gemMatch.groups.currentValue) {
+        const currentValue = gemMatch.groups.currentValue;
+        dep.currentValue = /\s*,\s*/.test(currentValue)
+          ? currentValue
+          : currentValue.slice(1, -1);
       } else {
         dep.skipReason = SkipReason.NoVersion;
       }
-- 
GitLab