From a07cee1b52632a5dd0622c6bb4c1a12c89639552 Mon Sep 17 00:00:00 2001
From: praveshtora <pravesh.tora@gmail.com>
Date: Mon, 22 Jul 2019 11:08:22 +0530
Subject: [PATCH] feat(gomod): upgrade dependencies in replace directive
 (#4059)

---
 lib/manager/gomod/extract.js                  | 15 ++++--
 lib/manager/gomod/update.js                   | 29 ++++++----
 .../gomod/__snapshots__/extract.spec.js.snap  |  8 +++
 .../gomod/__snapshots__/update.spec.js.snap   |  4 ++
 test/manager/gomod/_fixtures/1/go.mod         |  2 +
 test/manager/gomod/extract.spec.js            |  3 +-
 test/manager/gomod/update.spec.js             | 54 +++++++++++++++++++
 7 files changed, 100 insertions(+), 15 deletions(-)

diff --git a/lib/manager/gomod/extract.js b/lib/manager/gomod/extract.js
index c8cd5b1e35..5e8ac9a7ad 100644
--- a/lib/manager/gomod/extract.js
+++ b/lib/manager/gomod/extract.js
@@ -5,14 +5,14 @@ module.exports = {
   extractPackageFile,
 };
 
-function getDep(lineNumber, match) {
+function getDep(lineNumber, match, type) {
   const [, , currentValue] = match;
   let [, depName] = match;
   depName = depName.replace(/"/g, '');
   const dep = {
     lineNumber,
     depName,
-    depType: 'require',
+    depType: type,
     currentValue,
   };
   if (!isVersion(currentValue)) {
@@ -43,10 +43,17 @@ function extractPackageFile(content) {
     const lines = content.split('\n');
     for (let lineNumber = 0; lineNumber < lines.length; lineNumber += 1) {
       let line = lines[lineNumber];
+      const replaceMatch = line.match(
+        /^replace\s+[^\s]+[\s]+[=][>]\s+([^\s]+)\s+([^\s]+)/
+      );
+      if (replaceMatch) {
+        const dep = getDep(lineNumber, replaceMatch, 'replace');
+        deps.push(dep);
+      }
       const requireMatch = line.match(/^require\s+([^\s]+)\s+([^\s]+)/);
       if (requireMatch) {
         logger.trace({ lineNumber }, `require line: "${line}"`);
-        const dep = getDep(lineNumber, requireMatch);
+        const dep = getDep(lineNumber, requireMatch, 'require');
         deps.push(dep);
       }
       if (line.trim() === 'require (') {
@@ -58,7 +65,7 @@ function extractPackageFile(content) {
           logger.trace(`reqLine: "${line}"`);
           if (multiMatch) {
             logger.trace({ lineNumber }, `require line: "${line}"`);
-            const dep = getDep(lineNumber, multiMatch);
+            const dep = getDep(lineNumber, multiMatch, 'require');
             dep.multiLine = true;
             deps.push(dep);
           } else if (line.trim() !== ')') {
diff --git a/lib/manager/gomod/update.js b/lib/manager/gomod/update.js
index e6b0ca5230..87f0dc5eef 100644
--- a/lib/manager/gomod/update.js
+++ b/lib/manager/gomod/update.js
@@ -8,7 +8,7 @@ module.exports = {
 function updateDependency(currentFileContent, upgrade) {
   try {
     logger.debug(`gomod.updateDependency: ${upgrade.newValue}`);
-    const { depName } = upgrade;
+    const { depName, depType } = upgrade;
     let depNameNoVersion = depName
       .split('/')
       .slice(0, 3)
@@ -28,13 +28,19 @@ function updateDependency(currentFileContent, upgrade) {
       );
       return null;
     }
-    let requireLine;
-    if (upgrade.multiLine) {
-      requireLine = new RegExp(/^(\s+[^\s]+)(\s+)([^\s]+)/);
-    } else {
-      requireLine = new RegExp(/^(require\s+[^\s]+)(\s+)([^\s]+)/);
+    let updateLineExp;
+    if (depType === 'replace') {
+      updateLineExp = new RegExp(
+        /^(replace\s+[^\s]+[\s]+[=][>]+\s+)([^\s]+\s+)([^\s]+)/
+      );
+    } else if (depType === 'require') {
+      if (upgrade.multiLine) {
+        updateLineExp = new RegExp(/^(\s+[^\s]+)(\s+)([^\s]+)/);
+      } else {
+        updateLineExp = new RegExp(/^(require\s+[^\s]+)(\s+)([^\s]+)/);
+      }
     }
-    if (!lineToChange.match(requireLine)) {
+    if (!lineToChange.match(updateLineExp)) {
       logger.debug('No image line found');
       return null;
     }
@@ -53,9 +59,9 @@ function updateDependency(currentFileContent, upgrade) {
       );
       const currentDateTime = DateTime.local().toFormat('yyyyMMddHHmmss');
       const newValue = `v0.0.0-${currentDateTime}-${newDigestRightSized}`;
-      newLine = lineToChange.replace(requireLine, `$1$2${newValue}`);
+      newLine = lineToChange.replace(updateLineExp, `$1$2${newValue}`);
     } else {
-      newLine = lineToChange.replace(requireLine, `$1$2${upgrade.newValue}`);
+      newLine = lineToChange.replace(updateLineExp, `$1$2${upgrade.newValue}`);
     }
     if (upgrade.updateType === 'major') {
       logger.debug({ depName }, 'gomod: major update');
@@ -73,7 +79,10 @@ function updateDependency(currentFileContent, upgrade) {
       ) {
         if (upgrade.currentValue.match(/^v(0|1)\./)) {
           // Add version
-          newLine = newLine.replace(requireLine, `$1/v${upgrade.newMajor}$2$3`);
+          newLine = newLine.replace(
+            updateLineExp,
+            `$1/v${upgrade.newMajor}$2$3`
+          );
         } else {
           // Replace version
           const [oldV] = upgrade.currentValue.split('.');
diff --git a/test/manager/gomod/__snapshots__/extract.spec.js.snap b/test/manager/gomod/__snapshots__/extract.spec.js.snap
index 24ffa3972b..23f2cabd79 100644
--- a/test/manager/gomod/__snapshots__/extract.spec.js.snap
+++ b/test/manager/gomod/__snapshots__/extract.spec.js.snap
@@ -636,5 +636,13 @@ Array [
     "depType": "require",
     "lineNumber": 8,
   },
+  Object {
+    "currentValue": "v0.0.0",
+    "datasource": "go",
+    "depName": "github.com/pravesht/gocql",
+    "depNameShort": "pravesht/gocql",
+    "depType": "replace",
+    "lineNumber": 11,
+  },
 ]
 `;
diff --git a/test/manager/gomod/__snapshots__/update.spec.js.snap b/test/manager/gomod/__snapshots__/update.spec.js.snap
index 06173bcf04..ddc3707e96 100644
--- a/test/manager/gomod/__snapshots__/update.spec.js.snap
+++ b/test/manager/gomod/__snapshots__/update.spec.js.snap
@@ -12,6 +12,8 @@ require gopkg.in/russross/blackfriday.v2 v2.0.0
 require github.com/Azure/azure-sdk-for-go v25.1.0+incompatible
 
 replace github.com/pkg/errors => ../errors
+replace golang.org/x/foo => github.com/pravesht/gocql v0.0.0
+
 "
 `;
 
@@ -94,5 +96,7 @@ require gopkg.in/russross/blackfriday.v1 v1.0.0
 require github.com/Azure/azure-sdk-for-go v25.1.0+incompatible
 
 replace github.com/pkg/errors => ../errors
+replace golang.org/x/foo => github.com/pravesht/gocql v0.0.0
+
 "
 `;
diff --git a/test/manager/gomod/_fixtures/1/go.mod b/test/manager/gomod/_fixtures/1/go.mod
index a3e27f4b42..479f73c809 100644
--- a/test/manager/gomod/_fixtures/1/go.mod
+++ b/test/manager/gomod/_fixtures/1/go.mod
@@ -9,3 +9,5 @@ require gopkg.in/russross/blackfriday.v1 v1.0.0
 require github.com/Azure/azure-sdk-for-go v25.1.0+incompatible
 
 replace github.com/pkg/errors => ../errors
+replace golang.org/x/foo => github.com/pravesht/gocql v0.0.0
+
diff --git a/test/manager/gomod/extract.spec.js b/test/manager/gomod/extract.spec.js
index c4e47b93cd..0b01a595b4 100644
--- a/test/manager/gomod/extract.spec.js
+++ b/test/manager/gomod/extract.spec.js
@@ -12,8 +12,9 @@ describe('lib/manager/gomod/extract', () => {
     it('extracts single-line requires', () => {
       const res = extractPackageFile(gomod1).deps;
       expect(res).toMatchSnapshot();
-      expect(res).toHaveLength(7);
+      expect(res).toHaveLength(8);
       expect(res.filter(e => e.skipReason)).toHaveLength(1);
+      expect(res.filter(e => e.depType === 'replace')).toHaveLength(1);
     });
     it('extracts multi-line requires', () => {
       const res = extractPackageFile(gomod2).deps;
diff --git a/test/manager/gomod/update.spec.js b/test/manager/gomod/update.spec.js
index 32bddeff6a..d182176616 100644
--- a/test/manager/gomod/update.spec.js
+++ b/test/manager/gomod/update.spec.js
@@ -11,6 +11,7 @@ describe('manager/gomod/update', () => {
         depName: 'github.com/pkg/errors',
         lineNumber: 2,
         newValue: 'v0.8.0',
+        depType: 'require',
       };
       const res = goUpdate.updateDependency(gomod1, upgrade);
       expect(res).not.toEqual(gomod1);
@@ -21,6 +22,7 @@ describe('manager/gomod/update', () => {
         depName: 'github.com/pkg/errors',
         lineNumber: 2,
         newValue: 'v0.8.0',
+        depType: 'require',
       };
       const res1 = goUpdate.updateDependency(gomod1, upgrade1);
       expect(res1).not.toEqual(gomod1);
@@ -29,6 +31,7 @@ describe('manager/gomod/update', () => {
         depName: 'github.com/aws/aws-sdk-go',
         lineNumber: 3,
         newValue: 'v1.15.36',
+        depType: 'require',
       };
       const res2 = goUpdate.updateDependency(res1, upgrade2);
       expect(res2).not.toEqual(res1);
@@ -51,6 +54,7 @@ describe('manager/gomod/update', () => {
         updateType: 'major',
         currentValue: 'v0.7.0',
         newValue: 'v2.0.0',
+        depType: 'require',
       };
       const res = goUpdate.updateDependency(gomod1, upgrade);
       expect(res).not.toEqual(gomod2);
@@ -65,6 +69,7 @@ describe('manager/gomod/update', () => {
         updateType: 'major',
         currentValue: 'v1.0.0',
         newValue: 'v2.0.0',
+        depType: 'require',
       };
       const res = goUpdate.updateDependency(gomod1, upgrade);
       expect(res).toMatchSnapshot();
@@ -92,6 +97,7 @@ describe('manager/gomod/update', () => {
         lineNumber: 8,
         multiLine: true,
         newValue: 'v1.8.0',
+        depType: 'require',
       };
       const res = goUpdate.updateDependency(gomod2, upgrade);
       expect(res).not.toEqual(gomod2);
@@ -103,6 +109,7 @@ describe('manager/gomod/update', () => {
         lineNumber: 57,
         multiLine: true,
         newValue: 'v4.8.0',
+        depType: 'require',
       };
       const res = goUpdate.updateDependency(gomod2, upgrade);
       expect(res).toMatchSnapshot();
@@ -118,6 +125,7 @@ describe('manager/gomod/update', () => {
         newValue: 'v2.0.0',
         newMajor: 2,
         updateType: 'major',
+        depType: 'require',
       };
       const res = goUpdate.updateDependency(gomod2, upgrade);
       expect(res).not.toEqual(gomod2);
@@ -133,6 +141,7 @@ describe('manager/gomod/update', () => {
         newValue: 'v3.0.0',
         newMajor: 3,
         updateType: 'major',
+        depType: 'require',
       };
       const res = goUpdate.updateDependency(gomod2, upgrade);
       expect(res).not.toEqual(gomod2);
@@ -148,6 +157,7 @@ describe('manager/gomod/update', () => {
         updateType: 'digest',
         currentDigest: '14d3d4c51834',
         newDigest: '123456123456abcdef',
+        depType: 'require',
       };
       const res = goUpdate.updateDependency(gomod2, upgrade);
       expect(res).not.toEqual(gomod2);
@@ -163,6 +173,7 @@ describe('manager/gomod/update', () => {
         updateType: 'digest',
         currentDigest: 'abcdefabcdef',
         newDigest: '14d3d4c51834000000',
+        depType: 'require',
       };
       const res = goUpdate.updateDependency(gomod2, upgrade);
       expect(res).toEqual(gomod2);
@@ -172,6 +183,7 @@ describe('manager/gomod/update', () => {
         depName: 'github.com/fatih/color',
         lineNumber: 8,
         newValue: 'v1.8.0',
+        depType: 'require',
       };
       const res = goUpdate.updateDependency(gomod2, upgrade);
       expect(res).toBeNull();
@@ -181,11 +193,53 @@ describe('manager/gomod/update', () => {
         depName: 'github.com/Azure/azure-sdk-for-go',
         lineNumber: 8,
         newValue: 'v26.0.0',
+        depType: 'require',
       };
       const res = goUpdate.updateDependency(gomod1, upgrade);
       expect(res).not.toEqual(gomod1);
       // Assert that the version still contains +incompatible tag.
       expect(res.includes(upgrade.newValue + '+incompatible')).toBe(true);
     });
+    it('handles replace line with minor version update', () => {
+      const upgrade = {
+        depName: 'github.com/pravesht/gocql',
+        lineNumber: 11,
+        newValue: 'v0.0.1',
+        depType: 'replace',
+      };
+      const res = goUpdate.updateDependency(gomod1, upgrade);
+      expect(res).not.toEqual(gomod1);
+      expect(res.includes(upgrade.newValue)).toBe(true);
+    });
+    it('handles replace line with major version update', () => {
+      const upgrade = {
+        depName: 'github.com/pravesht/gocql',
+        lineNumber: 11,
+        newValue: 'v2.0.0',
+        depType: 'replace',
+        currentValue: 'v0.7.0',
+        newMajor: 2,
+        updateType: 'major',
+      };
+      const res = goUpdate.updateDependency(gomod1, upgrade);
+      expect(res).not.toEqual(gomod1);
+      expect(res.includes(upgrade.newValue)).toBe(true);
+    });
+    it('handles replace line with digest', () => {
+      const upgrade = {
+        depName: 'github.com/pravesht/gocql',
+        lineNumber: 11,
+        newValue: 'v2.0.0',
+        depType: 'replace',
+        currentValue: 'v0.7.0',
+        newMajor: 2,
+        updateType: 'digest',
+        currentDigest: '14d3d4c51834',
+        newDigest: '123456123456abcdef',
+      };
+      const res = goUpdate.updateDependency(gomod1, upgrade);
+      expect(res).not.toEqual(gomod1);
+      expect(res.includes(upgrade.newDigest.substring(0, 12))).toBe(true);
+    });
   });
 });
-- 
GitLab