From c1ee1ef32f3c30f113d3b64ab5b3cd78ceef97bd Mon Sep 17 00:00:00 2001
From: Joshua Gleitze <dev@joshuagleitze.de>
Date: Tue, 21 Jan 2020 11:04:31 +0200
Subject: [PATCH] =?UTF-8?q?feat(gradle):=20Support=20template=20expression?=
 =?UTF-8?q?=20for=20version=20variables=E2=80=A6=20(#5195)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 lib/manager/gradle/build-gradle.ts       |  26 ++--
 test/manager/gradle/build-gradle.spec.ts | 170 ++++++++++++++++++++++-
 2 files changed, 183 insertions(+), 13 deletions(-)

diff --git a/lib/manager/gradle/build-gradle.ts b/lib/manager/gradle/build-gradle.ts
index 8188e11ebd..7445665dcd 100644
--- a/lib/manager/gradle/build-gradle.ts
+++ b/lib/manager/gradle/build-gradle.ts
@@ -22,10 +22,12 @@ interface UpdateFunction {
   ): string;
 }
 
+const groovyQuotes = `(?:["'](?:""|'')?)`;
+
 // https://github.com/patrikerdes/gradle-use-latest-versions-plugin/blob/8cf9c3917b8b04ba41038923cab270d2adda3aa6/src/main/groovy/se/patrikerdes/DependencyUpdate.groovy#L27-L29
 function moduleStringVersionFormatMatch(dependency: GradleDependency): RegExp {
   return new RegExp(
-    `(["']${dependency.group}:${dependency.name}:)[^$].*?(([:@].*?)?["'])`
+    `(${groovyQuotes}${dependency.group}:${dependency.name}:)[^$].*?(([:@].*?)?${groovyQuotes})`
   );
 }
 
@@ -33,7 +35,7 @@ function groovyPluginStringVersionFormatMatch(
   dependency: GradleDependency
 ): RegExp {
   return new RegExp(
-    `(id\\s+["']${dependency.group}["']\\s+version\\s+["'])[^$].*?(["'])`
+    `(id\\s+${groovyQuotes}${dependency.group}${groovyQuotes}\\s+version\\s+${groovyQuotes})[^$].*?(${groovyQuotes})`
   );
 }
 
@@ -48,9 +50,9 @@ function kotlinPluginStringVersionFormatMatch(
 function moduleMapVersionFormatMatch(dependency: GradleDependency): RegExp {
   // prettier-ignore
   return new RegExp(
-    `(group\\s*:\\s*["']${dependency.group}["']\\s*,\\s*` +
-    `name\\s*:\\s*["']${dependency.name}["']\\s*,\\s*` +
-    `version\\s*:\\s*["']).*?(["'])`
+    `(group\\s*:\\s*${groovyQuotes}${dependency.group}${groovyQuotes}\\s*,\\s*` +
+    `name\\s*:\\s*${groovyQuotes}${dependency.name}${groovyQuotes}\\s*,\\s*` +
+    `version\\s*:\\s*${groovyQuotes})[^{}$"']+?(${groovyQuotes})`
   );
 }
 
@@ -61,7 +63,7 @@ function moduleKotlinNamedArgumentVersionFormatMatch(
   return new RegExp(
     `(group\\s*=\\s*"${dependency.group}"\\s*,\\s*` +
     `name\\s*=\\s*"${dependency.name}"\\s*,\\s*` +
-    `version\\s*=\\s*").*?(")`
+    `version\\s*=\\s*")[^{}$]*?(")`
   );
 }
 
@@ -70,9 +72,9 @@ function moduleMapVariableVersionFormatMatch(
 ): RegExp {
   // prettier-ignore
   return new RegExp(
-    `group\\s*:\\s*["']${dependency.group}["']\\s*,\\s*` +
-    `name\\s*:\\s*["']${dependency.name}["']\\s*,\\s*` +
-    `version\\s*:\\s*([^\\s"')]+)\\s*`
+    `group\\s*:\\s*${groovyQuotes}${dependency.group}${groovyQuotes}\\s*,\\s*` +
+    `name\\s*:\\s*${groovyQuotes}${dependency.name}${groovyQuotes}\\s*,\\s*` +
+    `version\\s*:\\s*(?:${groovyQuotes}\\$)?{?([^\\s"'{}$)]+)}?${groovyQuotes}?\\s*`
   );
 }
 
@@ -83,7 +85,7 @@ function moduleKotlinNamedArgumentVariableVersionFormatMatch(
   return new RegExp(
     `group\\s*=\\s*"${dependency.group}"\\s*,\\s*` +
     `name\\s*=\\s*"${dependency.name}"\\s*,\\s*` +
-    `version\\s*=\\s*([^\\s"]+?)[\\s\\),]`
+    `version\\s*=\\s*(?:"\\$)?{?([^\\s"{}$]+?)}?"?[\\s\\),]`
   );
 }
 
@@ -91,7 +93,7 @@ function moduleStringVariableInterpolationVersionFormatMatch(
   dependency: GradleDependency
 ): RegExp {
   return new RegExp(
-    `["']${dependency.group}:${dependency.name}:\\$([^{].*?)["']`
+    `${groovyQuotes}${dependency.group}:${dependency.name}:\\$([^{].*?)${groovyQuotes}`
   );
 }
 
@@ -99,7 +101,7 @@ function moduleStringVariableExpressionVersionFormatMatch(
   dependency: GradleDependency
 ): RegExp {
   return new RegExp(
-    `["']${dependency.group}:${dependency.name}:\\$\{([^{].*?)}["']`
+    `${groovyQuotes}${dependency.group}:${dependency.name}:\\$\{([^{].*?)}${groovyQuotes}`
   );
 }
 
diff --git a/test/manager/gradle/build-gradle.spec.ts b/test/manager/gradle/build-gradle.spec.ts
index f6344814ce..eb4b9ab885 100644
--- a/test/manager/gradle/build-gradle.spec.ts
+++ b/test/manager/gradle/build-gradle.spec.ts
@@ -66,7 +66,7 @@ describe('lib/manager/gradle/updateGradleVersion', () => {
     );
   });
 
-  it('returns a file updated if the version defined as map is found', () => {
+  it('returns an updated file if the version in single quotes defined as map is found', () => {
     const gradleFile = `compile group  : 'mysql'               ,
                name   : 'mysql-connector-java',
                version: '6.0.5'`;
@@ -82,6 +82,54 @@ describe('lib/manager/gradle/updateGradleVersion', () => {
     );
   });
 
+  it('returns an updated file if the version in double quotes defined as map is found', () => {
+    const gradleFile = `compile group  : 'mysql'               ,
+               name   : 'mysql-connector-java',
+               version: "6.0.5"`;
+    const updatedGradleFile = updateGradleVersion(
+      gradleFile,
+      { group: 'mysql', name: 'mysql-connector-java', version: '6.0.5' },
+      '7.0.0'
+    );
+    expect(updatedGradleFile).toEqual(
+      `compile group  : 'mysql'               ,
+               name   : 'mysql-connector-java',
+               version: "7.0.0"`
+    );
+  });
+
+  it('returns an updated file if the version in triple single quotes defined as map is found', () => {
+    const gradleFile = `compile group  : 'mysql'               ,
+               name   : 'mysql-connector-java',
+               version: '''6.0.5'''`;
+    const updatedGradleFile = updateGradleVersion(
+      gradleFile,
+      { group: 'mysql', name: 'mysql-connector-java', version: '6.0.5' },
+      '7.0.0'
+    );
+    expect(updatedGradleFile).toEqual(
+      `compile group  : 'mysql'               ,
+               name   : 'mysql-connector-java',
+               version: '''7.0.0'''`
+    );
+  });
+
+  it('returns an updated file if the version in triple double quotes defined as map is found', () => {
+    const gradleFile = `compile group  : 'mysql'               ,
+               name   : 'mysql-connector-java',
+               version: """6.0.5"""`;
+    const updatedGradleFile = updateGradleVersion(
+      gradleFile,
+      { group: 'mysql', name: 'mysql-connector-java', version: '6.0.5' },
+      '7.0.0'
+    );
+    expect(updatedGradleFile).toEqual(
+      `compile group  : 'mysql'               ,
+               name   : 'mysql-connector-java',
+               version: """7.0.0"""`
+    );
+  });
+
   it('returns a file updated if the version defined as a Kotlin named argument is found', () => {
     const gradleFile = `compile(group   = "mysql"               ,
                name    = "mysql-connector-java",
@@ -146,6 +194,86 @@ describe('lib/manager/gradle/updateGradleVersion', () => {
     );
   });
 
+  it('returns an updated file if the version defined in a variable in a simple template string without curly braces as a map is found', () => {
+    const gradleFile = `String mysqlVersion = "6.0.5"
+               compile group  : 'mysql'               ,
+               name           : 'mysql-connector-java',
+               version        : "$mysqlVersion"
+               `;
+    const updatedGradleFile = updateGradleVersion(
+      gradleFile,
+      { group: 'mysql', name: 'mysql-connector-java', version: '6.0.5' },
+      '7.0.0'
+    );
+    expect(updatedGradleFile).toEqual(
+      `String mysqlVersion = "7.0.0"
+               compile group  : 'mysql'               ,
+               name           : 'mysql-connector-java',
+               version        : "$mysqlVersion"
+               `
+    );
+  });
+
+  it('returns an updated file if the version defined in a variable in a simple template string with curly braces as a map is found', () => {
+    const gradleFile = `String mysqlVersion = "6.0.5"
+               compile group  : 'mysql'               ,
+               name           : 'mysql-connector-java',
+               version        : "\${mysqlVersion}"
+               `;
+    const updatedGradleFile = updateGradleVersion(
+      gradleFile,
+      { group: 'mysql', name: 'mysql-connector-java', version: '6.0.5' },
+      '7.0.0'
+    );
+    expect(updatedGradleFile).toEqual(
+      `String mysqlVersion = "7.0.0"
+               compile group  : 'mysql'               ,
+               name           : 'mysql-connector-java',
+               version        : "\${mysqlVersion}"
+               `
+    );
+  });
+
+  it('returns an updated file if the version defined in a variable in a triple template string without curly braces as a map is found', () => {
+    const gradleFile = `String mysqlVersion = "6.0.5"
+               compile group  : 'mysql'               ,
+               name           : 'mysql-connector-java',
+               version        : """$mysqlVersion"""
+               `;
+    const updatedGradleFile = updateGradleVersion(
+      gradleFile,
+      { group: 'mysql', name: 'mysql-connector-java', version: '6.0.5' },
+      '7.0.0'
+    );
+    expect(updatedGradleFile).toEqual(
+      `String mysqlVersion = "7.0.0"
+               compile group  : 'mysql'               ,
+               name           : 'mysql-connector-java',
+               version        : """$mysqlVersion"""
+               `
+    );
+  });
+
+  it('returns an updated file if the version defined in a variable in a triple template string with curly braces as a map is found', () => {
+    const gradleFile = `String mysqlVersion = "6.0.5"
+               compile group  : 'mysql'               ,
+               name           : 'mysql-connector-java',
+               version        : """\${mysqlVersion}"""
+               `;
+    const updatedGradleFile = updateGradleVersion(
+      gradleFile,
+      { group: 'mysql', name: 'mysql-connector-java', version: '6.0.5' },
+      '7.0.0'
+    );
+    expect(updatedGradleFile).toEqual(
+      `String mysqlVersion = "7.0.0"
+               compile group  : 'mysql'               ,
+               name           : 'mysql-connector-java',
+               version        : """\${mysqlVersion}"""
+               `
+    );
+  });
+
   it('should returns a file updated if the version defined in a variable as a Kotlin named argument is found', () => {
     const gradleFile = `val mysqlVersion = "6.0.5"
                compile(group = "mysql"               ,
@@ -166,6 +294,46 @@ describe('lib/manager/gradle/updateGradleVersion', () => {
     );
   });
 
+  it('returns an updated file if the version defined in a variable in a template string without curly braces as a Kotlin named argument is found', () => {
+    const gradleFile = `val mysqlVersion = "6.0.5"
+               compile(group = "mysql"               ,
+               name          = "mysql-connector-java",
+               version       = "$mysqlVersion")
+               `;
+    const updatedGradleFile = updateGradleVersion(
+      gradleFile,
+      { group: 'mysql', name: 'mysql-connector-java', version: '6.0.5' },
+      '7.0.0'
+    );
+    expect(updatedGradleFile).toEqual(
+      `val mysqlVersion = "7.0.0"
+               compile(group = "mysql"               ,
+               name          = "mysql-connector-java",
+               version       = "$mysqlVersion")
+               `
+    );
+  });
+
+  it('returns an updated file if the version defined in a variable in a template string with curly braces as a Kotlin named argument is found', () => {
+    const gradleFile = `val mysqlVersion = "6.0.5"
+               compile(group = "mysql"               ,
+               name          = "mysql-connector-java",
+               version       = "\${mysqlVersion}")
+               `;
+    const updatedGradleFile = updateGradleVersion(
+      gradleFile,
+      { group: 'mysql', name: 'mysql-connector-java', version: '6.0.5' },
+      '7.0.0'
+    );
+    expect(updatedGradleFile).toEqual(
+      `val mysqlVersion = "7.0.0"
+               compile(group = "mysql"               ,
+               name          = "mysql-connector-java",
+               version       = "\${mysqlVersion}")
+               `
+    );
+  });
+
   it('should replace a external groovy variable assigned to a specific dependency', () => {
     const gradleFile =
       'runtime (  "mysql:mysql-connector-java:${mysqlVersion}"  )'; // eslint-disable-line no-template-curly-in-string
-- 
GitLab