diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependency.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependency.java index 738c595b0ff..2931e91435e 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependency.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependency.java @@ -36,10 +36,13 @@ import org.openrewrite.kotlin.tree.K; import org.openrewrite.maven.MavenDownloadingException; import org.openrewrite.maven.table.MavenMetadataFailures; +import org.openrewrite.properties.PropertiesVisitor; +import org.openrewrite.properties.tree.Properties; import org.openrewrite.semver.DependencyMatcher; import org.openrewrite.semver.Semver; import java.util.*; +import java.util.concurrent.ConcurrentHashMap; import static java.util.Collections.singletonList; import static java.util.Objects.requireNonNull; @@ -59,6 +62,9 @@ public class ChangeDependency extends Recipe { @EqualsAndHashCode.Exclude transient MavenMetadataFailures metadataFailures = new MavenMetadataFailures(this); + @EqualsAndHashCode.Exclude + transient Map pendingPropertyUpdates = new ConcurrentHashMap<>(); + @Option(displayName = "Old groupId", description = "The old groupId to replace. The groupId is the first part of a dependency coordinate 'com.google.guava:guava:VERSION'. Supports glob expressions.", example = "org.openrewrite.recipe") @@ -161,7 +167,7 @@ public Validated validate() { @Override public TreeVisitor getVisitor() { - return Preconditions.check(new FindGradleProject(FindGradleProject.SearchCriteria.Marker).getVisitor(), new JavaIsoVisitor() { + TreeVisitor gradleVisitor = Preconditions.check(new FindGradleProject(FindGradleProject.SearchCriteria.Marker).getVisitor(), new JavaIsoVisitor() { final DependencyMatcher depMatcher = requireNonNull(DependencyMatcher.build(oldGroupId + ":" + oldArtifactId).getValue()); final DependencyMatcher existingMatcher = requireNonNull(DependencyMatcher.build(newGroupId + ":" + newArtifactId + (newVersion == null ? "" : ":" + newVersion)).getValue()); @@ -315,10 +321,22 @@ private J.MethodInvocation updateDependency(J.MethodInvocation m, ExecutionConte } } if (original != updated) { - String replacement = DependencyNotation.toStringNotation(updated); - J.Literal newLiteral = literal.withValue(replacement) - .withValueSource(gstring.getDelimiter() + replacement + gstring.getDelimiter()); - m = m.withArguments(singletonList(newLiteral)); + // Always preserve GString structure, only update the literal prefix + String oldGav = original.getGroupId() + ":" + original.getArtifactId(); + String newGav = updated.getGroupId() + ":" + updated.getArtifactId(); + String oldValue = (String) literal.getValue(); + String updatedValue = oldValue.replace(oldGav, newGav); + J.Literal updatedLiteral = literal.withValue(updatedValue).withValueSource(updatedValue); + m = m.withArguments(singletonList( + gstring.withStrings(ListUtils.mapFirst(strings, s -> updatedLiteral)) + )); + // If version was resolved, schedule property update + if (updated.getVersion() != null && !Objects.equals(original.getVersion(), updated.getVersion())) { + String varName = extractVersionVariableName(strings); + if (varName != null) { + pendingPropertyUpdates.put(varName, updated.getVersion()); + } + } } } } @@ -608,10 +626,22 @@ private J.MethodInvocation updateDependency(J.MethodInvocation m, ExecutionConte } } if (original != updated) { - String replacement = DependencyNotation.toStringNotation(updated); - J.Literal newLiteral = literal.withValue(replacement) - .withValueSource(template.getDelimiter() + replacement + template.getDelimiter()); - m = m.withArguments(singletonList(newLiteral)); + // Always preserve StringTemplate structure, only update the literal prefix + String oldGav = original.getGroupId() + ":" + original.getArtifactId(); + String newGav = updated.getGroupId() + ":" + updated.getArtifactId(); + String oldValue = (String) literal.getValue(); + String updatedValue = oldValue.replace(oldGav, newGav); + J.Literal updatedLiteral = literal.withValue(updatedValue).withValueSource(updatedValue); + m = m.withArguments(singletonList( + template.withStrings(ListUtils.mapFirst(strings, s -> updatedLiteral)) + )); + // If version was resolved, schedule property update + if (updated.getVersion() != null && !Objects.equals(original.getVersion(), updated.getVersion())) { + String varName = extractVersionVariableName(strings); + if (varName != null) { + pendingPropertyUpdates.put(varName, updated.getVersion()); + } + } } } } @@ -694,5 +724,46 @@ private GradleProject updateGradleModel(GradleProject gp, ExecutionContext ctx) return gp; } }); + + return new TreeVisitor() { + @Override + public @Nullable Tree visit(@Nullable Tree tree, ExecutionContext ctx) { + if (tree instanceof Properties.File && !pendingPropertyUpdates.isEmpty()) { + return new PropertiesVisitor() { + @Override + public Properties.Entry visitEntry(Properties.Entry entry, ExecutionContext ctx) { + String version = pendingPropertyUpdates.get(entry.getKey()); + if (version != null && !version.equals(entry.getValue().getText())) { + return entry.withValue(entry.getValue().withText(version)); + } + return entry; + } + }.visit(tree, ctx); + } + return gradleVisitor.visit(tree, ctx); + } + }; } + + private static @Nullable String extractVersionVariableName(List strings) { + if (strings.size() >= 2) { + J versionPart = strings.get(strings.size() - 1); + // Groovy GString: strings[1] is G.GString.Value wrapping J.Identifier + if (versionPart instanceof G.GString.Value) { + J tree = ((G.GString.Value) versionPart).getTree(); + if (tree instanceof J.Identifier) { + return ((J.Identifier) tree).getSimpleName(); + } + } + // Kotlin StringTemplate: strings[1] is K.StringTemplate.Expression wrapping J.Identifier + if (versionPart instanceof K.StringTemplate.Expression) { + J tree = ((K.StringTemplate.Expression) versionPart).getTree(); + if (tree instanceof J.Identifier) { + return ((J.Identifier) tree).getSimpleName(); + } + } + } + return null; + } + } diff --git a/rewrite-gradle/src/test/java/org/openrewrite/gradle/ChangeDependencyTest.java b/rewrite-gradle/src/test/java/org/openrewrite/gradle/ChangeDependencyTest.java index 6f5503aa174..d344eb01479 100644 --- a/rewrite-gradle/src/test/java/org/openrewrite/gradle/ChangeDependencyTest.java +++ b/rewrite-gradle/src/test/java/org/openrewrite/gradle/ChangeDependencyTest.java @@ -24,6 +24,7 @@ import static org.openrewrite.gradle.Assertions.buildGradle; import static org.openrewrite.gradle.Assertions.buildGradleKts; import static org.openrewrite.gradle.toolingapi.Assertions.withToolingApi; +import static org.openrewrite.properties.Assertions.properties; class ChangeDependencyTest implements RewriteTest { @Override @@ -238,6 +239,15 @@ implementation platform("org.apache.commons:commons-lang3:3.11") void worksWithGString() { rewriteRun( spec -> spec.recipe(new ChangeDependency("commons-lang", "commons-lang", "org.apache.commons", "commons-lang3", "3.11.x", null, null, true)), + properties( + """ + commonsLangVersion=2.6 + """, + """ + commonsLangVersion=3.11 + """, + spec -> spec.path("gradle.properties") + ), buildGradle( """ plugins { @@ -248,9 +258,8 @@ void worksWithGString() { mavenCentral() } - def version = '2.6' dependencies { - implementation platform("commons-lang:commons-lang:${version}") + implementation platform("commons-lang:commons-lang:${commonsLangVersion}") } """, """ @@ -262,9 +271,8 @@ implementation platform("commons-lang:commons-lang:${version}") mavenCentral() } - def version = '2.6' dependencies { - implementation platform("org.apache.commons:commons-lang3:3.11") + implementation platform("org.apache.commons:commons-lang3:${commonsLangVersion}") } """ ) @@ -548,6 +556,15 @@ void kotlinDsl() { void kotlinDslStringInterpolation() { rewriteRun( spec -> spec.recipe(new ChangeDependency("commons-lang", "commons-lang", "org.apache.commons", "commons-lang3", "3.11.x", null, null, true)), + properties( + """ + commonsLangVersion=2.6 + """, + """ + commonsLangVersion=3.11 + """, + spec -> spec.path("gradle.properties") + ), buildGradleKts( """ plugins { @@ -558,8 +575,8 @@ void kotlinDslStringInterpolation() { mavenCentral() } + val commonsLangVersion: String by project dependencies { - val commonsLangVersion = "2.6" implementation("commons-lang:commons-lang:${commonsLangVersion}") } """, @@ -572,9 +589,9 @@ void kotlinDslStringInterpolation() { mavenCentral() } + val commonsLangVersion: String by project dependencies { - val commonsLangVersion = "2.6" - implementation("org.apache.commons:commons-lang3:3.11") + implementation("org.apache.commons:commons-lang3:${commonsLangVersion}") } """ )