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..d6463ca422a 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependency.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependency.java @@ -21,7 +21,6 @@ import org.jspecify.annotations.Nullable; import org.openrewrite.*; import org.openrewrite.gradle.internal.ChangeStringLiteral; -import org.openrewrite.maven.tree.*; import org.openrewrite.gradle.marker.GradleDependencyConfiguration; import org.openrewrite.gradle.marker.GradleProject; import org.openrewrite.gradle.search.FindGradleProject; @@ -30,23 +29,27 @@ import org.openrewrite.internal.ListUtils; import org.openrewrite.internal.StringUtils; import org.openrewrite.java.JavaIsoVisitor; -import org.openrewrite.java.tree.Expression; +import org.openrewrite.java.JavaVisitor; import org.openrewrite.java.tree.J; import org.openrewrite.java.tree.JavaSourceFile; import org.openrewrite.kotlin.tree.K; +import org.openrewrite.marker.Markup; import org.openrewrite.maven.MavenDownloadingException; +import org.openrewrite.maven.tree.*; 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 static java.util.Collections.singletonList; import static java.util.Objects.requireNonNull; @Value @EqualsAndHashCode(callSuper = false) -public class ChangeDependency extends Recipe { +public class ChangeDependency extends ScanningRecipe { + private static final String GRADLE_PROPERTIES_FILE_NAME = "gradle.properties"; // Individual dependencies tend to appear in several places within a given dependency graph. // Minimize the number of allocations by caching the updated dependencies. @@ -159,9 +162,85 @@ public Validated validate() { )); } + public static class Accumulator { + Map versionVariableUpdates = new HashMap<>(); + Map> versionVariableUsages = new HashMap<>(); + } + + @Override + public Accumulator getInitialValue(ExecutionContext ctx) { + return new Accumulator(); + } + + @Override + public TreeVisitor getScanner(Accumulator acc) { + return new JavaVisitor() { + @Nullable + GradleProject gradleProject; + + @Override + public boolean isAcceptable(SourceFile sourceFile, ExecutionContext ctx) { + return (sourceFile instanceof G.CompilationUnit && sourceFile.getSourcePath().toString().endsWith(".gradle")) || + (sourceFile instanceof K.CompilationUnit && sourceFile.getSourcePath().toString().endsWith(".gradle.kts")); + } + + @Override + public @Nullable J visit(@Nullable Tree tree, ExecutionContext ctx) { + if (tree instanceof JavaSourceFile) { + gradleProject = tree.getMarkers().findFirst(GradleProject.class).orElse(null); + if (gradleProject == null) { + return (J) tree; + } + } + return super.visit(tree, ctx); + } + + @Override + public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { + J.MethodInvocation m = (J.MethodInvocation) super.visitMethodInvocation(method, ctx); + + new GradleDependency.Matcher().get(getCursor()).ifPresent(dep -> { + String varName = dep.getVersionVariable(); + if (varName != null) { + acc.versionVariableUsages + .computeIfAbsent(varName, k -> new HashSet<>()) + .add(new GroupArtifact(dep.getGroupId(), dep.getArtifactId())); + } + }); + + new GradleDependency.Matcher() + .groupId(oldGroupId) + .artifactId(oldArtifactId) + .get(getCursor()) + .ifPresent(dep -> { + String varName = dep.getVersionVariable(); + if (varName != null && !StringUtils.isBlank(newVersion)) { + resolveAndRecordVersion(varName, m, dep, ctx); + } + }); + + return m; + } + + private void resolveAndRecordVersion(String varName, J.MethodInvocation m, GradleDependency dep, ExecutionContext ctx) { + String resolvedGroupId = !StringUtils.isBlank(newGroupId) ? newGroupId : dep.getGroupId(); + String resolvedArtifactId = !StringUtils.isBlank(newArtifactId) ? newArtifactId : dep.getArtifactId(); + try { + String resolvedVersion = new DependencyVersionSelector(metadataFailures, gradleProject, null) + .select(new GroupArtifact(resolvedGroupId, resolvedArtifactId), m.getSimpleName(), newVersion, versionPattern, ctx); + if (resolvedVersion != null) { + acc.versionVariableUpdates.put(varName, resolvedVersion); + } + } catch (MavenDownloadingException e) { + acc.versionVariableUpdates.put(varName, e); + } + } + }; + } + @Override - public TreeVisitor getVisitor() { - return Preconditions.check(new FindGradleProject(FindGradleProject.SearchCriteria.Marker).getVisitor(), new JavaIsoVisitor() { + public TreeVisitor getVisitor(Accumulator acc) { + 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()); @@ -197,9 +276,6 @@ public boolean isAcceptable(SourceFile sourceFile, ExecutionContext ctx) { return super.visit(tree, ctx); } - /** - * Avoid duplicating dependencies when the target dependency already exists in the project. - */ private JavaSourceFile maybeRemoveDuplicateTargetDependency(JavaSourceFile sourceFile, ExecutionContext ctx) { Optional maybeGp = sourceFile.getMarkers().findFirst(GradleProject.class); if (!maybeGp.isPresent()){ @@ -238,386 +314,77 @@ public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, Execu .groupId(oldGroupId) .artifactId(oldArtifactId); - if (!gradleDependencyMatcher.get(getCursor()).isPresent()) { + Optional maybeDep = gradleDependencyMatcher.get(getCursor()); + if (!maybeDep.isPresent()) { return m; } - List depArgs = m.getArguments(); - if (depArgs.get(0) instanceof J.Literal || depArgs.get(0) instanceof G.GString || depArgs.get(0) instanceof G.MapEntry || depArgs.get(0) instanceof G.MapLiteral || depArgs.get(0) instanceof J.Assignment || depArgs.get(0) instanceof K.StringTemplate) { - m = updateDependency(m, ctx); - } else if (depArgs.get(0) instanceof J.MethodInvocation && - ("platform".equals(((J.MethodInvocation) depArgs.get(0)).getSimpleName()) || - "enforcedPlatform".equals(((J.MethodInvocation) depArgs.get(0)).getSimpleName()))) { - m = m.withArguments(ListUtils.mapFirst(depArgs, platform -> updateDependency((J.MethodInvocation) platform, ctx))); + return updateDependency(m, maybeDep.get(), ctx); + } + + @Override + public J.VariableDeclarations.NamedVariable visitVariable(J.VariableDeclarations.NamedVariable variable, ExecutionContext ctx) { + J.VariableDeclarations.NamedVariable v = super.visitVariable(variable, ctx); + if (!canUpdateVariable(v.getSimpleName())) { + return v; + } + Object scanResult = acc.versionVariableUpdates.get(v.getSimpleName()); + if (scanResult instanceof Exception) { + return Markup.warn(v, (Exception) scanResult); } + if (scanResult instanceof String && v.getInitializer() instanceof J.Literal) { + String resolvedVersion = (String) scanResult; + J.Literal initializer = (J.Literal) v.getInitializer(); + if (initializer.getValue() instanceof String && !resolvedVersion.equals(initializer.getValue())) { + v = v.withInitializer(ChangeStringLiteral.withStringValue(initializer, resolvedVersion)); + } + } + return v; + } - return m; + private boolean canUpdateVariable(String varName) { + return ChangeDependency.this.canUpdateVariable(varName, depMatcher, acc); } - private J.MethodInvocation updateDependency(J.MethodInvocation m, ExecutionContext ctx) { - List depArgs = m.getArguments(); - if (depArgs.get(0) instanceof J.Literal) { - String gav = (String) ((J.Literal) depArgs.get(0)).getValue(); - if (gav != null) { - Dependency original = DependencyNotation.parse(gav); - if (original != null) { - Dependency updated = original; - if (!StringUtils.isBlank(newGroupId) && !Objects.equals(updated.getGroupId(), newGroupId)) { - updated = updated.withGav(updated.getGav().withGroupId(newGroupId)); - } - if (!StringUtils.isBlank(newArtifactId) && !updated.getArtifactId().equals(newArtifactId)) { - updated = updated.withGav(updated.getGav().withArtifactId(newArtifactId)); - } - if (!StringUtils.isBlank(newVersion) && (!StringUtils.isBlank(original.getVersion()) || Boolean.TRUE.equals(overrideManagedVersion))) { - String resolvedVersion; - try { - resolvedVersion = new DependencyVersionSelector(metadataFailures, gradleProject, null) - .select(new GroupArtifact(updated.getGroupId(), updated.getArtifactId()), m.getSimpleName(), newVersion, versionPattern, ctx); - } catch (MavenDownloadingException e) { - return e.warn(m); - } - if (resolvedVersion != null && !resolvedVersion.equals(updated.getVersion())) { - updated = updated.withGav(updated.getGav().withVersion(resolvedVersion)); - } - } - if (original != updated) { - String replacement = DependencyNotation.toStringNotation(updated); - m = m.withArguments(ListUtils.mapFirst(m.getArguments(), arg -> ChangeStringLiteral.withStringValue((J.Literal) arg, replacement))); - } - } - } - } else if (m.getArguments().get(0) instanceof G.GString) { - G.GString gstring = (G.GString) depArgs.get(0); - List strings = gstring.getStrings(); - if (strings.size() >= 2 && strings.get(0) instanceof J.Literal && - ((J.Literal) strings.get(0)).getValue() != null) { - - J.Literal literal = (J.Literal) strings.get(0); - Dependency original = DependencyNotation.parse((String) requireNonNull(literal.getValue())); - if (original != null) { - Dependency updated = original; - if (!StringUtils.isBlank(newGroupId) && !Objects.equals(updated.getGroupId(), newGroupId)) { - updated = updated.withGav(updated.getGav().withGroupId(newGroupId)); - } - if (!StringUtils.isBlank(newArtifactId) && !updated.getArtifactId().equals(newArtifactId)) { - updated = updated.withGav(updated.getGav().withArtifactId(newArtifactId)); - } - if (!StringUtils.isBlank(newVersion)) { - String resolvedVersion; - try { - resolvedVersion = new DependencyVersionSelector(metadataFailures, gradleProject, null) - .select(new GroupArtifact(updated.getGroupId(), updated.getArtifactId()), m.getSimpleName(), newVersion, versionPattern, ctx); - } catch (MavenDownloadingException e) { - return e.warn(m); - } - if (resolvedVersion != null && !resolvedVersion.equals(updated.getVersion())) { - updated = updated.withGav(updated.getGav().withVersion(resolvedVersion)); - } - } - 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)); - } - } - } - } else if (m.getArguments().get(0) instanceof G.MapEntry) { - G.MapEntry groupEntry = null; - G.MapEntry artifactEntry = null; - G.MapEntry versionEntry = null; - String groupId = null; - String artifactId = null; - String version = null; - - for (Expression e : depArgs) { - if (!(e instanceof G.MapEntry)) { - continue; - } - G.MapEntry arg = (G.MapEntry) e; - if (!(arg.getKey() instanceof J.Literal) || !(arg.getValue() instanceof J.Literal)) { - continue; - } - J.Literal key = (J.Literal) arg.getKey(); - J.Literal value = (J.Literal) arg.getValue(); - if (!(key.getValue() instanceof String) || !(value.getValue() instanceof String)) { - continue; - } - String keyValue = (String) key.getValue(); - String valueValue = (String) value.getValue(); - switch (keyValue) { - case "group": - groupEntry = arg; - groupId = valueValue; - break; - case "name": - artifactEntry = arg; - artifactId = valueValue; - break; - case "version": - versionEntry = arg; - version = valueValue; - break; - } - } - if (groupId == null || artifactId == null) { - return m; - } - if (!depMatcher.matches(groupId, artifactId)) { - return m; - } - String updatedGroupId = groupId; - if (!StringUtils.isBlank(newGroupId) && !updatedGroupId.equals(newGroupId)) { - updatedGroupId = newGroupId; - } - String updatedArtifactId = artifactId; - if (!StringUtils.isBlank(newArtifactId) && !updatedArtifactId.equals(newArtifactId)) { - updatedArtifactId = newArtifactId; - } - String updatedVersion = version; - if (!StringUtils.isBlank(newVersion) && (!StringUtils.isBlank(version) || Boolean.TRUE.equals(overrideManagedVersion))) { - String resolvedVersion; - try { - resolvedVersion = new DependencyVersionSelector(metadataFailures, gradleProject, null) - .select(new GroupArtifact(updatedGroupId, updatedArtifactId), m.getSimpleName(), newVersion, versionPattern, ctx); - } catch (MavenDownloadingException e) { - return e.warn(m); - } - if (resolvedVersion != null && !resolvedVersion.equals(updatedVersion)) { - updatedVersion = resolvedVersion; - } - } + private J.MethodInvocation updateDependency(J.MethodInvocation m, GradleDependency dep, ExecutionContext ctx) { + GradleDependency updated = dep; - if (!updatedGroupId.equals(groupId) || !updatedArtifactId.equals(artifactId) || updatedVersion != null && !updatedVersion.equals(version)) { - G.MapEntry finalGroup = groupEntry; - String finalGroupIdValue = updatedGroupId; - G.MapEntry finalArtifact = artifactEntry; - String finalArtifactIdValue = updatedArtifactId; - G.MapEntry finalVersion = versionEntry; - String finalVersionValue = updatedVersion; - m = m.withArguments(ListUtils.map(m.getArguments(), arg -> { - if (arg == finalGroup) { - return finalGroup.withValue(ChangeStringLiteral.withStringValue((J.Literal) finalGroup.getValue(), finalGroupIdValue)); - } - if (arg == finalArtifact) { - return finalArtifact.withValue(ChangeStringLiteral.withStringValue((J.Literal) finalArtifact.getValue(), finalArtifactIdValue)); - } - if (arg == finalVersion) { - return finalVersion.withValue(ChangeStringLiteral.withStringValue((J.Literal) finalVersion.getValue(), finalVersionValue)); - } - return arg; - })); - } - } else if (m.getArguments().get(0) instanceof G.MapLiteral) { - G.MapLiteral map = (G.MapLiteral) depArgs.get(0); - G.MapEntry groupEntry = null; - G.MapEntry artifactEntry = null; - G.MapEntry versionEntry = null; - String groupId = null; - String artifactId = null; - String version = null; - - for (G.MapEntry arg : map.getElements()) { - if (!(arg.getKey() instanceof J.Literal) || !(arg.getValue() instanceof J.Literal)) { - continue; - } - J.Literal key = (J.Literal) arg.getKey(); - J.Literal value = (J.Literal) arg.getValue(); - if (!(key.getValue() instanceof String) || !(value.getValue() instanceof String)) { - continue; - } - String keyValue = (String) key.getValue(); - String valueValue = (String) value.getValue(); - switch (keyValue) { - case "group": - groupEntry = arg; - groupId = valueValue; - break; - case "name": - artifactEntry = arg; - artifactId = valueValue; - break; - case "version": - versionEntry = arg; - version = valueValue; - break; - } - } - if (groupId == null || artifactId == null) { - return m; - } - if (!depMatcher.matches(groupId, artifactId)) { - return m; - } - String updatedGroupId = groupId; - if (!StringUtils.isBlank(newGroupId) && !updatedGroupId.equals(newGroupId)) { - updatedGroupId = newGroupId; - } - String updatedArtifactId = artifactId; - if (!StringUtils.isBlank(newArtifactId) && !updatedArtifactId.equals(newArtifactId)) { - updatedArtifactId = newArtifactId; - } - String updatedVersion = version; - if (!StringUtils.isBlank(newVersion) && (!StringUtils.isBlank(version) || Boolean.TRUE.equals(overrideManagedVersion))) { - String resolvedVersion; - try { - resolvedVersion = new DependencyVersionSelector(metadataFailures, gradleProject, null) - .select(new GroupArtifact(updatedGroupId, updatedArtifactId), m.getSimpleName(), newVersion, versionPattern, ctx); - } catch (MavenDownloadingException e) { - return e.warn(m); - } - if (resolvedVersion != null && !resolvedVersion.equals(updatedVersion)) { - updatedVersion = resolvedVersion; - } - } + if (!StringUtils.isBlank(newGroupId)) { + updated = updated.withDeclaredGroupId(newGroupId); + } + if (!StringUtils.isBlank(newArtifactId)) { + updated = updated.withDeclaredArtifactId(newArtifactId); + } - if (!updatedGroupId.equals(groupId) || !updatedArtifactId.equals(artifactId) || updatedVersion != null && !updatedVersion.equals(version)) { - G.MapEntry finalGroup = groupEntry; - String finalGroupIdValue = updatedGroupId; - G.MapEntry finalArtifact = artifactEntry; - String finalArtifactIdValue = updatedArtifactId; - G.MapEntry finalVersion = versionEntry; - String finalVersionValue = updatedVersion; - m = m.withArguments(ListUtils.mapFirst(m.getArguments(), arg -> { - G.MapLiteral mapLiteral = (G.MapLiteral) arg; - return mapLiteral.withElements(ListUtils.map(mapLiteral.getElements(), e -> { - if (e == finalGroup) { - return finalGroup.withValue(ChangeStringLiteral.withStringValue((J.Literal) finalGroup.getValue(), finalGroupIdValue)); - } - if (e == finalArtifact) { - return finalArtifact.withValue(ChangeStringLiteral.withStringValue((J.Literal) finalArtifact.getValue(), finalArtifactIdValue)); - } - if (e == finalVersion) { - return finalVersion.withValue(ChangeStringLiteral.withStringValue((J.Literal) finalVersion.getValue(), finalVersionValue)); - } - return e; - })); - })); + String varName = dep.getVersionVariable(); + if (varName != null && !canUpdateVariable(varName)) { + Object scanResult = acc.versionVariableUpdates.get(varName); + if (scanResult instanceof Exception) { + return ((MavenDownloadingException) scanResult).warn(m); } - } else if (m.getArguments().get(0) instanceof J.Assignment) { - J.Assignment groupAssignment = null; - J.Assignment artifactAssignment = null; - J.Assignment versionAssignment = null; - String groupId = null; - String artifactId = null; - String version = null; - - for (Expression e : depArgs) { - if (!(e instanceof J.Assignment)) { - continue; - } - J.Assignment arg = (J.Assignment) e; - if (!(arg.getVariable() instanceof J.Identifier) || !(arg.getAssignment() instanceof J.Literal)) { - continue; - } - J.Identifier identifier = (J.Identifier) arg.getVariable(); - J.Literal assignment = (J.Literal) arg.getAssignment(); - if (!(assignment.getValue() instanceof String)) { - continue; - } - String valueValue = (String) assignment.getValue(); - switch (identifier.getSimpleName()) { - case "group": - groupAssignment = arg; - groupId = valueValue; - break; - case "name": - artifactAssignment = arg; - artifactId = valueValue; - break; - case "version": - versionAssignment = arg; - version = valueValue; - break; - } - } - if (groupId == null || artifactId == null) { - return m; - } - if (!depMatcher.matches(groupId, artifactId)) { - return m; - } - String updatedGroupId = groupId; - if (!StringUtils.isBlank(newGroupId) && !updatedGroupId.equals(newGroupId)) { - updatedGroupId = newGroupId; - } - String updatedArtifactId = artifactId; - if (!StringUtils.isBlank(newArtifactId) && !updatedArtifactId.equals(newArtifactId)) { - updatedArtifactId = newArtifactId; + if (scanResult instanceof String) { + updated = updated.withDeclaredVersion((String) scanResult); } - String updatedVersion = version; - if (!StringUtils.isBlank(newVersion) && (!StringUtils.isBlank(version) || Boolean.TRUE.equals(overrideManagedVersion))) { + } else if (varName == null) { + String declaredVersion = dep.getDeclaredVersion(); + if (!StringUtils.isBlank(newVersion) && (!StringUtils.isBlank(declaredVersion) || Boolean.TRUE.equals(overrideManagedVersion))) { String resolvedVersion; try { resolvedVersion = new DependencyVersionSelector(metadataFailures, gradleProject, null) - .select(new GroupArtifact(updatedGroupId, updatedArtifactId), m.getSimpleName(), newVersion, versionPattern, ctx); + .select(new GroupArtifact( + !StringUtils.isBlank(newGroupId) ? newGroupId : dep.getGroupId(), + !StringUtils.isBlank(newArtifactId) ? newArtifactId : dep.getArtifactId()), + dep.getConfigurationName(), newVersion, versionPattern, ctx); } catch (MavenDownloadingException e) { return e.warn(m); } - if (resolvedVersion != null && !resolvedVersion.equals(updatedVersion)) { - updatedVersion = resolvedVersion; - } - } - - if (!updatedGroupId.equals(groupId) || !updatedArtifactId.equals(artifactId) || updatedVersion != null && !updatedVersion.equals(version)) { - J.Assignment finalGroup = groupAssignment; - String finalGroupIdValue = updatedGroupId; - J.Assignment finalArtifact = artifactAssignment; - String finalArtifactIdValue = updatedArtifactId; - J.Assignment finalVersion = versionAssignment; - String finalVersionValue = updatedVersion; - m = m.withArguments(ListUtils.map(m.getArguments(), arg -> { - if (arg == finalGroup) { - return finalGroup.withAssignment(ChangeStringLiteral.withStringValue((J.Literal) finalGroup.getAssignment(), finalGroupIdValue)); - } - if (arg == finalArtifact) { - return finalArtifact.withAssignment(ChangeStringLiteral.withStringValue((J.Literal) finalArtifact.getAssignment(), finalArtifactIdValue)); - } - if (arg == finalVersion) { - return finalVersion.withAssignment(ChangeStringLiteral.withStringValue((J.Literal) finalVersion.getAssignment(), finalVersionValue)); - } - return arg; - })); - } - } else if (depArgs.get(0) instanceof K.StringTemplate) { - K.StringTemplate template = (K.StringTemplate) depArgs.get(0); - List strings = template.getStrings(); - if (strings.size() >= 2 && strings.get(0) instanceof J.Literal && - ((J.Literal) strings.get(0)).getValue() != null) { - - J.Literal literal = (J.Literal) strings.get(0); - Dependency original = DependencyNotation.parse((String) requireNonNull(literal.getValue())); - if (original != null) { - Dependency updated = original; - if (!StringUtils.isBlank(newGroupId) && !Objects.equals(updated.getGroupId(), newGroupId)) { - updated = updated.withGav(updated.getGav().withGroupId(newGroupId)); - } - if (!StringUtils.isBlank(newArtifactId) && !updated.getArtifactId().equals(newArtifactId)) { - updated = updated.withGav(updated.getGav().withArtifactId(newArtifactId)); - } - if (!StringUtils.isBlank(newVersion)) { - String resolvedVersion; - try { - resolvedVersion = new DependencyVersionSelector(metadataFailures, gradleProject, null) - .select(new GroupArtifact(updated.getGroupId(), updated.getArtifactId()), m.getSimpleName(), newVersion, versionPattern, ctx); - } catch (MavenDownloadingException e) { - return e.warn(m); - } - if (resolvedVersion != null && !resolvedVersion.equals(updated.getVersion())) { - updated = updated.withGav(updated.getGav().withVersion(resolvedVersion)); - } - } - 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)); - } + if (resolvedVersion != null && !resolvedVersion.equals(declaredVersion)) { + updated = updated.withDeclaredVersion(resolvedVersion); } } } - return m; + return updated.getTree(); } private GradleProject updateGradleModel(GradleProject gp, ExecutionContext ctx) { @@ -694,5 +461,51 @@ private GradleProject updateGradleModel(GradleProject gp, ExecutionContext ctx) return gp; } }); + + DependencyMatcher propsMatcher = requireNonNull(DependencyMatcher.build(oldGroupId + ":" + oldArtifactId).getValue()); + return new TreeVisitor() { + @Override + public @Nullable Tree visit(@Nullable Tree tree, ExecutionContext ctx) { + if (tree instanceof Properties.File) { + Properties.File propsFile = (Properties.File) tree; + if (propsFile.getSourcePath().endsWith(GRADLE_PROPERTIES_FILE_NAME) && !acc.versionVariableUpdates.isEmpty()) { + return new PropertiesVisitor() { + @Override + public Properties visitEntry(Properties.Entry entry, ExecutionContext ctx) { + if (!canUpdateVariable(entry.getKey(), propsMatcher, acc)) { + return entry; + } + Object scanResult = acc.versionVariableUpdates.get(entry.getKey()); + if (scanResult instanceof Exception) { + return Markup.warn(entry, (Exception) scanResult); + } + if (scanResult instanceof String) { + String resolvedVersion = (String) scanResult; + if (!resolvedVersion.equals(entry.getValue().getText())) { + return entry.withValue(entry.getValue().withText(resolvedVersion)); + } + } + return entry; + } + }.visitNonNull(tree, ctx); + } + return tree; + } + return gradleVisitor.visit(tree, ctx); + } + }; + } + + private boolean canUpdateVariable(String varName, DependencyMatcher depMatcher, Accumulator acc) { + Set usages = acc.versionVariableUsages.get(varName); + if (usages == null) { + return true; + } + for (GroupArtifact ga : usages) { + if (!depMatcher.matches(ga.getGroupId(), ga.getArtifactId())) { + return false; + } + } + return true; } } diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/trait/GradleDependency.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/trait/GradleDependency.java index 7e0bafaed84..7c34604dfa7 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/trait/GradleDependency.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/trait/GradleDependency.java @@ -1024,15 +1024,20 @@ public GradleDependency withDeclaredGroupId(String newGroupId) { } else if (firstArg instanceof K.StringTemplate) { K.StringTemplate template = (K.StringTemplate) firstArg; List strings = template.getStrings(); - if (!strings.isEmpty() && strings.get(0) instanceof J.Literal) { + if (strings.size() >= 2 && strings.get(0) instanceof J.Literal) { J.Literal literal = (J.Literal) strings.get(0); - Dependency dep = DependencyNotation.parse((String) literal.getValue()); + String originalValue = (String) literal.getValue(); + Dependency dep = DependencyNotation.parse(originalValue); if (dep != null && !newGroupId.equals(dep.getGroupId())) { Dependency updatedDep = dep.withGav(dep.getGav().withGroupId(newGroupId)); String replacement = DependencyNotation.toStringNotation(updatedDep); + if (originalValue.endsWith(":")) { + replacement = replacement + ":"; + } J.Literal newLiteral = literal.withValue(replacement) - .withValueSource(template.getDelimiter() + replacement + template.getDelimiter()); - updated = m.withArguments(singletonList(newLiteral)); + .withValueSource(replacement); + K.StringTemplate updatedTemplate = template.withStrings(ListUtils.mapFirst(strings, s -> newLiteral)); + updated = m.withArguments(singletonList(updatedTemplate)); } } } @@ -1164,15 +1169,20 @@ public GradleDependency withDeclaredArtifactId(String newArtifactId) { } else if (firstArg instanceof K.StringTemplate) { K.StringTemplate template = (K.StringTemplate) firstArg; List strings = template.getStrings(); - if (!strings.isEmpty() && strings.get(0) instanceof J.Literal) { + if (strings.size() >= 2 && strings.get(0) instanceof J.Literal) { J.Literal literal = (J.Literal) strings.get(0); - Dependency dep = DependencyNotation.parse((String) literal.getValue()); + String originalValue = (String) literal.getValue(); + Dependency dep = DependencyNotation.parse(originalValue); if (dep != null && !newArtifactId.equals(dep.getArtifactId())) { Dependency updatedDep = dep.withGav(dep.getGav().withArtifactId(newArtifactId)); String replacement = DependencyNotation.toStringNotation(updatedDep); + if (originalValue.endsWith(":")) { + replacement = replacement + ":"; + } J.Literal newLiteral = literal.withValue(replacement) - .withValueSource(template.getDelimiter() + replacement + template.getDelimiter()); - updated = m.withArguments(singletonList(newLiteral)); + .withValueSource(replacement); + K.StringTemplate updatedTemplate = template.withStrings(ListUtils.mapFirst(strings, s -> newLiteral)); + updated = m.withArguments(singletonList(updatedTemplate)); } } } @@ -1235,7 +1245,7 @@ public GradleDependency withDeclaredVersion(@Nullable String newVersion) { } } } else if (firstArg instanceof G.GString) { - // For GString, we convert to a simple string literal with the new version + // For GString, collapse to a single-element GString to preserve Groovy command expression formatting G.GString gstring = (G.GString) firstArg; List strings = gstring.getStrings(); if (strings.size() >= 2 && strings.get(0) instanceof J.Literal) { @@ -1245,8 +1255,9 @@ public GradleDependency withDeclaredVersion(@Nullable String newVersion) { Dependency updatedDep = dep.withGav(dep.getGav().withVersion(newVersion)); String replacement = DependencyNotation.toStringNotation(updatedDep); J.Literal newLiteral = literal.withValue(replacement) - .withValueSource(gstring.getDelimiter() + replacement + gstring.getDelimiter()); - updated = m.withArguments(singletonList(newLiteral)); + .withValueSource(replacement); + G.GString collapsed = gstring.withStrings(singletonList(newLiteral)); + updated = m.withArguments(singletonList(collapsed)); } } } else if (firstArg instanceof G.MapEntry || firstArg instanceof G.MapLiteral) { @@ -1335,7 +1346,8 @@ public GradleDependency withDeclaredVersion(@Nullable String newVersion) { Dependency updatedDep = dep.withGav(dep.getGav().withVersion(newVersion)); String replacement = DependencyNotation.toStringNotation(updatedDep); J.Literal newLiteral = literal.withValue(replacement) - .withValueSource(template.getDelimiter() + replacement + template.getDelimiter()); + .withValueSource(template.getDelimiter() + replacement + template.getDelimiter()) + .withPrefix(template.getPrefix()); updated = m.withArguments(singletonList(newLiteral)); } } 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..5cc32d938d5 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 @@ -262,9 +263,99 @@ implementation platform("commons-lang:commons-lang:${version}") mavenCentral() } - def version = '2.6' + def version = '3.11' dependencies { - implementation platform("org.apache.commons:commons-lang3:3.11") + implementation platform("org.apache.commons:commons-lang3:${version}") + } + """ + ) + ); + } + + @Test + void worksWithGStringFromGradleProperties() { + 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 { + id "java-library" + } + + repositories { + mavenCentral() + } + + dependencies { + implementation platform("commons-lang:commons-lang:${commonsLangVersion}") + } + """, + """ + plugins { + id "java-library" + } + + repositories { + mavenCentral() + } + + dependencies { + implementation platform("org.apache.commons:commons-lang3:${commonsLangVersion}") + } + """ + ) + ); + } + + @Test + void kotlinDslStringInterpolationFromGradleProperties() { + 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 { + `java-library` + } + + repositories { + mavenCentral() + } + + val commonsLangVersion: String by project + dependencies { + implementation("commons-lang:commons-lang:${commonsLangVersion}") + } + """, + """ + plugins { + `java-library` + } + + repositories { + mavenCentral() + } + + val commonsLangVersion: String by project + dependencies { + implementation("org.apache.commons:commons-lang3:${commonsLangVersion}") } """ ) @@ -573,8 +664,8 @@ void kotlinDslStringInterpolation() { } dependencies { - val commonsLangVersion = "2.6" - implementation("org.apache.commons:commons-lang3:3.11") + val commonsLangVersion = "3.11" + implementation("org.apache.commons:commons-lang3:${commonsLangVersion}") } """ ) @@ -849,4 +940,125 @@ void noDuplicateJacksonDatabindDependenciesInGradle() { ) ); } + + @Test + void sharedGStringVersionVariableCollapsesToLiteral() { + rewriteRun( + spec -> spec.recipe(new ChangeDependency("commons-lang", "commons-lang", "org.apache.commons", "commons-lang3", "3.11.x", null, null, true)), + buildGradle( + """ + plugins { + id "java-library" + } + + repositories { + mavenCentral() + } + + def version = '2.6' + dependencies { + implementation "commons-lang:commons-lang:${version}" + implementation "com.google.guava:guava:${version}" + } + """, + """ + plugins { + id "java-library" + } + + repositories { + mavenCentral() + } + + def version = '2.6' + dependencies { + implementation "org.apache.commons:commons-lang3:3.11" + implementation "com.google.guava:guava:${version}" + } + """ + ) + ); + } + + @Test + void sharedGradlePropertiesVersionVariableCollapsesToLiteral() { + rewriteRun( + spec -> spec.recipe(new ChangeDependency("commons-lang", "commons-lang", "org.apache.commons", "commons-lang3", "3.11.x", null, null, true)), + properties( + """ + sharedVersion=2.6 + """, + spec -> spec.path("gradle.properties") + ), + buildGradle( + """ + plugins { + id "java-library" + } + + repositories { + mavenCentral() + } + + dependencies { + implementation "commons-lang:commons-lang:${sharedVersion}" + implementation "com.google.guava:guava:${sharedVersion}" + } + """, + """ + plugins { + id "java-library" + } + + repositories { + mavenCentral() + } + + dependencies { + implementation "org.apache.commons:commons-lang3:3.11" + implementation "com.google.guava:guava:${sharedVersion}" + } + """ + ) + ); + } + + @Test + void sharedKotlinDslStringTemplateVersionVariableCollapsesToLiteral() { + rewriteRun( + spec -> spec.recipe(new ChangeDependency("commons-lang", "commons-lang", "org.apache.commons", "commons-lang3", "3.11.x", null, null, true)), + buildGradleKts( + """ + plugins { + `java-library` + } + + repositories { + mavenCentral() + } + + dependencies { + val version = "2.6" + implementation("commons-lang:commons-lang:${version}") + implementation("com.google.guava:guava:${version}") + } + """, + """ + plugins { + `java-library` + } + + repositories { + mavenCentral() + } + + dependencies { + val version = "2.6" + implementation("org.apache.commons:commons-lang3:3.11") + implementation("com.google.guava:guava:${version}") + } + """ + ) + ); + } } diff --git a/rewrite-gradle/src/test/java/org/openrewrite/gradle/trait/GradleDependencyTest.java b/rewrite-gradle/src/test/java/org/openrewrite/gradle/trait/GradleDependencyTest.java index 1a338048ab0..0b80bfaea40 100644 --- a/rewrite-gradle/src/test/java/org/openrewrite/gradle/trait/GradleDependencyTest.java +++ b/rewrite-gradle/src/test/java/org/openrewrite/gradle/trait/GradleDependencyTest.java @@ -382,6 +382,246 @@ void multiComponentLiterals() { ); } + @Test + void withDeclaredGroupIdPreservesGString() { + rewriteRun( + spec -> spec.beforeRecipe(withToolingApi()) + .recipe(RewriteTest.toRecipe(() -> new GradleDependency.Matcher() + .groupId("com.google.guava").artifactId("guava") + .asVisitor(dep -> dep.withDeclaredGroupId("org.example").getTree()))), + buildGradle( + """ + plugins { + id "java" + } + + repositories { + mavenCentral() + } + + dependencies { + def version = "28.2-jre" + implementation "com.google.guava:guava:${version}" + } + """, + """ + plugins { + id "java" + } + + repositories { + mavenCentral() + } + + dependencies { + def version = "28.2-jre" + implementation "org.example:guava:${version}" + } + """ + ) + ); + } + + @Test + void withDeclaredArtifactIdPreservesGString() { + rewriteRun( + spec -> spec.beforeRecipe(withToolingApi()) + .recipe(RewriteTest.toRecipe(() -> new GradleDependency.Matcher() + .groupId("com.google.guava").artifactId("guava") + .asVisitor(dep -> dep.withDeclaredArtifactId("banana").getTree()))), + buildGradle( + """ + plugins { + id "java" + } + + repositories { + mavenCentral() + } + + dependencies { + def version = "28.2-jre" + implementation "com.google.guava:guava:${version}" + } + """, + """ + plugins { + id "java" + } + + repositories { + mavenCentral() + } + + dependencies { + def version = "28.2-jre" + implementation "com.google.guava:banana:${version}" + } + """ + ) + ); + } + + @Test + void withDeclaredVersionCollapsesGString() { + rewriteRun( + spec -> spec.beforeRecipe(withToolingApi()) + .recipe(RewriteTest.toRecipe(() -> new GradleDependency.Matcher() + .groupId("com.google.guava").artifactId("guava") + .asVisitor(dep -> dep.withDeclaredVersion("29.0-jre").getTree()))), + buildGradle( + """ + plugins { + id "java" + } + + repositories { + mavenCentral() + } + + dependencies { + def version = "28.2-jre" + implementation "com.google.guava:guava:${version}" + } + """, + """ + plugins { + id "java" + } + + repositories { + mavenCentral() + } + + dependencies { + def version = "28.2-jre" + implementation "com.google.guava:guava:29.0-jre" + } + """ + ) + ); + } + + @Test + void withDeclaredGroupIdPreservesKotlinStringTemplate() { + rewriteRun( + spec -> spec.beforeRecipe(withToolingApi()) + .recipe(RewriteTest.toRecipe(() -> new GradleDependency.Matcher() + .groupId("com.google.guava").artifactId("guava") + .asVisitor(dep -> dep.withDeclaredGroupId("org.example").getTree()))), + buildGradleKts( + """ + plugins { + `java-library` + } + + repositories { + mavenCentral() + } + + dependencies { + val version = "28.2-jre" + implementation("com.google.guava:guava:${version}") + } + """, + """ + plugins { + `java-library` + } + + repositories { + mavenCentral() + } + + dependencies { + val version = "28.2-jre" + implementation("org.example:guava:${version}") + } + """ + ) + ); + } + + @Test + void withDeclaredArtifactIdPreservesKotlinStringTemplate() { + rewriteRun( + spec -> spec.beforeRecipe(withToolingApi()) + .recipe(RewriteTest.toRecipe(() -> new GradleDependency.Matcher() + .groupId("com.google.guava").artifactId("guava") + .asVisitor(dep -> dep.withDeclaredArtifactId("banana").getTree()))), + buildGradleKts( + """ + plugins { + `java-library` + } + + repositories { + mavenCentral() + } + + dependencies { + val version = "28.2-jre" + implementation("com.google.guava:guava:${version}") + } + """, + """ + plugins { + `java-library` + } + + repositories { + mavenCentral() + } + + dependencies { + val version = "28.2-jre" + implementation("com.google.guava:banana:${version}") + } + """ + ) + ); + } + + @Test + void withDeclaredVersionCollapsesKotlinStringTemplate() { + rewriteRun( + spec -> spec.beforeRecipe(withToolingApi()) + .recipe(RewriteTest.toRecipe(() -> new GradleDependency.Matcher() + .groupId("com.google.guava").artifactId("guava") + .asVisitor(dep -> dep.withDeclaredVersion("29.0-jre").getTree()))), + buildGradleKts( + """ + plugins { + `java-library` + } + + repositories { + mavenCentral() + } + + dependencies { + val version = "28.2-jre" + implementation("com.google.guava:guava:${version}") + } + """, + """ + plugins { + `java-library` + } + + repositories { + mavenCentral() + } + + dependencies { + val version = "28.2-jre" + implementation("com.google.guava:guava:29.0-jre") + } + """ + ) + ); + } + @Test void multiComponentLiteralsTwoArgs() { rewriteRun(