From 3cec44cfa78e37a57c84e6428056787fc00ade7c Mon Sep 17 00:00:00 2001
From: Hasan Awad <90554456+hasanwhitesource@users.noreply.github.com>
Date: Thu, 11 Aug 2022 08:29:53 +0300
Subject: [PATCH] fix(manager/gomod): add multi-line replace detection (#17111)

---
 lib/modules/manager/gomod/extract.spec.ts | 73 +++++++++++++++++++++++
 lib/modules/manager/gomod/extract.ts      | 11 ++++
 lib/modules/manager/gomod/update.spec.ts  | 20 +++++++
 lib/modules/manager/gomod/update.ts       | 12 +++-
 4 files changed, 113 insertions(+), 3 deletions(-)

diff --git a/lib/modules/manager/gomod/extract.spec.ts b/lib/modules/manager/gomod/extract.spec.ts
index 0ca53a8a73..49f87ee7b6 100644
--- a/lib/modules/manager/gomod/extract.spec.ts
+++ b/lib/modules/manager/gomod/extract.spec.ts
@@ -40,5 +40,78 @@ describe('modules/manager/gomod/extract', () => {
       expect(res).toHaveLength(58);
       expect(res?.filter((e) => e.skipReason)).toHaveLength(0);
     });
+
+    it('extracts replace directives from multi-line and single line', () => {
+      const goMod = `
+module github.com/renovate-tests/gomod
+go 1.18
+replace golang.org/x/foo => github.com/pravesht/gocql v0.0.0
+replace (
+      k8s.io/client-go => k8s.io/client-go v0.21.9
+      )
+replace (
+  k8s.io/cloud-provider => k8s.io/cloud-provider v0.17.3
+  k8s.io/cluster-bootstrap => k8s.io/cluster-bootstrap v0.17.3 // indirect
+  k8s.io/code-generator => k8s.io/code-generator v0.17.3
+)`;
+      const res = extractPackageFile(goMod);
+      expect(res).toEqual({
+        constraints: {
+          go: '^1.18',
+        },
+        deps: [
+          {
+            managerData: {
+              lineNumber: 2,
+            },
+            depName: 'go',
+            depType: 'golang',
+            currentValue: '1.18',
+            datasource: 'golang-version',
+            versioning: 'npm',
+            rangeStrategy: 'replace',
+          },
+          {
+            managerData: {
+              lineNumber: 3,
+            },
+            depName: 'github.com/pravesht/gocql',
+            depType: 'replace',
+            currentValue: 'v0.0.0',
+            datasource: 'go',
+          },
+          {
+            managerData: {
+              lineNumber: 5,
+              multiLine: true,
+            },
+            depName: 'k8s.io/client-go',
+            depType: 'replace',
+            currentValue: 'v0.21.9',
+            datasource: 'go',
+          },
+          {
+            managerData: {
+              lineNumber: 8,
+              multiLine: true,
+            },
+            depName: 'k8s.io/cloud-provider',
+            depType: 'replace',
+            currentValue: 'v0.17.3',
+            datasource: 'go',
+          },
+          {
+            managerData: {
+              lineNumber: 10,
+              multiLine: true,
+            },
+            depName: 'k8s.io/code-generator',
+            depType: 'replace',
+            currentValue: 'v0.17.3',
+            datasource: 'go',
+          },
+        ],
+      });
+    });
   });
 });
diff --git a/lib/modules/manager/gomod/extract.ts b/lib/modules/manager/gomod/extract.ts
index 61d30af520..5bfe991295 100644
--- a/lib/modules/manager/gomod/extract.ts
+++ b/lib/modules/manager/gomod/extract.ts
@@ -88,6 +88,17 @@ export function extractPackageFile(content: string): PackageFile | null {
         );
         lineNumber = reachedLine;
         deps.push(...detectedDeps);
+      } else if (line.trim() === 'replace (') {
+        logger.trace(`Matched multi-line replace on line ${lineNumber}`);
+        const matcher = regEx(/^\s+[^\s]+[\s]+[=][>]\s+([^\s]+)\s+([^\s]+)/);
+        const { reachedLine, detectedDeps } = parseMultiLine(
+          lineNumber,
+          lines,
+          matcher,
+          'replace'
+        );
+        lineNumber = reachedLine;
+        deps.push(...detectedDeps);
       }
     }
   } catch (err) /* istanbul ignore next */ {
diff --git a/lib/modules/manager/gomod/update.spec.ts b/lib/modules/manager/gomod/update.spec.ts
index 6b80f5893d..0f1fb82db7 100644
--- a/lib/modules/manager/gomod/update.spec.ts
+++ b/lib/modules/manager/gomod/update.spec.ts
@@ -318,6 +318,26 @@ describe('modules/manager/gomod/update', () => {
       expect(res).toContain('github.com/caarlos0/env/v6 v6.1.0');
     });
 
+    it('handles multiline replace update', () => {
+      const fileContent = `
+      go 1.18
+      replace (
+        k8s.io/client-go => k8s.io/client-go v0.21.9
+      )`;
+      const upgrade = {
+        depName: 'k8s.io/client-go',
+        managerData: { lineNumber: 3, multiLine: true },
+        newValue: 'v2.2.2',
+        depType: 'replace',
+        currentValue: 'v0.21.9',
+        newMajor: 2,
+        updateType: 'major' as UpdateType,
+      };
+      const res = updateDependency({ fileContent, upgrade });
+      expect(res).not.toEqual(fileContent);
+      expect(res).toContain('k8s.io/client-go/v2 => k8s.io/client-go v2.2.2');
+    });
+
     it('should return null for replacement', () => {
       const res = updateDependency({
         fileContent: '',
diff --git a/lib/modules/manager/gomod/update.ts b/lib/modules/manager/gomod/update.ts
index f6abe980c6..de7f06b346 100644
--- a/lib/modules/manager/gomod/update.ts
+++ b/lib/modules/manager/gomod/update.ts
@@ -46,9 +46,15 @@ export function updateDependency({
       updateLineExp = regEx(/(?<depPart>go)(?<divider>\s+)[^\s]+/);
     }
     if (depType === 'replace') {
-      updateLineExp = regEx(
-        /^(?<depPart>replace\s+[^\s]+[\s]+[=][>]+\s+)(?<divider>[^\s]+\s+)[^\s]+/
-      );
+      if (upgrade.managerData.multiLine) {
+        updateLineExp = regEx(
+          /^(?<depPart>\s+[^\s]+[\s]+[=][>]+\s+)(?<divider>[^\s]+\s+)[^\s]+/
+        );
+      } else {
+        updateLineExp = regEx(
+          /^(?<depPart>replace\s+[^\s]+[\s]+[=][>]+\s+)(?<divider>[^\s]+\s+)[^\s]+/
+        );
+      }
     } else if (depType === 'require') {
       if (upgrade.managerData.multiLine) {
         updateLineExp = regEx(/^(?<depPart>\s+[^\s]+)(?<divider>\s+)[^\s]+/);
-- 
GitLab