From baeb00d5c1b034f395ec80d9875ba86f1a8ee5c0 Mon Sep 17 00:00:00 2001 From: Sam Snyder Date: Wed, 8 Oct 2025 18:17:08 -0700 Subject: [PATCH 01/19] Moving functionality into GradleDependency trait --- .../openrewrite/gradle/ChangeDependency.java | 2 +- .../gradle/ChangeDependencyArtifactId.java | 2 +- .../gradle/ChangeDependencyClassifier.java | 2 +- .../gradle/ChangeDependencyConfiguration.java | 7 +- .../gradle/ChangeDependencyExtension.java | 2 +- .../gradle/ChangeDependencyGroupId.java | 2 +- .../gradle/DependencyUseMapNotation.java | 2 +- .../gradle/DependencyUseStringNotation.java | 9 +- .../RemoveRedundantDependencyVersions.java | 44 +- .../gradle/UpgradeDependencyVersion.java | 448 +++---------- .../UpgradeTransitiveDependencyVersion.java | 6 +- .../gradle/internal/Dependency.java | 5 + .../DependencyStringNotationConverter.java | 69 +- .../internal/InsertDependencyComparator.java | 1 + .../gradle/trait/GradleDependency.java | 592 +++++++++++++++++- ...SpringDependencyManagementPluginEntry.java | 2 +- 16 files changed, 752 insertions(+), 443 deletions(-) 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 63a42b2a07..5dbd0cb5e7 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,7 @@ import org.jspecify.annotations.Nullable; import org.openrewrite.*; import org.openrewrite.gradle.internal.ChangeStringLiteral; -import org.openrewrite.gradle.internal.Dependency; +import org.openrewrite.maven.tree.Dependency; import org.openrewrite.gradle.internal.DependencyStringNotationConverter; import org.openrewrite.gradle.marker.GradleDependencyConfiguration; import org.openrewrite.gradle.marker.GradleProject; diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependencyArtifactId.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependencyArtifactId.java index f42c338f78..02f346b04b 100755 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependencyArtifactId.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependencyArtifactId.java @@ -20,7 +20,7 @@ import org.jspecify.annotations.Nullable; import org.openrewrite.*; import org.openrewrite.gradle.internal.ChangeStringLiteral; -import org.openrewrite.gradle.internal.Dependency; +import org.openrewrite.maven.tree.Dependency; import org.openrewrite.gradle.internal.DependencyStringNotationConverter; import org.openrewrite.gradle.marker.GradleDependencyConfiguration; import org.openrewrite.gradle.marker.GradleProject; diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependencyClassifier.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependencyClassifier.java index 13b5a0c595..9045f6a48b 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependencyClassifier.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependencyClassifier.java @@ -20,7 +20,7 @@ import org.jspecify.annotations.Nullable; import org.openrewrite.*; import org.openrewrite.gradle.internal.ChangeStringLiteral; -import org.openrewrite.gradle.internal.Dependency; +import org.openrewrite.maven.tree.Dependency; import org.openrewrite.gradle.internal.DependencyStringNotationConverter; import org.openrewrite.gradle.marker.GradleDependencyConfiguration; import org.openrewrite.gradle.marker.GradleProject; diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependencyConfiguration.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependencyConfiguration.java index 363d9cf99c..2a7f0fdc2f 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependencyConfiguration.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependencyConfiguration.java @@ -19,7 +19,8 @@ import lombok.Value; import org.jspecify.annotations.Nullable; import org.openrewrite.*; -import org.openrewrite.gradle.internal.Dependency; +import org.openrewrite.maven.tree.Dependency; +import org.openrewrite.maven.tree.GroupArtifactVersion; import org.openrewrite.gradle.internal.DependencyStringNotationConverter; import org.openrewrite.gradle.trait.GradleDependency; import org.openrewrite.groovy.GroovyIsoVisitor; @@ -206,7 +207,9 @@ public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, Execu Dependency dependency; if ("project".equals(inner.getSimpleName())) { - dependency = new Dependency("", ((String) value.getValue()).substring(1), null, null, null); + dependency = Dependency.builder() + .gav(new GroupArtifactVersion("", ((String) value.getValue()).substring(1), null)) + .build(); } else { dependency = DependencyStringNotationConverter.parse((String) value.getValue()); } diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependencyExtension.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependencyExtension.java index 5490238487..5b8cc04bd4 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependencyExtension.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependencyExtension.java @@ -20,7 +20,7 @@ import org.jspecify.annotations.Nullable; import org.openrewrite.*; import org.openrewrite.gradle.internal.ChangeStringLiteral; -import org.openrewrite.gradle.internal.Dependency; +import org.openrewrite.maven.tree.Dependency; import org.openrewrite.gradle.internal.DependencyStringNotationConverter; import org.openrewrite.gradle.trait.GradleDependency; import org.openrewrite.groovy.GroovyIsoVisitor; diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependencyGroupId.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependencyGroupId.java index 8112ea9b63..eeaf7a62eb 100755 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependencyGroupId.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependencyGroupId.java @@ -20,7 +20,7 @@ import org.jspecify.annotations.Nullable; import org.openrewrite.*; import org.openrewrite.gradle.internal.ChangeStringLiteral; -import org.openrewrite.gradle.internal.Dependency; +import org.openrewrite.maven.tree.Dependency; import org.openrewrite.gradle.internal.DependencyStringNotationConverter; import org.openrewrite.gradle.marker.GradleDependencyConfiguration; import org.openrewrite.gradle.marker.GradleProject; diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/DependencyUseMapNotation.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/DependencyUseMapNotation.java index 94310cbc1e..db281236de 100755 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/DependencyUseMapNotation.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/DependencyUseMapNotation.java @@ -16,7 +16,7 @@ package org.openrewrite.gradle; import org.openrewrite.*; -import org.openrewrite.gradle.internal.Dependency; +import org.openrewrite.maven.tree.Dependency; import org.openrewrite.gradle.internal.DependencyStringNotationConverter; import org.openrewrite.gradle.trait.GradleDependency; import org.openrewrite.groovy.GroovyVisitor; diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/DependencyUseStringNotation.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/DependencyUseStringNotation.java index 1b6adbc49a..888acecb5f 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/DependencyUseStringNotation.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/DependencyUseStringNotation.java @@ -20,7 +20,8 @@ import org.openrewrite.Preconditions; import org.openrewrite.Recipe; import org.openrewrite.TreeVisitor; -import org.openrewrite.gradle.internal.Dependency; +import org.openrewrite.maven.tree.Dependency; +import org.openrewrite.maven.tree.GroupArtifactVersion; import org.openrewrite.gradle.trait.GradleDependency; import org.openrewrite.groovy.tree.G; import org.openrewrite.java.JavaVisitor; @@ -162,7 +163,11 @@ public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) String classifier = coerceToStringNotation(mapNotation.get("classifier")); String extension = coerceToStringNotation(mapNotation.get("ext")); - Dependency dependency = new Dependency(group, name, version, classifier, extension); + Dependency dependency = Dependency.builder() + .gav(new GroupArtifactVersion(group, name, version)) + .classifier(classifier) + .type(extension) + .build(); String stringNotation = dependency.toStringNotation(); return new J.Literal(randomId(), prefix, markers, stringNotation, "\"" + stringNotation + "\"", emptyList(), JavaType.Primitive.String); diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/RemoveRedundantDependencyVersions.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/RemoveRedundantDependencyVersions.java index c3f5bc9a26..b3987989b6 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/RemoveRedundantDependencyVersions.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/RemoveRedundantDependencyVersions.java @@ -19,8 +19,7 @@ import lombok.Value; import org.jspecify.annotations.Nullable; import org.openrewrite.*; -import org.openrewrite.gradle.internal.ChangeStringLiteral; -import org.openrewrite.gradle.internal.Dependency; +import org.openrewrite.maven.tree.Dependency; import org.openrewrite.gradle.internal.DependencyStringNotationConverter; import org.openrewrite.gradle.marker.GradleDependencyConfiguration; import org.openrewrite.gradle.marker.GradleDependencyConstraint; @@ -29,7 +28,6 @@ import org.openrewrite.gradle.trait.GradleDependency; import org.openrewrite.gradle.trait.SpringDependencyManagementPluginEntry; import org.openrewrite.groovy.tree.G; -import org.openrewrite.internal.ListUtils; import org.openrewrite.internal.StringUtils; import org.openrewrite.java.JavaIsoVisitor; import org.openrewrite.java.JavaVisitor; @@ -453,7 +451,7 @@ public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, Execu if (springPluginManagedDependencies.containsKey(dep.getGav().asGroupArtifact())) { if (matchesComparator(springPluginManagedDependencies.get(dep.getGav().asGroupArtifact()), dep.getVersion())) { - return maybeRemoveVersion(m); + return gradleDependency.removeVersion().getTree(); } } @@ -461,7 +459,7 @@ public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, Execu for (ResolvedPom platform : platforms.get(m.getSimpleName())) { String managedVersion = platform.getManagedVersion(dep.getGroupId(), dep.getArtifactId(), null, dep.getRequested().getClassifier()); if (matchesComparator(managedVersion, dep.getVersion())) { - return maybeRemoveVersion(m); + return gradleDependency.removeVersion().getTree(); } } } @@ -472,7 +470,7 @@ public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, Execu for (ResolvedPom platform : platforms.get(configuration.getName())) { String managedVersion = platform.getManagedVersion(dep.getGroupId(), dep.getArtifactId(), null, dep.getRequested().getClassifier()); if (matchesComparator(managedVersion, dep.getVersion())) { - return maybeRemoveVersion(m); + return gradleDependency.removeVersion().getTree(); } } } @@ -482,40 +480,6 @@ public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, Execu return m; } - private J.MethodInvocation maybeRemoveVersion(J.MethodInvocation m) { - if (m.getArguments().get(0) instanceof J.Literal) { - J.Literal l = (J.Literal) m.getArguments().get(0); - if (l.getType() == JavaType.Primitive.String) { - Dependency dep = DependencyStringNotationConverter.parse((String) l.getValue()); - if (dep == null || dep.getClassifier() != null || dep.getExt() != null) { - return m; - } - return m.withArguments(ListUtils.mapFirst(m.getArguments(), arg -> - ChangeStringLiteral.withStringValue(l, dep.withVersion(null).toStringNotation())) - ); - } - } else if (m.getArguments().get(0) instanceof G.MapLiteral) { - return m.withArguments(ListUtils.mapFirst(m.getArguments(), arg -> { - G.MapLiteral mapLiteral = (G.MapLiteral) arg; - return mapLiteral.withElements(ListUtils.map(mapLiteral.getElements(), entry -> { - if (entry.getKey() instanceof J.Literal && "version".equals(((J.Literal) entry.getKey()).getValue())) { - return null; - } - return entry; - })); - })); - } else if (m.getArguments().get(0) instanceof G.MapEntry) { - return m.withArguments(ListUtils.map(m.getArguments(), arg -> { - G.MapEntry entry = (G.MapEntry) arg; - if (entry.getKey() instanceof J.Literal && "version".equals(((J.Literal) entry.getKey()).getValue())) { - return null; - } - return entry; - })); - } - return m; - } - private @Nullable String getSpringBootVersionFromConfiguration(String configuration) { GradleDependencyConfiguration testRuntimeConfiguration = gp.getConfiguration(configuration); if (testRuntimeConfiguration != null) { diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/UpgradeDependencyVersion.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/UpgradeDependencyVersion.java index 05903b7aba..e314ce23a9 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/UpgradeDependencyVersion.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/UpgradeDependencyVersion.java @@ -22,7 +22,7 @@ import org.jspecify.annotations.Nullable; import org.openrewrite.*; import org.openrewrite.gradle.internal.ChangeStringLiteral; -import org.openrewrite.gradle.internal.Dependency; +import org.openrewrite.maven.tree.Dependency; import org.openrewrite.gradle.internal.DependencyStringNotationConverter; import org.openrewrite.gradle.marker.GradleProject; import org.openrewrite.gradle.trait.GradleDependency; @@ -73,10 +73,10 @@ public class UpgradeDependencyVersion extends ScanningRecipe getScanner(DependencyVersionState acc) { @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")); + (sourceFile instanceof K.CompilationUnit && sourceFile.getSourcePath().toString().endsWith(".gradle.kts")); } @Override @@ -167,261 +167,57 @@ public boolean isAcceptable(SourceFile sourceFile, ExecutionContext ctx) { @Override public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { J.MethodInvocation m = (J.MethodInvocation) super.visitMethodInvocation(method, ctx); - GradleDependency.Matcher gradleDependencyMatcher = new GradleDependency.Matcher(); - - if (gradleDependencyMatcher.get(getCursor()).isPresent()) { - 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 J.Assignment || depArgs.get(0) instanceof K.StringTemplate) { - gatherVariables(m); - } else if (depArgs.get(0) instanceof J.MethodInvocation && - ("platform".equals(((J.MethodInvocation) depArgs.get(0)).getSimpleName()) || - "enforcedPlatform".equals(((J.MethodInvocation) depArgs.get(0)).getSimpleName()))) { - gatherVariables((J.MethodInvocation) depArgs.get(0)); - } + GradleDependency gradleDependency = new GradleDependency.Matcher().get(getCursor()).orElse(null); + if (gradleDependency == null) { + return m; + } - if (m.getArguments().get(0) instanceof G.MapEntry) { - String declaredGroupId = null; - String declaredArtifactId = null; - String declaredVersion = null; + // Gather variables for dependencies that use variables + gatherVariables(gradleDependency); - for (Expression e : m.getArguments()) { - if (!(e instanceof G.MapEntry)) { - continue; - } - G.MapEntry arg = (G.MapEntry) e; - if (!(arg.getKey() instanceof J.Literal)) { - continue; - } - J.Literal key = (J.Literal) arg.getKey(); - String valueValue = null; - if (arg.getValue() instanceof J.Literal) { - J.Literal value = (J.Literal) arg.getValue(); - if (value.getValue() instanceof String) { - valueValue = (String) value.getValue(); - } - } else if (arg.getValue() instanceof J.Identifier) { - J.Identifier value = (J.Identifier) arg.getValue(); - valueValue = value.getSimpleName(); - } else if (arg.getValue() instanceof J.FieldAccess) { - valueValue = arg.getValue().printTrimmed(getCursor()); - } else if (arg.getValue() instanceof J.MethodInvocation) { - J.MethodInvocation methodInvocation = (J.MethodInvocation) arg.getValue(); - valueValue = extractPropertyNameFromMethodInvocation(methodInvocation); - } else if (arg.getValue() instanceof G.Binary) { - valueValue = extractPropertyNameFromGBinary((G.Binary) arg.getValue()); - } else if (arg.getValue() instanceof G.GString) { - G.GString value = (G.GString) arg.getValue(); - List strings = value.getStrings(); - if (!strings.isEmpty() && strings.get(0) instanceof G.GString.Value) { - G.GString.Value versionGStringValue = (G.GString.Value) strings.get(0); - if (versionGStringValue.getTree() instanceof J.Identifier) { - valueValue = ((J.Identifier) versionGStringValue.getTree()).getSimpleName(); - } else if (versionGStringValue.getTree() instanceof J.FieldAccess) { - valueValue = versionGStringValue.getTree().printTrimmed(getCursor()); - } else if (versionGStringValue.getTree() instanceof J.MethodInvocation) { - valueValue = extractPropertyNameFromMethodInvocation((J.MethodInvocation) versionGStringValue.getTree()); - } else if (versionGStringValue.getTree() instanceof G.Binary) { - valueValue = extractPropertyNameFromGBinary((G.Binary) versionGStringValue.getTree()); - } - } - } - if (!(key.getValue() instanceof String)) { - continue; - } - String keyValue = (String) key.getValue(); - switch (keyValue) { - case "group": - declaredGroupId = valueValue; - break; - case "name": - declaredArtifactId = valueValue; - break; - case "version": - declaredVersion = valueValue; - break; - } - } - if (declaredGroupId == null || declaredArtifactId == null || declaredVersion == null) { - return m; - } + // Extract declared coordinates using the trait + String declaredGroupId = gradleDependency.getDeclaredGroupId(); + String declaredArtifactId = gradleDependency.getDeclaredArtifactId(); + String declaredVersion = gradleDependency.getDeclaredVersion(); - String versionVariableName = declaredVersion; - GroupArtifact ga = new GroupArtifact(declaredGroupId, declaredArtifactId); - if (gradleProject != null) { - acc.getConfigurationPerGAPerModule().computeIfAbsent(getGradleProjectKey(gradleProject), k -> new HashMap<>()) - .computeIfAbsent(ga, k -> new HashSet<>()) - .add(m.getSimpleName()); - } - if (acc.gaToNewVersion.containsKey(ga) || !shouldResolveVersion(declaredGroupId, declaredArtifactId)) { - return m; - } - try { - String resolvedVersion = new DependencyVersionSelector(metadataFailures, gradleProject, null) - .select(new GroupArtifact(declaredGroupId, declaredArtifactId), m.getSimpleName(), newVersion, versionPattern, ctx); - acc.versionPropNameToGA - .computeIfAbsent(versionVariableName, k -> new HashMap<>()) - .computeIfAbsent(ga, k -> new HashSet<>()) - .add(m.getSimpleName()); - // It is fine for this value to be null, record it in the map to avoid future lookups - acc.gaToNewVersion.put(ga, resolvedVersion); - } catch (MavenDownloadingException e) { - acc.gaToNewVersion.put(ga, e); - return m; - } - } else if (m.getArguments().get(0) instanceof J.Assignment) { - String declaredGroupId = null; - String declaredArtifactId = null; - String declaredVersion = null; - - for (Expression e : m.getArguments()) { - if (!(e instanceof J.Assignment)) { - continue; - } - J.Assignment assignment = (J.Assignment) e; - if (!(assignment.getVariable() instanceof J.Identifier)) { - continue; - } - J.Identifier variable = (J.Identifier) assignment.getVariable(); - String valueValue = null; - if (assignment.getAssignment() instanceof J.Literal) { - J.Literal value = (J.Literal) assignment.getAssignment(); - if (value.getValue() instanceof String) { - valueValue = (String) value.getValue(); - } - } else if (assignment.getAssignment() instanceof J.Identifier) { - J.Identifier value = (J.Identifier) assignment.getAssignment(); - valueValue = value.getSimpleName(); - } else if (assignment.getAssignment() instanceof K.StringTemplate) { - K.StringTemplate value = (K.StringTemplate) assignment.getAssignment(); - List strings = value.getStrings(); - if (!strings.isEmpty() && strings.get(0) instanceof K.StringTemplate.Expression) { - K.StringTemplate.Expression versionTemplateValue = (K.StringTemplate.Expression) strings.get(0); - if (versionTemplateValue.getTree() instanceof J.Identifier) { - valueValue = ((J.Identifier) versionTemplateValue.getTree()).getSimpleName(); - } - } - } - switch (variable.getSimpleName()) { - case "group": - declaredGroupId = valueValue; - break; - case "name": - declaredArtifactId = valueValue; - break; - case "version": - declaredVersion = valueValue; - break; - } - } - if (declaredGroupId == null || declaredArtifactId == null || declaredVersion == null) { - return m; - } + if (declaredGroupId == null || declaredArtifactId == null || declaredVersion == null) { + return m; + } - String versionVariableName = declaredVersion; - GroupArtifact ga = new GroupArtifact(declaredGroupId, declaredArtifactId); - if (gradleProject != null) { - acc.getConfigurationPerGAPerModule().computeIfAbsent(getGradleProjectKey(gradleProject), k -> new HashMap<>()) - .computeIfAbsent(ga, k -> new HashSet<>()) - .add(m.getSimpleName()); - } - if (acc.gaToNewVersion.containsKey(ga) || !shouldResolveVersion(declaredGroupId, declaredArtifactId)) { - return m; - } - try { - String resolvedVersion = new DependencyVersionSelector(metadataFailures, gradleProject, null) - .select(new GroupArtifact(declaredGroupId, declaredArtifactId), m.getSimpleName(), newVersion, versionPattern, ctx); + // Record the dependency and resolve its version if needed + GroupArtifact ga = new GroupArtifact(declaredGroupId, declaredArtifactId); + if (gradleProject != null) { + acc.getConfigurationPerGAPerModule() + .computeIfAbsent(getGradleProjectKey(gradleProject), k -> new HashMap<>()) + .computeIfAbsent(ga, k -> new HashSet<>()) + .add(m.getSimpleName()); + } + + if (!acc.gaToNewVersion.containsKey(ga) && shouldResolveVersion(declaredGroupId, declaredArtifactId)) { + try { + String resolvedVersion = new DependencyVersionSelector(metadataFailures, gradleProject, null) + .select(ga, m.getSimpleName(), newVersion, versionPattern, ctx); + + // If this uses a version variable, record that mapping + // Check if this is a variable (not a literal version like "1.0" or "1.0-SNAPSHOT") + String versionVar = gradleDependency.getVersionVariable(); + if (versionVar != null || (declaredVersion != null && !declaredVersion.matches("^[\\d\\.].*"))) { + String versionPropName = versionVar != null ? versionVar : declaredVersion; acc.versionPropNameToGA - .computeIfAbsent(versionVariableName, k -> new HashMap<>()) - .computeIfAbsent(ga, k -> new HashSet<>()) - .add(m.getSimpleName()); - // It is fine for this value to be null, record it in the map to avoid future lookups - acc.gaToNewVersion.put(ga, resolvedVersion); - } catch (MavenDownloadingException e) { - acc.gaToNewVersion.put(ga, e); - return m; - } - } else { - for (Expression depArg : m.getArguments()) { - Dependency dep = null; - String versionVariableName = null; - if (depArg instanceof J.Literal && ((J.Literal) depArg).getValue() instanceof String) { - String gav = (String) ((J.Literal) depArg).getValue(); - dep = DependencyStringNotationConverter.parse(gav); - if (dep != null) { - versionVariableName = dep.getVersion(); - } - } else if (depArg instanceof G.GString) { - G.GString gString = (G.GString) depArg; - List strings = gString.getStrings(); - if (strings.size() != 2 || !(strings.get(0) instanceof J.Literal) || !(strings.get(1) instanceof G.GString.Value)) { - continue; - } - J.Literal groupArtifact = (J.Literal) strings.get(0); - G.GString.Value versionValue = (G.GString.Value) strings.get(1); - if (!(groupArtifact.getValue() instanceof String)) { - continue; - } - dep = DependencyStringNotationConverter.parse((String) groupArtifact.getValue()); - if (dep == null) { - continue; - } - if (versionValue.getTree() instanceof J.FieldAccess) { - J.FieldAccess f = (J.FieldAccess) versionValue.getTree(); - versionVariableName = f.toString(); - } else if (versionValue.getTree() instanceof J.Identifier) { - versionVariableName = ((J.Identifier) versionValue.getTree()).getSimpleName(); - } else if (versionValue.getTree() instanceof J.MethodInvocation) { - versionVariableName = extractPropertyNameFromMethodInvocation((J.MethodInvocation) versionValue.getTree()); - } else if (versionValue.getTree() instanceof G.Binary) { - versionVariableName = extractPropertyNameFromGBinary((G.Binary) versionValue.getTree()); - } - } else if (depArg instanceof K.StringTemplate) { - K.StringTemplate template = (K.StringTemplate) depArg; - List strings = template.getStrings(); - if (strings.size() != 2 || !(strings.get(0) instanceof J.Literal) || !(strings.get(1) instanceof K.StringTemplate.Expression)) { - continue; - } - J.Literal groupArtifact = (J.Literal) strings.get(0); - K.StringTemplate.Expression versionValue = (K.StringTemplate.Expression) strings.get(1); - if (!(versionValue.getTree() instanceof J.Identifier) || !(groupArtifact.getValue() instanceof String)) { - continue; - } - dep = DependencyStringNotationConverter.parse((String) groupArtifact.getValue()); - versionVariableName = ((J.Identifier) versionValue.getTree()).getSimpleName(); - } else if (depArg instanceof J.Binary && ((J.Binary) depArg).getLeft() instanceof J.Literal && ((J.Binary) depArg).getRight() instanceof J.Identifier) { - dep = DependencyStringNotationConverter.parse((String) ((J.Literal) ((J.Binary) depArg).getLeft()).getValue()); - versionVariableName = ((J.Identifier) ((J.Binary) depArg).getRight()).getSimpleName(); - } - if (dep == null || versionVariableName == null) { - continue; - } - GroupArtifact ga = new GroupArtifact(dep.getGroupId(), dep.getArtifactId()); - if (gradleProject != null) { - acc.getConfigurationPerGAPerModule().computeIfAbsent(getGradleProjectKey(gradleProject), k -> new HashMap<>()) - .computeIfAbsent(ga, k -> new HashSet<>()) - .add(m.getSimpleName()); - } - if (acc.gaToNewVersion.containsKey(ga) || !shouldResolveVersion(dep.getGroupId(), dep.getArtifactId())) { - continue; - } - try { - String resolvedVersion = new DependencyVersionSelector(metadataFailures, gradleProject, null) - .select(new GroupArtifact(dep.getGroupId(), dep.getArtifactId()), m.getSimpleName(), newVersion, versionPattern, ctx); - acc.versionPropNameToGA - .computeIfAbsent(versionVariableName, k -> new HashMap<>()) - .computeIfAbsent(ga, k -> new HashSet<>()) - .add(m.getSimpleName()); - // It is fine for this value to be null, record it in the map to avoid future lookups - acc.gaToNewVersion.put(ga, resolvedVersion); - } catch (MavenDownloadingException e) { - acc.gaToNewVersion.put(ga, e); - } + .computeIfAbsent(versionPropName, k -> new HashMap<>()) + .computeIfAbsent(ga, k -> new HashSet<>()) + .add(m.getSimpleName()); } + + // Record the resolved version (may be null) + acc.gaToNewVersion.put(ga, resolvedVersion); + } catch (MavenDownloadingException e) { + acc.gaToNewVersion.put(ga, e); } } + return m; } - // Some recipes make use of UpgradeDependencyVersion as an implementation detail. // Those other recipes might not know up-front which dependency needs upgrading // So they use the UpgradeDependencyVersion recipe with null groupId and artifactId to pre-populate all data they could possibly need @@ -429,92 +225,26 @@ public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) private boolean shouldResolveVersion(String declaredGroupId, String declaredArtifactId) { //noinspection ConstantValue return (groupId == null || artifactId == null) || - new DependencyMatcher(groupId, artifactId, null).matches(declaredGroupId, declaredArtifactId); + new DependencyMatcher(groupId, artifactId, null).matches(declaredGroupId, declaredArtifactId); } /** - * Extract property name from method invocation patterns like {@code property('guavaVersion')} - * or {@code findProperty('guavaVersion')}. + * Gathers version variable names for dependencies using the GradleDependency trait. */ - private @Nullable String extractPropertyNameFromMethodInvocation(J.MethodInvocation mi) { - if (PROPERTY_METHOD.matches(mi, true) || FIND_PROPERTY_METHOD.matches(mi, true)) { - if (!mi.getArguments().isEmpty() && mi.getArguments().get(0) instanceof J.Literal) { - J.Literal literal = (J.Literal) mi.getArguments().get(0); - if (literal.getValue() instanceof String) { - return (String) literal.getValue(); - } - } + private void gatherVariables(GradleDependency gradleDependency) { + String versionVariableName = gradleDependency.getVersionVariable(); + if (versionVariableName == null) { + return; } - return null; - } - /** - * Handle Groovy binary access like {@code project.properties['guavaVersion']} - * where the binary operator is {@code Access} (bracket notation). - */ - private @Nullable String extractPropertyNameFromGBinary(G.Binary binary) { - if (binary.getOperator() == G.Binary.Type.Access && binary.getRight() instanceof J.Literal) { - J.Literal right = (J.Literal) binary.getRight(); - if (right.getValue() instanceof String) { - return (String) right.getValue(); - } - } - return null; - } + String groupId = gradleDependency.getGroupId(); + String artifactId = gradleDependency.getArtifactId(); - private void gatherVariables(J.MethodInvocation method) { - List depArgs = method.getArguments(); - if (depArgs.size() == 1) { - Expression arg = depArgs.get(0); - J.Literal groupArtifact = null; - Object versionVariable = null; - if (arg instanceof G.GString) { - List strings = ((G.GString) arg).getStrings(); - if (strings.size() == 2 && strings.get(0) instanceof J.Literal && strings.get(1) instanceof G.GString.Value) { - groupArtifact = (J.Literal) strings.get(0); - versionVariable = ((G.GString.Value) strings.get(1)).getTree(); - } - } else if (arg instanceof K.StringTemplate) { - List strings = ((K.StringTemplate) arg).getStrings(); - if (strings.size() == 2 && strings.get(0) instanceof J.Literal && strings.get(1) instanceof K.StringTemplate.Expression) { - groupArtifact = (J.Literal) strings.get(0); - versionVariable = ((K.StringTemplate.Expression) strings.get(1)).getTree(); - } - } - if (groupArtifact != null && groupArtifact.getValue() instanceof String && versionVariable instanceof J.Identifier) { - Dependency dep = DependencyStringNotationConverter.parse((String) groupArtifact.getValue()); - if (dep != null && shouldResolveVersion(dep.getGroupId(), dep.getArtifactId())) { - acc.variableNames.computeIfAbsent(((J.Identifier) versionVariable).getSimpleName(), it -> new HashMap<>()) - .computeIfAbsent(new GroupArtifact(dep.getGroupId(), dep.getArtifactId()), it -> new HashSet<>()) - .add(method.getSimpleName()); - } - } - } else if (depArgs.size() >= 3) { - Expression groupValue = null; - Expression artifactValue = null; - Expression versionExp = null; - if (depArgs.get(0) instanceof G.MapEntry && depArgs.get(1) instanceof G.MapEntry && depArgs.get(2) instanceof G.MapEntry) { - groupValue = ((G.MapEntry) depArgs.get(0)).getValue(); - artifactValue = ((G.MapEntry) depArgs.get(1)).getValue(); - versionExp = ((G.MapEntry) depArgs.get(2)).getValue(); - - } else if (depArgs.get(0) instanceof J.Assignment && depArgs.get(1) instanceof J.Assignment && depArgs.get(2) instanceof J.Assignment) { - groupValue = ((J.Assignment) depArgs.get(0)).getAssignment(); - artifactValue = ((J.Assignment) depArgs.get(1)).getAssignment(); - versionExp = ((J.Assignment) depArgs.get(2)).getAssignment(); - } - if (groupValue instanceof J.Literal && artifactValue instanceof J.Literal && (versionExp instanceof J.Identifier || versionExp instanceof J.FieldAccess)) { - J.Literal groupLiteral = (J.Literal) groupValue; - J.Literal artifactLiteral = (J.Literal) artifactValue; - if (groupLiteral.getValue() instanceof String && artifactLiteral.getValue() instanceof String && shouldResolveVersion((String) groupLiteral.getValue(), (String) artifactLiteral.getValue())) { - String versionVariableName = versionExp instanceof J.Identifier ? - ((J.Identifier) versionExp).getSimpleName() : - (versionExp).printTrimmed(getCursor()); - acc.variableNames.computeIfAbsent(versionVariableName, it -> new HashMap<>()) - .computeIfAbsent(new GroupArtifact((String) groupLiteral.getValue(), (String) artifactLiteral.getValue()), it -> new HashSet<>()) - .add(method.getSimpleName()); - } - } + if (shouldResolveVersion(groupId, artifactId)) { + J.MethodInvocation method = gradleDependency.getTree(); + acc.variableNames.computeIfAbsent(versionVariableName, it -> new HashMap<>()) + .computeIfAbsent(new GroupArtifact(groupId, artifactId), it -> new HashSet<>()) + .add(method.getSimpleName()); } } }; @@ -554,11 +284,9 @@ public boolean isAcceptable(SourceFile sf, ExecutionContext ctx) { if (!matcher.matches(groupArtifact.getGroupId(), groupArtifact.getArtifactId())) { continue; } - String selectedVersion = versionSelector.select(groupArtifact, null, newVersion, versionPattern, ctx); GroupArtifactVersion gav = new GroupArtifactVersion(groupArtifact.getGroupId(), groupArtifact.getArtifactId(), selectedVersion); upgrades.add(gav); - } } else { @@ -636,7 +364,7 @@ private class UpdateGradle extends JavaVisitor { @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")); + (sourceFile instanceof K.CompilationUnit && sourceFile.getSourcePath().toString().endsWith(".gradle.kts")); } @Override @@ -668,14 +396,14 @@ public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) GradleDependency.Matcher gradleDependencyMatcher = new GradleDependency.Matcher(); if (gradleDependencyMatcher.get(getCursor()).isPresent()) { - 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 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))); - } + GradleDependency gradleDependency = gradleDependencyMatcher.get(getCursor()).get(); + + // Get the tree to update (handles platform wrappers automatically) + J.MethodInvocation treeToUpdate = gradleDependency.getTreeToUpdate(); + J.MethodInvocation updatedTree = updateDependency(treeToUpdate, ctx); + + // Wrap the updated tree if necessary (handles platform wrappers automatically) + m = gradleDependency.wrapUpdatedTree(updatedTree); } else if ("ext".equals(method.getSimpleName()) && getCursor().firstEnclosingOrThrow(SourceFile.class).getSourcePath().endsWith("settings.gradle")) { // rare case that gradle versions are set via settings.gradle ext block (only possible for Groovy DSL) m = (J.MethodInvocation) new JavaIsoVisitor() { @@ -724,13 +452,13 @@ public J.Assignment visitAssignment(J.Assignment assignment, ExecutionContext ex return m.withArguments(ListUtils.map(m.getArguments(), arg -> (Expression) new JavaIsoVisitor() { @Override public J.Assignment visitAssignment(J.Assignment assignment, ExecutionContext executionContext) { - if(assignment.getVariable() instanceof J.Identifier && acc.versionPropNameToGA.containsKey(((J.Identifier) assignment.getVariable()).getSimpleName())) { + if (assignment.getVariable() instanceof J.Identifier && acc.versionPropNameToGA.containsKey(((J.Identifier) assignment.getVariable()).getSimpleName())) { acc.variableNames .computeIfAbsent(((J.Identifier) assignment.getVariable()).getSimpleName(), it -> acc.versionPropNameToGA.get(((J.Identifier) assignment.getVariable()).getSimpleName())); } return super.visitAssignment(assignment, executionContext); } - }.visit(arg, ctx))); + }.visitNonNull(arg, ctx))); } return m; } @@ -794,8 +522,8 @@ private J.MethodInvocation updateDependency(J.MethodInvocation method, Execution } Dependency dep = DependencyStringNotationConverter.parse(gav); if (dep != null && dependencyMatcher.matches(dep.getGroupId(), dep.getArtifactId()) && - dep.getVersion() != null && - !dep.getVersion().startsWith("$")) { + dep.getVersion() != null && + !dep.getVersion().startsWith("$")) { Object scanResult = acc.gaToNewVersion.get(new GroupArtifact(dep.getGroupId(), dep.getArtifactId())); if (scanResult instanceof Exception) { getCursor().putMessage(UPDATE_VERSION_ERROR_KEY, scanResult); @@ -810,7 +538,7 @@ private J.MethodInvocation updateDependency(J.MethodInvocation method, Execution } String newGav = dep - .withVersion(selectedVersion) + .withGav(dep.getGav().withVersion(selectedVersion)) .toStringNotation(); return literal .withValue(newGav) @@ -829,8 +557,8 @@ private J.MethodInvocation updateDependency(J.MethodInvocation method, Execution List depArgs = m.getArguments(); if (depArgs.size() >= 3) { if (depArgs.get(0) instanceof G.MapEntry && - depArgs.get(1) instanceof G.MapEntry && - depArgs.get(2) instanceof G.MapEntry) { + depArgs.get(1) instanceof G.MapEntry && + depArgs.get(2) instanceof G.MapEntry) { Expression groupValue = ((G.MapEntry) depArgs.get(0)).getValue(); Expression artifactValue = ((G.MapEntry) depArgs.get(1)).getValue(); if (!(groupValue instanceof J.Literal) || !(artifactValue instanceof J.Literal)) { @@ -896,8 +624,8 @@ private J.MethodInvocation updateDependency(J.MethodInvocation method, Execution replaceVariableValue(versionVariableName, m, (String) groupLiteral.getValue(), (String) artifactLiteral.getValue()); } } else if (depArgs.get(0) instanceof J.Assignment && - depArgs.get(1) instanceof J.Assignment && - depArgs.get(2) instanceof J.Assignment) { + depArgs.get(1) instanceof J.Assignment && + depArgs.get(2) instanceof J.Assignment) { Expression groupValue = ((J.Assignment) depArgs.get(0)).getAssignment(); Expression artifactValue = ((J.Assignment) depArgs.get(1)).getAssignment(); if (!(groupValue instanceof J.Literal) || !(artifactValue instanceof J.Literal)) { @@ -986,8 +714,8 @@ private class UpdateVariable extends JavaIsoVisitor { public J.VariableDeclarations.NamedVariable visitVariable(J.VariableDeclarations.NamedVariable variable, ExecutionContext ctx) { J.VariableDeclarations.NamedVariable v = super.visitVariable(variable, ctx); if (!(v.getInitializer() instanceof J.Literal) || - ((J.Literal) v.getInitializer()).getValue() == null || - ((J.Literal) v.getInitializer()).getType() != JavaType.Primitive.String) { + ((J.Literal) v.getInitializer()).getValue() == null || + ((J.Literal) v.getInitializer()).getType() != JavaType.Primitive.String) { return v; } Map.Entry> gaWithConfigs = getGroupArtifactWithConfigs((v.getSimpleName())); @@ -1007,9 +735,9 @@ public J.VariableDeclarations.NamedVariable visitVariable(J.VariableDeclarations public J.Assignment visitAssignment(J.Assignment assignment, ExecutionContext ctx) { J.Assignment a = super.visitAssignment(assignment, ctx); if (!(a.getVariable() instanceof J.Identifier) || - !(a.getAssignment() instanceof J.Literal) || - ((J.Literal) a.getAssignment()).getValue() == null || - ((J.Literal) a.getAssignment()).getType() != JavaType.Primitive.String) { + !(a.getAssignment() instanceof J.Literal) || + ((J.Literal) a.getAssignment()).getValue() == null || + ((J.Literal) a.getAssignment()).getType() != JavaType.Primitive.String) { return a; } Map.Entry> gaWithConfigs = getGroupArtifactWithConfigs(((J.Identifier) a.getVariable()).getSimpleName()); diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/UpgradeTransitiveDependencyVersion.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/UpgradeTransitiveDependencyVersion.java index 84d853eb85..b8dcff66ab 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/UpgradeTransitiveDependencyVersion.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/UpgradeTransitiveDependencyVersion.java @@ -357,7 +357,7 @@ public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) if (depArg instanceof G.GString) { List strings = ((G.GString) depArg).getStrings(); if (strings.size() == 2 && strings.get(0) instanceof J.Literal && (((J.Literal) strings.get(0)).getValue() instanceof String) && strings.get(1) instanceof G.GString.Value) { - org.openrewrite.gradle.internal.Dependency dep = DependencyStringNotationConverter.parse((String) ((J.Literal) strings.get(0)).getValue()); + Dependency dep = DependencyStringNotationConverter.parse((String) ((J.Literal) strings.get(0)).getValue()); if (dep != null) { G.GString.Value versionValue = (G.GString.Value) strings.get(1); acc.versionPropNameToGA.put(versionValue.getTree().toString(), new GroupArtifact(dep.getGroupId(), dep.getArtifactId())); @@ -366,7 +366,7 @@ public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) } else if (depArg instanceof K.StringTemplate) { List strings = ((K.StringTemplate) depArg).getStrings(); if (strings.size() == 2 && strings.get(0) instanceof J.Literal && (((J.Literal) strings.get(0)).getValue() instanceof String) && strings.get(1) instanceof K.StringTemplate.Expression) { - org.openrewrite.gradle.internal.Dependency dep = DependencyStringNotationConverter.parse((String) ((J.Literal) strings.get(0)).getValue()); + Dependency dep = DependencyStringNotationConverter.parse((String) ((J.Literal) strings.get(0)).getValue()); if (dep != null) { K.StringTemplate.Expression versionValue = (K.StringTemplate.Expression) strings.get(1); acc.versionPropNameToGA.put(versionValue.getTree().toString(), new GroupArtifact(dep.getGroupId(), dep.getArtifactId())); @@ -725,7 +725,7 @@ public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, Execu if (m2.getSimpleName().equals(config)) { existingConstraint = m2; if (m2.getArguments().get(0) instanceof J.Literal) { - org.openrewrite.gradle.internal.Dependency notation = DependencyStringNotationConverter.parse((String) requireNonNull(((J.Literal) m2.getArguments().get(0)).getValue())); + Dependency notation = DependencyStringNotationConverter.parse((String) requireNonNull(((J.Literal) m2.getArguments().get(0)).getValue())); if (notation == null) { continue; } diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/internal/Dependency.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/internal/Dependency.java index 5dc449944a..8ed800e181 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/internal/Dependency.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/internal/Dependency.java @@ -21,6 +21,11 @@ import org.jspecify.annotations.Nullable; import org.openrewrite.maven.tree.GroupArtifactVersion; +/** + * @deprecated Use {@link org.openrewrite.maven.tree.Dependency} instead. + * This class is retained for compatibility but will be removed in a future release. + */ +@Deprecated @Value @With @EqualsAndHashCode diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/internal/DependencyStringNotationConverter.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/internal/DependencyStringNotationConverter.java index dbb9cb86a1..a1a81ce9ba 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/internal/DependencyStringNotationConverter.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/internal/DependencyStringNotationConverter.java @@ -16,6 +16,8 @@ package org.openrewrite.gradle.internal; import org.jspecify.annotations.Nullable; +import org.openrewrite.maven.tree.Dependency; +import org.openrewrite.maven.tree.GroupArtifactVersion; public class DependencyStringNotationConverter { @@ -23,7 +25,10 @@ public class DependencyStringNotationConverter { * @param notation a String in the format group:artifact:version * @return A corresponding Dependency or null if the notation could not be parsed */ - public static @Nullable Dependency parse(String notation) { + public static @Nullable Dependency parse(@Nullable String notation) { + if (notation == null) { + return null; + } int idx = notation.lastIndexOf('@'); if (idx == -1) { return parse(notation, null); @@ -37,8 +42,14 @@ public class DependencyStringNotationConverter { return parse(notation, null); } - private static @Nullable Dependency parse(String notation, @Nullable String ext) { - Dependency dependency = new Dependency(null, null, null, null, ext); + private static @Nullable Dependency parse(@Nullable String notation, @Nullable String type) { + if (notation == null) { + return null; + } + String groupId = null; + String artifactId = null; + String version = null; + String classifier = null; int count = 0; int idx = 0; @@ -46,33 +57,49 @@ public class DependencyStringNotationConverter { while (++cur < notation.length()) { if (':' == notation.charAt(cur)) { String fragment = notation.substring(idx, cur); - dependency = assignValue(dependency, count, fragment); + switch (count) { + case 0: + groupId = fragment; + break; + case 1: + artifactId = fragment; + break; + case 2: + version = fragment; + break; + case 3: + classifier = fragment; + break; + } idx = cur + 1; count++; } } - dependency = assignValue(dependency, count, notation.substring(idx, cur)); - count++; - - if (count < 2 || count > 4) { - return null; - } - - return dependency; - } - - private static Dependency assignValue(Dependency dependency, int count, String fragment) { + String fragment = notation.substring(idx, cur); switch (count) { case 0: - return dependency.withGroupId(fragment); + groupId = fragment; + break; case 1: - return dependency.withArtifactId(fragment); + artifactId = fragment; + break; case 2: - return dependency.withVersion(fragment); + version = fragment; + break; case 3: - return dependency.withClassifier(fragment); - default: - throw new IllegalArgumentException("Invalid count parameter: " + count); + classifier = fragment; + break; } + count++; + + if (count < 2 || count > 4) { + return null; + } + + return Dependency.builder() + .gav(new GroupArtifactVersion(groupId, artifactId, version)) + .classifier(classifier) + .type(type) + .build(); } } diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/internal/InsertDependencyComparator.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/internal/InsertDependencyComparator.java index 83dd5a6372..e151c4cb83 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/internal/InsertDependencyComparator.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/internal/InsertDependencyComparator.java @@ -21,6 +21,7 @@ import org.openrewrite.java.tree.Expression; import org.openrewrite.java.tree.J; import org.openrewrite.java.tree.Statement; +import org.openrewrite.maven.tree.Dependency; import java.util.*; 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 aed7c849de..f89e51cab1 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 @@ -21,15 +21,18 @@ import org.openrewrite.Cursor; import org.openrewrite.Tree; import org.openrewrite.TreeVisitor; +import org.openrewrite.gradle.internal.ChangeStringLiteral; import org.openrewrite.gradle.internal.DependencyStringNotationConverter; import org.openrewrite.gradle.marker.GradleDependencyConfiguration; import org.openrewrite.gradle.marker.GradleProject; import org.openrewrite.groovy.tree.G; +import org.openrewrite.internal.ListUtils; import org.openrewrite.internal.StringUtils; import org.openrewrite.java.JavaVisitor; import org.openrewrite.java.MethodMatcher; import org.openrewrite.java.tree.Expression; import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.JavaType; import org.openrewrite.kotlin.tree.K; import org.openrewrite.maven.tree.Dependency; import org.openrewrite.maven.tree.GroupArtifactVersion; @@ -51,6 +54,575 @@ public class GradleDependency implements Trait { @Getter ResolvedDependency resolvedDependency; + /** + * Gets the resolved group ID of the dependency after Gradle's dependency resolution. + * + * @return The resolved group ID (e.g., "com.google.guava" from "com.google.guava:guava:VERSION") + */ + public String getGroupId() { + return resolvedDependency.getGroupId(); + } + + /** + * Gets the resolved artifact ID of the dependency after Gradle's dependency resolution. + * + * @return The resolved artifact ID (e.g., "guava" from "com.google.guava:guava:VERSION") + */ + public String getArtifactId() { + return resolvedDependency.getArtifactId(); + } + + /** + * Gets the declared group ID from the dependency declaration. + * This may be a literal value or a variable name/expression. + * For platform dependencies, automatically looks at the inner dependency. + * + * @return The declared group ID string, or null if it cannot be determined + */ + public @Nullable String getDeclaredGroupId() { + // If this is a platform dependency, delegate to the inner dependency + if (isPlatform()) { + GradleDependency platformDep = getPlatformDependency(); + return platformDep != null ? platformDep.getDeclaredGroupId() : null; + } + + J.MethodInvocation m = cursor.getValue(); + List depArgs = m.getArguments(); + + if (depArgs.isEmpty()) { + return null; + } + + Expression arg = depArgs.get(0); + + // String literal notation: "group:artifact:version" + if (arg instanceof J.Literal && ((J.Literal) arg).getValue() instanceof String) { + Dependency dep = + DependencyStringNotationConverter.parse((String) ((J.Literal) arg).getValue()); + return dep != null ? dep.getGroupId() : null; + } + + // GString notation: "group:artifact:$version" + if (arg instanceof G.GString) { + List strings = ((G.GString) arg).getStrings(); + if (!strings.isEmpty() && strings.get(0) instanceof J.Literal) { + Dependency dep = + DependencyStringNotationConverter.parse((String) ((J.Literal) strings.get(0)).getValue()); + return dep != null ? dep.getGroupId() : null; + } + } + + // Kotlin string template: "group:artifact:$version" + if (arg instanceof K.StringTemplate) { + List strings = ((K.StringTemplate) arg).getStrings(); + if (!strings.isEmpty() && strings.get(0) instanceof J.Literal) { + Dependency dep = + DependencyStringNotationConverter.parse((String) ((J.Literal) strings.get(0)).getValue()); + return dep != null ? dep.getGroupId() : null; + } + } + + // Map notation - look for "group" entry + if (depArgs.size() >= 2) { + for (Expression e : depArgs) { + if (e instanceof G.MapEntry) { + G.MapEntry entry = (G.MapEntry) e; + if (entry.getKey() instanceof J.Literal && + "group".equals(((J.Literal) entry.getKey()).getValue())) { + return extractValueAsString(entry.getValue()); + } + } else if (e instanceof J.Assignment) { + J.Assignment assignment = (J.Assignment) e; + if (assignment.getVariable() instanceof J.Identifier && + "group".equals(((J.Identifier) assignment.getVariable()).getSimpleName())) { + return extractValueAsString(assignment.getAssignment()); + } + } + } + } + + // Map literal notation + if (arg instanceof G.MapLiteral) { + for (G.MapEntry entry : ((G.MapLiteral) arg).getElements()) { + if (entry.getKey() instanceof J.Literal && + "group".equals(((J.Literal) entry.getKey()).getValue())) { + return extractValueAsString(entry.getValue()); + } + } + } + + return null; + } + + /** + * Gets the declared artifact ID from the dependency declaration. + * This may be a literal value or a variable name/expression. + * For platform dependencies, automatically looks at the inner dependency. + * + * @return The declared artifact ID string, or null if it cannot be determined + */ + public @Nullable String getDeclaredArtifactId() { + // If this is a platform dependency, delegate to the inner dependency + if (isPlatform()) { + GradleDependency platformDep = getPlatformDependency(); + return platformDep != null ? platformDep.getDeclaredArtifactId() : null; + } + + J.MethodInvocation m = cursor.getValue(); + List depArgs = m.getArguments(); + + if (depArgs.isEmpty()) { + return null; + } + + Expression arg = depArgs.get(0); + + // String literal notation: "group:artifact:version" + if (arg instanceof J.Literal && ((J.Literal) arg).getValue() instanceof String) { + Dependency dep = DependencyStringNotationConverter.parse((String) ((J.Literal) arg).getValue()); + return dep != null ? dep.getArtifactId() : null; + } + + // GString notation: "group:artifact:$version" + if (arg instanceof G.GString) { + List strings = ((G.GString) arg).getStrings(); + if (!strings.isEmpty() && strings.get(0) instanceof J.Literal) { + Dependency dep = DependencyStringNotationConverter.parse((String) ((J.Literal) strings.get(0)).getValue()); + return dep != null ? dep.getArtifactId() : null; + } + } + + // Kotlin string template: "group:artifact:$version" + if (arg instanceof K.StringTemplate) { + List strings = ((K.StringTemplate) arg).getStrings(); + if (!strings.isEmpty() && strings.get(0) instanceof J.Literal) { + Dependency dep = DependencyStringNotationConverter.parse((String) ((J.Literal) strings.get(0)).getValue()); + return dep != null ? dep.getArtifactId() : null; + } + } + + // Map notation - look for "name" or "artifact" entry + if (depArgs.size() >= 2) { + for (Expression e : depArgs) { + if (e instanceof G.MapEntry) { + G.MapEntry entry = (G.MapEntry) e; + if (entry.getKey() instanceof J.Literal) { + String key = (String) ((J.Literal) entry.getKey()).getValue(); + if ("name".equals(key) || "artifact".equals(key)) { + return extractValueAsString(entry.getValue()); + } + } + } else if (e instanceof J.Assignment) { + J.Assignment assignment = (J.Assignment) e; + if (assignment.getVariable() instanceof J.Identifier) { + String key = ((J.Identifier) assignment.getVariable()).getSimpleName(); + if ("name".equals(key) || "artifact".equals(key)) { + return extractValueAsString(assignment.getAssignment()); + } + } + } + } + } + + // Map literal notation + if (arg instanceof G.MapLiteral) { + for (G.MapEntry entry : ((G.MapLiteral) arg).getElements()) { + if (entry.getKey() instanceof J.Literal) { + String key = (String) ((J.Literal) entry.getKey()).getValue(); + if ("name".equals(key) || "artifact".equals(key)) { + return extractValueAsString(entry.getValue()); + } + } + } + } + + return null; + } + + /** + * Gets the declared version from the dependency declaration. + * This may be a literal value or a variable name/expression. + * For platform dependencies, automatically looks at the inner dependency. + * + * @return The declared version string (literal or variable name), or null if it cannot be determined + */ + public @Nullable String getDeclaredVersion() { + // For version variables, getVersionVariable() already handles this + String versionVar = getVersionVariable(); + if (versionVar != null) { + return versionVar; + } + + // If this is a platform dependency, delegate to the inner dependency + if (isPlatform()) { + GradleDependency platformDep = getPlatformDependency(); + return platformDep != null ? platformDep.getDeclaredVersion() : null; + } + + J.MethodInvocation m = cursor.getValue(); + List depArgs = m.getArguments(); + + if (depArgs.isEmpty()) { + return null; + } + + Expression arg = depArgs.get(0); + + // String literal notation: "group:artifact:version" + if (arg instanceof J.Literal && ((J.Literal) arg).getValue() instanceof String) { + Dependency dep = + DependencyStringNotationConverter.parse((String) ((J.Literal) arg).getValue()); + return dep != null ? dep.getVersion() : null; + } + + // Map notation - look for "version" entry + if (depArgs.size() >= 3) { + for (Expression e : depArgs) { + if (e instanceof G.MapEntry) { + G.MapEntry entry = (G.MapEntry) e; + if (entry.getKey() instanceof J.Literal && + "version".equals(((J.Literal) entry.getKey()).getValue())) { + return extractValueAsString(entry.getValue()); + } + } else if (e instanceof J.Assignment) { + J.Assignment assignment = (J.Assignment) e; + if (assignment.getVariable() instanceof J.Identifier && + "version".equals(((J.Identifier) assignment.getVariable()).getSimpleName())) { + return extractValueAsString(assignment.getAssignment()); + } + } + } + } + + // Map literal notation + if (arg instanceof G.MapLiteral) { + for (G.MapEntry entry : ((G.MapLiteral) arg).getElements()) { + if (entry.getKey() instanceof J.Literal && + "version".equals(((J.Literal) entry.getKey()).getValue())) { + return extractValueAsString(entry.getValue()); + } + } + } + + return null; + } + + /** + * Helper method to extract a string value from various expression types. + * Handles literals, identifiers, field access, method invocations, and GStrings. + */ + private @Nullable String extractValueAsString(Expression value) { + if (value instanceof J.Literal) { + Object literalValue = ((J.Literal) value).getValue(); + return literalValue instanceof String ? (String) literalValue : null; + } else if (value instanceof J.Identifier) { + return ((J.Identifier) value).getSimpleName(); + } else if (value instanceof J.FieldAccess) { + return value.printTrimmed(cursor); + } else if (value instanceof J.MethodInvocation) { + // Handle property('name') or findProperty('name') patterns + J.MethodInvocation mi = (J.MethodInvocation) value; + String methodName = mi.getSimpleName(); + if (("property".equals(methodName) || "findProperty".equals(methodName)) && + mi.getArguments().size() == 1 && + mi.getArguments().get(0) instanceof J.Literal) { + Object arg = ((J.Literal) mi.getArguments().get(0)).getValue(); + if (arg instanceof String) { + return (String) arg; + } + } + + // Also handle project.property('name') and project.findProperty('name') + if (mi.getSelect() instanceof J.Identifier && + "project".equals(((J.Identifier) mi.getSelect()).getSimpleName()) && + ("property".equals(methodName) || "findProperty".equals(methodName)) && + mi.getArguments().size() == 1 && + mi.getArguments().get(0) instanceof J.Literal) { + Object arg = ((J.Literal) mi.getArguments().get(0)).getValue(); + if (arg instanceof String) { + return (String) arg; + } + } + + // For other method invocations, return the full expression + return value.printTrimmed(cursor); + } else if (value instanceof G.Binary) { + G.Binary binary = (G.Binary) value; + + // Handle project.properties['name'] pattern (G.Binary with Access operator) + if (binary.getOperator() == G.Binary.Type.Access && binary.getRight() instanceof J.Literal) { + J.Literal right = (J.Literal) binary.getRight(); + if (right.getValue() instanceof String) { + return (String) right.getValue(); + } + } + + // Handle binary expressions like "$guavaVersion" + "-jre" + if (binary.getLeft() instanceof G.GString) { + G.GString left = (G.GString) binary.getLeft(); + List strings = left.getStrings(); + if (strings.size() == 1 && strings.get(0) instanceof G.GString.Value) { + G.GString.Value gStringValue = (G.GString.Value) strings.get(0); + Tree tree = gStringValue.getTree(); + if (tree instanceof J.Identifier) { + return ((J.Identifier) tree).getSimpleName(); + } + } + } else if (binary.getLeft() instanceof J.Identifier) { + return ((J.Identifier) binary.getLeft()).getSimpleName(); + } + + // For other binary expressions, return the full expression + return value.printTrimmed(cursor); + } else if (value instanceof G.GString) { + G.GString gString = (G.GString) value; + List strings = gString.getStrings(); + if (!strings.isEmpty() && strings.get(0) instanceof G.GString.Value) { + G.GString.Value gStringValue = (G.GString.Value) strings.get(0); + Tree tree = gStringValue.getTree(); + if (tree instanceof J.Identifier) { + return ((J.Identifier) tree).getSimpleName(); + } else if (tree instanceof J.FieldAccess) { + return tree.printTrimmed(cursor); + } else if (tree instanceof J.MethodInvocation) { + // Recursively handle method invocations within GString + return extractValueAsString((J.MethodInvocation) tree); + } else if (tree instanceof G.Binary) { + // Recursively handle binary expressions within GString + return extractValueAsString((G.Binary) tree); + } + } + } else if (value instanceof K.StringTemplate) { + K.StringTemplate template = (K.StringTemplate) value; + List strings = template.getStrings(); + if (!strings.isEmpty() && strings.get(0) instanceof K.StringTemplate.Expression) { + K.StringTemplate.Expression templateExp = (K.StringTemplate.Expression) strings.get(0); + Tree tree = templateExp.getTree(); + if (tree instanceof J.Identifier) { + return ((J.Identifier) tree).getSimpleName(); + } + } + } + return null; + } + + /** + * Checks if this dependency declaration uses platform() or enforcedPlatform(). + * + * @return true if the dependency is wrapped in platform() or enforcedPlatform() + */ + public boolean isPlatform() { + J.MethodInvocation m = cursor.getValue(); + if (m.getArguments().isEmpty()) { + return false; + } + Expression firstArg = m.getArguments().get(0); + if (!(firstArg instanceof J.MethodInvocation)) { + return false; + } + String methodName = ((J.MethodInvocation) firstArg).getSimpleName(); + return "platform".equals(methodName) || "enforcedPlatform".equals(methodName); + } + + /** + * Gets the inner dependency if this dependency uses platform() or enforcedPlatform(). + * For example, for {@code implementation platform("com.example:bom:1.0")}, this returns + * the GradleDependency representing {@code platform("com.example:bom:1.0")}. + * + * @return The inner GradleDependency if this is a platform dependency, or null otherwise + */ + public @Nullable GradleDependency getPlatformDependency() { + J.MethodInvocation m = cursor.getValue(); + if (m.getArguments().isEmpty()) { + return null; + } + Expression firstArg = m.getArguments().get(0); + if (!(firstArg instanceof J.MethodInvocation)) { + return null; + } + J.MethodInvocation platformMethod = (J.MethodInvocation) firstArg; + String methodName = platformMethod.getSimpleName(); + if (!"platform".equals(methodName) && !"enforcedPlatform".equals(methodName)) { + return null; + } + + // Try to get the dependency trait for the inner platform() method invocation + return new Matcher().get(platformMethod, cursor).orElse(null); + } + + /** + * Gets the version variable name if the dependency's version is specified via a variable reference + * rather than a literal value. Handles various dependency notation formats. + * If this dependency uses platform() or enforcedPlatform(), this method automatically + * looks at the inner dependency declaration. + * + * @return The variable name used for the version, or null if the version is a literal or cannot be determined + */ + public @Nullable String getVersionVariable() { + // If this is a platform dependency, delegate to the inner dependency + if (isPlatform()) { + GradleDependency platformDep = getPlatformDependency(); + return platformDep != null ? platformDep.getVersionVariable() : null; + } + + J.MethodInvocation m = cursor.getValue(); + List depArgs = m.getArguments(); + + if (depArgs.isEmpty()) { + return null; + } + + Expression arg = depArgs.get(0); + + // Handle "group:artifact:$version" patterns with GString + if (arg instanceof G.GString) { + List strings = ((G.GString) arg).getStrings(); + if (strings.size() == 2 && strings.get(0) instanceof J.Literal && strings.get(1) instanceof G.GString.Value) { + Object versionTree = ((G.GString.Value) strings.get(1)).getTree(); + if (versionTree instanceof J.Identifier) { + return ((J.Identifier) versionTree).getSimpleName(); + } else if (versionTree instanceof J.FieldAccess) { + return ((J.FieldAccess) versionTree).printTrimmed(cursor); + } + } + } + + // Handle "group:artifact:$version" patterns with Kotlin StringTemplate + if (arg instanceof K.StringTemplate) { + List strings = ((K.StringTemplate) arg).getStrings(); + if (strings.size() == 2 && strings.get(0) instanceof J.Literal && strings.get(1) instanceof K.StringTemplate.Expression) { + Object versionTree = ((K.StringTemplate.Expression) strings.get(1)).getTree(); + if (versionTree instanceof J.Identifier) { + return ((J.Identifier) versionTree).getSimpleName(); + } + } + } + + // Handle map notation: group: 'x', name: 'y', version: variableName + if (depArgs.size() >= 3) { + Expression versionExp = null; + + if (depArgs.get(0) instanceof G.MapEntry && depArgs.get(1) instanceof G.MapEntry && depArgs.get(2) instanceof G.MapEntry) { + versionExp = ((G.MapEntry) depArgs.get(2)).getValue(); + } else if (depArgs.get(0) instanceof J.Assignment && depArgs.get(1) instanceof J.Assignment && depArgs.get(2) instanceof J.Assignment) { + versionExp = ((J.Assignment) depArgs.get(2)).getAssignment(); + } + + if (versionExp instanceof J.Identifier) { + return ((J.Identifier) versionExp).getSimpleName(); + } else if (versionExp instanceof J.FieldAccess) { + return ((J.FieldAccess) versionExp).printTrimmed(cursor); + } + } + + return null; + } + + /** + * Gets the tree (J.MethodInvocation) that should be modified when updating this dependency. + * For regular dependencies, returns the current tree. + * For dependencies with platform wrappers, returns the inner platform() or enforcedPlatform() method invocation. + * + * @return The J.MethodInvocation to update + */ + public J.MethodInvocation getTreeToUpdate() { + if (isPlatform()) { + // The first argument is a platform() or enforcedPlatform() call + J.MethodInvocation m = getTree(); + if (!m.getArguments().isEmpty() && m.getArguments().get(0) instanceof J.MethodInvocation) { + return (J.MethodInvocation) m.getArguments().get(0); + } + } + return getTree(); + } + + /** + * Wraps an updated dependency tree with the appropriate wrapper if needed. + * For regular dependencies, returns the updated tree as-is. + * For dependencies with platform wrappers, wraps the updated platform() call with the outer configuration method. + * + * @param updatedTree The updated dependency tree (potentially from getTreeToUpdate()) + * @return The properly wrapped J.MethodInvocation + */ + public J.MethodInvocation wrapUpdatedTree(J.MethodInvocation updatedTree) { + if (!isPlatform()) { + return updatedTree; + } + + J.MethodInvocation m = getTree(); + // Replace the platform method invocation argument with the updated one + return m.withArguments(ListUtils.mapFirst(m.getArguments(), arg -> updatedTree)); + } + + /** + * Removes the version from a dependency declaration. + * This method handles various dependency notation formats including string literals, + * map literals, and map entries. + * If this dependency uses platform() or enforcedPlatform(), this method automatically + * operates on the inner dependency declaration. + * + * @return A new GradleDependency with the version removed from the cursor's method invocation, + * or the original GradleDependency if the version cannot be removed + */ + public GradleDependency removeVersion() { + J.MethodInvocation m = cursor.getValue(); + J.MethodInvocation updated = m; + + // If this is a platform dependency, we need to update the inner dependency + if (isPlatform() && m.getArguments().get(0) instanceof J.MethodInvocation) { + J.MethodInvocation platformMethod = (J.MethodInvocation) m.getArguments().get(0); + GradleDependency platformDep = getPlatformDependency(); + if (platformDep != null) { + GradleDependency updatedPlatformDep = platformDep.removeVersion(); + if (updatedPlatformDep != platformDep) { + // The inner dependency was modified, so update the outer method invocation + updated = m.withArguments(ListUtils.mapFirst(m.getArguments(), arg -> + platformMethod.withArguments(updatedPlatformDep.getTree().getArguments()) + )); + return new GradleDependency(new Cursor(cursor.getParent(), updated), resolvedDependency); + } + } + return this; + } + + if (m.getArguments().get(0) instanceof J.Literal) { + J.Literal l = (J.Literal) m.getArguments().get(0); + if (l.getType() == JavaType.Primitive.String) { + Dependency dep = DependencyStringNotationConverter.parse((String) l.getValue()); + if (dep == null || dep.getClassifier() != null || (dep.getType() != null && !"jar".equals(dep.getType()))) { + return this; + } + updated = m.withArguments(ListUtils.mapFirst(m.getArguments(), arg -> + ChangeStringLiteral.withStringValue(l, dep.withGav(dep.getGav().withVersion(null)).toStringNotation())) + ); + } + } else if (m.getArguments().get(0) instanceof G.MapLiteral) { + updated = m.withArguments(ListUtils.mapFirst(m.getArguments(), arg -> { + G.MapLiteral mapLiteral = (G.MapLiteral) arg; + return mapLiteral.withElements(ListUtils.map(mapLiteral.getElements(), entry -> { + if (entry.getKey() instanceof J.Literal && "version".equals(((J.Literal) entry.getKey()).getValue())) { + return null; + } + return entry; + })); + })); + } else if (m.getArguments().get(0) instanceof G.MapEntry) { + updated = m.withArguments(ListUtils.map(m.getArguments(), arg -> { + G.MapEntry entry = (G.MapEntry) arg; + if (entry.getKey() instanceof J.Literal && "version".equals(((J.Literal) entry.getKey()).getValue())) { + return null; + } + return entry; + })); + } + + if (updated == m) { + return this; + } + + return new GradleDependency(new Cursor(cursor.getParent(), updated), resolvedDependency); + } + public static class Matcher extends GradleTraitMatcher { private static final MethodMatcher DEPENDENCY_DSL_MATCHER = new MethodMatcher("DependencyHandlerSpec *(..)"); @@ -116,7 +688,7 @@ public J visitMethodInvocation(J.MethodInvocation method, P p) { return null; } - org.openrewrite.gradle.internal.Dependency dependency = null; + Dependency dependency = null; Expression argument = methodInvocation.getArguments().get(0); if (argument instanceof J.Literal || argument instanceof G.GString || argument instanceof G.MapEntry || argument instanceof G.MapLiteral || argument instanceof J.Assignment || argument instanceof K.StringTemplate) { dependency = parseDependency(methodInvocation.getArguments()); @@ -173,10 +745,10 @@ public J visitMethodInvocation(J.MethodInvocation method, P p) { .depth(-1) .gav(new ResolvedGroupArtifactVersion(null, dependency.getGroupId(), dependency.getArtifactId(), dependency.getVersion() != null ? dependency.getVersion() : "", null)) .classifier(dependency.getClassifier()) - .type(dependency.getExt()) + .type(dependency.getType()) .requested(Dependency.builder() .scope(methodInvocation.getSimpleName()) - .type(dependency.getExt()) + .type(dependency.getType()) .gav(new GroupArtifactVersion(dependency.getGroupId(), dependency.getArtifactId(), dependency.getVersion())) .classifier(dependency.getClassifier()) .build()) @@ -193,7 +765,7 @@ public J visitMethodInvocation(J.MethodInvocation method, P p) { * So if this Trait has figured out which declaration made the request resulting in a particular resolved dependency * use that more-accurate information instead. */ - private static ResolvedDependency withRequested(ResolvedDependency resolved, org.openrewrite.gradle.internal.Dependency requested) { + private static ResolvedDependency withRequested(ResolvedDependency resolved, Dependency requested) { return resolved.withRequested(resolved.getRequested().withGav(requested.getGav())); } @@ -219,7 +791,7 @@ private boolean withinDependencyConstraintsBlock(Cursor cursor) { return withinBlock(cursor, "constraints") && withinDependenciesBlock(cursor); } - private org.openrewrite.gradle.internal.@Nullable Dependency parseDependency(List arguments) { + private @Nullable Dependency parseDependency(List arguments) { Expression argument = arguments.get(0); if (argument instanceof J.Literal) { return DependencyStringNotationConverter.parse((String) ((J.Literal) argument).getValue()); @@ -266,7 +838,9 @@ private boolean withinDependencyConstraintsBlock(Cursor cursor) { return null; } - return new org.openrewrite.gradle.internal.Dependency(group, artifact, null, null, null); + return Dependency.builder() + .gav(new GroupArtifactVersion(group, artifact, null)) + .build(); } else if (argument instanceof K.StringTemplate) { K.StringTemplate template = (K.StringTemplate) argument; List strings = template.getStrings(); @@ -278,7 +852,7 @@ private boolean withinDependencyConstraintsBlock(Cursor cursor) { return null; } - private static org.openrewrite.gradle.internal.@Nullable Dependency getMapEntriesDependency(List arguments) { + private static @Nullable Dependency getMapEntriesDependency(List arguments) { String group = null; String artifact = null; @@ -307,7 +881,9 @@ private boolean withinDependencyConstraintsBlock(Cursor cursor) { return null; } - return new org.openrewrite.gradle.internal.Dependency(group, artifact, null, null, null); + return org.openrewrite.maven.tree.Dependency.builder() + .gav(new GroupArtifactVersion(group, artifact, null)) + .build(); } } } diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/trait/SpringDependencyManagementPluginEntry.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/trait/SpringDependencyManagementPluginEntry.java index 396fd4f62e..1c31a12432 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/trait/SpringDependencyManagementPluginEntry.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/trait/SpringDependencyManagementPluginEntry.java @@ -23,7 +23,6 @@ import org.openrewrite.SourceFile; import org.openrewrite.gradle.DependencyVersionSelector; import org.openrewrite.gradle.internal.ChangeStringLiteral; -import org.openrewrite.gradle.internal.Dependency; import org.openrewrite.gradle.internal.DependencyStringNotationConverter; import org.openrewrite.gradle.marker.GradleProject; import org.openrewrite.groovy.tree.G; @@ -36,6 +35,7 @@ import org.openrewrite.kotlin.tree.K; import org.openrewrite.maven.MavenDownloadingException; import org.openrewrite.maven.table.MavenMetadataFailures; +import org.openrewrite.maven.tree.Dependency; import org.openrewrite.maven.tree.GroupArtifact; import org.openrewrite.maven.tree.GroupArtifactVersion; import org.openrewrite.semver.DependencyMatcher; From 6722b6a9ddabd24089aaf81afb8dbe7aa8ead06f Mon Sep 17 00:00:00 2001 From: Sam Snyder Date: Thu, 9 Oct 2025 19:08:54 -0700 Subject: [PATCH 02/19] Move DependencyStringNotationConverter functionality onto Dependency itself --- .../DependencyStringNotationConverter.java | 86 +----- .../gradle/internal/DependencyTest.java | 64 ---- .../openrewrite/maven/tree/Dependency.java | 190 ++++++++++++ .../maven/tree/DependencyTest.java | 277 ++++++++++++++++++ 4 files changed, 476 insertions(+), 141 deletions(-) delete mode 100644 rewrite-gradle/src/test/java/org/openrewrite/gradle/internal/DependencyTest.java create mode 100644 rewrite-maven/src/test/java/org/openrewrite/maven/tree/DependencyTest.java diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/internal/DependencyStringNotationConverter.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/internal/DependencyStringNotationConverter.java index a1a81ce9ba..7b34d2fcef 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/internal/DependencyStringNotationConverter.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/internal/DependencyStringNotationConverter.java @@ -17,89 +17,21 @@ import org.jspecify.annotations.Nullable; import org.openrewrite.maven.tree.Dependency; -import org.openrewrite.maven.tree.GroupArtifactVersion; +/** + * @deprecated Use {@link Dependency#parse(String)} instead. + * This class is retained for backward compatibility. + */ +@Deprecated public class DependencyStringNotationConverter { /** - * @param notation a String in the format group:artifact:version + * @param notation a String in the format group:artifact:version:classifier@extension * @return A corresponding Dependency or null if the notation could not be parsed + * @deprecated Use {@link Dependency#parse(String)} instead */ + @Deprecated public static @Nullable Dependency parse(@Nullable String notation) { - if (notation == null) { - return null; - } - int idx = notation.lastIndexOf('@'); - if (idx == -1) { - return parse(notation, null); - } - - int versionIdx = notation.lastIndexOf(':'); - if (versionIdx < idx) { - return parse(notation.substring(0, idx), notation.substring(idx + 1)); - } - - return parse(notation, null); - } - - private static @Nullable Dependency parse(@Nullable String notation, @Nullable String type) { - if (notation == null) { - return null; - } - String groupId = null; - String artifactId = null; - String version = null; - String classifier = null; - - int count = 0; - int idx = 0; - int cur = 0; - while (++cur < notation.length()) { - if (':' == notation.charAt(cur)) { - String fragment = notation.substring(idx, cur); - switch (count) { - case 0: - groupId = fragment; - break; - case 1: - artifactId = fragment; - break; - case 2: - version = fragment; - break; - case 3: - classifier = fragment; - break; - } - idx = cur + 1; - count++; - } - } - String fragment = notation.substring(idx, cur); - switch (count) { - case 0: - groupId = fragment; - break; - case 1: - artifactId = fragment; - break; - case 2: - version = fragment; - break; - case 3: - classifier = fragment; - break; - } - count++; - - if (count < 2 || count > 4) { - return null; - } - - return Dependency.builder() - .gav(new GroupArtifactVersion(groupId, artifactId, version)) - .classifier(classifier) - .type(type) - .build(); + return Dependency.parse(notation); } } diff --git a/rewrite-gradle/src/test/java/org/openrewrite/gradle/internal/DependencyTest.java b/rewrite-gradle/src/test/java/org/openrewrite/gradle/internal/DependencyTest.java deleted file mode 100644 index f76aed43f0..0000000000 --- a/rewrite-gradle/src/test/java/org/openrewrite/gradle/internal/DependencyTest.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright 2025 the original author or authors. - *

- * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - *

- * https://www.apache.org/licenses/LICENSE-2.0 - *

- * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.openrewrite.gradle.internal; - -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -class DependencyTest { - @Test - void toStringNotationWithAllFieldsPresent() { - Dependency dep = new Dependency("com.example", "artifact", "1.0.0", "sources", "jar"); - assertEquals("com.example:artifact:1.0.0:sources@jar", dep.toStringNotation()); - } - - @Test - void toStringNotationWithOnlyGroupAndArtifact() { - Dependency dep = new Dependency("com.example", "artifact", null, null, null); - assertEquals("com.example:artifact", dep.toStringNotation()); - } - - @Test - void toStringNotationWithNullGroup() { - Dependency dep = new Dependency(null, "artifact", "1.0.0", null, null); - assertEquals(":artifact:1.0.0", dep.toStringNotation()); - } - - @Test - void toStringNotationWithNullVersionButClassifierPresent() { - Dependency dep = new Dependency("com.example", "artifact", null, "sources", null); - assertEquals("com.example:artifact::sources", dep.toStringNotation()); - } - - @Test - void toStringNotationWithNullVersionAndClassifierButExtensionPresent() { - Dependency dep = new Dependency("com.example", "artifact", null, null, "jar"); - assertEquals("com.example:artifact@jar", dep.toStringNotation()); - } - - @Test - void toStringNotationWithNullClassifierButExtensionPresent() { - Dependency dep = new Dependency("com.example", "artifact", "1.0.0", null, "jar"); - assertEquals("com.example:artifact:1.0.0@jar", dep.toStringNotation()); - } - - @Test - void toStringNotationWithOnlyArtifact() { - Dependency dep = new Dependency(null, "artifact", null, null, null); - assertEquals(":artifact", dep.toStringNotation()); - } -} diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/tree/Dependency.java b/rewrite-maven/src/main/java/org/openrewrite/maven/tree/Dependency.java index 9cf7e04ce6..ff782b3cb1 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/tree/Dependency.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/tree/Dependency.java @@ -75,4 +75,194 @@ public String getArtifactId() { public String toString() { return gav.toString(); } + + /** + * Returns a Gradle-style string notation for this dependency. + *

+ * Format: {@code "group:name:version:classifier@extension"} + *

+ * All parts are optional except the artifact name. + * + * @return the dependency in Gradle string notation + */ + public String toStringNotation() { + StringBuilder sb = new StringBuilder(); + // Build against spec from gradle docs, all options are optional apart from name + // configurationName "group:name:version:classifier@extension" + if (gav.getGroupId() != null) { + sb.append(gav.getGroupId()); + } + sb.append(":").append(gav.getArtifactId()); + + if (gav.getVersion() != null) { + sb.append(":").append(gav.getVersion()); + } else if (classifier != null) { + sb.append(":"); + } + + if (classifier != null) { + sb.append(":").append(classifier); + } + + if (type != null) { + sb.append("@").append(type); + } + + return sb.toString(); + } + + /** + * Alias for {@link #getType()} for compatibility with Gradle terminology. + * In Gradle, the file extension is referred to as "ext" while Maven uses "type". + * + * @return the type/extension of the dependency + */ + public @Nullable String getExt() { + return type; + } + + /** + * Convenience method to update the group ID. + * Equivalent to {@code withGav(gav.withGroupId(groupId))}. + * + * @param groupId the new group ID + * @return a new Dependency with the updated group ID + */ + public Dependency withGroupId(@Nullable String groupId) { + return withGav(gav.withGroupId(groupId)); + } + + /** + * Convenience method to update the artifact ID. + * Equivalent to {@code withGav(gav.withArtifactId(artifactId))}. + * + * @param artifactId the new artifact ID + * @return a new Dependency with the updated artifact ID + */ + public Dependency withArtifactId(String artifactId) { + return withGav(gav.withArtifactId(artifactId)); + } + + /** + * Convenience method to update the version. + * Equivalent to {@code withGav(gav.withVersion(version))}. + * + * @param version the new version + * @return a new Dependency with the updated version + */ + public Dependency withVersion(@Nullable String version) { + return withGav(gav.withVersion(version)); + } + + /** + * Alias for {@link #withType(String)} for compatibility with Gradle terminology. + * In Gradle, the file extension is referred to as "ext" while Maven uses "type". + * + * @param ext the new type/extension + * @return a new Dependency with the updated type + */ + public Dependency withExt(@Nullable String ext) { + return withType(ext); + } + + /** + * Parses a Gradle-style dependency string notation into a Dependency object. + *

+ * Format: {@code "group:artifact:version:classifier@extension"} + *

+ * All parts are optional except the artifact name. The minimum valid notation is ":artifact". + * + * @param notation a String in the format group:artifact:version:classifier@extension + * @return A corresponding Dependency or null if the notation could not be parsed + */ + public static @Nullable Dependency parse(@Nullable String notation) { + if (notation == null) { + return null; + } + int idx = notation.lastIndexOf('@'); + if (idx == -1) { + return parse(notation, null); + } + + int versionIdx = notation.lastIndexOf(':'); + if (versionIdx < idx) { + return parse(notation.substring(0, idx), notation.substring(idx + 1)); + } + + return parse(notation, null); + } + + private static @Nullable Dependency parse(@Nullable String notation, @Nullable String type) { + if (notation == null) { + return null; + } + String groupId = null; + String artifactId = null; + String version = null; + String classifier = null; + + int count = 0; + int idx = 0; + int cur = 0; + while (cur < notation.length()) { + if (':' == notation.charAt(cur)) { + String fragment = notation.substring(idx, cur); + switch (count) { + case 0: + groupId = fragment; + break; + case 1: + artifactId = fragment; + break; + case 2: + version = fragment; + break; + case 3: + classifier = fragment; + break; + } + idx = cur + 1; + count++; + } + cur++; + } + // Handle the last fragment + if (idx < notation.length()) { + String fragment = notation.substring(idx); + switch (count) { + case 0: + artifactId = fragment; + break; + case 1: + artifactId = fragment; + break; + case 2: + version = fragment; + break; + case 3: + classifier = fragment; + break; + } + count++; + } + + // Validate we have at least group and artifact + if (count < 2 || count > 4 || artifactId == null || artifactId.isEmpty()) { + return null; + } + + // Handle empty strings in version and classifier + if ("".equals(version)) { + version = ""; // Preserve empty strings + } + if ("".equals(classifier)) { + classifier = ""; // Preserve empty strings + } + + return Dependency.builder() + .gav(new GroupArtifactVersion(groupId, artifactId, version)) + .classifier(classifier) + .type(type) + .build(); + } } diff --git a/rewrite-maven/src/test/java/org/openrewrite/maven/tree/DependencyTest.java b/rewrite-maven/src/test/java/org/openrewrite/maven/tree/DependencyTest.java new file mode 100644 index 0000000000..9a492e4d44 --- /dev/null +++ b/rewrite-maven/src/test/java/org/openrewrite/maven/tree/DependencyTest.java @@ -0,0 +1,277 @@ +/* + * Copyright 2025 the original author or authors. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.maven.tree; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class DependencyTest { + + @Test + void toStringNotationWithAllFieldsPresent() { + Dependency dep = Dependency.builder() + .gav(new GroupArtifactVersion("com.example", "artifact", "1.0.0")) + .classifier("sources") + .type("jar") + .build(); + assertThat(dep.toStringNotation()).isEqualTo("com.example:artifact:1.0.0:sources@jar"); + } + + @Test + void toStringNotationWithOnlyGroupAndArtifact() { + Dependency dep = Dependency.builder() + .gav(new GroupArtifactVersion("com.example", "artifact", null)) + .build(); + assertThat(dep.toStringNotation()).isEqualTo("com.example:artifact"); + } + + @Test + void toStringNotationWithNullGroup() { + Dependency dep = Dependency.builder() + .gav(new GroupArtifactVersion(null, "artifact", "1.0.0")) + .build(); + assertThat(dep.toStringNotation()).isEqualTo(":artifact:1.0.0"); + } + + @Test + void toStringNotationWithNullVersionButClassifierPresent() { + Dependency dep = Dependency.builder() + .gav(new GroupArtifactVersion("com.example", "artifact", null)) + .classifier("sources") + .build(); + assertThat(dep.toStringNotation()).isEqualTo("com.example:artifact::sources"); + } + + @Test + void toStringNotationWithNullVersionAndClassifierButExtensionPresent() { + Dependency dep = Dependency.builder() + .gav(new GroupArtifactVersion("com.example", "artifact", null)) + .type("jar") + .build(); + assertThat(dep.toStringNotation()).isEqualTo("com.example:artifact@jar"); + } + + @Test + void toStringNotationWithNullClassifierButExtensionPresent() { + Dependency dep = Dependency.builder() + .gav(new GroupArtifactVersion("com.example", "artifact", "1.0.0")) + .type("jar") + .build(); + assertThat(dep.toStringNotation()).isEqualTo("com.example:artifact:1.0.0@jar"); + } + + @Test + void toStringNotationWithOnlyArtifact() { + Dependency dep = Dependency.builder() + .gav(new GroupArtifactVersion(null, "artifact", null)) + .build(); + assertThat(dep.toStringNotation()).isEqualTo(":artifact"); + } + + // Tests for parse() + @Test + void parseWithAllFieldsPresent() { + Dependency dep = Dependency.parse("com.example:artifact:1.0.0:sources@jar"); + assertThat(dep).isNotNull(); + assertThat(dep.getGroupId()).isEqualTo("com.example"); + assertThat(dep.getArtifactId()).isEqualTo("artifact"); + assertThat(dep.getVersion()).isEqualTo("1.0.0"); + assertThat(dep.getClassifier()).isEqualTo("sources"); + assertThat(dep.getType()).isEqualTo("jar"); + } + + @Test + void parseWithOnlyGroupAndArtifact() { + Dependency dep = Dependency.parse("com.example:artifact"); + assertThat(dep).isNotNull(); + assertThat(dep.getGroupId()).isEqualTo("com.example"); + assertThat(dep.getArtifactId()).isEqualTo("artifact"); + assertThat(dep.getVersion()).isNull(); + assertThat(dep.getClassifier()).isNull(); + assertThat(dep.getType()).isNull(); + } + + @Test + void parseWithNullGroup() { + Dependency dep = Dependency.parse(":artifact:1.0.0"); + assertThat(dep).isNotNull(); + // Empty string group is preserved in parse + assertThat(dep.getGroupId()).isEqualTo(""); + assertThat(dep.getArtifactId()).isEqualTo("artifact"); + assertThat(dep.getVersion()).isEqualTo("1.0.0"); + assertThat(dep.getClassifier()).isNull(); + assertThat(dep.getType()).isNull(); + } + + @Test + void parseWithNullVersionButClassifierPresent() { + Dependency dep = Dependency.parse("com.example:artifact::sources"); + assertThat(dep).isNotNull(); + assertThat(dep.getGroupId()).isEqualTo("com.example"); + assertThat(dep.getArtifactId()).isEqualTo("artifact"); + assertThat(dep.getVersion()).isEqualTo(""); + assertThat(dep.getClassifier()).isEqualTo("sources"); + assertThat(dep.getType()).isNull(); + } + + @Test + void parseWithNullVersionAndClassifierButExtensionPresent() { + Dependency dep = Dependency.parse("com.example:artifact@jar"); + assertThat(dep).isNotNull(); + assertThat(dep.getGroupId()).isEqualTo("com.example"); + assertThat(dep.getArtifactId()).isEqualTo("artifact"); + assertThat(dep.getVersion()).isNull(); + assertThat(dep.getClassifier()).isNull(); + assertThat(dep.getType()).isEqualTo("jar"); + } + + @Test + void parseWithNullClassifierButExtensionPresent() { + Dependency dep = Dependency.parse("com.example:artifact:1.0.0@jar"); + assertThat(dep).isNotNull(); + assertThat(dep.getGroupId()).isEqualTo("com.example"); + assertThat(dep.getArtifactId()).isEqualTo("artifact"); + assertThat(dep.getVersion()).isEqualTo("1.0.0"); + assertThat(dep.getClassifier()).isNull(); + assertThat(dep.getType()).isEqualTo("jar"); + } + + @Test + void parseWithOnlyArtifact() { + Dependency dep = Dependency.parse(":artifact"); + assertThat(dep).isNotNull(); + // Empty string group is preserved in parse + assertThat(dep.getGroupId()).isEqualTo(""); + assertThat(dep.getArtifactId()).isEqualTo("artifact"); + assertThat(dep.getVersion()).isNull(); + assertThat(dep.getClassifier()).isNull(); + assertThat(dep.getType()).isNull(); + } + + @Test + void parseNull() { + assertThat(Dependency.parse(null)).isNull(); + } + + @Test + void parseInvalidNotationTooFewComponents() { + assertThat(Dependency.parse("artifact")).isNull(); + } + + @Test + void parseInvalidNotationTooManyComponents() { + assertThat(Dependency.parse("group:artifact:version:classifier:extra")).isNull(); + } + + @Test + void parseInvalidNotationTooManyComponentsWithExtension() { + assertThat(Dependency.parse("group:artifact:version:classifier:extra@jar")).isNull(); + } + + // Round-trip tests + @Test + void parseAndToStringNotationRoundTrip() { + String notation = "com.example:artifact:1.0.0:sources@jar"; + Dependency dep = Dependency.parse(notation); + assertThat(dep).isNotNull(); + assertThat(dep.toStringNotation()).isEqualTo(notation); + } + + @Test + void parseAndToStringNotationRoundTripMinimal() { + String notation = ":artifact"; + Dependency dep = Dependency.parse(notation); + assertThat(dep).isNotNull(); + assertThat(dep.toStringNotation()).isEqualTo(notation); + } + + @Test + void parseAndToStringNotationRoundTripWithEmptyVersion() { + String notation = "com.example:artifact::sources"; + Dependency dep = Dependency.parse(notation); + assertThat(dep).isNotNull(); + assertThat(dep.toStringNotation()).isEqualTo(notation); + } + + // Tests for convenience methods + @Test + void withGroupId() { + Dependency original = Dependency.parse("com.example:artifact:1.0.0"); + assertThat(original).isNotNull(); + Dependency updated = original.withGroupId("org.example"); + assertThat(updated.getGroupId()).isEqualTo("org.example"); + assertThat(updated.getArtifactId()).isEqualTo("artifact"); + assertThat(updated.getVersion()).isEqualTo("1.0.0"); + } + + @Test + void withArtifactId() { + Dependency original = Dependency.parse("com.example:artifact:1.0.0"); + assertThat(original).isNotNull(); + Dependency updated = original.withArtifactId("new-artifact"); + assertThat(updated.getGroupId()).isEqualTo("com.example"); + assertThat(updated.getArtifactId()).isEqualTo("new-artifact"); + assertThat(updated.getVersion()).isEqualTo("1.0.0"); + } + + @Test + void withVersion() { + Dependency original = Dependency.parse("com.example:artifact:1.0.0"); + assertThat(original).isNotNull(); + Dependency updated = original.withVersion("2.0.0"); + assertThat(updated.getGroupId()).isEqualTo("com.example"); + assertThat(updated.getArtifactId()).isEqualTo("artifact"); + assertThat(updated.getVersion()).isEqualTo("2.0.0"); + } + + @Test + void withExt() { + Dependency original = Dependency.parse("com.example:artifact:1.0.0"); + assertThat(original).isNotNull(); + Dependency updated = original.withExt("war"); + assertThat(updated.getGroupId()).isEqualTo("com.example"); + assertThat(updated.getArtifactId()).isEqualTo("artifact"); + assertThat(updated.getVersion()).isEqualTo("1.0.0"); + assertThat(updated.getType()).isEqualTo("war"); + assertThat(updated.getExt()).isEqualTo("war"); + } + + @Test + void getExt() { + Dependency dep = Dependency.parse("com.example:artifact:1.0.0@war"); + assertThat(dep).isNotNull(); + assertThat(dep.getExt()).isEqualTo("war"); + assertThat(dep.getType()).isEqualTo("war"); + } + + @Test + void parseWithAllFieldsPresentUsingFluentAssertions() { + Dependency dep = Dependency.parse("com.example:artifact:1.0.0:sources@jar"); + + assertThat(dep) + .isNotNull() + .satisfies(d -> { + assertThat(d.getGroupId()).isEqualTo("com.example"); + assertThat(d.getArtifactId()).isEqualTo("artifact"); + assertThat(d.getVersion()).isEqualTo("1.0.0"); + assertThat(d.getClassifier()).isEqualTo("sources"); + assertThat(d.getType()).isEqualTo("jar"); + }) + .extracting(Dependency::toStringNotation) + .isEqualTo("com.example:artifact:1.0.0:sources@jar"); + } +} \ No newline at end of file From c1823ac1a3ecf8e31ee61419b20b8014f29363e3 Mon Sep 17 00:00:00 2001 From: Sam Snyder Date: Thu, 9 Oct 2025 19:32:52 -0700 Subject: [PATCH 03/19] Move DependencyStringNotationConverter functionality onto Dependency itself --- .../openrewrite/gradle/ChangeDependency.java | 12 +++++----- .../gradle/ChangeDependencyArtifactId.java | 5 ++-- .../gradle/ChangeDependencyClassifier.java | 3 +-- .../gradle/ChangeDependencyConfiguration.java | 7 +++--- .../gradle/ChangeDependencyExtension.java | 3 +-- .../gradle/ChangeDependencyGroupId.java | 5 ++-- .../gradle/DependencyUseMapNotation.java | 3 +-- .../RemoveRedundantDependencyVersions.java | 5 ++-- .../gradle/UpgradeDependencyVersion.java | 12 ++++------ .../UpgradeTransitiveDependencyVersion.java | 7 +++--- .../internal/InsertDependencyComparator.java | 3 ++- .../gradle/trait/GradleDependency.java | 23 +++++++++---------- ...SpringDependencyManagementPluginEntry.java | 5 ++-- 13 files changed, 40 insertions(+), 53 deletions(-) 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 5dbd0cb5e7..54fdb24355 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependency.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependency.java @@ -22,7 +22,6 @@ import org.openrewrite.*; import org.openrewrite.gradle.internal.ChangeStringLiteral; import org.openrewrite.maven.tree.Dependency; -import org.openrewrite.gradle.internal.DependencyStringNotationConverter; import org.openrewrite.gradle.marker.GradleDependencyConfiguration; import org.openrewrite.gradle.marker.GradleProject; import org.openrewrite.gradle.search.FindGradleProject; @@ -176,6 +175,7 @@ public TreeVisitor getVisitor() { return Preconditions.check(new FindGradleProject(FindGradleProject.SearchCriteria.Marker).getVisitor(), new JavaIsoVisitor() { final DependencyMatcher depMatcher = requireNonNull(DependencyMatcher.build(oldGroupId + ":" + oldArtifactId).getValue()); + @SuppressWarnings("NotNullFieldNotInitialized") GradleProject gradleProject; @Override @@ -194,7 +194,7 @@ public boolean isAcceptable(SourceFile sourceFile, ExecutionContext ctx) { gradleProject = maybeGp.get(); - sourceFile = (JavaSourceFile) super.visit(sourceFile, ctx); + sourceFile = (JavaSourceFile) super.visitNonNull(sourceFile, ctx); if (sourceFile != tree) { sourceFile = sourceFile.withMarkers(sourceFile.getMarkers().setByType(updateGradleModel(gradleProject))); if (changeManagedDependency == null || changeManagedDependency) { @@ -236,10 +236,10 @@ private J.MethodInvocation updateDependency(J.MethodInvocation m, ExecutionConte if (depArgs.get(0) instanceof J.Literal) { String gav = (String) ((J.Literal) depArgs.get(0)).getValue(); if (gav != null) { - Dependency original = DependencyStringNotationConverter.parse(gav); + Dependency original = Dependency.parse(gav); if (original != null) { Dependency updated = original; - if (!StringUtils.isBlank(newGroupId) && !updated.getGroupId().equals(newGroupId)) { + if (!StringUtils.isBlank(newGroupId) && !Objects.equals(updated.getGroupId(), newGroupId)) { updated = updated.withGroupId(newGroupId); } if (!StringUtils.isBlank(newArtifactId) && !updated.getArtifactId().equals(newArtifactId)) { @@ -270,7 +270,7 @@ private J.MethodInvocation updateDependency(J.MethodInvocation m, ExecutionConte ((J.Literal) strings.get(0)).getValue() != null) { J.Literal literal = (J.Literal) strings.get(0); - Dependency original = DependencyStringNotationConverter.parse((String) requireNonNull(literal.getValue())); + Dependency original = Dependency.parse((String) requireNonNull(literal.getValue())); if (original != null) { Dependency updated = original; if (!StringUtils.isBlank(newGroupId) && !updated.getGroupId().equals(newGroupId)) { @@ -563,7 +563,7 @@ private J.MethodInvocation updateDependency(J.MethodInvocation m, ExecutionConte ((J.Literal) strings.get(0)).getValue() != null) { J.Literal literal = (J.Literal) strings.get(0); - Dependency original = DependencyStringNotationConverter.parse((String) requireNonNull(literal.getValue())); + Dependency original = Dependency.parse((String) requireNonNull(literal.getValue())); if (original != null) { Dependency updated = original; if (!StringUtils.isBlank(newGroupId) && !updated.getGroupId().equals(newGroupId)) { diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependencyArtifactId.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependencyArtifactId.java index 02f346b04b..99e7e16ddb 100755 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependencyArtifactId.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependencyArtifactId.java @@ -21,7 +21,6 @@ import org.openrewrite.*; import org.openrewrite.gradle.internal.ChangeStringLiteral; import org.openrewrite.maven.tree.Dependency; -import org.openrewrite.gradle.internal.DependencyStringNotationConverter; import org.openrewrite.gradle.marker.GradleDependencyConfiguration; import org.openrewrite.gradle.marker.GradleProject; import org.openrewrite.gradle.trait.GradleDependency; @@ -138,7 +137,7 @@ private J.MethodInvocation updateDependency(J.MethodInvocation m) { if (depArgs.get(0) instanceof J.Literal) { String gav = (String) ((J.Literal) depArgs.get(0)).getValue(); if (gav != null) { - Dependency dependency = DependencyStringNotationConverter.parse(gav); + Dependency dependency = Dependency.parse(gav); if (dependency != null && !newArtifactId.equals(dependency.getArtifactId())) { Dependency newDependency = dependency.withArtifactId(newArtifactId); m = m.withArguments(ListUtils.mapFirst(m.getArguments(), arg -> ChangeStringLiteral.withStringValue((J.Literal) arg, newDependency.toStringNotation()))); @@ -148,7 +147,7 @@ private J.MethodInvocation updateDependency(J.MethodInvocation m) { List strings = ((G.GString) depArgs.get(0)).getStrings(); if (strings.size() >= 2 && strings.get(0) instanceof J.Literal) { - Dependency dependency = DependencyStringNotationConverter.parse((String) requireNonNull(((J.Literal) strings.get(0)).getValue())); + Dependency dependency = Dependency.parse((String) requireNonNull(((J.Literal) strings.get(0)).getValue())); if (dependency != null && !newArtifactId.equals(dependency.getArtifactId())) { Dependency newDependency = dependency.withArtifactId(newArtifactId); String replacement = newDependency.toStringNotation(); diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependencyClassifier.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependencyClassifier.java index 9045f6a48b..4522c227bd 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependencyClassifier.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependencyClassifier.java @@ -21,7 +21,6 @@ import org.openrewrite.*; import org.openrewrite.gradle.internal.ChangeStringLiteral; import org.openrewrite.maven.tree.Dependency; -import org.openrewrite.gradle.internal.DependencyStringNotationConverter; import org.openrewrite.gradle.marker.GradleDependencyConfiguration; import org.openrewrite.gradle.marker.GradleProject; import org.openrewrite.gradle.trait.GradleDependency; @@ -123,7 +122,7 @@ public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, Execu if (depArgs.get(0) instanceof J.Literal) { String gav = (String) ((J.Literal) depArgs.get(0)).getValue(); if (gav != null) { - Dependency dependency = DependencyStringNotationConverter.parse(gav); + Dependency dependency = Dependency.parse(gav); if (dependency != null && dependency.getVersion() != null && !Objects.equals(newClassifier, dependency.getClassifier())) { Dependency newDependency = dependency.withClassifier(newClassifier); m = m.withArguments(ListUtils.mapFirst(m.getArguments(), arg -> ChangeStringLiteral.withStringValue((J.Literal) arg, newDependency.toStringNotation()))); diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependencyConfiguration.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependencyConfiguration.java index 2a7f0fdc2f..6898ef1e5d 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependencyConfiguration.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependencyConfiguration.java @@ -21,7 +21,6 @@ import org.openrewrite.*; import org.openrewrite.maven.tree.Dependency; import org.openrewrite.maven.tree.GroupArtifactVersion; -import org.openrewrite.gradle.internal.DependencyStringNotationConverter; import org.openrewrite.gradle.trait.GradleDependency; import org.openrewrite.groovy.GroovyIsoVisitor; import org.openrewrite.groovy.tree.G; @@ -111,7 +110,7 @@ public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, Execu return m; } - Dependency dependency = DependencyStringNotationConverter.parse((String) arg.getValue()); + Dependency dependency = Dependency.parse((String) arg.getValue()); if (dependency == null || !dependencyMatcher.matches(dependency.getGroupId(), dependency.getArtifactId())) { return m; } @@ -126,7 +125,7 @@ public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, Execu return m; } - Dependency dependency = DependencyStringNotationConverter.parse((String) groupArtifact.getValue()); + Dependency dependency = Dependency.parse((String) groupArtifact.getValue()); if (dependency == null || !dependencyMatcher.matches(dependency.getGroupId(), dependency.getArtifactId())) { return m; } @@ -211,7 +210,7 @@ public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, Execu .gav(new GroupArtifactVersion("", ((String) value.getValue()).substring(1), null)) .build(); } else { - dependency = DependencyStringNotationConverter.parse((String) value.getValue()); + dependency = Dependency.parse((String) value.getValue()); } if (dependency == null || !dependencyMatcher.matches(dependency.getGroupId(), dependency.getArtifactId())) { diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependencyExtension.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependencyExtension.java index 5b8cc04bd4..0d0a4de2c2 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependencyExtension.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependencyExtension.java @@ -21,7 +21,6 @@ import org.openrewrite.*; import org.openrewrite.gradle.internal.ChangeStringLiteral; import org.openrewrite.maven.tree.Dependency; -import org.openrewrite.gradle.internal.DependencyStringNotationConverter; import org.openrewrite.gradle.trait.GradleDependency; import org.openrewrite.groovy.GroovyIsoVisitor; import org.openrewrite.groovy.tree.G; @@ -97,7 +96,7 @@ public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, Execu if (depArgs.get(0) instanceof J.Literal) { String gav = (String) ((J.Literal) depArgs.get(0)).getValue(); if (gav != null) { - Dependency dependency = DependencyStringNotationConverter.parse(gav); + Dependency dependency = Dependency.parse(gav); if (dependency != null && !newExtension.equals(dependency.getExt())) { Dependency newDependency = dependency.withExt(newExtension); m = m.withArguments(ListUtils.mapFirst(m.getArguments(), arg -> ChangeStringLiteral.withStringValue((J.Literal) arg, newDependency.toStringNotation()))); diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependencyGroupId.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependencyGroupId.java index eeaf7a62eb..a942b4ed8e 100755 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependencyGroupId.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependencyGroupId.java @@ -21,7 +21,6 @@ import org.openrewrite.*; import org.openrewrite.gradle.internal.ChangeStringLiteral; import org.openrewrite.maven.tree.Dependency; -import org.openrewrite.gradle.internal.DependencyStringNotationConverter; import org.openrewrite.gradle.marker.GradleDependencyConfiguration; import org.openrewrite.gradle.marker.GradleProject; import org.openrewrite.gradle.trait.GradleDependency; @@ -138,7 +137,7 @@ private J.MethodInvocation updateDependency(J.MethodInvocation m) { if (depArgs.get(0) instanceof J.Literal) { String gav = (String) ((J.Literal) depArgs.get(0)).getValue(); if (gav != null) { - Dependency dependency = DependencyStringNotationConverter.parse(gav); + Dependency dependency = Dependency.parse(gav); if (dependency != null && !newGroupId.equals(dependency.getGroupId())) { Dependency newDependency = dependency.withGroupId(newGroupId); m = m.withArguments(ListUtils.mapFirst(m.getArguments(), arg -> ChangeStringLiteral.withStringValue((J.Literal) arg, newDependency.toStringNotation()))); @@ -148,7 +147,7 @@ private J.MethodInvocation updateDependency(J.MethodInvocation m) { List strings = ((G.GString) depArgs.get(0)).getStrings(); if (strings.size() >= 2 && strings.get(0) instanceof J.Literal) { - Dependency dependency = DependencyStringNotationConverter.parse((String) requireNonNull(((J.Literal) strings.get(0)).getValue())); + Dependency dependency = Dependency.parse((String) requireNonNull(((J.Literal) strings.get(0)).getValue())); if (dependency != null && !newGroupId.equals(dependency.getGroupId())) { Dependency newDependency = dependency.withGroupId(newGroupId); String replacement = newDependency.toStringNotation(); diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/DependencyUseMapNotation.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/DependencyUseMapNotation.java index db281236de..90afbe901d 100755 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/DependencyUseMapNotation.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/DependencyUseMapNotation.java @@ -17,7 +17,6 @@ import org.openrewrite.*; import org.openrewrite.maven.tree.Dependency; -import org.openrewrite.gradle.internal.DependencyStringNotationConverter; import org.openrewrite.gradle.trait.GradleDependency; import org.openrewrite.groovy.GroovyVisitor; import org.openrewrite.groovy.tree.G; @@ -146,7 +145,7 @@ private static J.MethodInvocation forBasicString(J.Method if (dependencyString == null) { return m; } - Dependency dependency = DependencyStringNotationConverter.parse(dependencyString); + Dependency dependency = Dependency.parse(dependencyString); if (dependency == null) { return m; } diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/RemoveRedundantDependencyVersions.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/RemoveRedundantDependencyVersions.java index b3987989b6..c2948591f8 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/RemoveRedundantDependencyVersions.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/RemoveRedundantDependencyVersions.java @@ -20,7 +20,6 @@ import org.jspecify.annotations.Nullable; import org.openrewrite.*; import org.openrewrite.maven.tree.Dependency; -import org.openrewrite.gradle.internal.DependencyStringNotationConverter; import org.openrewrite.gradle.marker.GradleDependencyConfiguration; import org.openrewrite.gradle.marker.GradleDependencyConstraint; import org.openrewrite.gradle.marker.GradleProject; @@ -197,7 +196,7 @@ public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, Execu if (m.getArguments().get(0) instanceof J.Literal) { J.Literal l = (J.Literal) m.getArguments().get(0); if (l.getType() == JavaType.Primitive.String) { - Dependency dependency = DependencyStringNotationConverter.parse((String) l.getValue()); + Dependency dependency = Dependency.parse((String) l.getValue()); gav = new GroupArtifactVersion(dependency.getGroupId(), dependency.getArtifactId(), dependency.getVersion()); } } else if (m.getArguments().get(0) instanceof G.MapEntry) { @@ -295,7 +294,7 @@ public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, Execu return m; } String value = (String) ((J.Literal) m.getArguments().get(0)).getValue(); - Dependency dependency = DependencyStringNotationConverter.parse(value); + Dependency dependency = Dependency.parse(value); try { getCursor().dropParentUntil(obj -> obj instanceof J.MethodInvocation && "constraints".equals(((J.MethodInvocation) obj).getSimpleName())).getValue(); if (shouldRemoveRedundantConstraint(dependency, gp.getConfiguration(m.getSimpleName()))) { diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/UpgradeDependencyVersion.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/UpgradeDependencyVersion.java index e314ce23a9..ede98bdacc 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/UpgradeDependencyVersion.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/UpgradeDependencyVersion.java @@ -23,7 +23,6 @@ import org.openrewrite.*; import org.openrewrite.gradle.internal.ChangeStringLiteral; import org.openrewrite.maven.tree.Dependency; -import org.openrewrite.gradle.internal.DependencyStringNotationConverter; import org.openrewrite.gradle.marker.GradleProject; import org.openrewrite.gradle.trait.GradleDependency; import org.openrewrite.groovy.tree.G; @@ -172,10 +171,7 @@ public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) return m; } - // Gather variables for dependencies that use variables gatherVariables(gradleDependency); - - // Extract declared coordinates using the trait String declaredGroupId = gradleDependency.getDeclaredGroupId(); String declaredArtifactId = gradleDependency.getDeclaredArtifactId(); String declaredVersion = gradleDependency.getDeclaredVersion(); @@ -229,7 +225,7 @@ private boolean shouldResolveVersion(String declaredGroupId, String declaredArti } /** - * Gathers version variable names for dependencies using the GradleDependency trait. + * Gathers version variable names for dependencies */ private void gatherVariables(GradleDependency gradleDependency) { String versionVariableName = gradleDependency.getVersionVariable(); @@ -477,7 +473,7 @@ private J.MethodInvocation updateDependency(J.MethodInvocation method, Execution if (!(versionValue.getTree() instanceof J.Identifier) || !(groupArtifact.getValue() instanceof String)) { return arg; } - Dependency dep = DependencyStringNotationConverter.parse((String) groupArtifact.getValue()); + Dependency dep = Dependency.parse((String) groupArtifact.getValue()); if (dep != null && dependencyMatcher.matches(dep.getGroupId(), dep.getArtifactId())) { Object scanResult = acc.gaToNewVersion.get(new GroupArtifact(dep.getGroupId(), dep.getArtifactId())); if (scanResult instanceof Exception) { @@ -499,7 +495,7 @@ private J.MethodInvocation updateDependency(J.MethodInvocation method, Execution if (!(versionValue.getTree() instanceof J.Identifier) || !(groupArtifact.getValue() instanceof String)) { return arg; } - Dependency dep = DependencyStringNotationConverter.parse((String) groupArtifact.getValue()); + Dependency dep = Dependency.parse((String) groupArtifact.getValue()); if (dep != null && dependencyMatcher.matches(dep.getGroupId(), dep.getArtifactId())) { Object scanResult = acc.gaToNewVersion.get(new GroupArtifact(dep.getGroupId(), dep.getArtifactId())); if (scanResult instanceof Exception) { @@ -520,7 +516,7 @@ private J.MethodInvocation updateDependency(J.MethodInvocation method, Execution getCursor().putMessage(UPDATE_VERSION_ERROR_KEY, new IllegalStateException("Unable to update version")); return arg; } - Dependency dep = DependencyStringNotationConverter.parse(gav); + Dependency dep = Dependency.parse(gav); if (dep != null && dependencyMatcher.matches(dep.getGroupId(), dep.getArtifactId()) && dep.getVersion() != null && !dep.getVersion().startsWith("$")) { diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/UpgradeTransitiveDependencyVersion.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/UpgradeTransitiveDependencyVersion.java index b8dcff66ab..c86b9fdf62 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/UpgradeTransitiveDependencyVersion.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/UpgradeTransitiveDependencyVersion.java @@ -21,7 +21,6 @@ import org.jspecify.annotations.Nullable; import org.openrewrite.*; import org.openrewrite.gradle.internal.ChangeStringLiteral; -import org.openrewrite.gradle.internal.DependencyStringNotationConverter; import org.openrewrite.gradle.marker.GradleDependencyConfiguration; import org.openrewrite.gradle.marker.GradleProject; import org.openrewrite.groovy.GroovyIsoVisitor; @@ -357,7 +356,7 @@ public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) if (depArg instanceof G.GString) { List strings = ((G.GString) depArg).getStrings(); if (strings.size() == 2 && strings.get(0) instanceof J.Literal && (((J.Literal) strings.get(0)).getValue() instanceof String) && strings.get(1) instanceof G.GString.Value) { - Dependency dep = DependencyStringNotationConverter.parse((String) ((J.Literal) strings.get(0)).getValue()); + Dependency dep = Dependency.parse((String) ((J.Literal) strings.get(0)).getValue()); if (dep != null) { G.GString.Value versionValue = (G.GString.Value) strings.get(1); acc.versionPropNameToGA.put(versionValue.getTree().toString(), new GroupArtifact(dep.getGroupId(), dep.getArtifactId())); @@ -366,7 +365,7 @@ public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) } else if (depArg instanceof K.StringTemplate) { List strings = ((K.StringTemplate) depArg).getStrings(); if (strings.size() == 2 && strings.get(0) instanceof J.Literal && (((J.Literal) strings.get(0)).getValue() instanceof String) && strings.get(1) instanceof K.StringTemplate.Expression) { - Dependency dep = DependencyStringNotationConverter.parse((String) ((J.Literal) strings.get(0)).getValue()); + Dependency dep = Dependency.parse((String) ((J.Literal) strings.get(0)).getValue()); if (dep != null) { K.StringTemplate.Expression versionValue = (K.StringTemplate.Expression) strings.get(1); acc.versionPropNameToGA.put(versionValue.getTree().toString(), new GroupArtifact(dep.getGroupId(), dep.getArtifactId())); @@ -725,7 +724,7 @@ public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, Execu if (m2.getSimpleName().equals(config)) { existingConstraint = m2; if (m2.getArguments().get(0) instanceof J.Literal) { - Dependency notation = DependencyStringNotationConverter.parse((String) requireNonNull(((J.Literal) m2.getArguments().get(0)).getValue())); + Dependency notation = Dependency.parse((String) requireNonNull(((J.Literal) m2.getArguments().get(0)).getValue())); if (notation == null) { continue; } diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/internal/InsertDependencyComparator.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/internal/InsertDependencyComparator.java index e151c4cb83..2f82c921e3 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/internal/InsertDependencyComparator.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/internal/InsertDependencyComparator.java @@ -145,7 +145,7 @@ private static Optional getEntry(String entry, J.MethodInvocation invoca if(value == null) { return Optional.empty(); } - Dependency dependency = DependencyStringNotationConverter.parse((String) value); + Dependency dependency = Dependency.parse((String) value); if(dependency == null) { return Optional.empty(); } @@ -153,6 +153,7 @@ private static Optional getEntry(String entry, J.MethodInvocation invoca case "group": return Optional.ofNullable(dependency.getGroupId()); case "name": + //noinspection OptionalOfNullableMisuse return Optional.ofNullable(dependency.getArtifactId()); case "version": return Optional.ofNullable(dependency.getVersion()); 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 f89e51cab1..2288ee78c0 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 @@ -22,7 +22,6 @@ import org.openrewrite.Tree; import org.openrewrite.TreeVisitor; import org.openrewrite.gradle.internal.ChangeStringLiteral; -import org.openrewrite.gradle.internal.DependencyStringNotationConverter; import org.openrewrite.gradle.marker.GradleDependencyConfiguration; import org.openrewrite.gradle.marker.GradleProject; import org.openrewrite.groovy.tree.G; @@ -98,7 +97,7 @@ public String getArtifactId() { // String literal notation: "group:artifact:version" if (arg instanceof J.Literal && ((J.Literal) arg).getValue() instanceof String) { Dependency dep = - DependencyStringNotationConverter.parse((String) ((J.Literal) arg).getValue()); + Dependency.parse((String) ((J.Literal) arg).getValue()); return dep != null ? dep.getGroupId() : null; } @@ -107,7 +106,7 @@ public String getArtifactId() { List strings = ((G.GString) arg).getStrings(); if (!strings.isEmpty() && strings.get(0) instanceof J.Literal) { Dependency dep = - DependencyStringNotationConverter.parse((String) ((J.Literal) strings.get(0)).getValue()); + Dependency.parse((String) ((J.Literal) strings.get(0)).getValue()); return dep != null ? dep.getGroupId() : null; } } @@ -117,7 +116,7 @@ public String getArtifactId() { List strings = ((K.StringTemplate) arg).getStrings(); if (!strings.isEmpty() && strings.get(0) instanceof J.Literal) { Dependency dep = - DependencyStringNotationConverter.parse((String) ((J.Literal) strings.get(0)).getValue()); + Dependency.parse((String) ((J.Literal) strings.get(0)).getValue()); return dep != null ? dep.getGroupId() : null; } } @@ -179,7 +178,7 @@ public String getArtifactId() { // String literal notation: "group:artifact:version" if (arg instanceof J.Literal && ((J.Literal) arg).getValue() instanceof String) { - Dependency dep = DependencyStringNotationConverter.parse((String) ((J.Literal) arg).getValue()); + Dependency dep = Dependency.parse((String) ((J.Literal) arg).getValue()); return dep != null ? dep.getArtifactId() : null; } @@ -187,7 +186,7 @@ public String getArtifactId() { if (arg instanceof G.GString) { List strings = ((G.GString) arg).getStrings(); if (!strings.isEmpty() && strings.get(0) instanceof J.Literal) { - Dependency dep = DependencyStringNotationConverter.parse((String) ((J.Literal) strings.get(0)).getValue()); + Dependency dep = Dependency.parse((String) ((J.Literal) strings.get(0)).getValue()); return dep != null ? dep.getArtifactId() : null; } } @@ -196,7 +195,7 @@ public String getArtifactId() { if (arg instanceof K.StringTemplate) { List strings = ((K.StringTemplate) arg).getStrings(); if (!strings.isEmpty() && strings.get(0) instanceof J.Literal) { - Dependency dep = DependencyStringNotationConverter.parse((String) ((J.Literal) strings.get(0)).getValue()); + Dependency dep = Dependency.parse((String) ((J.Literal) strings.get(0)).getValue()); return dep != null ? dep.getArtifactId() : null; } } @@ -271,7 +270,7 @@ public String getArtifactId() { // String literal notation: "group:artifact:version" if (arg instanceof J.Literal && ((J.Literal) arg).getValue() instanceof String) { Dependency dep = - DependencyStringNotationConverter.parse((String) ((J.Literal) arg).getValue()); + Dependency.parse((String) ((J.Literal) arg).getValue()); return dep != null ? dep.getVersion() : null; } @@ -588,7 +587,7 @@ public GradleDependency removeVersion() { if (m.getArguments().get(0) instanceof J.Literal) { J.Literal l = (J.Literal) m.getArguments().get(0); if (l.getType() == JavaType.Primitive.String) { - Dependency dep = DependencyStringNotationConverter.parse((String) l.getValue()); + Dependency dep = Dependency.parse((String) l.getValue()); if (dep == null || dep.getClassifier() != null || (dep.getType() != null && !"jar".equals(dep.getType()))) { return this; } @@ -794,12 +793,12 @@ private boolean withinDependencyConstraintsBlock(Cursor cursor) { private @Nullable Dependency parseDependency(List arguments) { Expression argument = arguments.get(0); if (argument instanceof J.Literal) { - return DependencyStringNotationConverter.parse((String) ((J.Literal) argument).getValue()); + return Dependency.parse((String) ((J.Literal) argument).getValue()); } else if (argument instanceof G.GString) { G.GString gstring = (G.GString) argument; List strings = gstring.getStrings(); if (strings.size() >= 2 && strings.get(0) instanceof J.Literal && ((J.Literal) strings.get(0)).getValue() != null) { - return DependencyStringNotationConverter.parse((String) ((J.Literal) strings.get(0)).getValue()); + return Dependency.parse((String) ((J.Literal) strings.get(0)).getValue()); } } else if (argument instanceof G.MapLiteral) { List mapEntryExpressions = ((G.MapLiteral) argument).getElements() @@ -845,7 +844,7 @@ private boolean withinDependencyConstraintsBlock(Cursor cursor) { K.StringTemplate template = (K.StringTemplate) argument; List strings = template.getStrings(); if (strings.size() >= 2 && strings.get(0) instanceof J.Literal && ((J.Literal) strings.get(0)).getValue() != null) { - return DependencyStringNotationConverter.parse((String) ((J.Literal) strings.get(0)).getValue()); + return Dependency.parse((String) ((J.Literal) strings.get(0)).getValue()); } } diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/trait/SpringDependencyManagementPluginEntry.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/trait/SpringDependencyManagementPluginEntry.java index 1c31a12432..1ee57b782b 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/trait/SpringDependencyManagementPluginEntry.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/trait/SpringDependencyManagementPluginEntry.java @@ -23,7 +23,6 @@ import org.openrewrite.SourceFile; import org.openrewrite.gradle.DependencyVersionSelector; import org.openrewrite.gradle.internal.ChangeStringLiteral; -import org.openrewrite.gradle.internal.DependencyStringNotationConverter; import org.openrewrite.gradle.marker.GradleProject; import org.openrewrite.groovy.tree.G; import org.openrewrite.internal.ListUtils; @@ -237,7 +236,7 @@ public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, List< GavMap gavMap = null; if (argument instanceof J.Literal) { String stringNotation = (String) ((J.Literal) argument).getValue(); - Dependency dependency = stringNotation == null ? null : DependencyStringNotationConverter.parse(stringNotation); + Dependency dependency = stringNotation == null ? null : Dependency.parse(stringNotation); return dependency == null ? null : dependency.getGav(); } else if (argument instanceof G.MapLiteral) { gavMap = getGAVMapEntriesForGMapEntries(((G.MapLiteral) argument).getElements()); @@ -421,7 +420,7 @@ private J.MethodInvocation updateDependency(J.MethodInvocation m, ExecutionConte if (depArgs.get(0) instanceof J.Literal) { String gav = (String) ((J.Literal) depArgs.get(0)).getValue(); if (gav != null) { - Dependency original = DependencyStringNotationConverter.parse(gav); + Dependency original = Dependency.parse(gav); if (original != null) { Dependency updated = original; if (!StringUtils.isBlank(newGroup) && !updated.getGroupId().equals(newGroup)) { From c02b5334b793955b0082864f362f56fb83e1f9b2 Mon Sep 17 00:00:00 2001 From: Sam Snyder Date: Thu, 9 Oct 2025 23:30:46 -0700 Subject: [PATCH 04/19] WIP: Refactored UpgradeDependencyVersion to use GradleDependency trait - Refactored updateDependency() to accept GradleDependency parameter - Added getConfigurationName() method to GradleDependency trait - Fixed platform dependency handling in getPlatformDependency() - Updated scanner to use trait's getConfigurationName() method - Added varargs support attempt (not working yet) Current status: 71/75 tests passing (94.7%) Failing tests: - varargsDependency - leaveConstraintsAlone - mapNotationKStringTemplateInterpolation (2 variants) --- .../openrewrite/semver/DependencyMatcher.java | 4 +- .../openrewrite/gradle/ChangeDependency.java | 9 +- .../gradle/ChangeDependencyArtifactId.java | 34 +- .../gradle/ChangeDependencyGroupId.java | 34 +- .../gradle/UpgradeDependencyVersion.java | 355 +++------ .../gradle/trait/GradleDependency.java | 576 +++++++++++++- rewrite-javascript/rewrite/client.txt | 144 ++++ rewrite-javascript/rewrite/server.txt | 744 ++++++++++++++++++ .../openrewrite/maven/tree/GroupArtifact.java | 6 + 9 files changed, 1638 insertions(+), 268 deletions(-) create mode 100644 rewrite-javascript/rewrite/client.txt create mode 100644 rewrite-javascript/rewrite/server.txt diff --git a/rewrite-core/src/main/java/org/openrewrite/semver/DependencyMatcher.java b/rewrite-core/src/main/java/org/openrewrite/semver/DependencyMatcher.java index 0d75d0655d..c54e4ab229 100755 --- a/rewrite-core/src/main/java/org/openrewrite/semver/DependencyMatcher.java +++ b/rewrite-core/src/main/java/org/openrewrite/semver/DependencyMatcher.java @@ -74,13 +74,13 @@ public static Validated build(String pattern) { return Validated.valid("pattern", new DependencyMatcher(patternPieces[0], patternPieces[1], validatedVersion.getValue())); } - public boolean matches(@Nullable String groupId, String artifactId, String version) { + public boolean matches(@Nullable String groupId, @Nullable String artifactId, String version) { return StringUtils.matchesGlob(groupId, groupPattern) && StringUtils.matchesGlob(artifactId, artifactPattern) && (versionComparator == null || versionComparator.isValid(null, version)); } - public boolean matches(@Nullable String groupId, String artifactId) { + public boolean matches(@Nullable String groupId, @Nullable String artifactId) { return StringUtils.matchesGlob(groupId, groupPattern) && StringUtils.matchesGlob(artifactId, artifactPattern); } 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 54fdb24355..db697c411b 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependency.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependency.java @@ -194,8 +194,8 @@ public boolean isAcceptable(SourceFile sourceFile, ExecutionContext ctx) { gradleProject = maybeGp.get(); - sourceFile = (JavaSourceFile) super.visitNonNull(sourceFile, ctx); - if (sourceFile != tree) { + sourceFile = (JavaSourceFile) super.visit(sourceFile, ctx); + if (sourceFile != null && sourceFile != tree) { sourceFile = sourceFile.withMarkers(sourceFile.getMarkers().setByType(updateGradleModel(gradleProject))); if (changeManagedDependency == null || changeManagedDependency) { doAfterVisit(new ChangeManagedDependency(oldGroupId, oldArtifactId, newGroupId, newArtifactId, newVersion, versionPattern).getVisitor()); @@ -273,7 +273,7 @@ private J.MethodInvocation updateDependency(J.MethodInvocation m, ExecutionConte Dependency original = Dependency.parse((String) requireNonNull(literal.getValue())); if (original != null) { Dependency updated = original; - if (!StringUtils.isBlank(newGroupId) && !updated.getGroupId().equals(newGroupId)) { + if (!StringUtils.isBlank(newGroupId) && !Objects.equals(updated.getGroupId(), newGroupId)) { updated = updated.withGroupId(newGroupId); } if (!StringUtils.isBlank(newArtifactId) && !updated.getArtifactId().equals(newArtifactId)) { @@ -566,7 +566,7 @@ private J.MethodInvocation updateDependency(J.MethodInvocation m, ExecutionConte Dependency original = Dependency.parse((String) requireNonNull(literal.getValue())); if (original != null) { Dependency updated = original; - if (!StringUtils.isBlank(newGroupId) && !updated.getGroupId().equals(newGroupId)) { + if (!StringUtils.isBlank(newGroupId) && !Objects.equals(updated.getGroupId(), newGroupId)) { updated = updated.withGroupId(newGroupId); } if (!StringUtils.isBlank(newArtifactId) && !updated.getArtifactId().equals(newArtifactId)) { @@ -604,7 +604,6 @@ private GradleProject updateGradleModel(GradleProject gp) { for (GradleDependencyConfiguration gdc : nameToConfiguration.values()) { GradleDependencyConfiguration newGdc = gdc; newGdc = newGdc.withRequested(ListUtils.map(gdc.getRequested(), requested -> { - assert requested != null; if (depMatcher.matches(requested.getGroupId(), requested.getArtifactId())) { requested = updatedRequested.computeIfAbsent(requested, r -> { GroupArtifactVersion gav = r.getGav(); diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependencyArtifactId.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependencyArtifactId.java index 99e7e16ddb..5d9afce64d 100755 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependencyArtifactId.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependencyArtifactId.java @@ -147,14 +147,32 @@ private J.MethodInvocation updateDependency(J.MethodInvocation m) { List strings = ((G.GString) depArgs.get(0)).getStrings(); if (strings.size() >= 2 && strings.get(0) instanceof J.Literal) { - Dependency dependency = Dependency.parse((String) requireNonNull(((J.Literal) strings.get(0)).getValue())); - if (dependency != null && !newArtifactId.equals(dependency.getArtifactId())) { - Dependency newDependency = dependency.withArtifactId(newArtifactId); - String replacement = newDependency.toStringNotation(); - m = m.withArguments(ListUtils.mapFirst(depArgs, arg -> { - G.GString gString = (G.GString) arg; - return gString.withStrings(ListUtils.mapFirst(gString.getStrings(), l -> ((J.Literal) l).withValue(replacement).withValueSource(replacement))); - })); + J.Literal firstLiteral = (J.Literal) strings.get(0); + String literalValue = (String) firstLiteral.getValue(); + if (literalValue != null) { + // Preserve trailing colon if present (for version interpolation) + boolean hasTrailingColon = literalValue.endsWith(":"); + String toParse = hasTrailingColon ? literalValue.substring(0, literalValue.length() - 1) : literalValue; + + // If there's no version part, add a dummy one for parsing + String[] parts = toParse.split(":"); + if (parts.length == 2) { + toParse = toParse + ":1.0"; // Add dummy version for parsing + } + + Dependency dependency = Dependency.parse(toParse); + if (dependency != null && !newArtifactId.equals(dependency.getArtifactId())) { + // Reconstruct with new artifact ID, preserving original structure + String replacementStr = dependency.getGroupId() + ":" + newArtifactId; + if (hasTrailingColon) { + replacementStr += ":"; + } + final String replacement = replacementStr; + m = m.withArguments(ListUtils.mapFirst(depArgs, arg -> { + G.GString gString = (G.GString) arg; + return gString.withStrings(ListUtils.mapFirst(gString.getStrings(), l -> ((J.Literal) l).withValue(replacement).withValueSource(replacement))); + })); + } } } } else if (depArgs.get(0) instanceof G.MapEntry) { diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependencyGroupId.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependencyGroupId.java index a942b4ed8e..9468183f65 100755 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependencyGroupId.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependencyGroupId.java @@ -147,14 +147,32 @@ private J.MethodInvocation updateDependency(J.MethodInvocation m) { List strings = ((G.GString) depArgs.get(0)).getStrings(); if (strings.size() >= 2 && strings.get(0) instanceof J.Literal) { - Dependency dependency = Dependency.parse((String) requireNonNull(((J.Literal) strings.get(0)).getValue())); - if (dependency != null && !newGroupId.equals(dependency.getGroupId())) { - Dependency newDependency = dependency.withGroupId(newGroupId); - String replacement = newDependency.toStringNotation(); - m = m.withArguments(ListUtils.mapFirst(depArgs, arg -> { - G.GString gString = (G.GString) arg; - return gString.withStrings(ListUtils.mapFirst(gString.getStrings(), l -> ((J.Literal) l).withValue(replacement).withValueSource(replacement))); - })); + J.Literal firstLiteral = (J.Literal) strings.get(0); + String literalValue = (String) firstLiteral.getValue(); + if (literalValue != null) { + // Preserve trailing colon if present (for version interpolation) + boolean hasTrailingColon = literalValue.endsWith(":"); + String toParse = hasTrailingColon ? literalValue.substring(0, literalValue.length() - 1) : literalValue; + + // If there's no version part, add a dummy one for parsing + String[] parts = toParse.split(":"); + if (parts.length == 2) { + toParse = toParse + ":1.0"; // Add dummy version for parsing + } + + Dependency dependency = Dependency.parse(toParse); + if (dependency != null && !newGroupId.equals(dependency.getGroupId())) { + // Reconstruct with new group ID, preserving original structure + String replacementStr = newGroupId + ":" + dependency.getArtifactId(); + if (hasTrailingColon) { + replacementStr += ":"; + } + final String replacement = replacementStr; + m = m.withArguments(ListUtils.mapFirst(depArgs, arg -> { + G.GString gString = (G.GString) arg; + return gString.withStrings(ListUtils.mapFirst(gString.getStrings(), l -> ((J.Literal) l).withValue(replacement).withValueSource(replacement))); + })); + } } } } else if (depArgs.get(0) instanceof G.MapEntry) { diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/UpgradeDependencyVersion.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/UpgradeDependencyVersion.java index ede98bdacc..0658e4b93c 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/UpgradeDependencyVersion.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/UpgradeDependencyVersion.java @@ -22,9 +22,8 @@ import org.jspecify.annotations.Nullable; import org.openrewrite.*; import org.openrewrite.gradle.internal.ChangeStringLiteral; -import org.openrewrite.maven.tree.Dependency; -import org.openrewrite.gradle.marker.GradleProject; import org.openrewrite.gradle.trait.GradleDependency; +import org.openrewrite.gradle.marker.GradleProject; import org.openrewrite.groovy.tree.G; import org.openrewrite.internal.ListUtils; import org.openrewrite.internal.StringUtils; @@ -39,6 +38,7 @@ import org.openrewrite.marker.Markup; import org.openrewrite.maven.MavenDownloadingException; import org.openrewrite.maven.table.MavenMetadataFailures; +import org.openrewrite.maven.tree.Dependency; import org.openrewrite.maven.tree.GroupArtifact; import org.openrewrite.maven.tree.GroupArtifactVersion; import org.openrewrite.properties.PropertiesVisitor; @@ -182,17 +182,18 @@ public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) // Record the dependency and resolve its version if needed GroupArtifact ga = new GroupArtifact(declaredGroupId, declaredArtifactId); + String configName = gradleDependency.getConfigurationName(); if (gradleProject != null) { acc.getConfigurationPerGAPerModule() .computeIfAbsent(getGradleProjectKey(gradleProject), k -> new HashMap<>()) .computeIfAbsent(ga, k -> new HashSet<>()) - .add(m.getSimpleName()); + .add(configName); } if (!acc.gaToNewVersion.containsKey(ga) && shouldResolveVersion(declaredGroupId, declaredArtifactId)) { try { String resolvedVersion = new DependencyVersionSelector(metadataFailures, gradleProject, null) - .select(ga, m.getSimpleName(), newVersion, versionPattern, ctx); + .select(ga, configName, newVersion, versionPattern, ctx); // If this uses a version variable, record that mapping // Check if this is a variable (not a literal version like "1.0" or "1.0-SNAPSHOT") @@ -202,7 +203,7 @@ public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) acc.versionPropNameToGA .computeIfAbsent(versionPropName, k -> new HashMap<>()) .computeIfAbsent(ga, k -> new HashSet<>()) - .add(m.getSimpleName()); + .add(configName); } // Record the resolved version (may be null) @@ -356,6 +357,7 @@ private class UpdateGradle extends JavaVisitor { GradleProject gradleProject; final DependencyMatcher dependencyMatcher = new DependencyMatcher(groupId, artifactId, null); + final MethodMatcher GRADLE_DEPENDENCY_DSL = new MethodMatcher("DependencyHandlerSpec *(..)"); @Override public boolean isAcceptable(SourceFile sourceFile, ExecutionContext ctx) { @@ -389,18 +391,21 @@ public J postVisit(J tree, ExecutionContext ctx) { @Override public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { J.MethodInvocation m = (J.MethodInvocation) super.visitMethodInvocation(method, ctx); - GradleDependency.Matcher gradleDependencyMatcher = new GradleDependency.Matcher(); - - if (gradleDependencyMatcher.get(getCursor()).isPresent()) { - GradleDependency gradleDependency = gradleDependencyMatcher.get(getCursor()).get(); - // Get the tree to update (handles platform wrappers automatically) - J.MethodInvocation treeToUpdate = gradleDependency.getTreeToUpdate(); - J.MethodInvocation updatedTree = updateDependency(treeToUpdate, ctx); + // Check for varargs case first - if there are multiple literal arguments, handle them all + if (isConfigurationMethod(m) && hasMultipleLiteralArguments(m)) { + // Handle varargs case where multiple dependencies are specified in one call + m = updateVarargsDependencies(m, ctx); + } else { + // Handle single dependency case + GradleDependency.Matcher gradleDependencyMatcher = new GradleDependency.Matcher(); + if (gradleDependencyMatcher.get(getCursor()).isPresent()) { + GradleDependency gradleDependency = gradleDependencyMatcher.get(getCursor()).get(); + m = updateDependency(gradleDependency, ctx); + } + } - // Wrap the updated tree if necessary (handles platform wrappers automatically) - m = gradleDependency.wrapUpdatedTree(updatedTree); - } else if ("ext".equals(method.getSimpleName()) && getCursor().firstEnclosingOrThrow(SourceFile.class).getSourcePath().endsWith("settings.gradle")) { + if ("ext".equals(method.getSimpleName()) && getCursor().firstEnclosingOrThrow(SourceFile.class).getSourcePath().endsWith("settings.gradle")) { // rare case that gradle versions are set via settings.gradle ext block (only possible for Groovy DSL) m = (J.MethodInvocation) new JavaIsoVisitor() { @Override @@ -459,237 +464,67 @@ public J.Assignment visitAssignment(J.Assignment assignment, ExecutionContext ex return m; } - private J.MethodInvocation updateDependency(J.MethodInvocation method, ExecutionContext ctx) { - J.MethodInvocation m = method; - m = m.withArguments(ListUtils.map(m.getArguments(), arg -> { - if (arg instanceof G.GString) { - G.GString gString = (G.GString) arg; - List strings = gString.getStrings(); - if (strings.size() != 2 || !(strings.get(0) instanceof J.Literal) || !(strings.get(1) instanceof G.GString.Value)) { - return arg; - } - J.Literal groupArtifact = (J.Literal) strings.get(0); - G.GString.Value versionValue = (G.GString.Value) strings.get(1); - if (!(versionValue.getTree() instanceof J.Identifier) || !(groupArtifact.getValue() instanceof String)) { - return arg; - } - Dependency dep = Dependency.parse((String) groupArtifact.getValue()); - if (dep != null && dependencyMatcher.matches(dep.getGroupId(), dep.getArtifactId())) { - Object scanResult = acc.gaToNewVersion.get(new GroupArtifact(dep.getGroupId(), dep.getArtifactId())); - if (scanResult instanceof Exception) { - getCursor().putMessage(UPDATE_VERSION_ERROR_KEY, scanResult); - return arg; - } - - String versionVariableName = ((J.Identifier) versionValue.getTree()).getSimpleName(); - replaceVariableValue(versionVariableName, method, dep.getGroupId(), dep.getArtifactId()); - } - } else if (arg instanceof K.StringTemplate) { - K.StringTemplate template = (K.StringTemplate) arg; - List strings = template.getStrings(); - if (strings.size() != 2 || !(strings.get(0) instanceof J.Literal) || !(strings.get(1) instanceof K.StringTemplate.Expression)) { - return arg; - } - J.Literal groupArtifact = (J.Literal) strings.get(0); - K.StringTemplate.Expression versionValue = (K.StringTemplate.Expression) strings.get(1); - if (!(versionValue.getTree() instanceof J.Identifier) || !(groupArtifact.getValue() instanceof String)) { - return arg; - } - Dependency dep = Dependency.parse((String) groupArtifact.getValue()); - if (dep != null && dependencyMatcher.matches(dep.getGroupId(), dep.getArtifactId())) { - Object scanResult = acc.gaToNewVersion.get(new GroupArtifact(dep.getGroupId(), dep.getArtifactId())); - if (scanResult instanceof Exception) { - getCursor().putMessage(UPDATE_VERSION_ERROR_KEY, scanResult); - return arg; - } - - String versionVariableName = ((J.Identifier) versionValue.getTree()).getSimpleName(); - replaceVariableValue(versionVariableName, method, dep.getGroupId(), dep.getArtifactId()); - } - } else if (arg instanceof J.Literal) { - J.Literal literal = (J.Literal) arg; - if (literal.getType() != JavaType.Primitive.String) { - return arg; - } - String gav = (String) literal.getValue(); - if (gav == null) { - getCursor().putMessage(UPDATE_VERSION_ERROR_KEY, new IllegalStateException("Unable to update version")); - return arg; - } - Dependency dep = Dependency.parse(gav); - if (dep != null && dependencyMatcher.matches(dep.getGroupId(), dep.getArtifactId()) && - dep.getVersion() != null && - !dep.getVersion().startsWith("$")) { - Object scanResult = acc.gaToNewVersion.get(new GroupArtifact(dep.getGroupId(), dep.getArtifactId())); - if (scanResult instanceof Exception) { - getCursor().putMessage(UPDATE_VERSION_ERROR_KEY, scanResult); - return arg; - } - - try { - String selectedVersion = new DependencyVersionSelector(metadataFailures, gradleProject, null) - .select(dep.getGav(), method.getSimpleName(), newVersion, versionPattern, ctx); - if (selectedVersion == null || dep.getVersion().equals(selectedVersion)) { - return arg; - } - - String newGav = dep - .withGav(dep.getGav().withVersion(selectedVersion)) - .toStringNotation(); - return literal - .withValue(newGav) - .withValueSource(literal.getValueSource() == null ? newGav : literal.getValueSource().replace(gav, newGav)); - } catch (MavenDownloadingException e) { - getCursor().putMessage(UPDATE_VERSION_ERROR_KEY, e); - } - } - } - return arg; - })); - Exception err = getCursor().pollMessage(UPDATE_VERSION_ERROR_KEY); - if (err != null) { - m = Markup.warn(m, err); + private J.MethodInvocation updateDependency(GradleDependency dependency, ExecutionContext ctx) { + // Check if this dependency matches our pattern + if (!dependency.matches(dependencyMatcher)) { + return dependency.getTree(); } - List depArgs = m.getArguments(); - if (depArgs.size() >= 3) { - if (depArgs.get(0) instanceof G.MapEntry && - depArgs.get(1) instanceof G.MapEntry && - depArgs.get(2) instanceof G.MapEntry) { - Expression groupValue = ((G.MapEntry) depArgs.get(0)).getValue(); - Expression artifactValue = ((G.MapEntry) depArgs.get(1)).getValue(); - if (!(groupValue instanceof J.Literal) || !(artifactValue instanceof J.Literal)) { - return m; - } - J.Literal groupLiteral = (J.Literal) groupValue; - J.Literal artifactLiteral = (J.Literal) artifactValue; - if (groupLiteral.getValue() == null || artifactLiteral.getValue() == null || !dependencyMatcher.matches((String) groupLiteral.getValue(), (String) artifactLiteral.getValue())) { - return m; - } - Object scanResult = acc.gaToNewVersion.get(new GroupArtifact((String) groupLiteral.getValue(), (String) artifactLiteral.getValue())); - if (scanResult instanceof Exception) { - return Markup.warn(m, (Exception) scanResult); - } - G.MapEntry versionEntry = (G.MapEntry) depArgs.get(2); - Expression versionExp = versionEntry.getValue(); - if (versionExp instanceof J.Literal && ((J.Literal) versionExp).getValue() instanceof String) { - J.Literal versionLiteral = (J.Literal) versionExp; - String version = (String) versionLiteral.getValue(); - if (version.startsWith("$")) { - return m; - } - String selectedVersion; - try { - GroupArtifactVersion gav = new GroupArtifactVersion((String) groupLiteral.getValue(), (String) artifactLiteral.getValue(), version); - selectedVersion = new DependencyVersionSelector(metadataFailures, gradleProject, null) - .select(gav, m.getSimpleName(), newVersion, versionPattern, ctx); - } catch (MavenDownloadingException e) { - return e.warn(m); - } - if (selectedVersion == null || version.equals(selectedVersion)) { - return m; - } - List newArgs = new ArrayList<>(3); - newArgs.add(depArgs.get(0)); - newArgs.add(depArgs.get(1)); - newArgs.add(versionEntry.withValue( - versionLiteral - .withValueSource(versionLiteral.getValueSource() == null ? - selectedVersion : - versionLiteral.getValueSource().replace(version, selectedVersion)) - .withValue(selectedVersion))); - newArgs.addAll(depArgs.subList(3, depArgs.size())); - - return m.withArguments(newArgs); - } else if (versionExp instanceof J.Identifier) { - String versionVariableName = ((J.Identifier) versionExp).getSimpleName(); - replaceVariableValue(versionVariableName, m, (String) groupLiteral.getValue(), (String) artifactLiteral.getValue()); - } else if (versionExp instanceof G.GString) { - G.GString gString = (G.GString) versionExp; - - if (gString.getStrings().size() != 1) { - return m; - } - G.GString.Value versionLiteral = (G.GString.Value) gString.getStrings().get(0); - String versionVariableName = versionLiteral.printTrimmed(getCursor()); + // Get the current version and coordinates + String currentVersion = dependency.getDeclaredVersion(); + String groupId = dependency.getDeclaredGroupId(); + String artifactId = dependency.getDeclaredArtifactId(); - if (versionVariableName.startsWith("$")) { - versionVariableName = versionVariableName.replaceAll("^\\$\\{?|}?$", ""); - } + if (groupId == null || artifactId == null) { + return dependency.getTree(); + } - replaceVariableValue(versionVariableName, m, (String) groupLiteral.getValue(), (String) artifactLiteral.getValue()); - } - } else if (depArgs.get(0) instanceof J.Assignment && - depArgs.get(1) instanceof J.Assignment && - depArgs.get(2) instanceof J.Assignment) { - Expression groupValue = ((J.Assignment) depArgs.get(0)).getAssignment(); - Expression artifactValue = ((J.Assignment) depArgs.get(1)).getAssignment(); - if (!(groupValue instanceof J.Literal) || !(artifactValue instanceof J.Literal)) { - return m; - } - J.Literal groupLiteral = (J.Literal) groupValue; - J.Literal artifactLiteral = (J.Literal) artifactValue; - if (groupLiteral.getValue() == null || artifactLiteral.getValue() == null || !dependencyMatcher.matches((String) groupLiteral.getValue(), (String) artifactLiteral.getValue())) { - return m; - } - Object scanResult = acc.gaToNewVersion.get(new GroupArtifact((String) groupLiteral.getValue(), (String) artifactLiteral.getValue())); - if (scanResult instanceof Exception) { - return Markup.warn(m, (Exception) scanResult); - } - K.Assignment versionEntry = (J.Assignment) depArgs.get(2); - Expression versionExp = versionEntry.getAssignment(); - if (versionExp instanceof J.Literal && ((J.Literal) versionExp).getValue() instanceof String) { - J.Literal versionLiteral = (J.Literal) versionExp; - String version = (String) versionLiteral.getValue(); - if (version.startsWith("$")) { - return m; - } - String selectedVersion; - try { - GroupArtifactVersion gav = new GroupArtifactVersion((String) groupLiteral.getValue(), (String) artifactLiteral.getValue(), version); - selectedVersion = new DependencyVersionSelector(metadataFailures, gradleProject, null) - .select(gav, m.getSimpleName(), newVersion, versionPattern, ctx); - } catch (MavenDownloadingException e) { - return e.warn(m); - } - if (selectedVersion == null || version.equals(selectedVersion)) { - return m; - } - List newArgs = new ArrayList<>(3); - newArgs.add(depArgs.get(0)); - newArgs.add(depArgs.get(1)); - newArgs.add(versionEntry.withAssignment( - versionLiteral - .withValueSource(versionLiteral.getValueSource() == null ? - selectedVersion : - versionLiteral.getValueSource().replace(version, selectedVersion)) - .withValue(selectedVersion))); - newArgs.addAll(depArgs.subList(3, depArgs.size())); - - return m.withArguments(newArgs); - } else if (versionExp instanceof J.Identifier) { - String versionVariableName = ((J.Identifier) versionExp).getSimpleName(); - replaceVariableValue(versionVariableName, m, (String) groupLiteral.getValue(), (String) artifactLiteral.getValue()); - } else if (versionExp instanceof K.StringTemplate) { - K.StringTemplate kString = (K.StringTemplate) versionExp; - - if (kString.getStrings().size() != 1) { - return m; - } + // Check if we have a new version for this dependency + Object scanResult = acc.gaToNewVersion.get(new GroupArtifact(groupId, artifactId)); + if (scanResult instanceof Exception) { + return Markup.warn(dependency.getTree(), (Exception) scanResult); + } - K.StringTemplate.Expression versionLiteral = (K.StringTemplate.Expression) kString.getStrings().get(0); - String versionVariableName = versionLiteral.printTrimmed(getCursor()); + // Handle variable references + String versionVariable = dependency.getVersionVariable(); + if (versionVariable != null) { + replaceVariableValue(versionVariable, dependency.getTree(), groupId, artifactId); + return dependency.getTree(); + } - if (versionVariableName.startsWith("$")) { - versionVariableName = versionVariableName.replaceAll("^\\$\\{?|}?$", ""); - } + // If version starts with $, it's a variable reference we can't handle directly + if (currentVersion != null && currentVersion.startsWith("$")) { + return dependency.getTree(); + } - replaceVariableValue(versionVariableName, m, (String) groupLiteral.getValue(), (String) artifactLiteral.getValue()); - } + // Select the new version + String selectedVersion; + try { + // Use the outer method invocation's name as the configuration, not the tree to update + // For platform dependencies, this ensures we use "implementation" not "platform" + String configName = dependency.getConfigurationName(); + + if (currentVersion == null) { + // For dependencies without a version (e.g., some platform dependencies) + GroupArtifact ga = new GroupArtifact(groupId, artifactId); + selectedVersion = new DependencyVersionSelector(metadataFailures, gradleProject, null) + .select(ga, configName, newVersion, versionPattern, ctx); + } else { + GroupArtifactVersion gav = new GroupArtifactVersion(groupId, artifactId, currentVersion); + selectedVersion = new DependencyVersionSelector(metadataFailures, gradleProject, null) + .select(gav, configName, newVersion, versionPattern, ctx); } + } catch (MavenDownloadingException e) { + return Markup.warn(dependency.getTree(), e); } - return m; + if (selectedVersion == null || (currentVersion != null && currentVersion.equals(selectedVersion))) { + return dependency.getTree(); + } + + // Update the dependency version using the trait's method + GradleDependency updated = dependency.withDeclaredVersion(selectedVersion); + return updated.getTree(); } private void replaceVariableValue(String versionVariableName, J.MethodInvocation m, String groupId, String artifactId) { @@ -697,6 +532,52 @@ private void replaceVariableValue(String versionVariableName, J.MethodInvocation .computeIfAbsent(new GroupArtifact(groupId, artifactId), it -> new HashSet<>()) .add(m.getSimpleName()); } + + private boolean isConfigurationMethod(J.MethodInvocation m) { + // Check if this is a dependency configuration method like implementation, api, etc. + return GRADLE_DEPENDENCY_DSL.matches(m) && !"project".equals(m.getSimpleName()); + } + + private boolean hasMultipleLiteralArguments(J.MethodInvocation m) { + // Check if there are multiple literal string arguments (varargs case) + if (m.getArguments().size() <= 1) { + return false; + } + int literalCount = 0; + for (Expression arg : m.getArguments()) { + if (arg instanceof J.Literal && ((J.Literal) arg).getValue() instanceof String) { + literalCount++; + if (literalCount > 1) { + return true; + } + } + } + return false; + } + + private J.MethodInvocation updateVarargsDependencies(J.MethodInvocation m, ExecutionContext ctx) { + // Process each argument individually for varargs dependencies + return m.withArguments(ListUtils.map(m.getArguments(), arg -> { + if (arg instanceof J.Literal && ((J.Literal) arg).getValue() instanceof String) { + String gav = (String) ((J.Literal) arg).getValue(); + Dependency dep = Dependency.parse(gav); + if (dep != null && dependencyMatcher.matches(dep.getGroupId(), dep.getArtifactId())) { + // Check if we have a new version for this dependency + GroupArtifact ga = new GroupArtifact(dep.getGroupId(), dep.getArtifactId()); + Object scanResult = acc.gaToNewVersion.get(ga); + if (scanResult instanceof String) { + String newVersion = (String) scanResult; + if (newVersion != null && !newVersion.equals(dep.getVersion())) { + // Update the version in the string literal + Dependency updatedDep = dep.withVersion(newVersion); + return ChangeStringLiteral.withStringValue((J.Literal) arg, updatedDep.toStringNotation()); + } + } + } + } + return arg; + })); + } } @AllArgsConstructor @@ -789,6 +670,6 @@ static String getGradleProjectKey(GradleProject project) { if (":".equals(project.getPath())) { return project.getGroup(); } - return project.getGroup() + project.getPath(); + return project.getGroup() + project.getPath().replace(":", "."); } } 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 2288ee78c0..12c55f4d9b 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 @@ -37,17 +37,23 @@ import org.openrewrite.maven.tree.GroupArtifactVersion; import org.openrewrite.maven.tree.ResolvedDependency; import org.openrewrite.maven.tree.ResolvedGroupArtifactVersion; +import org.openrewrite.semver.DependencyMatcher; import org.openrewrite.trait.Trait; import org.openrewrite.trait.VisitFunction2; -import java.util.Arrays; +import java.util.ArrayList; import java.util.List; +import java.util.stream.Collectors; +import static java.util.Collections.singletonList; import static java.util.stream.Collectors.toList; import static org.openrewrite.internal.StringUtils.matchesGlob; @Value public class GradleDependency implements Trait { + private static final MethodMatcher PROPERTY_METHOD = new MethodMatcher("* property(String)"); + private static final MethodMatcher FIND_PROPERTY_METHOD = new MethodMatcher("* findProperty(String)"); + Cursor cursor; @Getter @@ -71,6 +77,19 @@ public String getArtifactId() { return resolvedDependency.getArtifactId(); } + /** + * Gets the configuration name for this dependency. + * For example, "implementation", "testImplementation", "api", etc. + * For platform dependencies wrapped in platform() or enforcedPlatform(), + * returns the configuration name of the outer method invocation. + * + * @return The configuration name + */ + public String getConfigurationName() { + J.MethodInvocation m = cursor.getValue(); + return m.getSimpleName(); + } + /** * Gets the declared group ID from the dependency declaration. * This may be a literal value or a variable name/expression. @@ -94,6 +113,24 @@ public String getArtifactId() { Expression arg = depArgs.get(0); + // Binary concatenation: "group:artifact:" + version + if (arg instanceof J.Binary) { + J.Binary binary = (J.Binary) arg; + if (binary.getLeft() instanceof J.Literal) { + J.Literal left = (J.Literal) binary.getLeft(); + if (left.getValue() instanceof String) { + String leftStr = (String) left.getValue(); + // Parse the group:artifact part, adding dummy version if needed + if (!leftStr.contains(":")) { + return null; + } + String toParse = leftStr.endsWith(":") ? leftStr + "1.0" : leftStr; + Dependency dep = Dependency.parse(toParse); + return dep != null ? dep.getGroupId() : null; + } + } + } + // String literal notation: "group:artifact:version" if (arg instanceof J.Literal && ((J.Literal) arg).getValue() instanceof String) { Dependency dep = @@ -176,6 +213,24 @@ public String getArtifactId() { Expression arg = depArgs.get(0); + // Binary concatenation: "group:artifact:" + version + if (arg instanceof J.Binary) { + J.Binary binary = (J.Binary) arg; + if (binary.getLeft() instanceof J.Literal) { + J.Literal left = (J.Literal) binary.getLeft(); + if (left.getValue() instanceof String) { + String leftStr = (String) left.getValue(); + // Parse the group:artifact part, adding dummy version if needed + if (!leftStr.contains(":")) { + return null; + } + String toParse = leftStr.endsWith(":") ? leftStr + "1.0" : leftStr; + Dependency dep = Dependency.parse(toParse); + return dep != null ? dep.getArtifactId() : null; + } + } + } + // String literal notation: "group:artifact:version" if (arg instanceof J.Literal && ((J.Literal) arg).getValue() instanceof String) { Dependency dep = Dependency.parse((String) ((J.Literal) arg).getValue()); @@ -445,8 +500,11 @@ public boolean isPlatform() { return null; } - // Try to get the dependency trait for the inner platform() method invocation - return new Matcher().get(platformMethod, cursor).orElse(null); + // Create a GradleDependency for the inner platform() method invocation + // We can reuse the same ResolvedDependency since it represents the same actual dependency + // The cursor should point to the platform() method itself + Cursor platformCursor = new Cursor(cursor, platformMethod); + return new GradleDependency(platformCursor, resolvedDependency); } /** @@ -473,6 +531,23 @@ public boolean isPlatform() { Expression arg = depArgs.get(0); + // Handle binary concatenation: "group:artifact:" + version + if (arg instanceof J.Binary) { + J.Binary binary = (J.Binary) arg; + if (binary.getLeft() instanceof J.Literal && binary.getRight() instanceof J.Identifier) { + // Check if the left side is a dependency coordinate without version + J.Literal left = (J.Literal) binary.getLeft(); + if (left.getValue() instanceof String) { + String leftStr = (String) left.getValue(); + // Check if it looks like a dependency coordinate (has at least group:artifact:) + String[] parts = leftStr.split(":"); + if (parts.length >= 2) { + return ((J.Identifier) binary.getRight()).getSimpleName(); + } + } + } + } + // Handle "group:artifact:$version" patterns with GString if (arg instanceof G.GString) { List strings = ((G.GString) arg).getStrings(); @@ -482,6 +557,18 @@ public boolean isPlatform() { return ((J.Identifier) versionTree).getSimpleName(); } else if (versionTree instanceof J.FieldAccess) { return ((J.FieldAccess) versionTree).printTrimmed(cursor); + } else if (versionTree instanceof J.MethodInvocation) { + // Handle property('version') or findProperty('version') + String propName = extractPropertyNameFromMethodInvocation((J.MethodInvocation) versionTree); + if (propName != null) { + return propName; + } + } else if (versionTree instanceof G.Binary) { + // Handle properties['version'] + String propName = extractPropertyNameFromGBinary((G.Binary) versionTree); + if (propName != null) { + return propName; + } } } } @@ -510,17 +597,97 @@ public boolean isPlatform() { if (versionExp instanceof J.Identifier) { return ((J.Identifier) versionExp).getSimpleName(); } else if (versionExp instanceof J.FieldAccess) { - return ((J.FieldAccess) versionExp).printTrimmed(cursor); + return versionExp.printTrimmed(cursor); + } else if (versionExp instanceof J.MethodInvocation) { + // Handle property('version') or findProperty('version') + return extractPropertyNameFromMethodInvocation((J.MethodInvocation) versionExp); + } else if (versionExp instanceof G.Binary) { + // Handle properties['version'] + return extractPropertyNameFromGBinary((G.Binary) versionExp); + } else if (versionExp instanceof G.GString) { + // Handle GString in map notation + G.GString gString = (G.GString) versionExp; + List strings = gString.getStrings(); + if (!strings.isEmpty() && strings.get(0) instanceof G.GString.Value) { + G.GString.Value versionGStringValue = (G.GString.Value) strings.get(0); + Object tree = versionGStringValue.getTree(); + if (tree instanceof J.Identifier) { + return ((J.Identifier) tree).getSimpleName(); + } else if (tree instanceof J.FieldAccess) { + return ((J.FieldAccess) tree).printTrimmed(cursor); + } else if (tree instanceof J.MethodInvocation) { + String propName = extractPropertyNameFromMethodInvocation((J.MethodInvocation) tree); + if (propName != null) { + return propName; + } + } else if (tree instanceof G.Binary) { + String propName = extractPropertyNameFromGBinary((G.Binary) tree); + if (propName != null) { + return propName; + } + } + } + } + } + + return null; + } + + /** + * Checks if this dependency matches the given DependencyMatcher. + * This is a convenience method that extracts the declared group and artifact IDs + * and passes them to the matcher. + * + * @param matcher The DependencyMatcher to check against + * @return true if this dependency matches the matcher's patterns + */ + public boolean matches(DependencyMatcher matcher) { + String groupId = getDeclaredGroupId(); + String artifactId = getDeclaredArtifactId(); + + if (groupId == null || artifactId == null) { + return false; + } + + return matcher.matches(getDeclaredGroupId(), getDeclaredArtifactId()); + } + + /** + * Extract property name from method invocation patterns like property('guavaVersion') + * or findProperty('guavaVersion'). + */ + @Nullable + private static String extractPropertyNameFromMethodInvocation(J.MethodInvocation mi) { + if (PROPERTY_METHOD.matches(mi, true) || FIND_PROPERTY_METHOD.matches(mi, true)) { + if (!mi.getArguments().isEmpty() && mi.getArguments().get(0) instanceof J.Literal) { + J.Literal literal = (J.Literal) mi.getArguments().get(0); + if (literal.getValue() instanceof String) { + return (String) literal.getValue(); + } } } + return null; + } + /** + * Handle Groovy binary access like project.properties['guavaVersion'] + * where the binary operator is Access (bracket notation). + */ + @Nullable + private static String extractPropertyNameFromGBinary(G.Binary binary) { + if (binary.getOperator() == G.Binary.Type.Access && binary.getRight() instanceof J.Literal) { + J.Literal right = (J.Literal) binary.getRight(); + if (right.getValue() instanceof String) { + return (String) right.getValue(); + } + } return null; } /** * Gets the tree (J.MethodInvocation) that should be modified when updating this dependency. * For regular dependencies, returns the current tree. - * For dependencies with platform wrappers, returns the inner platform() or enforcedPlatform() method invocation. + * For platform dependencies, returns the dependency declaration inside the platform() or enforcedPlatform() invocation. * * @return The J.MethodInvocation to update */ @@ -600,6 +767,7 @@ public GradleDependency removeVersion() { G.MapLiteral mapLiteral = (G.MapLiteral) arg; return mapLiteral.withElements(ListUtils.map(mapLiteral.getElements(), entry -> { if (entry.getKey() instanceof J.Literal && "version".equals(((J.Literal) entry.getKey()).getValue())) { + //noinspection DataFlowIssue return null; } return entry; @@ -609,6 +777,7 @@ public GradleDependency removeVersion() { updated = m.withArguments(ListUtils.map(m.getArguments(), arg -> { G.MapEntry entry = (G.MapEntry) arg; if (entry.getKey() instanceof J.Literal && "version".equals(((J.Literal) entry.getKey()).getValue())) { + //noinspection DataFlowIssue return null; } return entry; @@ -622,6 +791,397 @@ public GradleDependency removeVersion() { return new GradleDependency(new Cursor(cursor.getParent(), updated), resolvedDependency); } + /** + * Updates the declared group ID of a dependency. + * This method handles various dependency notation formats including string literals, + * GStrings, map entries, map literals, assignments, and Kotlin string templates. + * If this dependency uses platform() or enforcedPlatform(), this method automatically + * operates on the inner dependency declaration. + * + * @param newGroupId The new group ID to set + * @return A new GradleDependency with the updated group ID, or the original if no change was made + */ + public GradleDependency withDeclaredGroupId(String newGroupId) { + if (StringUtils.isBlank(newGroupId)) { + return this; + } + + J.MethodInvocation m = getTree(); + + // Handle platform dependencies + if (isPlatform() && m.getArguments().get(0) instanceof J.MethodInvocation) { + GradleDependency platformDep = getPlatformDependency(); + if (platformDep != null) { + GradleDependency updated = platformDep.withDeclaredGroupId(newGroupId); + if (updated != platformDep) { + return new GradleDependency(new Cursor(cursor.getParent(), + m.withArguments(ListUtils.mapFirst(m.getArguments(), arg -> updated.getTree()))), + resolvedDependency); + } + } + return this; + } + + J.MethodInvocation updated = m; + Expression firstArg = m.getArguments().get(0); + + if (firstArg instanceof J.Literal) { + String gav = (String) ((J.Literal) firstArg).getValue(); + if (gav != null) { + Dependency dep = Dependency.parse(gav); + if (dep != null && !newGroupId.equals(dep.getGroupId())) { + Dependency updatedDep = dep.withGroupId(newGroupId); + updated = m.withArguments(ListUtils.mapFirst(m.getArguments(), + arg -> ChangeStringLiteral.withStringValue((J.Literal) arg, updatedDep.toStringNotation()))); + } + } + } else if (firstArg instanceof G.GString) { + G.GString gstring = (G.GString) firstArg; + List strings = gstring.getStrings(); + if (strings.size() >= 2 && strings.get(0) instanceof J.Literal) { + J.Literal literal = (J.Literal) strings.get(0); + Dependency dep = Dependency.parse((String) literal.getValue()); + if (dep != null && !newGroupId.equals(dep.getGroupId())) { + Dependency updatedDep = dep.withGroupId(newGroupId); + String replacement = updatedDep.toStringNotation(); + J.Literal newLiteral = literal.withValue(replacement) + .withValueSource(gstring.getDelimiter() + replacement + gstring.getDelimiter()); + updated = m.withArguments(singletonList(newLiteral)); + } + } + } else if (firstArg instanceof G.MapEntry || firstArg instanceof G.MapLiteral) { + List entries = firstArg instanceof G.MapLiteral ? + ((G.MapLiteral) firstArg).getElements() : m.getArguments().stream() + .filter(G.MapEntry.class::isInstance) + .map(G.MapEntry.class::cast) + .collect(Collectors.toList()); + + for (G.MapEntry entry : entries) { + if (entry.getKey() instanceof J.Literal && + "group".equals(((J.Literal) entry.getKey()).getValue()) && + entry.getValue() instanceof J.Literal) { + String currentGroup = (String) ((J.Literal) entry.getValue()).getValue(); + if (!newGroupId.equals(currentGroup)) { + G.MapEntry updatedEntry = entry.withValue( + ChangeStringLiteral.withStringValue((J.Literal) entry.getValue(), newGroupId)); + + if (firstArg instanceof G.MapLiteral) { + G.MapLiteral mapLiteral = (G.MapLiteral) firstArg; + updated = m.withArguments(ListUtils.mapFirst(m.getArguments(), arg -> + mapLiteral.withElements(ListUtils.map(mapLiteral.getElements(), + e -> e == entry ? updatedEntry : e)))); + } else { + updated = m.withArguments(ListUtils.map(m.getArguments(), + arg -> arg == entry ? updatedEntry : arg)); + } + } + break; + } + } + } else if (firstArg instanceof J.Assignment) { + List updatedArgs = m.getArguments(); + for (Expression updatedArg : updatedArgs) { + if (updatedArg instanceof J.Assignment) { + J.Assignment assignment = (J.Assignment) updatedArg; + if (assignment.getVariable() instanceof J.Identifier && + "group".equals(((J.Identifier) assignment.getVariable()).getSimpleName()) && + assignment.getAssignment() instanceof J.Literal) { + String currentGroup = (String) ((J.Literal) assignment.getAssignment()).getValue(); + if (!newGroupId.equals(currentGroup)) { + J.Assignment updatedAssignment = assignment.withAssignment( + ChangeStringLiteral.withStringValue((J.Literal) assignment.getAssignment(), newGroupId)); + updated = m.withArguments(ListUtils.map(m.getArguments(), + arg -> arg == assignment ? updatedAssignment : arg)); + } + break; + } + } + } + } else if (firstArg instanceof K.StringTemplate) { + K.StringTemplate template = (K.StringTemplate) firstArg; + List strings = template.getStrings(); + if (strings.size() >= 2 && strings.get(0) instanceof J.Literal) { + J.Literal literal = (J.Literal) strings.get(0); + Dependency dep = Dependency.parse((String) literal.getValue()); + if (dep != null && !newGroupId.equals(dep.getGroupId())) { + Dependency updatedDep = dep.withGroupId(newGroupId); + String replacement = updatedDep.toStringNotation(); + J.Literal newLiteral = literal.withValue(replacement) + .withValueSource(template.getDelimiter() + replacement + template.getDelimiter()); + updated = m.withArguments(singletonList(newLiteral)); + } + } + } + + return updated == m ? this : + new GradleDependency(new Cursor(cursor.getParent(), updated), resolvedDependency); + } + + /** + * Updates the declared artifact ID of a dependency. + * This method handles various dependency notation formats including string literals, + * GStrings, map entries, map literals, assignments, and Kotlin string templates. + * If this dependency uses platform() or enforcedPlatform(), this method automatically + * operates on the inner dependency declaration. + * + * @param newArtifactId The new artifact ID to set + * @return A new GradleDependency with the updated artifact ID, or the original if no change was made + */ + public GradleDependency withDeclaredArtifactId(String newArtifactId) { + if (StringUtils.isBlank(newArtifactId)) { + return this; + } + + J.MethodInvocation m = getTree(); + + // Handle platform dependencies + if (isPlatform() && m.getArguments().get(0) instanceof J.MethodInvocation) { + GradleDependency platformDep = getPlatformDependency(); + if (platformDep != null) { + GradleDependency updated = platformDep.withDeclaredArtifactId(newArtifactId); + if (updated != platformDep) { + return new GradleDependency(new Cursor(cursor.getParent(), + m.withArguments(ListUtils.mapFirst(m.getArguments(), arg -> updated.getTree()))), + resolvedDependency); + } + } + return this; + } + + J.MethodInvocation updated = m; + Expression firstArg = m.getArguments().get(0); + + if (firstArg instanceof J.Literal) { + String gav = (String) ((J.Literal) firstArg).getValue(); + if (gav != null) { + Dependency dep = Dependency.parse(gav); + if (dep != null && !newArtifactId.equals(dep.getArtifactId())) { + Dependency updatedDep = dep.withArtifactId(newArtifactId); + updated = m.withArguments(ListUtils.mapFirst(m.getArguments(), + arg -> ChangeStringLiteral.withStringValue((J.Literal) arg, updatedDep.toStringNotation()))); + } + } + } else if (firstArg instanceof G.GString) { + G.GString gstring = (G.GString) firstArg; + List strings = gstring.getStrings(); + if (strings.size() >= 2 && strings.get(0) instanceof J.Literal) { + J.Literal literal = (J.Literal) strings.get(0); + Dependency dep = Dependency.parse((String) literal.getValue()); + if (dep != null && !newArtifactId.equals(dep.getArtifactId())) { + Dependency updatedDep = dep.withArtifactId(newArtifactId); + String replacement = updatedDep.toStringNotation(); + J.Literal newLiteral = literal.withValue(replacement) + .withValueSource(gstring.getDelimiter() + replacement + gstring.getDelimiter()); + updated = m.withArguments(singletonList(newLiteral)); + } + } + } else if (firstArg instanceof G.MapEntry || firstArg instanceof G.MapLiteral) { + List entries = firstArg instanceof G.MapLiteral ? + ((G.MapLiteral) firstArg).getElements() : m.getArguments().stream() + .filter(G.MapEntry.class::isInstance) + .map(G.MapEntry.class::cast) + .collect(Collectors.toList()); + + for (G.MapEntry entry : entries) { + if (entry.getKey() instanceof J.Literal && + "name".equals(((J.Literal) entry.getKey()).getValue()) && + entry.getValue() instanceof J.Literal) { + String currentArtifact = (String) ((J.Literal) entry.getValue()).getValue(); + if (!newArtifactId.equals(currentArtifact)) { + G.MapEntry updatedEntry = entry.withValue( + ChangeStringLiteral.withStringValue((J.Literal) entry.getValue(), newArtifactId)); + + if (firstArg instanceof G.MapLiteral) { + G.MapLiteral mapLiteral = (G.MapLiteral) firstArg; + updated = m.withArguments(ListUtils.mapFirst(m.getArguments(), arg -> + mapLiteral.withElements(ListUtils.map(mapLiteral.getElements(), + e -> e == entry ? updatedEntry : e)))); + } else { + updated = m.withArguments(ListUtils.map(m.getArguments(), + arg -> arg == entry ? updatedEntry : arg)); + } + } + break; + } + } + } else if (firstArg instanceof J.Assignment) { + List updatedArgs = m.getArguments(); + for (Expression updatedArg : updatedArgs) { + if (updatedArg instanceof J.Assignment) { + J.Assignment assignment = (J.Assignment) updatedArg; + if (assignment.getVariable() instanceof J.Identifier && + "name".equals(((J.Identifier) assignment.getVariable()).getSimpleName()) && + assignment.getAssignment() instanceof J.Literal) { + String currentArtifact = (String) ((J.Literal) assignment.getAssignment()).getValue(); + if (!newArtifactId.equals(currentArtifact)) { + J.Assignment updatedAssignment = assignment.withAssignment( + ChangeStringLiteral.withStringValue((J.Literal) assignment.getAssignment(), newArtifactId)); + updated = m.withArguments(ListUtils.map(m.getArguments(), + arg -> arg == assignment ? updatedAssignment : arg)); + } + break; + } + } + } + } else if (firstArg instanceof K.StringTemplate) { + K.StringTemplate template = (K.StringTemplate) firstArg; + List strings = template.getStrings(); + if (strings.size() >= 2 && strings.get(0) instanceof J.Literal) { + J.Literal literal = (J.Literal) strings.get(0); + Dependency dep = Dependency.parse((String) literal.getValue()); + if (dep != null && !newArtifactId.equals(dep.getArtifactId())) { + Dependency updatedDep = dep.withArtifactId(newArtifactId); + String replacement = updatedDep.toStringNotation(); + J.Literal newLiteral = literal.withValue(replacement) + .withValueSource(template.getDelimiter() + replacement + template.getDelimiter()); + updated = m.withArguments(singletonList(newLiteral)); + } + } + } + + return updated == m ? this : + new GradleDependency(new Cursor(cursor.getParent(), updated), resolvedDependency); + } + + /** + * Updates the declared version of a dependency. + * This method handles various dependency notation formats including string literals, + * GStrings, map entries, map literals, assignments, and Kotlin string templates. + * If this dependency uses platform() or enforcedPlatform(), this method automatically + * operates on the inner dependency declaration. + * + * @param newVersion The new version to set (can be null to remove version) + * @return A new GradleDependency with the updated version, or the original if no change was made + */ + public GradleDependency withDeclaredVersion(@Nullable String newVersion) { + if (newVersion == null) { + return this; + } + if (newVersion.isEmpty()) { + return removeVersion(); + } + + J.MethodInvocation m = getTree(); + + // Handle platform dependencies + if (isPlatform() && m.getArguments().get(0) instanceof J.MethodInvocation) { + GradleDependency platformDep = getPlatformDependency(); + if (platformDep != null) { + GradleDependency updated = platformDep.withDeclaredVersion(newVersion); + if (updated != platformDep) { + return new GradleDependency(new Cursor(cursor.getParent(), + m.withArguments(ListUtils.mapFirst(m.getArguments(), arg -> updated.getTree()))), + resolvedDependency); + } + } + return this; + } + + J.MethodInvocation updated = m; + Expression firstArg = m.getArguments().get(0); + + if (firstArg instanceof J.Literal) { + String gav = (String) ((J.Literal) firstArg).getValue(); + if (gav != null) { + Dependency dep = Dependency.parse(gav); + if (dep != null && !newVersion.equals(dep.getVersion())) { + Dependency updatedDep = dep.withVersion(newVersion); + updated = m.withArguments(ListUtils.mapFirst(m.getArguments(), + arg -> ChangeStringLiteral.withStringValue((J.Literal) arg, updatedDep.toStringNotation()))); + } + } + } else if (firstArg instanceof G.GString) { + // For GString, we convert to a simple string literal with the new version + G.GString gstring = (G.GString) firstArg; + List strings = gstring.getStrings(); + if (strings.size() >= 2 && strings.get(0) instanceof J.Literal) { + J.Literal literal = (J.Literal) strings.get(0); + Dependency dep = Dependency.parse((String) literal.getValue()); + if (dep != null) { + Dependency updatedDep = dep.withVersion(newVersion); + String replacement = updatedDep.toStringNotation(); + J.Literal newLiteral = literal.withValue(replacement) + .withValueSource(gstring.getDelimiter() + replacement + gstring.getDelimiter()); + updated = m.withArguments(singletonList(newLiteral)); + } + } + } else if (firstArg instanceof G.MapEntry || firstArg instanceof G.MapLiteral) { + List entries = firstArg instanceof G.MapLiteral ? + new ArrayList<>(((G.MapLiteral) firstArg).getElements()) : + m.getArguments().stream() + .filter(G.MapEntry.class::isInstance) + .map(G.MapEntry.class::cast) + .collect(Collectors.toList()); + + boolean versionFound = false; + for (G.MapEntry entry : entries) { + if (entry.getKey() instanceof J.Literal && + "version".equals(((J.Literal) entry.getKey()).getValue()) && + entry.getValue() instanceof J.Literal) { + String currentVersion = (String) ((J.Literal) entry.getValue()).getValue(); + if (!newVersion.equals(currentVersion)) { + G.MapEntry updatedEntry = entry.withValue( + ChangeStringLiteral.withStringValue((J.Literal) entry.getValue(), newVersion)); + + if (firstArg instanceof G.MapLiteral) { + G.MapLiteral mapLiteral = (G.MapLiteral) firstArg; + updated = m.withArguments(ListUtils.mapFirst(m.getArguments(), arg -> + mapLiteral.withElements(ListUtils.map(mapLiteral.getElements(), + e -> e == entry ? updatedEntry : e)))); + } else { + updated = m.withArguments(ListUtils.map(m.getArguments(), + arg -> arg == entry ? updatedEntry : arg)); + } + } + versionFound = true; + break; + } + } + + // If no version entry exists, we need to add one + if (!versionFound && firstArg instanceof G.MapLiteral) { + // TODO Add version entry to map literal - this is complex and may need special handling + } + } else if (firstArg instanceof J.Assignment) { + List updatedArgs = m.getArguments(); + for (Expression updatedArg : updatedArgs) { + if (updatedArg instanceof J.Assignment) { + J.Assignment assignment = (J.Assignment) updatedArg; + if (assignment.getVariable() instanceof J.Identifier && + "version".equals(((J.Identifier) assignment.getVariable()).getSimpleName()) && + assignment.getAssignment() instanceof J.Literal) { + String currentVersion = (String) ((J.Literal) assignment.getAssignment()).getValue(); + if (!newVersion.equals(currentVersion)) { + J.Assignment updatedAssignment = assignment.withAssignment( + ChangeStringLiteral.withStringValue((J.Literal) assignment.getAssignment(), newVersion)); + updated = m.withArguments(ListUtils.map(m.getArguments(), + arg -> arg == assignment ? updatedAssignment : arg)); + } + break; + } + } + } + // TODO handle adding version when none exsits + } else if (firstArg instanceof K.StringTemplate) { + // For StringTemplate, we convert to a simple string literal with the new version + K.StringTemplate template = (K.StringTemplate) firstArg; + List strings = template.getStrings(); + if (strings.size() >= 2 && strings.get(0) instanceof J.Literal) { + J.Literal literal = (J.Literal) strings.get(0); + Dependency dep = Dependency.parse((String) literal.getValue()); + if (dep != null) { + Dependency updatedDep = dep.withVersion(newVersion); + String replacement = updatedDep.toStringNotation(); + J.Literal newLiteral = literal.withValue(replacement) + .withValueSource(template.getDelimiter() + replacement + template.getDelimiter()); + updated = m.withArguments(singletonList(newLiteral)); + } + } + } + + return updated == m ? this : new GradleDependency(new Cursor(cursor.getParent(), updated), resolvedDependency); + } + public static class Matcher extends GradleTraitMatcher { private static final MethodMatcher DEPENDENCY_DSL_MATCHER = new MethodMatcher("DependencyHandlerSpec *(..)"); @@ -692,7 +1252,7 @@ public J visitMethodInvocation(J.MethodInvocation method, P p) { if (argument instanceof J.Literal || argument instanceof G.GString || argument instanceof G.MapEntry || argument instanceof G.MapLiteral || argument instanceof J.Assignment || argument instanceof K.StringTemplate) { dependency = parseDependency(methodInvocation.getArguments()); } else if (argument instanceof J.Binary && ((J.Binary) argument).getLeft() instanceof J.Literal) { - dependency = parseDependency(Arrays.asList(((J.Binary) argument).getLeft())); + dependency = parseDependency(singletonList(((J.Binary) argument).getLeft())); } else if (argument instanceof J.MethodInvocation) { if ("platform".equals(((J.MethodInvocation) argument).getSimpleName()) || "enforcedPlatform".equals(((J.MethodInvocation) argument).getSimpleName())) { @@ -742,13 +1302,13 @@ public J visitMethodInvocation(J.MethodInvocation method, P p) { // Couldn't find the actual resolved dependency, return a virtualized one instead ResolvedDependency resolvedDependency = ResolvedDependency.builder() .depth(-1) - .gav(new ResolvedGroupArtifactVersion(null, dependency.getGroupId(), dependency.getArtifactId(), dependency.getVersion() != null ? dependency.getVersion() : "", null)) + .gav(new ResolvedGroupArtifactVersion(null, dependency.getGroupId() == null ? "" : dependency.getGroupId(), dependency.getArtifactId(), dependency.getVersion() != null ? dependency.getVersion() : "", null)) .classifier(dependency.getClassifier()) .type(dependency.getType()) .requested(Dependency.builder() .scope(methodInvocation.getSimpleName()) .type(dependency.getType()) - .gav(new GroupArtifactVersion(dependency.getGroupId(), dependency.getArtifactId(), dependency.getVersion())) + .gav(dependency.getGav()) .classifier(dependency.getClassifier()) .build()) .build(); diff --git a/rewrite-javascript/rewrite/client.txt b/rewrite-javascript/rewrite/client.txt new file mode 100644 index 0000000000..7a382d6f8f --- /dev/null +++ b/rewrite-javascript/rewrite/client.txt @@ -0,0 +1,144 @@ +{"state":"NO_CHANGE"} + JavaScriptSender:42 => await q.getAndSend(j, j2 => j2.id); + JavaScriptReceiver:567 => draft.id = await q.receive(j.id); +{"state":"NO_CHANGE"} + JavaScriptSender:43 => await q.getAndSend(j, j2 => j2.prefix, space => this.visitSpace(space, q)); + JavaScriptReceiver:568 => draft.prefix = await q.receive(j.prefix, space => this.visitSpace(space, q)); +{"state":"NO_CHANGE"} + JavaScriptSender:44 => await q.getAndSend(j, j2 => j2.markers); + JavaScriptReceiver:569 => draft.markers = await q.receive(j.markers); +{"state":"NO_CHANGE"} + JavaScriptSender:49 => await q.getAndSend(cu, c => c.sourcePath); + JavaScriptReceiver:577 => draft.sourcePath = await q.receive(cu.sourcePath); +{"state":"NO_CHANGE"} + JavaScriptSender:50 => await q.getAndSend(cu, c => c.charsetName); + JavaScriptReceiver:578 => draft.charsetName = await q.receive(cu.charsetName); +{"state":"NO_CHANGE"} + JavaScriptSender:51 => await q.getAndSend(cu, c => c.charsetBomMarked); + JavaScriptReceiver:579 => draft.charsetBomMarked = await q.receive(cu.charsetBomMarked); +{"state":"NO_CHANGE"} + JavaScriptSender:52 => await q.getAndSend(cu, c => c.checksum); + JavaScriptReceiver:580 => draft.checksum = await q.receive(cu.checksum); +{"state":"NO_CHANGE"} + JavaScriptSender:53 => await q.getAndSend(cu, c => c.fileAttributes); + JavaScriptReceiver:581 => draft.fileAttributes = await q.receive(cu.fileAttributes); +{"state":"CHANGE"} + JavaScriptSender:54 => await q.getAndSendList(cu, c => c.statements, stmt => stmt.element.id, stmt => this.visitRightPadded(stmt, q)); + JavaScriptReceiver:582 => draft.statements = await q.receiveListDefined(cu.statements, stmt => this.visitRightPadded(stmt, q)); +{"state":"ADD","valueType":"org.openrewrite.java.tree.JRightPadded"} + JavaScriptSender:54 => await q.getAndSendList(cu, c => c.statements, stmt => stmt.element.id, stmt => this.visitRightPadded(stmt, q)); + undefined +{"state":"ADD","valueType":"org.openrewrite.javascript.tree.JS$Import"} + JavaScriptSender:510 => return this.javaSender.visitRightPadded(right, q); + JavaScriptReceiver:1106 => return this.javaReceiverDelegate.visitRightPadded(right, q) +{"state":"ADD","value":"49502576-da0b-46ec-afae-c763626588c5"} + JavaScriptSender:42 => await q.getAndSend(j, j2 => j2.id); + JavaScriptReceiver:567 => draft.id = await q.receive(j.id); +{"state":"ADD","valueType":"org.openrewrite.java.tree.Space"} + JavaScriptSender:43 => await q.getAndSend(j, j2 => j2.prefix, space => this.visitSpace(space, q)); + JavaScriptReceiver:568 => draft.prefix = await q.receive(j.prefix, space => this.visitSpace(space, q)); +{"state":"ADD"} + JavaScriptSender:522 => return this.javaSender.visitSpace(space, q); + JavaScriptReceiver:1118 => return this.javaReceiverDelegate.visitSpace(space, q); +{"state":"ADD","valueType":"org.openrewrite.marker.Markers"} + JavaScriptSender:44 => await q.getAndSend(j, j2 => j2.markers); + JavaScriptReceiver:569 => draft.markers = await q.receive(j.markers); +{"state":"ADD","value":"c80d2c98-f36a-4f09-aff9-fcada004e5d4"} + JavaScriptSender:44 => await q.getAndSend(j, j2 => j2.markers); + undefined +{"state":"ADD"} + JavaScriptSender:137 => await q.getAndSendList(jsImport, el => el.modifiers, el => el.id, el => this.visit(el, q)); + JavaScriptReceiver:680 => draft.modifiers = await q.receiveListDefined(draft.modifiers, el => this.visitDefined(el, q)); +{"state":"ADD","valueType":"org.openrewrite.javascript.tree.JS$ImportClause"} + JavaScriptSender:138 => await q.getAndSend(jsImport, el => el.importClause, el => this.visit(el, q)); + JavaScriptReceiver:681 => draft.importClause = await q.receive(draft.importClause, el => this.visitDefined(el, q)); +{"state":"ADD","value":"3d34b6c1-5d85-47e5-bf94-e406dce1c7bd"} + JavaScriptSender:42 => await q.getAndSend(j, j2 => j2.id); + JavaScriptReceiver:567 => draft.id = await q.receive(j.id); +{"state":"ADD","valueType":"org.openrewrite.java.tree.Space"} + JavaScriptSender:43 => await q.getAndSend(j, j2 => j2.prefix, space => this.visitSpace(space, q)); + JavaScriptReceiver:568 => draft.prefix = await q.receive(j.prefix, space => this.visitSpace(space, q)); +{"state":"ADD"} + JavaScriptSender:522 => return this.javaSender.visitSpace(space, q); + JavaScriptReceiver:1118 => return this.javaReceiverDelegate.visitSpace(space, q); +{"state":"ADD","valueType":"org.openrewrite.marker.Markers"} + JavaScriptSender:44 => await q.getAndSend(j, j2 => j2.markers); + JavaScriptReceiver:569 => draft.markers = await q.receive(j.markers); +{"state":"ADD","value":"c80d2c98-f36a-4f09-aff9-fcada004e5d4"} + JavaScriptSender:44 => await q.getAndSend(j, j2 => j2.markers); + undefined +{"state":"ADD","value":false} + JavaScriptSender:146 => await q.getAndSend(jsImportClause, el => el.typeOnly); + JavaScriptReceiver:690 => draft.typeOnly = await q.receive(draft.typeOnly); +{"state":"ADD","valueType":"org.openrewrite.java.tree.JRightPadded"} + JavaScriptSender:147 => await q.getAndSend(jsImportClause, el => el.name, el => this.visitRightPadded(el, q)); + JavaScriptReceiver:691 => draft.name = await q.receive(draft.name, el => this.visitRightPadded(el, q)); +{"state":"ADD","valueType":"org.openrewrite.java.tree.J$Identifier"} + JavaScriptSender:510 => return this.javaSender.visitRightPadded(right, q); + JavaScriptReceiver:1106 => return this.javaReceiverDelegate.visitRightPadded(right, q) +{"state":"ADD","value":"7fa7b9a9-1825-493f-abe5-e9888ebced4e"} + JavaScriptSender:510 => return this.javaSender.visitRightPadded(right, q); + undefined +{"state":"NO_CHANGE"} + JavaScriptSender:148 => await q.getAndSend(jsImportClause, el => el.namedBindings, el => this.visit(el, q)); + JavaScriptReceiver:692 => draft.namedBindings = await q.receive(draft.namedBindings, el => this.visitDefined(el, q)); +{"state":"ADD","valueType":"org.openrewrite.java.tree.JLeftPadded"} + JavaScriptSender:139 => await q.getAndSend(jsImport, el => el.moduleSpecifier, el => this.visitLeftPadded(el, q)); + JavaScriptReceiver:682 => draft.moduleSpecifier = await q.receive(draft.moduleSpecifier, el => this.visitLeftPadded(el, q)); +{"state":"ADD","valueType":"org.openrewrite.java.tree.Space"} + JavaScriptSender:514 => return this.javaSender.visitLeftPadded(left, q); + JavaScriptReceiver:1110 => return this.javaReceiverDelegate.visitLeftPadded(left, q); +{"state":"ADD"} + JavaScriptSender:514 => return this.javaSender.visitLeftPadded(left, q); + undefined +{"state":"NO_CHANGE"} + JavaScriptSender:140 => await q.getAndSend(jsImport, el => el.attributes, el => this.visit(el, q)); + JavaScriptReceiver:683 => draft.attributes = await q.receive(draft.attributes, el => this.visitDefined(el, q)); +{"state":"NO_CHANGE"} + JavaScriptSender:141 => await q.getAndSend(jsImport, el => el.initializer, el => this.visitLeftPadded(el, q)); + JavaScriptReceiver:684 => draft.initializer = await q.receive(draft.initializer, el => this.visitLeftPadded(el, q)); +{"state":"CHANGE"} + JavaScriptSender:54 => await q.getAndSendList(cu, c => c.statements, stmt => stmt.element.id, stmt => this.visitRightPadded(stmt, q)); + undefined +{"state":"CHANGE"} + JavaScriptSender:510 => return this.javaSender.visitRightPadded(right, q); + JavaScriptReceiver:1106 => return this.javaReceiverDelegate.visitRightPadded(right, q) +{"state":"NO_CHANGE"} + JavaScriptSender:510 => return this.javaSender.visitRightPadded(right, q); + undefined +{"state":"ADD","value":"6c66a7bd-3424-426e-9462-443b62b98a01"} + JavaScriptSender:42 => await q.getAndSend(j, j2 => j2.id); + JavaScriptReceiver:567 => draft.id = await q.receive(j.id); +{"state":"ADD","valueType":"org.openrewrite.java.tree.Space"} + JavaScriptSender:43 => await q.getAndSend(j, j2 => j2.prefix, space => this.visitSpace(space, q)); + JavaScriptReceiver:568 => draft.prefix = await q.receive(j.prefix, space => this.visitSpace(space, q)); +{"state":"ADD"} + JavaScriptSender:522 => return this.javaSender.visitSpace(space, q); + JavaScriptReceiver:1118 => return this.javaReceiverDelegate.visitSpace(space, q); +{"state":"ADD","valueType":"org.openrewrite.marker.Markers"} + JavaScriptSender:44 => await q.getAndSend(j, j2 => j2.markers); + JavaScriptReceiver:569 => draft.markers = await q.receive(j.markers); +{"state":"ADD","value":"c80d2c98-f36a-4f09-aff9-fcada004e5d4"} + JavaScriptSender:44 => await q.getAndSend(j, j2 => j2.markers); + undefined +{"state":"ADD"} + JavaScriptSender:66 => await q.getAndSendList(arrowFunction, el => el.leadingAnnotations, el => el.id, el => this.visit(el, q)); + JavaScriptReceiver:597 => draft.leadingAnnotations = await q.receiveListDefined(draft.leadingAnnotations, el => this.visitDefined(el, q)); +{"state":"ADD"} + JavaScriptSender:67 => await q.getAndSendList(arrowFunction, el => el.modifiers, el => el.id, el => this.visit(el, q)); + JavaScriptReceiver:598 => draft.modifiers = await q.receiveListDefined(draft.modifiers, el => this.visitDefined(el, q)); +{"state":"NO_CHANGE"} + JavaScriptSender:68 => await q.getAndSend(arrowFunction, el => el.typeParameters, el => this.visit(el, q)); + JavaScriptReceiver:599 => draft.typeParameters = await q.receive(draft.typeParameters, el => this.visitDefined(el, q)); +{"state":"ADD","valueType":"org.openrewrite.java.tree.J$Lambda"} + JavaScriptSender:69 => await q.getAndSend(arrowFunction, el => el.lambda, el => this.visit(el, q)); + JavaScriptReceiver:600 => draft.lambda = await q.receive(draft.lambda, el => this.visitDefined(el, q)); +{"state":"ADD","value":"afa2a3d0-844a-4691-805f-c9348a45c9a8"} + JavaScriptSender:38 => return this.javaSender.visit(tree, p, parent); + JavaScriptReceiver:561 => return this.javaReceiverDelegate.visit(tree, p, parent); +{"state":"NO_CHANGE"} + JavaScriptSender:70 => await q.getAndSend(arrowFunction, el => el.returnTypeExpression, el => this.visit(el, q)); + JavaScriptReceiver:601 => draft.returnTypeExpression = await q.receive(draft.returnTypeExpression, el => this.visitDefined(el, q)); +{"state":"NO_CHANGE"} + JavaScriptSender:55 => await q.getAndSend(cu, c => c.eof, space => this.visitSpace(space, q)); + JavaScriptReceiver:583 => draft.eof = await q.receive(cu.eof, space => this.visitSpace(space, q)); diff --git a/rewrite-javascript/rewrite/server.txt b/rewrite-javascript/rewrite/server.txt new file mode 100644 index 0000000000..fd0c04f055 --- /dev/null +++ b/rewrite-javascript/rewrite/server.txt @@ -0,0 +1,744 @@ +{"state":"ADD","value":"5f2eaede-135f-4ab4-bab6-3cb5e8e8d03e"} + JsonSender:25 => await q.getAndSend(j, j2 => j2.id); + JsonReceiver:102 => draft.id = await q.receive(j.id); +{"state":"ADD","valueType":"org.openrewrite.json.tree.Space","ref":0} + JsonSender:26 => await q.getAndSend(j, j2 => asRef(j2.prefix), + JsonReceiver:103 => draft.prefix = await q.receive(j.prefix, async space => await this.visitSpace(space, q)); +{"state":"ADD"} + JsonSender:79 => await q.getAndSendList(space, s => s.comments, c => c.text + c.suffix, async c => { + JsonReceiver:162 => draft.comments = await q.receiveListDefined(space.comments, async c => { +{"state":"ADD","value":""} + JsonSender:85 => await q.getAndSend(space, s => s.whitespace); + JsonReceiver:170 => draft.whitespace = await q.receive(space.whitespace); +{"state":"ADD","valueType":"org.openrewrite.marker.Markers","ref":1} + JsonSender:28 => await q.getAndSend(j, j2 => j2.markers); + JsonReceiver:104 => draft.markers = await q.receive(j.markers); +{"state":"ADD","value":"c80d2c98-f36a-4f09-aff9-fcada004e5d4"} + JsonSender:28 => await q.getAndSend(j, j2 => j2.markers); + undefined +{"state":"ADD","value":"package.json"} + JsonSender:33 => await q.getAndSend(document, d => d.sourcePath); + JsonReceiver:110 => draft.sourcePath = await q.receive(document.sourcePath); +{"state":"NO_CHANGE"} + JsonSender:34 => await q.getAndSend(document, d => d.charsetName); + JsonReceiver:111 => draft.charsetName = await q.receive(document.charsetName); +{"state":"NO_CHANGE"} + JsonSender:35 => await q.getAndSend(document, d => d.charsetBomMarked); + JsonReceiver:112 => draft.charsetBomMarked = await q.receive(document.charsetBomMarked); +{"state":"NO_CHANGE"} + JsonSender:36 => await q.getAndSend(document, d => d.checksum); + JsonReceiver:113 => draft.checksum = await q.receive(document.checksum); +{"state":"NO_CHANGE"} + JsonSender:37 => await q.getAndSend(document, d => d.fileAttributes); + JsonReceiver:114 => draft.fileAttributes = await q.receive(document.fileAttributes); +{"state":"ADD","valueType":"org.openrewrite.json.tree.Json$JsonObject"} + JsonSender:38 => await q.getAndSend(document, d => d.value, + JsonReceiver:115 => draft.value = await q.receive(document.value, async j => await this.visit(j, q)!); +{"state":"ADD","value":"57fc51b1-d8e8-4317-9ae3-2ea0ef1c9b1c"} + JsonSender:25 => await q.getAndSend(j, j2 => j2.id); + JsonReceiver:102 => draft.id = await q.receive(j.id); +{"state":"ADD","valueType":"org.openrewrite.json.tree.Space","ref":2} + JsonSender:26 => await q.getAndSend(j, j2 => asRef(j2.prefix), + JsonReceiver:103 => draft.prefix = await q.receive(j.prefix, async space => await this.visitSpace(space, q)); +{"state":"ADD"} + JsonSender:79 => await q.getAndSendList(space, s => s.comments, c => c.text + c.suffix, async c => { + JsonReceiver:162 => draft.comments = await q.receiveListDefined(space.comments, async c => { +{"state":"ADD","value":""} + JsonSender:85 => await q.getAndSend(space, s => s.whitespace); + JsonReceiver:170 => draft.whitespace = await q.receive(space.whitespace); +{"state":"ADD","ref":1} + JsonSender:28 => await q.getAndSend(j, j2 => j2.markers); + JsonReceiver:104 => draft.markers = await q.receive(j.markers); +{"state":"ADD"} + JsonSender:73 => await q.getAndSendList(obj, o => o.members, j => j.element.id, + JsonReceiver:155 => draft.members = await q.receiveListDefined(obj.members, +{"state":"ADD","valueType":"org.openrewrite.json.tree.RightPadded"} + JsonSender:73 => await q.getAndSendList(obj, o => o.members, j => j.element.id, + undefined +{"state":"ADD","valueType":"org.openrewrite.json.tree.Json$Member"} + JsonSender:90 => await q.getAndSend(right, r => r.element, j => this.visit(j, q)); + JsonReceiver:179 => draft.element = await p.receive(right.element, async j => await this.visit(j, p)!) as Draft; +{"state":"ADD","value":"86243acf-31a2-422c-a381-ae5f76765610"} + JsonSender:25 => await q.getAndSend(j, j2 => j2.id); + JsonReceiver:102 => draft.id = await q.receive(j.id); +{"state":"ADD","valueType":"org.openrewrite.json.tree.Space","ref":3} + JsonSender:26 => await q.getAndSend(j, j2 => asRef(j2.prefix), + JsonReceiver:103 => draft.prefix = await q.receive(j.prefix, async space => await this.visitSpace(space, q)); +{"state":"ADD"} + JsonSender:79 => await q.getAndSendList(space, s => s.comments, c => c.text + c.suffix, async c => { + JsonReceiver:162 => draft.comments = await q.receiveListDefined(space.comments, async c => { +{"state":"ADD","value":""} + JsonSender:85 => await q.getAndSend(space, s => s.whitespace); + JsonReceiver:170 => draft.whitespace = await q.receive(space.whitespace); +{"state":"ADD","ref":1} + JsonSender:28 => await q.getAndSend(j, j2 => j2.markers); + JsonReceiver:104 => draft.markers = await q.receive(j.markers); +{"state":"ADD","valueType":"org.openrewrite.json.tree.RightPadded"} + JsonSender:67 => await q.getAndSend(member, m => m.key, async j => await this.visitRightPadded(j, q)); + JsonReceiver:146 => draft.key = await q.receive(member.key, +{"state":"ADD","valueType":"org.openrewrite.json.tree.Json$Literal"} + JsonSender:90 => await q.getAndSend(right, r => r.element, j => this.visit(j, q)); + JsonReceiver:179 => draft.element = await p.receive(right.element, async j => await this.visit(j, p)!) as Draft; +{"state":"ADD","value":"e96fdf63-8d71-4eaa-96cb-23c65ee9bc8d"} + JsonSender:25 => await q.getAndSend(j, j2 => j2.id); + JsonReceiver:102 => draft.id = await q.receive(j.id); +{"state":"ADD","valueType":"org.openrewrite.json.tree.Space","ref":4} + JsonSender:26 => await q.getAndSend(j, j2 => asRef(j2.prefix), + JsonReceiver:103 => draft.prefix = await q.receive(j.prefix, async space => await this.visitSpace(space, q)); +{"state":"ADD"} + JsonSender:79 => await q.getAndSendList(space, s => s.comments, c => c.text + c.suffix, async c => { + JsonReceiver:162 => draft.comments = await q.receiveListDefined(space.comments, async c => { +{"state":"ADD","value":"\n "} + JsonSender:85 => await q.getAndSend(space, s => s.whitespace); + JsonReceiver:170 => draft.whitespace = await q.receive(space.whitespace); +{"state":"ADD","ref":1} + JsonSender:28 => await q.getAndSend(j, j2 => j2.markers); + JsonReceiver:104 => draft.markers = await q.receive(j.markers); +{"state":"ADD","value":"\"name\""} + JsonSender:61 => await q.getAndSend(literal, lit => lit.source); + JsonReceiver:139 => draft.source = await q.receive(literal.source); +{"state":"ADD","value":"name"} + JsonSender:62 => await q.getAndSend(literal, lit => lit.value); + JsonReceiver:140 => draft.value = await q.receive(literal.value); +{"state":"ADD","valueType":"org.openrewrite.json.tree.Space","ref":5} + JsonSender:91 => await q.getAndSend(right, r => asRef(r.after), async space => await this.visitSpace(space, q)); + JsonReceiver:180 => draft.after = await p.receive(right.after, async space => await this.visitSpace(space, p)); +{"state":"ADD"} + JsonSender:79 => await q.getAndSendList(space, s => s.comments, c => c.text + c.suffix, async c => { + JsonReceiver:162 => draft.comments = await q.receiveListDefined(space.comments, async c => { +{"state":"ADD","value":""} + JsonSender:85 => await q.getAndSend(space, s => s.whitespace); + JsonReceiver:170 => draft.whitespace = await q.receive(space.whitespace); +{"state":"ADD","ref":1} + JsonSender:92 => await q.getAndSend(right, r => r.markers); + JsonReceiver:181 => draft.markers = await p.receiveMarkers(right.markers); +{"state":"ADD","valueType":"org.openrewrite.json.tree.Json$Literal"} + JsonSender:68 => await q.getAndSend(member, m => m.value, async j => await this.visit(j, q)); + JsonReceiver:148 => draft.value = await q.receive(member.value, +{"state":"ADD","value":"d3391974-e787-498b-89ea-b18e36b03a25"} + JsonSender:25 => await q.getAndSend(j, j2 => j2.id); + JsonReceiver:102 => draft.id = await q.receive(j.id); +{"state":"ADD","valueType":"org.openrewrite.json.tree.Space","ref":6} + JsonSender:26 => await q.getAndSend(j, j2 => asRef(j2.prefix), + JsonReceiver:103 => draft.prefix = await q.receive(j.prefix, async space => await this.visitSpace(space, q)); +{"state":"ADD"} + JsonSender:79 => await q.getAndSendList(space, s => s.comments, c => c.text + c.suffix, async c => { + JsonReceiver:162 => draft.comments = await q.receiveListDefined(space.comments, async c => { +{"state":"ADD","value":" "} + JsonSender:85 => await q.getAndSend(space, s => s.whitespace); + JsonReceiver:170 => draft.whitespace = await q.receive(space.whitespace); +{"state":"ADD","ref":1} + JsonSender:28 => await q.getAndSend(j, j2 => j2.markers); + JsonReceiver:104 => draft.markers = await q.receive(j.markers); +{"state":"ADD","value":"\"test-project\""} + JsonSender:61 => await q.getAndSend(literal, lit => lit.source); + JsonReceiver:139 => draft.source = await q.receive(literal.source); +{"state":"ADD","value":"test-project"} + JsonSender:62 => await q.getAndSend(literal, lit => lit.value); + JsonReceiver:140 => draft.value = await q.receive(literal.value); +{"state":"ADD","valueType":"org.openrewrite.json.tree.Space","ref":7} + JsonSender:91 => await q.getAndSend(right, r => asRef(r.after), async space => await this.visitSpace(space, q)); + JsonReceiver:180 => draft.after = await p.receive(right.after, async space => await this.visitSpace(space, p)); +{"state":"ADD"} + JsonSender:79 => await q.getAndSendList(space, s => s.comments, c => c.text + c.suffix, async c => { + JsonReceiver:162 => draft.comments = await q.receiveListDefined(space.comments, async c => { +{"state":"ADD","value":""} + JsonSender:85 => await q.getAndSend(space, s => s.whitespace); + JsonReceiver:170 => draft.whitespace = await q.receive(space.whitespace); +{"state":"ADD","ref":1} + JsonSender:92 => await q.getAndSend(right, r => r.markers); + JsonReceiver:181 => draft.markers = await p.receiveMarkers(right.markers); +{"state":"ADD","valueType":"org.openrewrite.json.tree.RightPadded"} + JsonSender:73 => await q.getAndSendList(obj, o => o.members, j => j.element.id, + undefined +{"state":"ADD","valueType":"org.openrewrite.json.tree.Json$Member"} + JsonSender:90 => await q.getAndSend(right, r => r.element, j => this.visit(j, q)); + JsonReceiver:179 => draft.element = await p.receive(right.element, async j => await this.visit(j, p)!) as Draft; +{"state":"ADD","value":"39b72eaf-336a-447d-a8f2-d50f3180c838"} + JsonSender:25 => await q.getAndSend(j, j2 => j2.id); + JsonReceiver:102 => draft.id = await q.receive(j.id); +{"state":"ADD","ref":3} + JsonSender:26 => await q.getAndSend(j, j2 => asRef(j2.prefix), + JsonReceiver:103 => draft.prefix = await q.receive(j.prefix, async space => await this.visitSpace(space, q)); +{"state":"ADD","ref":1} + JsonSender:28 => await q.getAndSend(j, j2 => j2.markers); + JsonReceiver:104 => draft.markers = await q.receive(j.markers); +{"state":"ADD","valueType":"org.openrewrite.json.tree.RightPadded"} + JsonSender:67 => await q.getAndSend(member, m => m.key, async j => await this.visitRightPadded(j, q)); + JsonReceiver:146 => draft.key = await q.receive(member.key, +{"state":"ADD","valueType":"org.openrewrite.json.tree.Json$Literal"} + JsonSender:90 => await q.getAndSend(right, r => r.element, j => this.visit(j, q)); + JsonReceiver:179 => draft.element = await p.receive(right.element, async j => await this.visit(j, p)!) as Draft; +{"state":"ADD","value":"45af3471-03f3-4557-88f4-79b82b6e1914"} + JsonSender:25 => await q.getAndSend(j, j2 => j2.id); + JsonReceiver:102 => draft.id = await q.receive(j.id); +{"state":"ADD","valueType":"org.openrewrite.json.tree.Space","ref":8} + JsonSender:26 => await q.getAndSend(j, j2 => asRef(j2.prefix), + JsonReceiver:103 => draft.prefix = await q.receive(j.prefix, async space => await this.visitSpace(space, q)); +{"state":"ADD"} + JsonSender:79 => await q.getAndSendList(space, s => s.comments, c => c.text + c.suffix, async c => { + JsonReceiver:162 => draft.comments = await q.receiveListDefined(space.comments, async c => { +{"state":"ADD","value":"\n "} + JsonSender:85 => await q.getAndSend(space, s => s.whitespace); + JsonReceiver:170 => draft.whitespace = await q.receive(space.whitespace); +{"state":"ADD","ref":1} + JsonSender:28 => await q.getAndSend(j, j2 => j2.markers); + JsonReceiver:104 => draft.markers = await q.receive(j.markers); +{"state":"ADD","value":"\"version\""} + JsonSender:61 => await q.getAndSend(literal, lit => lit.source); + JsonReceiver:139 => draft.source = await q.receive(literal.source); +{"state":"ADD","value":"version"} + JsonSender:62 => await q.getAndSend(literal, lit => lit.value); + JsonReceiver:140 => draft.value = await q.receive(literal.value); +{"state":"ADD","valueType":"org.openrewrite.json.tree.Space","ref":9} + JsonSender:91 => await q.getAndSend(right, r => asRef(r.after), async space => await this.visitSpace(space, q)); + JsonReceiver:180 => draft.after = await p.receive(right.after, async space => await this.visitSpace(space, p)); +{"state":"ADD"} + JsonSender:79 => await q.getAndSendList(space, s => s.comments, c => c.text + c.suffix, async c => { + JsonReceiver:162 => draft.comments = await q.receiveListDefined(space.comments, async c => { +{"state":"ADD","value":""} + JsonSender:85 => await q.getAndSend(space, s => s.whitespace); + JsonReceiver:170 => draft.whitespace = await q.receive(space.whitespace); +{"state":"ADD","ref":1} + JsonSender:92 => await q.getAndSend(right, r => r.markers); + JsonReceiver:181 => draft.markers = await p.receiveMarkers(right.markers); +{"state":"ADD","valueType":"org.openrewrite.json.tree.Json$Literal"} + JsonSender:68 => await q.getAndSend(member, m => m.value, async j => await this.visit(j, q)); + JsonReceiver:148 => draft.value = await q.receive(member.value, +{"state":"ADD","value":"f2c6f962-7408-44b0-8082-cf2784b5b540"} + JsonSender:25 => await q.getAndSend(j, j2 => j2.id); + JsonReceiver:102 => draft.id = await q.receive(j.id); +{"state":"ADD","valueType":"org.openrewrite.json.tree.Space","ref":10} + JsonSender:26 => await q.getAndSend(j, j2 => asRef(j2.prefix), + JsonReceiver:103 => draft.prefix = await q.receive(j.prefix, async space => await this.visitSpace(space, q)); +{"state":"ADD"} + JsonSender:79 => await q.getAndSendList(space, s => s.comments, c => c.text + c.suffix, async c => { + JsonReceiver:162 => draft.comments = await q.receiveListDefined(space.comments, async c => { +{"state":"ADD","value":" "} + JsonSender:85 => await q.getAndSend(space, s => s.whitespace); + JsonReceiver:170 => draft.whitespace = await q.receive(space.whitespace); +{"state":"ADD","ref":1} + JsonSender:28 => await q.getAndSend(j, j2 => j2.markers); + JsonReceiver:104 => draft.markers = await q.receive(j.markers); +{"state":"ADD","value":"\"1.0.0\""} + JsonSender:61 => await q.getAndSend(literal, lit => lit.source); + JsonReceiver:139 => draft.source = await q.receive(literal.source); +{"state":"ADD","value":"1.0.0"} + JsonSender:62 => await q.getAndSend(literal, lit => lit.value); + JsonReceiver:140 => draft.value = await q.receive(literal.value); +{"state":"ADD","valueType":"org.openrewrite.json.tree.Space","ref":11} + JsonSender:91 => await q.getAndSend(right, r => asRef(r.after), async space => await this.visitSpace(space, q)); + JsonReceiver:180 => draft.after = await p.receive(right.after, async space => await this.visitSpace(space, p)); +{"state":"ADD"} + JsonSender:79 => await q.getAndSendList(space, s => s.comments, c => c.text + c.suffix, async c => { + JsonReceiver:162 => draft.comments = await q.receiveListDefined(space.comments, async c => { +{"state":"ADD","value":""} + JsonSender:85 => await q.getAndSend(space, s => s.whitespace); + JsonReceiver:170 => draft.whitespace = await q.receive(space.whitespace); +{"state":"ADD","ref":1} + JsonSender:92 => await q.getAndSend(right, r => r.markers); + JsonReceiver:181 => draft.markers = await p.receiveMarkers(right.markers); +{"state":"ADD","valueType":"org.openrewrite.json.tree.RightPadded"} + JsonSender:73 => await q.getAndSendList(obj, o => o.members, j => j.element.id, + undefined +{"state":"ADD","valueType":"org.openrewrite.json.tree.Json$Member"} + JsonSender:90 => await q.getAndSend(right, r => r.element, j => this.visit(j, q)); + JsonReceiver:179 => draft.element = await p.receive(right.element, async j => await this.visit(j, p)!) as Draft; +{"state":"ADD","value":"3073e8a3-0590-4600-945c-f1b783654496"} + JsonSender:25 => await q.getAndSend(j, j2 => j2.id); + JsonReceiver:102 => draft.id = await q.receive(j.id); +{"state":"ADD","ref":3} + JsonSender:26 => await q.getAndSend(j, j2 => asRef(j2.prefix), + JsonReceiver:103 => draft.prefix = await q.receive(j.prefix, async space => await this.visitSpace(space, q)); +{"state":"ADD","ref":1} + JsonSender:28 => await q.getAndSend(j, j2 => j2.markers); + JsonReceiver:104 => draft.markers = await q.receive(j.markers); +{"state":"ADD","valueType":"org.openrewrite.json.tree.RightPadded"} + JsonSender:67 => await q.getAndSend(member, m => m.key, async j => await this.visitRightPadded(j, q)); + JsonReceiver:146 => draft.key = await q.receive(member.key, +{"state":"ADD","valueType":"org.openrewrite.json.tree.Json$Literal"} + JsonSender:90 => await q.getAndSend(right, r => r.element, j => this.visit(j, q)); + JsonReceiver:179 => draft.element = await p.receive(right.element, async j => await this.visit(j, p)!) as Draft; +{"state":"ADD","value":"258d9339-f02e-49a5-a04b-d920f2de6522"} + JsonSender:25 => await q.getAndSend(j, j2 => j2.id); + JsonReceiver:102 => draft.id = await q.receive(j.id); +{"state":"ADD","valueType":"org.openrewrite.json.tree.Space","ref":12} + JsonSender:26 => await q.getAndSend(j, j2 => asRef(j2.prefix), + JsonReceiver:103 => draft.prefix = await q.receive(j.prefix, async space => await this.visitSpace(space, q)); +{"state":"ADD"} + JsonSender:79 => await q.getAndSendList(space, s => s.comments, c => c.text + c.suffix, async c => { + JsonReceiver:162 => draft.comments = await q.receiveListDefined(space.comments, async c => { +{"state":"ADD","value":"\n "} + JsonSender:85 => await q.getAndSend(space, s => s.whitespace); + JsonReceiver:170 => draft.whitespace = await q.receive(space.whitespace); +{"state":"ADD","ref":1} + JsonSender:28 => await q.getAndSend(j, j2 => j2.markers); + JsonReceiver:104 => draft.markers = await q.receive(j.markers); +{"state":"ADD","value":"\"dependencies\""} + JsonSender:61 => await q.getAndSend(literal, lit => lit.source); + JsonReceiver:139 => draft.source = await q.receive(literal.source); +{"state":"ADD","value":"dependencies"} + JsonSender:62 => await q.getAndSend(literal, lit => lit.value); + JsonReceiver:140 => draft.value = await q.receive(literal.value); +{"state":"ADD","valueType":"org.openrewrite.json.tree.Space","ref":13} + JsonSender:91 => await q.getAndSend(right, r => asRef(r.after), async space => await this.visitSpace(space, q)); + JsonReceiver:180 => draft.after = await p.receive(right.after, async space => await this.visitSpace(space, p)); +{"state":"ADD"} + JsonSender:79 => await q.getAndSendList(space, s => s.comments, c => c.text + c.suffix, async c => { + JsonReceiver:162 => draft.comments = await q.receiveListDefined(space.comments, async c => { +{"state":"ADD","value":""} + JsonSender:85 => await q.getAndSend(space, s => s.whitespace); + JsonReceiver:170 => draft.whitespace = await q.receive(space.whitespace); +{"state":"ADD","ref":1} + JsonSender:92 => await q.getAndSend(right, r => r.markers); + JsonReceiver:181 => draft.markers = await p.receiveMarkers(right.markers); +{"state":"ADD","valueType":"org.openrewrite.json.tree.Json$JsonObject"} + JsonSender:68 => await q.getAndSend(member, m => m.value, async j => await this.visit(j, q)); + JsonReceiver:148 => draft.value = await q.receive(member.value, +{"state":"ADD","value":"6710bf6f-ae24-4f91-a8a6-16c9c1f436a6"} + JsonSender:25 => await q.getAndSend(j, j2 => j2.id); + JsonReceiver:102 => draft.id = await q.receive(j.id); +{"state":"ADD","valueType":"org.openrewrite.json.tree.Space","ref":14} + JsonSender:26 => await q.getAndSend(j, j2 => asRef(j2.prefix), + JsonReceiver:103 => draft.prefix = await q.receive(j.prefix, async space => await this.visitSpace(space, q)); +{"state":"ADD"} + JsonSender:79 => await q.getAndSendList(space, s => s.comments, c => c.text + c.suffix, async c => { + JsonReceiver:162 => draft.comments = await q.receiveListDefined(space.comments, async c => { +{"state":"ADD","value":" "} + JsonSender:85 => await q.getAndSend(space, s => s.whitespace); + JsonReceiver:170 => draft.whitespace = await q.receive(space.whitespace); +{"state":"ADD","ref":1} + JsonSender:28 => await q.getAndSend(j, j2 => j2.markers); + JsonReceiver:104 => draft.markers = await q.receive(j.markers); +{"state":"ADD"} + JsonSender:73 => await q.getAndSendList(obj, o => o.members, j => j.element.id, + JsonReceiver:155 => draft.members = await q.receiveListDefined(obj.members, +{"state":"ADD","valueType":"org.openrewrite.json.tree.RightPadded"} + JsonSender:73 => await q.getAndSendList(obj, o => o.members, j => j.element.id, + undefined +{"state":"ADD","valueType":"org.openrewrite.json.tree.Json$Member"} + JsonSender:90 => await q.getAndSend(right, r => r.element, j => this.visit(j, q)); + JsonReceiver:179 => draft.element = await p.receive(right.element, async j => await this.visit(j, p)!) as Draft; +{"state":"ADD","value":"d4ba7eb8-932a-4a92-ad98-f458eb866be9"} + JsonSender:25 => await q.getAndSend(j, j2 => j2.id); + JsonReceiver:102 => draft.id = await q.receive(j.id); +{"state":"ADD","ref":3} + JsonSender:26 => await q.getAndSend(j, j2 => asRef(j2.prefix), + JsonReceiver:103 => draft.prefix = await q.receive(j.prefix, async space => await this.visitSpace(space, q)); +{"state":"ADD","ref":1} + JsonSender:28 => await q.getAndSend(j, j2 => j2.markers); + JsonReceiver:104 => draft.markers = await q.receive(j.markers); +{"state":"ADD","valueType":"org.openrewrite.json.tree.RightPadded"} + JsonSender:67 => await q.getAndSend(member, m => m.key, async j => await this.visitRightPadded(j, q)); + JsonReceiver:146 => draft.key = await q.receive(member.key, +{"state":"ADD","valueType":"org.openrewrite.json.tree.Json$Literal"} + JsonSender:90 => await q.getAndSend(right, r => r.element, j => this.visit(j, q)); + JsonReceiver:179 => draft.element = await p.receive(right.element, async j => await this.visit(j, p)!) as Draft; +{"state":"ADD","value":"ef15efb5-cffa-423e-8e93-a9833e9f4bfd"} + JsonSender:25 => await q.getAndSend(j, j2 => j2.id); + JsonReceiver:102 => draft.id = await q.receive(j.id); +{"state":"ADD","valueType":"org.openrewrite.json.tree.Space","ref":15} + JsonSender:26 => await q.getAndSend(j, j2 => asRef(j2.prefix), + JsonReceiver:103 => draft.prefix = await q.receive(j.prefix, async space => await this.visitSpace(space, q)); +{"state":"ADD"} + JsonSender:79 => await q.getAndSendList(space, s => s.comments, c => c.text + c.suffix, async c => { + JsonReceiver:162 => draft.comments = await q.receiveListDefined(space.comments, async c => { +{"state":"ADD","value":"\n "} + JsonSender:85 => await q.getAndSend(space, s => s.whitespace); + JsonReceiver:170 => draft.whitespace = await q.receive(space.whitespace); +{"state":"ADD","ref":1} + JsonSender:28 => await q.getAndSend(j, j2 => j2.markers); + JsonReceiver:104 => draft.markers = await q.receive(j.markers); +{"state":"ADD","value":"\"lodash\""} + JsonSender:61 => await q.getAndSend(literal, lit => lit.source); + JsonReceiver:139 => draft.source = await q.receive(literal.source); +{"state":"ADD","value":"lodash"} + JsonSender:62 => await q.getAndSend(literal, lit => lit.value); + JsonReceiver:140 => draft.value = await q.receive(literal.value); +{"state":"ADD","valueType":"org.openrewrite.json.tree.Space","ref":16} + JsonSender:91 => await q.getAndSend(right, r => asRef(r.after), async space => await this.visitSpace(space, q)); + JsonReceiver:180 => draft.after = await p.receive(right.after, async space => await this.visitSpace(space, p)); +{"state":"ADD"} + JsonSender:79 => await q.getAndSendList(space, s => s.comments, c => c.text + c.suffix, async c => { + JsonReceiver:162 => draft.comments = await q.receiveListDefined(space.comments, async c => { +{"state":"ADD","value":""} + JsonSender:85 => await q.getAndSend(space, s => s.whitespace); + JsonReceiver:170 => draft.whitespace = await q.receive(space.whitespace); +{"state":"ADD","ref":1} + JsonSender:92 => await q.getAndSend(right, r => r.markers); + JsonReceiver:181 => draft.markers = await p.receiveMarkers(right.markers); +{"state":"ADD","valueType":"org.openrewrite.json.tree.Json$Literal"} + JsonSender:68 => await q.getAndSend(member, m => m.value, async j => await this.visit(j, q)); + JsonReceiver:148 => draft.value = await q.receive(member.value, +{"state":"ADD","value":"d156576d-41cd-4f8b-acc3-0feb68985821"} + JsonSender:25 => await q.getAndSend(j, j2 => j2.id); + JsonReceiver:102 => draft.id = await q.receive(j.id); +{"state":"ADD","valueType":"org.openrewrite.json.tree.Space","ref":17} + JsonSender:26 => await q.getAndSend(j, j2 => asRef(j2.prefix), + JsonReceiver:103 => draft.prefix = await q.receive(j.prefix, async space => await this.visitSpace(space, q)); +{"state":"ADD"} + JsonSender:79 => await q.getAndSendList(space, s => s.comments, c => c.text + c.suffix, async c => { + JsonReceiver:162 => draft.comments = await q.receiveListDefined(space.comments, async c => { +{"state":"ADD","value":" "} + JsonSender:85 => await q.getAndSend(space, s => s.whitespace); + JsonReceiver:170 => draft.whitespace = await q.receive(space.whitespace); +{"state":"ADD","ref":1} + JsonSender:28 => await q.getAndSend(j, j2 => j2.markers); + JsonReceiver:104 => draft.markers = await q.receive(j.markers); +{"state":"ADD","value":"\"^4.17.21\""} + JsonSender:61 => await q.getAndSend(literal, lit => lit.source); + JsonReceiver:139 => draft.source = await q.receive(literal.source); +{"state":"ADD","value":"^4.17.21"} + JsonSender:62 => await q.getAndSend(literal, lit => lit.value); + JsonReceiver:140 => draft.value = await q.receive(literal.value); +{"state":"ADD","valueType":"org.openrewrite.json.tree.Space","ref":18} + JsonSender:91 => await q.getAndSend(right, r => asRef(r.after), async space => await this.visitSpace(space, q)); + JsonReceiver:180 => draft.after = await p.receive(right.after, async space => await this.visitSpace(space, p)); +{"state":"ADD"} + JsonSender:79 => await q.getAndSendList(space, s => s.comments, c => c.text + c.suffix, async c => { + JsonReceiver:162 => draft.comments = await q.receiveListDefined(space.comments, async c => { +{"state":"ADD","value":"\n "} + JsonSender:85 => await q.getAndSend(space, s => s.whitespace); + JsonReceiver:170 => draft.whitespace = await q.receive(space.whitespace); +{"state":"ADD","ref":1} + JsonSender:92 => await q.getAndSend(right, r => r.markers); + JsonReceiver:181 => draft.markers = await p.receiveMarkers(right.markers); +{"state":"ADD","valueType":"org.openrewrite.json.tree.Space","ref":19} + JsonSender:91 => await q.getAndSend(right, r => asRef(r.after), async space => await this.visitSpace(space, q)); + JsonReceiver:180 => draft.after = await p.receive(right.after, async space => await this.visitSpace(space, p)); +{"state":"ADD"} + JsonSender:79 => await q.getAndSendList(space, s => s.comments, c => c.text + c.suffix, async c => { + JsonReceiver:162 => draft.comments = await q.receiveListDefined(space.comments, async c => { +{"state":"ADD","value":""} + JsonSender:85 => await q.getAndSend(space, s => s.whitespace); + JsonReceiver:170 => draft.whitespace = await q.receive(space.whitespace); +{"state":"ADD","ref":1} + JsonSender:92 => await q.getAndSend(right, r => r.markers); + JsonReceiver:181 => draft.markers = await p.receiveMarkers(right.markers); +{"state":"ADD","valueType":"org.openrewrite.json.tree.RightPadded"} + JsonSender:73 => await q.getAndSendList(obj, o => o.members, j => j.element.id, + undefined +{"state":"ADD","valueType":"org.openrewrite.json.tree.Json$Member"} + JsonSender:90 => await q.getAndSend(right, r => r.element, j => this.visit(j, q)); + JsonReceiver:179 => draft.element = await p.receive(right.element, async j => await this.visit(j, p)!) as Draft; +{"state":"ADD","value":"f371ec73-f998-462f-adaf-23c3b4c64939"} + JsonSender:25 => await q.getAndSend(j, j2 => j2.id); + JsonReceiver:102 => draft.id = await q.receive(j.id); +{"state":"ADD","ref":3} + JsonSender:26 => await q.getAndSend(j, j2 => asRef(j2.prefix), + JsonReceiver:103 => draft.prefix = await q.receive(j.prefix, async space => await this.visitSpace(space, q)); +{"state":"ADD","ref":1} + JsonSender:28 => await q.getAndSend(j, j2 => j2.markers); + JsonReceiver:104 => draft.markers = await q.receive(j.markers); +{"state":"ADD","valueType":"org.openrewrite.json.tree.RightPadded"} + JsonSender:67 => await q.getAndSend(member, m => m.key, async j => await this.visitRightPadded(j, q)); + JsonReceiver:146 => draft.key = await q.receive(member.key, +{"state":"ADD","valueType":"org.openrewrite.json.tree.Json$Literal"} + JsonSender:90 => await q.getAndSend(right, r => r.element, j => this.visit(j, q)); + JsonReceiver:179 => draft.element = await p.receive(right.element, async j => await this.visit(j, p)!) as Draft; +{"state":"ADD","value":"efff142d-5ee2-4da8-b9dc-bee6d97b920a"} + JsonSender:25 => await q.getAndSend(j, j2 => j2.id); + JsonReceiver:102 => draft.id = await q.receive(j.id); +{"state":"ADD","valueType":"org.openrewrite.json.tree.Space","ref":20} + JsonSender:26 => await q.getAndSend(j, j2 => asRef(j2.prefix), + JsonReceiver:103 => draft.prefix = await q.receive(j.prefix, async space => await this.visitSpace(space, q)); +{"state":"ADD"} + JsonSender:79 => await q.getAndSendList(space, s => s.comments, c => c.text + c.suffix, async c => { + JsonReceiver:162 => draft.comments = await q.receiveListDefined(space.comments, async c => { +{"state":"ADD","value":"\n "} + JsonSender:85 => await q.getAndSend(space, s => s.whitespace); + JsonReceiver:170 => draft.whitespace = await q.receive(space.whitespace); +{"state":"ADD","ref":1} + JsonSender:28 => await q.getAndSend(j, j2 => j2.markers); + JsonReceiver:104 => draft.markers = await q.receive(j.markers); +{"state":"ADD","value":"\"devDependencies\""} + JsonSender:61 => await q.getAndSend(literal, lit => lit.source); + JsonReceiver:139 => draft.source = await q.receive(literal.source); +{"state":"ADD","value":"devDependencies"} + JsonSender:62 => await q.getAndSend(literal, lit => lit.value); + JsonReceiver:140 => draft.value = await q.receive(literal.value); +{"state":"ADD","valueType":"org.openrewrite.json.tree.Space","ref":21} + JsonSender:91 => await q.getAndSend(right, r => asRef(r.after), async space => await this.visitSpace(space, q)); + JsonReceiver:180 => draft.after = await p.receive(right.after, async space => await this.visitSpace(space, p)); +{"state":"ADD"} + JsonSender:79 => await q.getAndSendList(space, s => s.comments, c => c.text + c.suffix, async c => { + JsonReceiver:162 => draft.comments = await q.receiveListDefined(space.comments, async c => { +{"state":"ADD","value":""} + JsonSender:85 => await q.getAndSend(space, s => s.whitespace); + JsonReceiver:170 => draft.whitespace = await q.receive(space.whitespace); +{"state":"ADD","ref":1} + JsonSender:92 => await q.getAndSend(right, r => r.markers); + JsonReceiver:181 => draft.markers = await p.receiveMarkers(right.markers); +{"state":"ADD","valueType":"org.openrewrite.json.tree.Json$JsonObject"} + JsonSender:68 => await q.getAndSend(member, m => m.value, async j => await this.visit(j, q)); + JsonReceiver:148 => draft.value = await q.receive(member.value, +{"state":"ADD","value":"2055fbf9-9873-493c-9f9b-5a7bcff45762"} + JsonSender:25 => await q.getAndSend(j, j2 => j2.id); + JsonReceiver:102 => draft.id = await q.receive(j.id); +{"state":"ADD","valueType":"org.openrewrite.json.tree.Space","ref":22} + JsonSender:26 => await q.getAndSend(j, j2 => asRef(j2.prefix), + JsonReceiver:103 => draft.prefix = await q.receive(j.prefix, async space => await this.visitSpace(space, q)); +{"state":"ADD"} + JsonSender:79 => await q.getAndSendList(space, s => s.comments, c => c.text + c.suffix, async c => { + JsonReceiver:162 => draft.comments = await q.receiveListDefined(space.comments, async c => { +{"state":"ADD","value":" "} + JsonSender:85 => await q.getAndSend(space, s => s.whitespace); + JsonReceiver:170 => draft.whitespace = await q.receive(space.whitespace); +{"state":"ADD","ref":1} + JsonSender:28 => await q.getAndSend(j, j2 => j2.markers); + JsonReceiver:104 => draft.markers = await q.receive(j.markers); +{"state":"ADD"} + JsonSender:73 => await q.getAndSendList(obj, o => o.members, j => j.element.id, + JsonReceiver:155 => draft.members = await q.receiveListDefined(obj.members, +{"state":"ADD","valueType":"org.openrewrite.json.tree.RightPadded"} + JsonSender:73 => await q.getAndSendList(obj, o => o.members, j => j.element.id, + undefined +{"state":"ADD","valueType":"org.openrewrite.json.tree.Json$Member"} + JsonSender:90 => await q.getAndSend(right, r => r.element, j => this.visit(j, q)); + JsonReceiver:179 => draft.element = await p.receive(right.element, async j => await this.visit(j, p)!) as Draft; +{"state":"ADD","value":"1c7cdf63-d593-44da-9a4e-ac3ac01e344d"} + JsonSender:25 => await q.getAndSend(j, j2 => j2.id); + JsonReceiver:102 => draft.id = await q.receive(j.id); +{"state":"ADD","ref":3} + JsonSender:26 => await q.getAndSend(j, j2 => asRef(j2.prefix), + JsonReceiver:103 => draft.prefix = await q.receive(j.prefix, async space => await this.visitSpace(space, q)); +{"state":"ADD","ref":1} + JsonSender:28 => await q.getAndSend(j, j2 => j2.markers); + JsonReceiver:104 => draft.markers = await q.receive(j.markers); +{"state":"ADD","valueType":"org.openrewrite.json.tree.RightPadded"} + JsonSender:67 => await q.getAndSend(member, m => m.key, async j => await this.visitRightPadded(j, q)); + JsonReceiver:146 => draft.key = await q.receive(member.key, +{"state":"ADD","valueType":"org.openrewrite.json.tree.Json$Literal"} + JsonSender:90 => await q.getAndSend(right, r => r.element, j => this.visit(j, q)); + JsonReceiver:179 => draft.element = await p.receive(right.element, async j => await this.visit(j, p)!) as Draft; +{"state":"ADD","value":"79c2ed6c-0201-49e5-bef3-91945bf2ff48"} + JsonSender:25 => await q.getAndSend(j, j2 => j2.id); + JsonReceiver:102 => draft.id = await q.receive(j.id); +{"state":"ADD","valueType":"org.openrewrite.json.tree.Space","ref":23} + JsonSender:26 => await q.getAndSend(j, j2 => asRef(j2.prefix), + JsonReceiver:103 => draft.prefix = await q.receive(j.prefix, async space => await this.visitSpace(space, q)); +{"state":"ADD"} + JsonSender:79 => await q.getAndSendList(space, s => s.comments, c => c.text + c.suffix, async c => { + JsonReceiver:162 => draft.comments = await q.receiveListDefined(space.comments, async c => { +{"state":"ADD","value":"\n "} + JsonSender:85 => await q.getAndSend(space, s => s.whitespace); + JsonReceiver:170 => draft.whitespace = await q.receive(space.whitespace); +{"state":"ADD","ref":1} + JsonSender:28 => await q.getAndSend(j, j2 => j2.markers); + JsonReceiver:104 => draft.markers = await q.receive(j.markers); +{"state":"ADD","value":"\"@types/lodash\""} + JsonSender:61 => await q.getAndSend(literal, lit => lit.source); + JsonReceiver:139 => draft.source = await q.receive(literal.source); +{"state":"ADD","value":"@types/lodash"} + JsonSender:62 => await q.getAndSend(literal, lit => lit.value); + JsonReceiver:140 => draft.value = await q.receive(literal.value); +{"state":"ADD","valueType":"org.openrewrite.json.tree.Space","ref":24} + JsonSender:91 => await q.getAndSend(right, r => asRef(r.after), async space => await this.visitSpace(space, q)); + JsonReceiver:180 => draft.after = await p.receive(right.after, async space => await this.visitSpace(space, p)); +{"state":"ADD"} + JsonSender:79 => await q.getAndSendList(space, s => s.comments, c => c.text + c.suffix, async c => { + JsonReceiver:162 => draft.comments = await q.receiveListDefined(space.comments, async c => { +{"state":"ADD","value":""} + JsonSender:85 => await q.getAndSend(space, s => s.whitespace); + JsonReceiver:170 => draft.whitespace = await q.receive(space.whitespace); +{"state":"ADD","ref":1} + JsonSender:92 => await q.getAndSend(right, r => r.markers); + JsonReceiver:181 => draft.markers = await p.receiveMarkers(right.markers); +{"state":"ADD","valueType":"org.openrewrite.json.tree.Json$Literal"} + JsonSender:68 => await q.getAndSend(member, m => m.value, async j => await this.visit(j, q)); + JsonReceiver:148 => draft.value = await q.receive(member.value, +{"state":"ADD","value":"cbb5231a-06a8-4a6f-ada0-243163f63ecf"} + JsonSender:25 => await q.getAndSend(j, j2 => j2.id); + JsonReceiver:102 => draft.id = await q.receive(j.id); +{"state":"ADD","valueType":"org.openrewrite.json.tree.Space","ref":25} + JsonSender:26 => await q.getAndSend(j, j2 => asRef(j2.prefix), + JsonReceiver:103 => draft.prefix = await q.receive(j.prefix, async space => await this.visitSpace(space, q)); +{"state":"ADD"} + JsonSender:79 => await q.getAndSendList(space, s => s.comments, c => c.text + c.suffix, async c => { + JsonReceiver:162 => draft.comments = await q.receiveListDefined(space.comments, async c => { +{"state":"ADD","value":" "} + JsonSender:85 => await q.getAndSend(space, s => s.whitespace); + JsonReceiver:170 => draft.whitespace = await q.receive(space.whitespace); +{"state":"ADD","ref":1} + JsonSender:28 => await q.getAndSend(j, j2 => j2.markers); + JsonReceiver:104 => draft.markers = await q.receive(j.markers); +{"state":"ADD","value":"\"^4.14.195\""} + JsonSender:61 => await q.getAndSend(literal, lit => lit.source); + JsonReceiver:139 => draft.source = await q.receive(literal.source); +{"state":"ADD","value":"^4.14.195"} + JsonSender:62 => await q.getAndSend(literal, lit => lit.value); + JsonReceiver:140 => draft.value = await q.receive(literal.value); +{"state":"ADD","valueType":"org.openrewrite.json.tree.Space","ref":26} + JsonSender:91 => await q.getAndSend(right, r => asRef(r.after), async space => await this.visitSpace(space, q)); + JsonReceiver:180 => draft.after = await p.receive(right.after, async space => await this.visitSpace(space, p)); +{"state":"ADD"} + JsonSender:79 => await q.getAndSendList(space, s => s.comments, c => c.text + c.suffix, async c => { + JsonReceiver:162 => draft.comments = await q.receiveListDefined(space.comments, async c => { +{"state":"ADD","value":"\n "} + JsonSender:85 => await q.getAndSend(space, s => s.whitespace); + JsonReceiver:170 => draft.whitespace = await q.receive(space.whitespace); +{"state":"ADD","ref":1} + JsonSender:92 => await q.getAndSend(right, r => r.markers); + JsonReceiver:181 => draft.markers = await p.receiveMarkers(right.markers); +{"state":"ADD","valueType":"org.openrewrite.json.tree.Space","ref":27} + JsonSender:91 => await q.getAndSend(right, r => asRef(r.after), async space => await this.visitSpace(space, q)); + JsonReceiver:180 => draft.after = await p.receive(right.after, async space => await this.visitSpace(space, p)); +{"state":"ADD"} + JsonSender:79 => await q.getAndSendList(space, s => s.comments, c => c.text + c.suffix, async c => { + JsonReceiver:162 => draft.comments = await q.receiveListDefined(space.comments, async c => { +{"state":"ADD","value":"\n"} + JsonSender:85 => await q.getAndSend(space, s => s.whitespace); + JsonReceiver:170 => draft.whitespace = await q.receive(space.whitespace); +{"state":"ADD","ref":1} + JsonSender:92 => await q.getAndSend(right, r => r.markers); + JsonReceiver:181 => draft.markers = await p.receiveMarkers(right.markers); +{"state":"ADD","valueType":"org.openrewrite.json.tree.Space","ref":28} + JsonSender:40 => await q.getAndSend(document, d => asRef(d.eof), + JsonReceiver:116 => draft.eof = await q.receive(document.eof, async space => await this.visitSpace(space, q)); +{"state":"ADD"} + JsonSender:79 => await q.getAndSendList(space, s => s.comments, c => c.text + c.suffix, async c => { + JsonReceiver:162 => draft.comments = await q.receiveListDefined(space.comments, async c => { +{"state":"ADD","value":""} + JsonSender:85 => await q.getAndSend(space, s => s.whitespace); + JsonReceiver:170 => draft.whitespace = await q.receive(space.whitespace); +{"state":"ADD","value":"2410ff3b-d200-48f4-a923-91a12602bb00"} + JavaScriptSender:42 => await q.getAndSend(j, j2 => j2.id); + JavaScriptReceiver:567 => draft.id = await q.receive(j.id); +{"state":"ADD","valueType":"org.openrewrite.java.tree.Space"} + JavaScriptSender:43 => await q.getAndSend(j, j2 => j2.prefix, space => this.visitSpace(space, q)); + JavaScriptReceiver:568 => draft.prefix = await q.receive(j.prefix, space => this.visitSpace(space, q)); +{"state":"ADD"} + JavaScriptSender:522 => return this.javaSender.visitSpace(space, q); + JavaScriptReceiver:1118 => return this.javaReceiverDelegate.visitSpace(space, q); +{"state":"ADD","ref":1} + JavaScriptSender:44 => await q.getAndSend(j, j2 => j2.markers); + JavaScriptReceiver:569 => draft.markers = await q.receive(j.markers); +{"state":"ADD","value":"680709506811822080.ts"} + JavaScriptSender:49 => await q.getAndSend(cu, c => c.sourcePath); + JavaScriptReceiver:577 => draft.sourcePath = await q.receive(cu.sourcePath); +{"state":"NO_CHANGE"} + JavaScriptSender:50 => await q.getAndSend(cu, c => c.charsetName); + JavaScriptReceiver:578 => draft.charsetName = await q.receive(cu.charsetName); +{"state":"ADD","value":false} + JavaScriptSender:51 => await q.getAndSend(cu, c => c.charsetBomMarked); + JavaScriptReceiver:579 => draft.charsetBomMarked = await q.receive(cu.charsetBomMarked); +{"state":"NO_CHANGE"} + JavaScriptSender:52 => await q.getAndSend(cu, c => c.checksum); + JavaScriptReceiver:580 => draft.checksum = await q.receive(cu.checksum); +{"state":"NO_CHANGE"} + JavaScriptSender:53 => await q.getAndSend(cu, c => c.fileAttributes); + JavaScriptReceiver:581 => draft.fileAttributes = await q.receive(cu.fileAttributes); +{"state":"ADD"} + JavaScriptSender:54 => await q.getAndSendList(cu, c => c.statements, stmt => stmt.element.id, stmt => this.visitRightPadded(stmt, q)); + JavaScriptReceiver:582 => draft.statements = await q.receiveListDefined(cu.statements, stmt => this.visitRightPadded(stmt, q)); +{"state":"ADD","valueType":"org.openrewrite.java.tree.JRightPadded"} + JavaScriptSender:54 => await q.getAndSendList(cu, c => c.statements, stmt => stmt.element.id, stmt => this.visitRightPadded(stmt, q)); + undefined +{"state":"ADD","valueType":"org.openrewrite.javascript.tree.JS$Import"} + JavaScriptSender:510 => return this.javaSender.visitRightPadded(right, q); + JavaScriptReceiver:1106 => return this.javaReceiverDelegate.visitRightPadded(right, q) +{"state":"ADD","value":"49502576-da0b-46ec-afae-c763626588c5"} + JavaScriptSender:42 => await q.getAndSend(j, j2 => j2.id); + JavaScriptReceiver:567 => draft.id = await q.receive(j.id); +{"state":"ADD","valueType":"org.openrewrite.java.tree.Space"} + JavaScriptSender:43 => await q.getAndSend(j, j2 => j2.prefix, space => this.visitSpace(space, q)); + JavaScriptReceiver:568 => draft.prefix = await q.receive(j.prefix, space => this.visitSpace(space, q)); +{"state":"ADD"} + JavaScriptSender:522 => return this.javaSender.visitSpace(space, q); + JavaScriptReceiver:1118 => return this.javaReceiverDelegate.visitSpace(space, q); +{"state":"ADD","ref":1} + JavaScriptSender:44 => await q.getAndSend(j, j2 => j2.markers); + JavaScriptReceiver:569 => draft.markers = await q.receive(j.markers); +{"state":"ADD"} + JavaScriptSender:137 => await q.getAndSendList(jsImport, el => el.modifiers, el => el.id, el => this.visit(el, q)); + JavaScriptReceiver:680 => draft.modifiers = await q.receiveListDefined(draft.modifiers, el => this.visitDefined(el, q)); +{"state":"ADD","valueType":"org.openrewrite.javascript.tree.JS$ImportClause"} + JavaScriptSender:138 => await q.getAndSend(jsImport, el => el.importClause, el => this.visit(el, q)); + JavaScriptReceiver:681 => draft.importClause = await q.receive(draft.importClause, el => this.visitDefined(el, q)); +{"state":"ADD","value":"3d34b6c1-5d85-47e5-bf94-e406dce1c7bd"} + JavaScriptSender:42 => await q.getAndSend(j, j2 => j2.id); + JavaScriptReceiver:567 => draft.id = await q.receive(j.id); +{"state":"ADD","valueType":"org.openrewrite.java.tree.Space"} + JavaScriptSender:43 => await q.getAndSend(j, j2 => j2.prefix, space => this.visitSpace(space, q)); + JavaScriptReceiver:568 => draft.prefix = await q.receive(j.prefix, space => this.visitSpace(space, q)); +{"state":"ADD"} + JavaScriptSender:522 => return this.javaSender.visitSpace(space, q); + JavaScriptReceiver:1118 => return this.javaReceiverDelegate.visitSpace(space, q); +{"state":"ADD","ref":1} + JavaScriptSender:44 => await q.getAndSend(j, j2 => j2.markers); + JavaScriptReceiver:569 => draft.markers = await q.receive(j.markers); +{"state":"ADD","value":false} + JavaScriptSender:146 => await q.getAndSend(jsImportClause, el => el.typeOnly); + JavaScriptReceiver:690 => draft.typeOnly = await q.receive(draft.typeOnly); +{"state":"ADD","valueType":"org.openrewrite.java.tree.JRightPadded"} + JavaScriptSender:147 => await q.getAndSend(jsImportClause, el => el.name, el => this.visitRightPadded(el, q)); + JavaScriptReceiver:691 => draft.name = await q.receive(draft.name, el => this.visitRightPadded(el, q)); +{"state":"ADD","valueType":"org.openrewrite.java.tree.J$Identifier"} + JavaScriptSender:510 => return this.javaSender.visitRightPadded(right, q); + JavaScriptReceiver:1106 => return this.javaReceiverDelegate.visitRightPadded(right, q) +{"state":"ADD","value":"7fa7b9a9-1825-493f-abe5-e9888ebced4e"} + JavaScriptSender:510 => return this.javaSender.visitRightPadded(right, q); + undefined +{"state":"NO_CHANGE"} + JavaScriptSender:148 => await q.getAndSend(jsImportClause, el => el.namedBindings, el => this.visit(el, q)); + JavaScriptReceiver:692 => draft.namedBindings = await q.receive(draft.namedBindings, el => this.visitDefined(el, q)); +{"state":"ADD","valueType":"org.openrewrite.java.tree.JLeftPadded"} + JavaScriptSender:139 => await q.getAndSend(jsImport, el => el.moduleSpecifier, el => this.visitLeftPadded(el, q)); + JavaScriptReceiver:682 => draft.moduleSpecifier = await q.receive(draft.moduleSpecifier, el => this.visitLeftPadded(el, q)); +{"state":"ADD","valueType":"org.openrewrite.java.tree.Space"} + JavaScriptSender:514 => return this.javaSender.visitLeftPadded(left, q); + JavaScriptReceiver:1110 => return this.javaReceiverDelegate.visitLeftPadded(left, q); +{"state":"ADD"} + JavaScriptSender:514 => return this.javaSender.visitLeftPadded(left, q); + undefined +{"state":"NO_CHANGE"} + JavaScriptSender:140 => await q.getAndSend(jsImport, el => el.attributes, el => this.visit(el, q)); + JavaScriptReceiver:683 => draft.attributes = await q.receive(draft.attributes, el => this.visitDefined(el, q)); +{"state":"NO_CHANGE"} + JavaScriptSender:141 => await q.getAndSend(jsImport, el => el.initializer, el => this.visitLeftPadded(el, q)); + JavaScriptReceiver:684 => draft.initializer = await q.receive(draft.initializer, el => this.visitLeftPadded(el, q)); +{"state":"ADD","valueType":"org.openrewrite.java.tree.JRightPadded"} + JavaScriptSender:54 => await q.getAndSendList(cu, c => c.statements, stmt => stmt.element.id, stmt => this.visitRightPadded(stmt, q)); + undefined +{"state":"ADD","valueType":"org.openrewrite.java.tree.J$VariableDeclarations"} + JavaScriptSender:510 => return this.javaSender.visitRightPadded(right, q); + JavaScriptReceiver:1106 => return this.javaReceiverDelegate.visitRightPadded(right, q) +{"state":"ADD","value":"c386461a-3b33-4e6a-bb6c-f0356caea42e"} + JavaScriptSender:510 => return this.javaSender.visitRightPadded(right, q); + undefined +{"state":"ADD","value":"6c66a7bd-3424-426e-9462-443b62b98a01"} + JavaScriptSender:42 => await q.getAndSend(j, j2 => j2.id); + JavaScriptReceiver:567 => draft.id = await q.receive(j.id); +{"state":"ADD","valueType":"org.openrewrite.java.tree.Space"} + JavaScriptSender:43 => await q.getAndSend(j, j2 => j2.prefix, space => this.visitSpace(space, q)); + JavaScriptReceiver:568 => draft.prefix = await q.receive(j.prefix, space => this.visitSpace(space, q)); +{"state":"ADD"} + JavaScriptSender:522 => return this.javaSender.visitSpace(space, q); + JavaScriptReceiver:1118 => return this.javaReceiverDelegate.visitSpace(space, q); +{"state":"ADD","ref":1} + JavaScriptSender:44 => await q.getAndSend(j, j2 => j2.markers); + JavaScriptReceiver:569 => draft.markers = await q.receive(j.markers); +{"state":"ADD"} + JavaScriptSender:66 => await q.getAndSendList(arrowFunction, el => el.leadingAnnotations, el => el.id, el => this.visit(el, q)); + JavaScriptReceiver:597 => draft.leadingAnnotations = await q.receiveListDefined(draft.leadingAnnotations, el => this.visitDefined(el, q)); +{"state":"ADD"} + JavaScriptSender:67 => await q.getAndSendList(arrowFunction, el => el.modifiers, el => el.id, el => this.visit(el, q)); + JavaScriptReceiver:598 => draft.modifiers = await q.receiveListDefined(draft.modifiers, el => this.visitDefined(el, q)); +{"state":"NO_CHANGE"} + JavaScriptSender:68 => await q.getAndSend(arrowFunction, el => el.typeParameters, el => this.visit(el, q)); + JavaScriptReceiver:599 => draft.typeParameters = await q.receive(draft.typeParameters, el => this.visitDefined(el, q)); +{"state":"ADD","valueType":"org.openrewrite.java.tree.J$Lambda"} + JavaScriptSender:69 => await q.getAndSend(arrowFunction, el => el.lambda, el => this.visit(el, q)); + JavaScriptReceiver:600 => draft.lambda = await q.receive(draft.lambda, el => this.visitDefined(el, q)); +{"state":"ADD","value":"afa2a3d0-844a-4691-805f-c9348a45c9a8"} + JavaScriptSender:38 => return this.javaSender.visit(tree, p, parent); + JavaScriptReceiver:561 => return this.javaReceiverDelegate.visit(tree, p, parent); +{"state":"NO_CHANGE"} + JavaScriptSender:70 => await q.getAndSend(arrowFunction, el => el.returnTypeExpression, el => this.visit(el, q)); + JavaScriptReceiver:601 => draft.returnTypeExpression = await q.receive(draft.returnTypeExpression, el => this.visitDefined(el, q)); +{"state":"ADD","valueType":"org.openrewrite.java.tree.Space"} + JavaScriptSender:55 => await q.getAndSend(cu, c => c.eof, space => this.visitSpace(space, q)); + JavaScriptReceiver:583 => draft.eof = await q.receive(cu.eof, space => this.visitSpace(space, q)); +{"state":"ADD"} + JavaScriptSender:522 => return this.javaSender.visitSpace(space, q); + JavaScriptReceiver:1118 => return this.javaReceiverDelegate.visitSpace(space, q); diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/tree/GroupArtifact.java b/rewrite-maven/src/main/java/org/openrewrite/maven/tree/GroupArtifact.java index fd88cbe0c5..21400892bd 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/tree/GroupArtifact.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/tree/GroupArtifact.java @@ -18,6 +18,7 @@ import lombok.Value; import lombok.With; +import org.jspecify.annotations.Nullable; import java.io.Serializable; @@ -26,4 +27,9 @@ public class GroupArtifact implements Serializable { String groupId; String artifactId; + + public GroupArtifact(@Nullable String groupId, String artifactId) { + this.groupId = groupId == null ? "" : groupId; + this.artifactId = artifactId; + } } From 4c1f89f13d84e21481222618407c1bc85bcd8ad7 Mon Sep 17 00:00:00 2001 From: Sam Snyder Date: Thu, 9 Oct 2025 23:41:19 -0700 Subject: [PATCH 05/19] Add varargs support to refactored UpgradeDependencyVersion - Added scanVarargsDependencies() to properly scan all literal dependencies in varargs calls - Added updateVarargsDependencies() to handle updating multiple dependencies in one call - The GradleDependency trait only processes first argument, so varargs need special handling - Varargs test now passes, but 3 other tests are failing and need investigation --- .../gradle/UpgradeDependencyVersion.java | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/UpgradeDependencyVersion.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/UpgradeDependencyVersion.java index 0658e4b93c..c6224bca80 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/UpgradeDependencyVersion.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/UpgradeDependencyVersion.java @@ -166,6 +166,13 @@ public boolean isAcceptable(SourceFile sourceFile, ExecutionContext ctx) { @Override public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { J.MethodInvocation m = (J.MethodInvocation) super.visitMethodInvocation(method, ctx); + + // Handle varargs case first - scan all literal dependencies + if (isConfigurationMethod(m) && hasMultipleLiteralArguments(m)) { + scanVarargsDependencies(m, ctx); + return m; + } + GradleDependency gradleDependency = new GradleDependency.Matcher().get(getCursor()).orElse(null); if (gradleDependency == null) { return m; @@ -225,6 +232,68 @@ private boolean shouldResolveVersion(String declaredGroupId, String declaredArti new DependencyMatcher(groupId, artifactId, null).matches(declaredGroupId, declaredArtifactId); } + private boolean isConfigurationMethod(J.MethodInvocation m) { + // Check if this is a dependency configuration method like implementation, api, etc. + MethodMatcher dependencyDsl = new MethodMatcher("DependencyHandlerSpec *(..)"); + return dependencyDsl.matches(m) && !"project".equals(m.getSimpleName()); + } + + private boolean hasMultipleLiteralArguments(J.MethodInvocation m) { + // Check if there are multiple literal string arguments (varargs case) + if (m.getArguments().size() <= 1) { + return false; + } + int literalCount = 0; + for (Expression arg : m.getArguments()) { + if (arg instanceof J.Literal && ((J.Literal) arg).getValue() instanceof String) { + literalCount++; + if (literalCount > 1) { + return true; + } + } + } + return false; + } + + private void scanVarargsDependencies(J.MethodInvocation m, ExecutionContext ctx) { + // Scan each literal dependency argument + for (Expression arg : m.getArguments()) { + if (arg instanceof J.Literal && ((J.Literal) arg).getValue() instanceof String) { + String gav = (String) ((J.Literal) arg).getValue(); + Dependency dep = Dependency.parse(gav); + if (dep != null) { + String declaredGroupId = dep.getGroupId(); + String declaredArtifactId = dep.getArtifactId(); + String declaredVersion = dep.getVersion(); + + if (declaredGroupId == null || declaredArtifactId == null || declaredVersion == null) { + continue; + } + + // Record the dependency and resolve its version if needed + GroupArtifact ga = new GroupArtifact(declaredGroupId, declaredArtifactId); + String configName = m.getSimpleName(); + if (gradleProject != null) { + acc.getConfigurationPerGAPerModule() + .computeIfAbsent(getGradleProjectKey(gradleProject), k -> new HashMap<>()) + .computeIfAbsent(ga, k -> new HashSet<>()) + .add(configName); + } + + if (!acc.gaToNewVersion.containsKey(ga) && shouldResolveVersion(declaredGroupId, declaredArtifactId)) { + try { + String resolvedVersion = new DependencyVersionSelector(metadataFailures, gradleProject, null) + .select(ga, configName, newVersion, versionPattern, ctx); + acc.gaToNewVersion.put(ga, resolvedVersion); + } catch (MavenDownloadingException e) { + acc.gaToNewVersion.put(ga, e); + } + } + } + } + } + } + /** * Gathers version variable names for dependencies */ From 58e84ffc12c715391849caa96553b1859066e739 Mon Sep 17 00:00:00 2001 From: Sam Snyder Date: Fri, 10 Oct 2025 00:33:40 -0700 Subject: [PATCH 06/19] Implement GradleMultiDependency trait for cleaner varargs handling - Created GradleMultiDependency trait to represent multiple dependencies in a single invocation - Uses synthetic J.MethodInvocation wrappers for each dependency - Provides map() method for clean functional transformation of dependencies - Refactored UpgradeDependencyVersion to use the new trait - Removed ad-hoc varargs handling code in favor of trait-based approach - Varargs test still passes, maintaining same 3 unrelated failing tests --- .../gradle/UpgradeDependencyVersion.java | 207 +++++++--------- .../gradle/trait/GradleDependency.java | 4 +- .../gradle/trait/GradleMultiDependency.java | 226 ++++++++++++++++++ 3 files changed, 315 insertions(+), 122 deletions(-) create mode 100644 rewrite-gradle/src/main/java/org/openrewrite/gradle/trait/GradleMultiDependency.java diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/UpgradeDependencyVersion.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/UpgradeDependencyVersion.java index c6224bca80..b0c2cff609 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/UpgradeDependencyVersion.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/UpgradeDependencyVersion.java @@ -23,6 +23,7 @@ import org.openrewrite.*; import org.openrewrite.gradle.internal.ChangeStringLiteral; import org.openrewrite.gradle.trait.GradleDependency; +import org.openrewrite.gradle.trait.GradleMultiDependency; import org.openrewrite.gradle.marker.GradleProject; import org.openrewrite.groovy.tree.G; import org.openrewrite.internal.ListUtils; @@ -167,24 +168,36 @@ public boolean isAcceptable(SourceFile sourceFile, ExecutionContext ctx) { public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { J.MethodInvocation m = (J.MethodInvocation) super.visitMethodInvocation(method, ctx); - // Handle varargs case first - scan all literal dependencies - if (isConfigurationMethod(m) && hasMultipleLiteralArguments(m)) { - scanVarargsDependencies(m, ctx); + // Check for multi-dependency (varargs) case first + GradleMultiDependency multiDependency = new GradleMultiDependency.Matcher().get(getCursor()).orElse(null); + if (multiDependency != null) { + // Scan each dependency in the multi-dependency + for (GradleDependency dep : multiDependency.getDependencies()) { + scanDependency(dep, ctx); + } return m; } + // Handle single dependency case GradleDependency gradleDependency = new GradleDependency.Matcher().get(getCursor()).orElse(null); if (gradleDependency == null) { return m; } + scanDependency(gradleDependency, ctx); + return m; + } + /** + * Scans a single dependency and records its information for later processing. + */ + private void scanDependency(GradleDependency gradleDependency, ExecutionContext ctx) { gatherVariables(gradleDependency); String declaredGroupId = gradleDependency.getDeclaredGroupId(); String declaredArtifactId = gradleDependency.getDeclaredArtifactId(); String declaredVersion = gradleDependency.getDeclaredVersion(); if (declaredGroupId == null || declaredArtifactId == null || declaredVersion == null) { - return m; + return; } // Record the dependency and resolve its version if needed @@ -219,9 +232,8 @@ public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) acc.gaToNewVersion.put(ga, e); } } - - return m; } + // Some recipes make use of UpgradeDependencyVersion as an implementation detail. // Those other recipes might not know up-front which dependency needs upgrading // So they use the UpgradeDependencyVersion recipe with null groupId and artifactId to pre-populate all data they could possibly need @@ -232,68 +244,6 @@ private boolean shouldResolveVersion(String declaredGroupId, String declaredArti new DependencyMatcher(groupId, artifactId, null).matches(declaredGroupId, declaredArtifactId); } - private boolean isConfigurationMethod(J.MethodInvocation m) { - // Check if this is a dependency configuration method like implementation, api, etc. - MethodMatcher dependencyDsl = new MethodMatcher("DependencyHandlerSpec *(..)"); - return dependencyDsl.matches(m) && !"project".equals(m.getSimpleName()); - } - - private boolean hasMultipleLiteralArguments(J.MethodInvocation m) { - // Check if there are multiple literal string arguments (varargs case) - if (m.getArguments().size() <= 1) { - return false; - } - int literalCount = 0; - for (Expression arg : m.getArguments()) { - if (arg instanceof J.Literal && ((J.Literal) arg).getValue() instanceof String) { - literalCount++; - if (literalCount > 1) { - return true; - } - } - } - return false; - } - - private void scanVarargsDependencies(J.MethodInvocation m, ExecutionContext ctx) { - // Scan each literal dependency argument - for (Expression arg : m.getArguments()) { - if (arg instanceof J.Literal && ((J.Literal) arg).getValue() instanceof String) { - String gav = (String) ((J.Literal) arg).getValue(); - Dependency dep = Dependency.parse(gav); - if (dep != null) { - String declaredGroupId = dep.getGroupId(); - String declaredArtifactId = dep.getArtifactId(); - String declaredVersion = dep.getVersion(); - - if (declaredGroupId == null || declaredArtifactId == null || declaredVersion == null) { - continue; - } - - // Record the dependency and resolve its version if needed - GroupArtifact ga = new GroupArtifact(declaredGroupId, declaredArtifactId); - String configName = m.getSimpleName(); - if (gradleProject != null) { - acc.getConfigurationPerGAPerModule() - .computeIfAbsent(getGradleProjectKey(gradleProject), k -> new HashMap<>()) - .computeIfAbsent(ga, k -> new HashSet<>()) - .add(configName); - } - - if (!acc.gaToNewVersion.containsKey(ga) && shouldResolveVersion(declaredGroupId, declaredArtifactId)) { - try { - String resolvedVersion = new DependencyVersionSelector(metadataFailures, gradleProject, null) - .select(ga, configName, newVersion, versionPattern, ctx); - acc.gaToNewVersion.put(ga, resolvedVersion); - } catch (MavenDownloadingException e) { - acc.gaToNewVersion.put(ga, e); - } - } - } - } - } - } - /** * Gathers version variable names for dependencies */ @@ -426,7 +376,6 @@ private class UpdateGradle extends JavaVisitor { GradleProject gradleProject; final DependencyMatcher dependencyMatcher = new DependencyMatcher(groupId, artifactId, null); - final MethodMatcher GRADLE_DEPENDENCY_DSL = new MethodMatcher("DependencyHandlerSpec *(..)"); @Override public boolean isAcceptable(SourceFile sourceFile, ExecutionContext ctx) { @@ -461,10 +410,16 @@ public J postVisit(J tree, ExecutionContext ctx) { public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { J.MethodInvocation m = (J.MethodInvocation) super.visitMethodInvocation(method, ctx); - // Check for varargs case first - if there are multiple literal arguments, handle them all - if (isConfigurationMethod(m) && hasMultipleLiteralArguments(m)) { - // Handle varargs case where multiple dependencies are specified in one call - m = updateVarargsDependencies(m, ctx); + // Check for multi-dependency (varargs) case first + GradleMultiDependency multiDependency = new GradleMultiDependency.Matcher().get(getCursor()).orElse(null); + if (multiDependency != null) { + // Use the map function to transform each dependency + m = multiDependency.map(dep -> { + if (!dep.matches(dependencyMatcher)) { + return dep; + } + return updateSingleDependency(dep, ctx); + }); } else { // Handle single dependency case GradleDependency.Matcher gradleDependencyMatcher = new GradleDependency.Matcher(); @@ -533,6 +488,66 @@ public J.Assignment visitAssignment(J.Assignment assignment, ExecutionContext ex return m; } + /** + * Updates a single dependency and returns the transformed GradleDependency. + * This method is used by the GradleMultiDependency.map() function. + */ + private GradleDependency updateSingleDependency(GradleDependency dependency, ExecutionContext ctx) { + // Get the current version and coordinates + String currentVersion = dependency.getDeclaredVersion(); + String groupId = dependency.getDeclaredGroupId(); + String artifactId = dependency.getDeclaredArtifactId(); + + if (groupId == null || artifactId == null) { + return dependency; + } + + // Check if we have a new version for this dependency + Object scanResult = acc.gaToNewVersion.get(new GroupArtifact(groupId, artifactId)); + if (scanResult instanceof Exception) { + // Can't mark warning on a synthetic tree, so just return unchanged + return dependency; + } + + // Handle variable references + String versionVariable = dependency.getVersionVariable(); + if (versionVariable != null) { + // Variable updates are handled separately + return dependency; + } + + // If version starts with $, it's a variable reference we can't handle directly + if (currentVersion != null && currentVersion.startsWith("$")) { + return dependency; + } + + // Select the new version + String selectedVersion; + try { + String configName = dependency.getConfigurationName(); + + if (currentVersion == null) { + // For dependencies without a version (e.g., some platform dependencies) + GroupArtifact ga = new GroupArtifact(groupId, artifactId); + selectedVersion = new DependencyVersionSelector(metadataFailures, gradleProject, null) + .select(ga, configName, newVersion, versionPattern, ctx); + } else { + GroupArtifactVersion gav = new GroupArtifactVersion(groupId, artifactId, currentVersion); + selectedVersion = new DependencyVersionSelector(metadataFailures, gradleProject, null) + .select(gav, configName, newVersion, versionPattern, ctx); + } + } catch (MavenDownloadingException e) { + return dependency; + } + + if (selectedVersion == null || (currentVersion != null && currentVersion.equals(selectedVersion))) { + return dependency; + } + + // Update the dependency version using the trait's method + return dependency.withDeclaredVersion(selectedVersion); + } + private J.MethodInvocation updateDependency(GradleDependency dependency, ExecutionContext ctx) { // Check if this dependency matches our pattern if (!dependency.matches(dependencyMatcher)) { @@ -601,52 +616,6 @@ private void replaceVariableValue(String versionVariableName, J.MethodInvocation .computeIfAbsent(new GroupArtifact(groupId, artifactId), it -> new HashSet<>()) .add(m.getSimpleName()); } - - private boolean isConfigurationMethod(J.MethodInvocation m) { - // Check if this is a dependency configuration method like implementation, api, etc. - return GRADLE_DEPENDENCY_DSL.matches(m) && !"project".equals(m.getSimpleName()); - } - - private boolean hasMultipleLiteralArguments(J.MethodInvocation m) { - // Check if there are multiple literal string arguments (varargs case) - if (m.getArguments().size() <= 1) { - return false; - } - int literalCount = 0; - for (Expression arg : m.getArguments()) { - if (arg instanceof J.Literal && ((J.Literal) arg).getValue() instanceof String) { - literalCount++; - if (literalCount > 1) { - return true; - } - } - } - return false; - } - - private J.MethodInvocation updateVarargsDependencies(J.MethodInvocation m, ExecutionContext ctx) { - // Process each argument individually for varargs dependencies - return m.withArguments(ListUtils.map(m.getArguments(), arg -> { - if (arg instanceof J.Literal && ((J.Literal) arg).getValue() instanceof String) { - String gav = (String) ((J.Literal) arg).getValue(); - Dependency dep = Dependency.parse(gav); - if (dep != null && dependencyMatcher.matches(dep.getGroupId(), dep.getArtifactId())) { - // Check if we have a new version for this dependency - GroupArtifact ga = new GroupArtifact(dep.getGroupId(), dep.getArtifactId()); - Object scanResult = acc.gaToNewVersion.get(ga); - if (scanResult instanceof String) { - String newVersion = (String) scanResult; - if (newVersion != null && !newVersion.equals(dep.getVersion())) { - // Update the version in the string literal - Dependency updatedDep = dep.withVersion(newVersion); - return ChangeStringLiteral.withStringValue((J.Literal) arg, updatedDep.toStringNotation()); - } - } - } - } - return arg; - })); - } } @AllArgsConstructor 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 12c55f4d9b..8902916d74 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 @@ -51,8 +51,6 @@ @Value public class GradleDependency implements Trait { - private static final MethodMatcher PROPERTY_METHOD = new MethodMatcher("* property(String)"); - private static final MethodMatcher FIND_PROPERTY_METHOD = new MethodMatcher("* findProperty(String)"); Cursor cursor; @@ -658,7 +656,7 @@ public boolean matches(DependencyMatcher matcher) { */ @Nullable private static String extractPropertyNameFromMethodInvocation(J.MethodInvocation mi) { - if (PROPERTY_METHOD.matches(mi, true) || FIND_PROPERTY_METHOD.matches(mi, true)) { + if (mi.getSimpleName().equals("property") || mi.getSimpleName().equals("findProperty")) { if (!mi.getArguments().isEmpty() && mi.getArguments().get(0) instanceof J.Literal) { J.Literal literal = (J.Literal) mi.getArguments().get(0); if (literal.getValue() instanceof String) { diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/trait/GradleMultiDependency.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/trait/GradleMultiDependency.java new file mode 100644 index 0000000000..45012f5b63 --- /dev/null +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/trait/GradleMultiDependency.java @@ -0,0 +1,226 @@ +/* + * Copyright 2024 the original author or authors. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.gradle.trait; + +import lombok.Getter; +import lombok.Value; +import org.jspecify.annotations.Nullable; +import org.openrewrite.Cursor; +import org.openrewrite.Tree; +import org.openrewrite.TreeVisitor; +import org.openrewrite.gradle.marker.GradleProject; +import org.openrewrite.internal.ListUtils; +import org.openrewrite.internal.StringUtils; +import org.openrewrite.java.JavaVisitor; +import org.openrewrite.java.MethodMatcher; +import org.openrewrite.java.tree.Expression; +import org.openrewrite.java.tree.J; +import org.openrewrite.maven.tree.Dependency; +import org.openrewrite.maven.tree.GroupArtifactVersion; +import org.openrewrite.maven.tree.ResolvedDependency; +import org.openrewrite.maven.tree.ResolvedGroupArtifactVersion; +import org.openrewrite.trait.Trait; +import org.openrewrite.trait.VisitFunction2; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * Represents multiple Gradle dependencies declared in a single method invocation (varargs). + * For example: implementation('dep1:1.0', 'dep2:2.0', 'dep3:3.0') + */ +@Value +public class GradleMultiDependency implements Trait { + Cursor cursor; + + @Getter + List dependencies; + + /** + * Gets the configuration name for these dependencies. + * For example, "implementation", "testImplementation", "api", etc. + * + * @return The configuration name + */ + public String getConfigurationName() { + J.MethodInvocation m = cursor.getValue(); + return m.getSimpleName(); + } + + /** + * Maps a transformation function over each GradleDependency in this multi-dependency. + * Returns a new J.MethodInvocation with updated arguments if any dependencies changed. + * + * @param mapper Function to transform each GradleDependency + * @return The updated J.MethodInvocation if any changes were made, or the original if not + */ + public J.MethodInvocation map(Function mapper) { + J.MethodInvocation m = cursor.getValue(); + List originalArgs = m.getArguments(); + List newArgs = new ArrayList<>(originalArgs.size()); + boolean anyChanged = false; + + for (int i = 0; i < originalArgs.size(); i++) { + Expression arg = originalArgs.get(i); + GradleDependency dep = findDependencyForArg(i); + + if (dep != null) { + GradleDependency mapped = mapper.apply(dep); + if (mapped != dep) { + // The dependency was modified + anyChanged = true; + // Extract the literal from the synthetic wrapper + J.MethodInvocation syntheticWrapper = mapped.getTree(); + if (!syntheticWrapper.getArguments().isEmpty()) { + newArgs.add(syntheticWrapper.getArguments().get(0)); + } else { + newArgs.add(arg); // Fallback to original + } + } else { + newArgs.add(arg); + } + } else { + // Not a dependency argument, keep as-is + newArgs.add(arg); + } + } + + return anyChanged ? m.withArguments(newArgs) : m; + } + + /** + * Finds the GradleDependency corresponding to the argument at the given index. + */ + @Nullable + private GradleDependency findDependencyForArg(int index) { + // Dependencies are created in the same order as the literal arguments + // So we need to count literal arguments to find the right dependency + J.MethodInvocation m = cursor.getValue(); + int literalIndex = 0; + for (int i = 0; i <= index && i < m.getArguments().size(); i++) { + Expression arg = m.getArguments().get(i); + if (arg instanceof J.Literal && ((J.Literal) arg).getValue() instanceof String) { + if (i == index) { + // This is the argument we're looking for + return literalIndex < dependencies.size() ? dependencies.get(literalIndex) : null; + } + literalIndex++; + } else if (i == index) { + // The target argument is not a literal string + return null; + } + } + return null; + } + + public static class Matcher extends GradleTraitMatcher { + private static final MethodMatcher DEPENDENCY_DSL_MATCHER = new MethodMatcher("DependencyHandlerSpec *(..)"); + + @Nullable + protected String configuration; + + public Matcher configuration(@Nullable String configuration) { + this.configuration = configuration; + return this; + } + + @Override + public

TreeVisitor asVisitor(VisitFunction2 visitor) { + return new JavaVisitor

() { + @Override + public J visitMethodInvocation(J.MethodInvocation method, P p) { + GradleMultiDependency multiDependency = test(getCursor()); + return multiDependency != null ? + (J) visitor.visit(multiDependency, p) : + super.visitMethodInvocation(method, p); + } + }; + } + + @Override + protected @Nullable GradleMultiDependency test(Cursor cursor) { + Object object = cursor.getValue(); + if (!(object instanceof J.MethodInvocation)) { + return null; + } + + J.MethodInvocation methodInvocation = (J.MethodInvocation) object; + + if (!withinDependenciesBlock(cursor)) { + return null; + } + + if (!DEPENDENCY_DSL_MATCHER.matches(methodInvocation) || "project".equals(methodInvocation.getSimpleName())) { + return null; + } + + if (!StringUtils.isBlank(configuration) && !methodInvocation.getSimpleName().equals(configuration)) { + return null; + } + + // Check if there are multiple literal string arguments (varargs case) + List args = methodInvocation.getArguments(); + if (args.size() <= 1) { + return null; // Not a multi-dependency + } + + List dependencies = new ArrayList<>(); + GradleProject gradleProject = getGradleProject(cursor); + + // Process each literal string argument as a dependency + for (Expression arg : args) { + if (arg instanceof J.Literal && ((J.Literal) arg).getValue() instanceof String) { + String gav = (String) ((J.Literal) arg).getValue(); + Dependency dep = Dependency.parse(gav); + if (dep != null) { + // Create a synthetic method invocation wrapping just this one dependency + J.MethodInvocation synthetic = methodInvocation.withArguments(Collections.singletonList(arg)); + Cursor syntheticCursor = new Cursor(cursor.getParent(), synthetic); + + // Create a resolved dependency for this + ResolvedDependency resolvedDependency = ResolvedDependency.builder() + .depth(-1) + .gav(new ResolvedGroupArtifactVersion( + null, + dep.getGroupId() != null ? dep.getGroupId() : "", + dep.getArtifactId(), + dep.getVersion() != null ? dep.getVersion() : "", + null)) + .type(dep.getType()) + .classifier(dep.getClassifier()) + .requested(dep) + .build(); + + dependencies.add(new GradleDependency(syntheticCursor, resolvedDependency)); + } + } + } + + if (dependencies.size() <= 1) { + return null; // Not really a multi-dependency if only one or zero dependencies found + } + + return new GradleMultiDependency(cursor, dependencies); + } + + private boolean withinDependenciesBlock(Cursor cursor) { + return withinBlock(cursor, "dependencies"); + } + } +} \ No newline at end of file From 955b475aa86a0983580709b1f2d74c6accf38aa5 Mon Sep 17 00:00:00 2001 From: Sam Snyder Date: Fri, 10 Oct 2025 00:44:55 -0700 Subject: [PATCH 07/19] Add filtering support to GradleMultiDependency - GradleMultiDependency now tracks groupId/artifactId filters from its Matcher - Added map(DependencyMatcher, mapper) method for explicit filtering - The trait only transforms dependencies that match the configured filters - This ensures selective updates when multiple dependencies are in one invocation - Added comprehensive tests to verify filtering behavior - Prevents unintended changes to non-matching dependencies in varargs --- .../gradle/UpgradeDependencyVersion.java | 14 +-- .../gradle/trait/GradleMultiDependency.java | 106 ++++++++++++++++-- .../GradleMultiDependencyFilterTest.java | 88 +++++++++++++++ 3 files changed, 192 insertions(+), 16 deletions(-) create mode 100644 rewrite-gradle/src/test/java/org/openrewrite/gradle/GradleMultiDependencyFilterTest.java diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/UpgradeDependencyVersion.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/UpgradeDependencyVersion.java index b0c2cff609..b3f0ee8f5f 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/UpgradeDependencyVersion.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/UpgradeDependencyVersion.java @@ -411,15 +411,13 @@ public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) J.MethodInvocation m = (J.MethodInvocation) super.visitMethodInvocation(method, ctx); // Check for multi-dependency (varargs) case first - GradleMultiDependency multiDependency = new GradleMultiDependency.Matcher().get(getCursor()).orElse(null); + GradleMultiDependency multiDependency = new GradleMultiDependency.Matcher() + .groupId(groupId) + .artifactId(artifactId) + .get(getCursor()).orElse(null); if (multiDependency != null) { - // Use the map function to transform each dependency - m = multiDependency.map(dep -> { - if (!dep.matches(dependencyMatcher)) { - return dep; - } - return updateSingleDependency(dep, ctx); - }); + // Use the map function to transform each dependency that matches our pattern + m = multiDependency.map(dependencyMatcher, dep -> updateSingleDependency(dep, ctx)); } else { // Handle single dependency case GradleDependency.Matcher gradleDependencyMatcher = new GradleDependency.Matcher(); diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/trait/GradleMultiDependency.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/trait/GradleMultiDependency.java index 45012f5b63..aec6b38145 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/trait/GradleMultiDependency.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/trait/GradleMultiDependency.java @@ -32,6 +32,7 @@ import org.openrewrite.maven.tree.GroupArtifactVersion; import org.openrewrite.maven.tree.ResolvedDependency; import org.openrewrite.maven.tree.ResolvedGroupArtifactVersion; +import org.openrewrite.semver.DependencyMatcher; import org.openrewrite.trait.Trait; import org.openrewrite.trait.VisitFunction2; @@ -52,6 +53,17 @@ public class GradleMultiDependency implements Trait { @Getter List dependencies; + /** + * Optional filters that were used when matching this multi-dependency. + * When present, the map() method will only apply transformations to dependencies + * that match these filters. + */ + @Nullable + String groupIdFilter; + + @Nullable + String artifactIdFilter; + /** * Gets the configuration name for these dependencies. * For example, "implementation", "testImplementation", "api", etc. @@ -63,8 +75,53 @@ public String getConfigurationName() { return m.getSimpleName(); } + /** + * Maps a transformation function over each GradleDependency in this multi-dependency + * that matches the provided DependencyMatcher. + * Returns a new J.MethodInvocation with updated arguments if any dependencies changed. + * + * @param matcher The DependencyMatcher to filter which dependencies to transform + * @param mapper Function to transform each matching GradleDependency + * @return The updated J.MethodInvocation if any changes were made, or the original if not + */ + public J.MethodInvocation map(DependencyMatcher matcher, Function mapper) { + J.MethodInvocation m = cursor.getValue(); + List originalArgs = m.getArguments(); + List newArgs = new ArrayList<>(originalArgs.size()); + boolean anyChanged = false; + + for (int i = 0; i < originalArgs.size(); i++) { + Expression arg = originalArgs.get(i); + GradleDependency dep = findDependencyForArg(i); + + if (dep != null && dep.matches(matcher)) { + GradleDependency mapped = mapper.apply(dep); + if (mapped != dep) { + // The dependency was modified + anyChanged = true; + // Extract the literal from the synthetic wrapper + J.MethodInvocation syntheticWrapper = mapped.getTree(); + if (!syntheticWrapper.getArguments().isEmpty()) { + newArgs.add(syntheticWrapper.getArguments().get(0)); + } else { + newArgs.add(arg); // Fallback to original + } + } else { + newArgs.add(arg); + } + } else { + // Not a dependency argument or doesn't match filters, keep as-is + newArgs.add(arg); + } + } + + return anyChanged ? m.withArguments(newArgs) : m; + } + /** * Maps a transformation function over each GradleDependency in this multi-dependency. + * If groupIdFilter and/or artifactIdFilter are set, only applies the transformation + * to dependencies that match those filters. * Returns a new J.MethodInvocation with updated arguments if any dependencies changed. * * @param mapper Function to transform each GradleDependency @@ -80,7 +137,7 @@ public J.MethodInvocation map(Function mappe Expression arg = originalArgs.get(i); GradleDependency dep = findDependencyForArg(i); - if (dep != null) { + if (dep != null && shouldTransform(dep)) { GradleDependency mapped = mapper.apply(dep); if (mapped != dep) { // The dependency was modified @@ -96,7 +153,7 @@ public J.MethodInvocation map(Function mappe newArgs.add(arg); } } else { - // Not a dependency argument, keep as-is + // Not a dependency argument or doesn't match filters, keep as-is newArgs.add(arg); } } @@ -104,25 +161,42 @@ public J.MethodInvocation map(Function mappe return anyChanged ? m.withArguments(newArgs) : m; } + /** + * Determines if a dependency should be transformed based on the configured filters. + */ + private boolean shouldTransform(GradleDependency dependency) { + if (groupIdFilter == null && artifactIdFilter == null) { + // No filters configured, transform all dependencies + return true; + } + + String groupId = dependency.getDeclaredGroupId(); + String artifactId = dependency.getDeclaredArtifactId(); + + boolean groupMatches = groupIdFilter == null || + (groupId != null && StringUtils.matchesGlob(groupId, groupIdFilter)); + boolean artifactMatches = artifactIdFilter == null || + (artifactId != null && StringUtils.matchesGlob(artifactId, artifactIdFilter)); + + return groupMatches && artifactMatches; + } + /** * Finds the GradleDependency corresponding to the argument at the given index. */ @Nullable private GradleDependency findDependencyForArg(int index) { - // Dependencies are created in the same order as the literal arguments - // So we need to count literal arguments to find the right dependency J.MethodInvocation m = cursor.getValue(); int literalIndex = 0; for (int i = 0; i <= index && i < m.getArguments().size(); i++) { Expression arg = m.getArguments().get(i); - if (arg instanceof J.Literal && ((J.Literal) arg).getValue() instanceof String) { + if (arg instanceof J.Literal) { if (i == index) { - // This is the argument we're looking for return literalIndex < dependencies.size() ? dependencies.get(literalIndex) : null; } literalIndex++; } else if (i == index) { - // The target argument is not a literal string + return null; } } @@ -135,11 +209,27 @@ public static class Matcher extends GradleTraitMatcher { @Nullable protected String configuration; + @Nullable + protected String groupId; + + @Nullable + protected String artifactId; + public Matcher configuration(@Nullable String configuration) { this.configuration = configuration; return this; } + public Matcher groupId(@Nullable String groupId) { + this.groupId = groupId; + return this; + } + + public Matcher artifactId(@Nullable String artifactId) { + this.artifactId = artifactId; + return this; + } + @Override public

TreeVisitor asVisitor(VisitFunction2 visitor) { return new JavaVisitor

() { @@ -216,7 +306,7 @@ public J visitMethodInvocation(J.MethodInvocation method, P p) { return null; // Not really a multi-dependency if only one or zero dependencies found } - return new GradleMultiDependency(cursor, dependencies); + return new GradleMultiDependency(cursor, dependencies, groupId, artifactId); } private boolean withinDependenciesBlock(Cursor cursor) { diff --git a/rewrite-gradle/src/test/java/org/openrewrite/gradle/GradleMultiDependencyFilterTest.java b/rewrite-gradle/src/test/java/org/openrewrite/gradle/GradleMultiDependencyFilterTest.java new file mode 100644 index 0000000000..d09f2da4d7 --- /dev/null +++ b/rewrite-gradle/src/test/java/org/openrewrite/gradle/GradleMultiDependencyFilterTest.java @@ -0,0 +1,88 @@ +/* + * Copyright 2024 the original author or authors. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.gradle; + +import org.junit.jupiter.api.Test; +import org.openrewrite.DocumentExample; +import org.openrewrite.test.RecipeSpec; +import org.openrewrite.test.RewriteTest; + +import static org.openrewrite.gradle.Assertions.buildGradle; + +class GradleMultiDependencyFilterTest implements RewriteTest { + + @Override + public void defaults(RecipeSpec spec) { + spec.recipe(new UpgradeDependencyVersion("com.fasterxml.jackson.core", "jackson-*", "2.17.0", null)); + } + + @DocumentExample + @Test + void upgradesOnlyMatchingDependenciesInVarargs() { + rewriteRun( + buildGradle( + """ + plugins { + id 'java-library' + } + + repositories { + mavenCentral() + } + + dependencies { + implementation( + 'com.fasterxml.jackson.core:jackson-databind:2.11.0', + 'com.google.guava:guava:29.0-jre', + 'com.fasterxml.jackson.core:jackson-core:2.11.0') + } + """, + """ + plugins { + id 'java-library' + } + + repositories { + mavenCentral() + } + + dependencies { + implementation( + 'com.fasterxml.jackson.core:jackson-databind:2.17.0', + 'com.google.guava:guava:29.0-jre', + 'com.fasterxml.jackson.core:jackson-core:2.17.0') + } + """ + ) + ); + } + + @Test + void doesNotChangeNonMatchingVarargs() { + rewriteRun( + spec -> spec.recipe(new UpgradeDependencyVersion("org.springframework", "*", "6.0.0", null)), + buildGradle( + """ + dependencies { + implementation( + 'com.fasterxml.jackson.core:jackson-databind:2.11.0', + 'com.google.guava:guava:29.0-jre') + } + """ + ) + ); + } +} \ No newline at end of file From ad2d82ecbbfae40b7fae05bdf0f0d65ae2099c44 Mon Sep 17 00:00:00 2001 From: Sam Snyder Date: Fri, 10 Oct 2025 01:30:05 -0700 Subject: [PATCH 08/19] WIP: Fix Kotlin string template and constraint handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add support for K.StringTemplate in map notation (GradleDependency.getVersionVariable) Fixes mapNotationKStringTemplateInterpolation test - Skip upgrading dependencies without versions unless they are platform dependencies Prevents upgrading constraint-governed dependencies Fixes leaveConstraintsAlone test All 75 UpgradeDependencyVersionTest tests passing 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../gradle/UpgradeDependencyVersion.java | 12 ++++++++++-- .../openrewrite/gradle/trait/GradleDependency.java | 13 +++++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/UpgradeDependencyVersion.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/UpgradeDependencyVersion.java index b3f0ee8f5f..e1d4b120c6 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/UpgradeDependencyVersion.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/UpgradeDependencyVersion.java @@ -525,7 +525,11 @@ private GradleDependency updateSingleDependency(GradleDependency dependency, Exe String configName = dependency.getConfigurationName(); if (currentVersion == null) { - // For dependencies without a version (e.g., some platform dependencies) + // Only handle dependencies without versions if they are platform dependencies + // Regular dependencies without versions are governed by constraints and should not be upgraded + if (!dependency.isPlatform()) { + return dependency; + } GroupArtifact ga = new GroupArtifact(groupId, artifactId); selectedVersion = new DependencyVersionSelector(metadataFailures, gradleProject, null) .select(ga, configName, newVersion, versionPattern, ctx); @@ -587,7 +591,11 @@ private J.MethodInvocation updateDependency(GradleDependency dependency, Executi String configName = dependency.getConfigurationName(); if (currentVersion == null) { - // For dependencies without a version (e.g., some platform dependencies) + // Only handle dependencies without versions if they are platform dependencies + // Regular dependencies without versions are governed by constraints and should not be upgraded + if (!dependency.isPlatform()) { + return dependency.getTree(); + } GroupArtifact ga = new GroupArtifact(groupId, artifactId); selectedVersion = new DependencyVersionSelector(metadataFailures, gradleProject, null) .select(ga, configName, newVersion, versionPattern, ctx); 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 8902916d74..fbd8c9ae10 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 @@ -625,6 +625,19 @@ public boolean isPlatform() { } } } + } else if (versionExp instanceof K.StringTemplate) { + // Handle Kotlin StringTemplate in map notation + K.StringTemplate template = (K.StringTemplate) versionExp; + List strings = template.getStrings(); + if (!strings.isEmpty() && strings.get(0) instanceof K.StringTemplate.Expression) { + K.StringTemplate.Expression versionTemplateExpr = (K.StringTemplate.Expression) strings.get(0); + Object tree = versionTemplateExpr.getTree(); + if (tree instanceof J.Identifier) { + return ((J.Identifier) tree).getSimpleName(); + } else if (tree instanceof J.FieldAccess) { + return ((J.FieldAccess) tree).printTrimmed(cursor); + } + } } } From 8ed538fddec4db7ac8383116e8dee8fc036226a5 Mon Sep 17 00:00:00 2001 From: Sam Snyder Date: Fri, 10 Oct 2025 02:24:08 -0700 Subject: [PATCH 09/19] Simplify version variable detection logic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove redundant regex check in UpgradeDependencyVersion scanning phase GradleDependency.getVersionVariable() already returns null for literal versions, so checking !declaredVersion.matches("^[\\d\\.].*") was unnecessary - Clean up scanDependency to use trait methods directly (getGroupId/getArtifactId) instead of getDeclaredGroupId/getDeclaredArtifactId - Simplify updateDependency to check declaredVersion == null instead of checking isPlatform() for constraint handling All 75 UpgradeDependencyVersionTest tests still passing 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../gradle/UpgradeDependencyVersion.java | 56 ++++++------------- .../gradle/trait/GradleDependency.java | 44 +++------------ .../gradle/trait/GradleMultiDependency.java | 3 +- 3 files changed, 27 insertions(+), 76 deletions(-) diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/UpgradeDependencyVersion.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/UpgradeDependencyVersion.java index e1d4b120c6..ee5c56b69d 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/UpgradeDependencyVersion.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/UpgradeDependencyVersion.java @@ -39,7 +39,6 @@ import org.openrewrite.marker.Markup; import org.openrewrite.maven.MavenDownloadingException; import org.openrewrite.maven.table.MavenMetadataFailures; -import org.openrewrite.maven.tree.Dependency; import org.openrewrite.maven.tree.GroupArtifact; import org.openrewrite.maven.tree.GroupArtifactVersion; import org.openrewrite.properties.PropertiesVisitor; @@ -192,16 +191,11 @@ public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) */ private void scanDependency(GradleDependency gradleDependency, ExecutionContext ctx) { gatherVariables(gradleDependency); - String declaredGroupId = gradleDependency.getDeclaredGroupId(); - String declaredArtifactId = gradleDependency.getDeclaredArtifactId(); - String declaredVersion = gradleDependency.getDeclaredVersion(); - - if (declaredGroupId == null || declaredArtifactId == null || declaredVersion == null) { - return; - } + String groupId = gradleDependency.getGroupId(); + String artifactId = gradleDependency.getArtifactId(); // Record the dependency and resolve its version if needed - GroupArtifact ga = new GroupArtifact(declaredGroupId, declaredArtifactId); + GroupArtifact ga = new GroupArtifact(groupId, artifactId); String configName = gradleDependency.getConfigurationName(); if (gradleProject != null) { acc.getConfigurationPerGAPerModule() @@ -210,24 +204,20 @@ private void scanDependency(GradleDependency gradleDependency, ExecutionContext .add(configName); } - if (!acc.gaToNewVersion.containsKey(ga) && shouldResolveVersion(declaredGroupId, declaredArtifactId)) { + if (!acc.gaToNewVersion.containsKey(ga) && shouldResolveVersion(groupId, artifactId)) { try { - String resolvedVersion = new DependencyVersionSelector(metadataFailures, gradleProject, null) - .select(ga, configName, newVersion, versionPattern, ctx); + String newVersion = new DependencyVersionSelector(metadataFailures, gradleProject, null) + .select(ga, configName, UpgradeDependencyVersion.this.newVersion, versionPattern, ctx); - // If this uses a version variable, record that mapping - // Check if this is a variable (not a literal version like "1.0" or "1.0-SNAPSHOT") String versionVar = gradleDependency.getVersionVariable(); - if (versionVar != null || (declaredVersion != null && !declaredVersion.matches("^[\\d\\.].*"))) { - String versionPropName = versionVar != null ? versionVar : declaredVersion; + if (versionVar != null) { acc.versionPropNameToGA - .computeIfAbsent(versionPropName, k -> new HashMap<>()) + .computeIfAbsent(versionVar, k -> new HashMap<>()) .computeIfAbsent(ga, k -> new HashSet<>()) .add(configName); } - // Record the resolved version (may be null) - acc.gaToNewVersion.put(ga, resolvedVersion); + acc.gaToNewVersion.put(ga, newVersion); } catch (MavenDownloadingException e) { acc.gaToNewVersion.put(ga, e); } @@ -557,13 +547,9 @@ private J.MethodInvocation updateDependency(GradleDependency dependency, Executi } // Get the current version and coordinates - String currentVersion = dependency.getDeclaredVersion(); - String groupId = dependency.getDeclaredGroupId(); - String artifactId = dependency.getDeclaredArtifactId(); - - if (groupId == null || artifactId == null) { - return dependency.getTree(); - } + String declaredVersion = dependency.getDeclaredVersion(); + String groupId = dependency.getGroupId(); + String artifactId = dependency.getArtifactId(); // Check if we have a new version for this dependency Object scanResult = acc.gaToNewVersion.get(new GroupArtifact(groupId, artifactId)); @@ -578,8 +564,8 @@ private J.MethodInvocation updateDependency(GradleDependency dependency, Executi return dependency.getTree(); } - // If version starts with $, it's a variable reference we can't handle directly - if (currentVersion != null && currentVersion.startsWith("$")) { + // If version starts with $, it's a variable reference we can't process directly + if (declaredVersion != null && declaredVersion.startsWith("$")) { return dependency.getTree(); } @@ -590,17 +576,11 @@ private J.MethodInvocation updateDependency(GradleDependency dependency, Executi // For platform dependencies, this ensures we use "implementation" not "platform" String configName = dependency.getConfigurationName(); - if (currentVersion == null) { + if (declaredVersion == null) { // Only handle dependencies without versions if they are platform dependencies - // Regular dependencies without versions are governed by constraints and should not be upgraded - if (!dependency.isPlatform()) { - return dependency.getTree(); - } - GroupArtifact ga = new GroupArtifact(groupId, artifactId); - selectedVersion = new DependencyVersionSelector(metadataFailures, gradleProject, null) - .select(ga, configName, newVersion, versionPattern, ctx); + return dependency.getTree(); } else { - GroupArtifactVersion gav = new GroupArtifactVersion(groupId, artifactId, currentVersion); + GroupArtifactVersion gav = new GroupArtifactVersion(groupId, artifactId, declaredVersion); selectedVersion = new DependencyVersionSelector(metadataFailures, gradleProject, null) .select(gav, configName, newVersion, versionPattern, ctx); } @@ -608,7 +588,7 @@ private J.MethodInvocation updateDependency(GradleDependency dependency, Executi return Markup.warn(dependency.getTree(), e); } - if (selectedVersion == null || (currentVersion != null && currentVersion.equals(selectedVersion))) { + if (selectedVersion == null || (declaredVersion != null && declaredVersion.equals(selectedVersion))) { return dependency.getTree(); } 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 fbd8c9ae10..ef7f5bcb69 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 @@ -75,6 +75,14 @@ public String getArtifactId() { return resolvedDependency.getArtifactId(); } + /** + * Gets the resolved version of the dependency + * @return the resolved version of the dependency, after any variable substitutions have taken place + */ + public String getVersion() { + return resolvedDependency.getVersion(); + } + /** * Gets the configuration name for this dependency. * For example, "implementation", "testImplementation", "api", etc. @@ -695,42 +703,6 @@ private static String extractPropertyNameFromGBinary(G.Binary binary) { return null; } - /** - * Gets the tree (J.MethodInvocation) that should be modified when updating this dependency. - * For regular dependencies, returns the current tree. - * For platform dependencies, returns the dependency declaration inside the platform() or enforcedPlatform() invocation. - * - * @return The J.MethodInvocation to update - */ - public J.MethodInvocation getTreeToUpdate() { - if (isPlatform()) { - // The first argument is a platform() or enforcedPlatform() call - J.MethodInvocation m = getTree(); - if (!m.getArguments().isEmpty() && m.getArguments().get(0) instanceof J.MethodInvocation) { - return (J.MethodInvocation) m.getArguments().get(0); - } - } - return getTree(); - } - - /** - * Wraps an updated dependency tree with the appropriate wrapper if needed. - * For regular dependencies, returns the updated tree as-is. - * For dependencies with platform wrappers, wraps the updated platform() call with the outer configuration method. - * - * @param updatedTree The updated dependency tree (potentially from getTreeToUpdate()) - * @return The properly wrapped J.MethodInvocation - */ - public J.MethodInvocation wrapUpdatedTree(J.MethodInvocation updatedTree) { - if (!isPlatform()) { - return updatedTree; - } - - J.MethodInvocation m = getTree(); - // Replace the platform method invocation argument with the updated one - return m.withArguments(ListUtils.mapFirst(m.getArguments(), arg -> updatedTree)); - } - /** * Removes the version from a dependency declaration. * This method handles various dependency notation formats including string literals, diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/trait/GradleMultiDependency.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/trait/GradleMultiDependency.java index aec6b38145..56332ff1af 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/trait/GradleMultiDependency.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/trait/GradleMultiDependency.java @@ -104,13 +104,12 @@ public J.MethodInvocation map(DependencyMatcher matcher, Function Date: Fri, 10 Oct 2025 15:52:14 -0700 Subject: [PATCH 10/19] WIP --- .../openrewrite/semver/DependencyMatcher.java | 21 +- .../gradle/UpgradeDependencyVersion.java | 54 ++-- .../gradle/trait/GradleDependency.java | 46 ++-- .../gradle/trait/GradleMultiDependency.java | 255 ++++++------------ 4 files changed, 152 insertions(+), 224 deletions(-) diff --git a/rewrite-core/src/main/java/org/openrewrite/semver/DependencyMatcher.java b/rewrite-core/src/main/java/org/openrewrite/semver/DependencyMatcher.java index c54e4ab229..3b7042d29c 100755 --- a/rewrite-core/src/main/java/org/openrewrite/semver/DependencyMatcher.java +++ b/rewrite-core/src/main/java/org/openrewrite/semver/DependencyMatcher.java @@ -15,6 +15,8 @@ */ package org.openrewrite.semver; +import lombok.Getter; +import lombok.With; import org.jspecify.annotations.Nullable; import org.openrewrite.Validated; import org.openrewrite.internal.StringUtils; @@ -22,26 +24,17 @@ import java.util.Collection; import java.util.Optional; +@With +@Getter public class DependencyMatcher { - public static final String STANDARD_OPTION_DESCRIPTION = - "Dependency patterns are a concise way of describing which dependencies are applicable to a recipe. " + - "Valid dependency patterns take one of these forms:\n\n" + - "* groupId:artifactId\n" + - "* groupId:artifactId:versionSelector\n" + - "* groupId:artifactId:versionSelector/versionPattern\n\n" + - "\"groupId\" and \"artifactId\" accept glob patterns.\n" + - "\"versionSelector\" accepts both literal version numbers and semver selectors.\n" + - "\"versionPattern\" is used for artifacts that encode variant/platform information in their version." + - "Guava is a common example of such a library. Guava appends \"-jre\" or \"-android\" to its version to indicate platform compatibility."; - - private final String groupPattern; - private final String artifactPattern; + private final @Nullable String groupPattern; + private final @Nullable String artifactPattern; @Nullable private final VersionComparator versionComparator; - public DependencyMatcher(String groupPattern, String artifactPattern, @Nullable VersionComparator versionComparator) { + public DependencyMatcher(@Nullable String groupPattern, @Nullable String artifactPattern, @Nullable VersionComparator versionComparator) { this.groupPattern = groupPattern; this.artifactPattern = artifactPattern; this.versionComparator = versionComparator; diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/UpgradeDependencyVersion.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/UpgradeDependencyVersion.java index ee5c56b69d..801725f268 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/UpgradeDependencyVersion.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/UpgradeDependencyVersion.java @@ -167,23 +167,22 @@ public boolean isAcceptable(SourceFile sourceFile, ExecutionContext ctx) { public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { J.MethodInvocation m = (J.MethodInvocation) super.visitMethodInvocation(method, ctx); - // Check for multi-dependency (varargs) case first + // Check for any dependency (single or varargs with literal strings) GradleMultiDependency multiDependency = new GradleMultiDependency.Matcher().get(getCursor()).orElse(null); if (multiDependency != null) { - // Scan each dependency in the multi-dependency + // Scan each dependency for (GradleDependency dep : multiDependency.getDependencies()) { scanDependency(dep, ctx); } return m; } - // Handle single dependency case + // Handle other dependency notations (map notation, GStrings, etc.) GradleDependency gradleDependency = new GradleDependency.Matcher().get(getCursor()).orElse(null); - if (gradleDependency == null) { - return m; + if (gradleDependency != null) { + scanDependency(gradleDependency, ctx); } - scanDependency(gradleDependency, ctx); return m; } /** @@ -400,20 +399,36 @@ public J postVisit(J tree, ExecutionContext ctx) { public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { J.MethodInvocation m = (J.MethodInvocation) super.visitMethodInvocation(method, ctx); - // Check for multi-dependency (varargs) case first + // Check for any dependency with literal strings (single or varargs) GradleMultiDependency multiDependency = new GradleMultiDependency.Matcher() .groupId(groupId) .artifactId(artifactId) .get(getCursor()).orElse(null); if (multiDependency != null) { + // Check for exceptions before calling map() + if (!multiDependency.isVarargs()) { + // For single dependencies, check for exceptions and mark warnings + for (GradleDependency dep : multiDependency.getDependencies()) { + String depGroupId = dep.getDeclaredGroupId(); + String depArtifactId = dep.getDeclaredArtifactId(); + if (depGroupId != null && depArtifactId != null) { + Object scanResult = acc.gaToNewVersion.get(new GroupArtifact(depGroupId, depArtifactId)); + if (scanResult instanceof Exception && dep.matches(dependencyMatcher)) { + return Markup.warn(m, (Exception) scanResult); + } + } + } + } // Use the map function to transform each dependency that matches our pattern - m = multiDependency.map(dependencyMatcher, dep -> updateSingleDependency(dep, ctx)); + m = multiDependency.map(dependencyMatcher, dep -> updateDependency(dep, ctx)); } else { - // Handle single dependency case + // Handle other dependency notations (map notation, GStrings, Kotlin templates, etc.) GradleDependency.Matcher gradleDependencyMatcher = new GradleDependency.Matcher(); if (gradleDependencyMatcher.get(getCursor()).isPresent()) { GradleDependency gradleDependency = gradleDependencyMatcher.get(getCursor()).get(); - m = updateDependency(gradleDependency, ctx); + if (gradleDependency.matches(dependencyMatcher)) { + m = updateNonLiteralDependency(gradleDependency, ctx); + } } } @@ -477,10 +492,12 @@ public J.Assignment visitAssignment(J.Assignment assignment, ExecutionContext ex } /** - * Updates a single dependency and returns the transformed GradleDependency. + * Updates a dependency and returns the transformed GradleDependency. * This method is used by the GradleMultiDependency.map() function. + * For single dependencies, the real cursor is used directly. + * For varargs, synthetic wrappers are created and unwrapped within map(). */ - private GradleDependency updateSingleDependency(GradleDependency dependency, ExecutionContext ctx) { + private GradleDependency updateDependency(GradleDependency dependency, ExecutionContext ctx) { // Get the current version and coordinates String currentVersion = dependency.getDeclaredVersion(); String groupId = dependency.getDeclaredGroupId(); @@ -540,12 +557,11 @@ private GradleDependency updateSingleDependency(GradleDependency dependency, Exe return dependency.withDeclaredVersion(selectedVersion); } - private J.MethodInvocation updateDependency(GradleDependency dependency, ExecutionContext ctx) { - // Check if this dependency matches our pattern - if (!dependency.matches(dependencyMatcher)) { - return dependency.getTree(); - } - + /** + * Updates a dependency that uses non-literal notation (map notation, GStrings, Kotlin templates, etc.) + * This handles dependencies that aren't represented as simple string literals. + */ + private J.MethodInvocation updateNonLiteralDependency(GradleDependency dependency, ExecutionContext ctx) { // Get the current version and coordinates String declaredVersion = dependency.getDeclaredVersion(); String groupId = dependency.getGroupId(); @@ -572,8 +588,6 @@ private J.MethodInvocation updateDependency(GradleDependency dependency, Executi // Select the new version String selectedVersion; try { - // Use the outer method invocation's name as the configuration, not the tree to update - // For platform dependencies, this ensures we use "implementation" not "platform" String configName = dependency.getConfigurationName(); if (declaredVersion == null) { 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 ef7f5bcb69..8d48e5a843 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 @@ -47,11 +47,15 @@ import static java.util.Collections.singletonList; import static java.util.stream.Collectors.toList; -import static org.openrewrite.internal.StringUtils.matchesGlob; @Value public class GradleDependency implements Trait { + private static final MethodMatcher DEPENDENCY_DSL_MATCHER = new MethodMatcher("DependencyHandlerSpec *(..)"); + public static boolean isDependencyDeclaration(J.MethodInvocation methodInvocation) { + return DEPENDENCY_DSL_MATCHER.matches(methodInvocation); + } + Cursor cursor; @Getter @@ -92,8 +96,7 @@ public String getVersion() { * @return The configuration name */ public String getConfigurationName() { - J.MethodInvocation m = cursor.getValue(); - return m.getSimpleName(); + return getTree().getSimpleName(); } /** @@ -1166,29 +1169,37 @@ public GradleDependency withDeclaredVersion(@Nullable String newVersion) { } public static class Matcher extends GradleTraitMatcher { - private static final MethodMatcher DEPENDENCY_DSL_MATCHER = new MethodMatcher("DependencyHandlerSpec *(..)"); @Nullable protected String configuration; @Nullable - protected String groupId; + protected DependencyMatcher matcher; - @Nullable - protected String artifactId; + public Matcher(@Nullable DependencyMatcher matcher) { + this.matcher = matcher; + } public Matcher configuration(@Nullable String configuration) { this.configuration = configuration; return this; } - public Matcher groupId(@Nullable String groupId) { - this.groupId = groupId; + public Matcher groupId(@Nullable String groupPattern) { + if(matcher == null) { + matcher = new DependencyMatcher(groupPattern, null, null); + } else { + matcher = matcher.withGroupPattern(groupPattern); + } return this; } - public Matcher artifactId(@Nullable String artifactId) { - this.artifactId = artifactId; + public Matcher artifactId(@Nullable String artifactPattern) { + if(matcher == null) { + matcher = new DependencyMatcher(null, artifactPattern, null); + } else { + matcher = matcher.withArtifactPattern(artifactPattern); + } return this; } @@ -1253,8 +1264,7 @@ public J visitMethodInvocation(J.MethodInvocation method, P p) { if (gdc != null) { if (gdc.isCanBeResolved()) { for (ResolvedDependency resolvedDependency : gdc.getResolved()) { - if ((groupId == null || matchesGlob(resolvedDependency.getGroupId(), groupId)) && - (artifactId == null || matchesGlob(resolvedDependency.getArtifactId(), artifactId))) { + if(matcher == null || matcher.matches(resolvedDependency.getGroupId(), resolvedDependency.getArtifactId())) { Dependency req = resolvedDependency.getRequested(); if ((req.getGroupId() == null || req.getGroupId().equals(dependency.getGroupId())) && req.getArtifactId().equals(dependency.getArtifactId())) { @@ -1264,10 +1274,9 @@ public J visitMethodInvocation(J.MethodInvocation method, P p) { } } else { for (GradleDependencyConfiguration transitiveConfiguration : gradleProject.configurationsExtendingFrom(gdc, true)) { - if (transitiveConfiguration.isCanBeResolved()) { + if (!transitiveConfiguration.isCanBeResolved()) { for (ResolvedDependency resolvedDependency : transitiveConfiguration.getResolved()) { - if ((groupId == null || matchesGlob(resolvedDependency.getGroupId(), groupId)) && - (artifactId == null || matchesGlob(resolvedDependency.getArtifactId(), artifactId))) { + if(matcher == null || matcher.matches(resolvedDependency.getGroupId(), resolvedDependency.getArtifactId())) { Dependency req = resolvedDependency.getRequested(); if ((req.getGroupId() == null || req.getGroupId().equals(dependency.getGroupId())) && req.getArtifactId().equals(dependency.getArtifactId())) { @@ -1280,9 +1289,8 @@ public J visitMethodInvocation(J.MethodInvocation method, P p) { } } - if ((groupId == null || matchesGlob(dependency.getGroupId(), groupId)) && - (artifactId == null || matchesGlob(dependency.getArtifactId(), artifactId))) { - // Couldn't find the actual resolved dependency, return a virtualized one instead + if(matcher == null || matcher.matches(dependency.getGroupId(), dependency.getArtifactId())) { + // Couldn't find the actual resolved dependency, return a synthetic one instead ResolvedDependency resolvedDependency = ResolvedDependency.builder() .depth(-1) .gav(new ResolvedGroupArtifactVersion(null, dependency.getGroupId() == null ? "" : dependency.getGroupId(), dependency.getArtifactId(), dependency.getVersion() != null ? dependency.getVersion() : "", null)) diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/trait/GradleMultiDependency.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/trait/GradleMultiDependency.java index 56332ff1af..6fb6fa90ff 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/trait/GradleMultiDependency.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/trait/GradleMultiDependency.java @@ -21,48 +21,40 @@ import org.openrewrite.Cursor; import org.openrewrite.Tree; import org.openrewrite.TreeVisitor; -import org.openrewrite.gradle.marker.GradleProject; -import org.openrewrite.internal.ListUtils; +import org.openrewrite.groovy.tree.G; import org.openrewrite.internal.StringUtils; import org.openrewrite.java.JavaVisitor; import org.openrewrite.java.MethodMatcher; import org.openrewrite.java.tree.Expression; import org.openrewrite.java.tree.J; import org.openrewrite.maven.tree.Dependency; -import org.openrewrite.maven.tree.GroupArtifactVersion; -import org.openrewrite.maven.tree.ResolvedDependency; -import org.openrewrite.maven.tree.ResolvedGroupArtifactVersion; import org.openrewrite.semver.DependencyMatcher; import org.openrewrite.trait.Trait; import org.openrewrite.trait.VisitFunction2; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.function.Function; -import java.util.stream.Collectors; /** - * Represents multiple Gradle dependencies declared in a single method invocation (varargs). - * For example: implementation('dep1:1.0', 'dep2:2.0', 'dep3:3.0') + * Represents one or more Gradle dependencies declared in a single method invocation. + * Gradle's groovy DLS allows invocations like: + * For varargs: implementation('g:a:1.0', 'g:a:2.0', 'dep3:3.0') + * There is not a corresponding dependency form in the kotlin DSL. */ @Value public class GradleMultiDependency implements Trait { Cursor cursor; - @Getter - List dependencies; - /** * Optional filters that were used when matching this multi-dependency. * When present, the map() method will only apply transformations to dependencies * that match these filters. */ - @Nullable - String groupIdFilter; + @Nullable DependencyMatcher matcher; - @Nullable - String artifactIdFilter; + @Getter + boolean varargs; /** * Gets the configuration name for these dependencies. @@ -71,135 +63,45 @@ public class GradleMultiDependency implements Trait { * @return The configuration name */ public String getConfigurationName() { - J.MethodInvocation m = cursor.getValue(); - return m.getSimpleName(); + return getTree().getSimpleName(); } /** - * Maps a transformation function over each GradleDependency in this multi-dependency - * that matches the provided DependencyMatcher. - * Returns a new J.MethodInvocation with updated arguments if any dependencies changed. - * - * @param matcher The DependencyMatcher to filter which dependencies to transform - * @param mapper Function to transform each matching GradleDependency - * @return The updated J.MethodInvocation if any changes were made, or the original if not + * Check if the cursor points at a method invocation that has multiple arguments which can be interpreted as Gradle's dependency string notation. + * This is useful to differentiate between the Gradle Groovy DSL's varargs method and the Gradle Kotlin DSL which has + * dependency methods which accept a sequence of strings representing the individual part of GAV coordinates. */ - public J.MethodInvocation map(DependencyMatcher matcher, Function mapper) { - J.MethodInvocation m = cursor.getValue(); - List originalArgs = m.getArguments(); - List newArgs = new ArrayList<>(originalArgs.size()); - boolean anyChanged = false; - - for (int i = 0; i < originalArgs.size(); i++) { - Expression arg = originalArgs.get(i); - GradleDependency dep = findDependencyForArg(i); - - if (dep != null && dep.matches(matcher)) { - GradleDependency mapped = mapper.apply(dep); - if (mapped != dep) { - // The dependency was modified - anyChanged = true; - // Extract the literal from the synthetic wrapper - J.MethodInvocation syntheticWrapper = mapped.getTree(); - if (!syntheticWrapper.getArguments().isEmpty()) { - newArgs.add(syntheticWrapper.getArguments().get(0)); - } else { - newArgs.add(arg); - } - } else { - newArgs.add(arg); - } - } else { - newArgs.add(arg); - } + private static boolean methodArgumentsContainMultipleDependencyNotations(Cursor methodCursor) { + Object maybeMi = methodCursor.getValue(); + if(!(maybeMi instanceof J.MethodInvocation)) { + return false; } - - return anyChanged ? m.withArguments(newArgs) : m; + J.MethodInvocation m = (J.MethodInvocation) maybeMi; + return m.getArguments().size() > 1 && m.getArguments().stream() + .filter(it -> Dependency.parse(it.print(methodCursor).trim()) != null) + .limit(2) + .count() > 1; } /** - * Maps a transformation function over each GradleDependency in this multi-dependency. - * If groupIdFilter and/or artifactIdFilter are set, only applies the transformation - * to dependencies that match those filters. + * Maps a transformation function over each GradleDependency in this multi-dependency + * that matches the provided DependencyMatcher. * Returns a new J.MethodInvocation with updated arguments if any dependencies changed. * - * @param mapper Function to transform each GradleDependency + * @param mapper Function to transform each matching GradleDependency * @return The updated J.MethodInvocation if any changes were made, or the original if not */ - public J.MethodInvocation map(Function mapper) { - J.MethodInvocation m = cursor.getValue(); - List originalArgs = m.getArguments(); - List newArgs = new ArrayList<>(originalArgs.size()); - boolean anyChanged = false; - - for (int i = 0; i < originalArgs.size(); i++) { - Expression arg = originalArgs.get(i); - GradleDependency dep = findDependencyForArg(i); - - if (dep != null && shouldTransform(dep)) { - GradleDependency mapped = mapper.apply(dep); - if (mapped != dep) { - // The dependency was modified - anyChanged = true; - // Extract the literal from the synthetic wrapper - J.MethodInvocation syntheticWrapper = mapped.getTree(); - if (!syntheticWrapper.getArguments().isEmpty()) { - newArgs.add(syntheticWrapper.getArguments().get(0)); - } else { - newArgs.add(arg); // Fallback to original - } - } else { - newArgs.add(arg); - } - } else { - // Not a dependency argument or doesn't match filters, keep as-is - newArgs.add(arg); - } - } - - return anyChanged ? m.withArguments(newArgs) : m; - } - - /** - * Determines if a dependency should be transformed based on the configured filters. - */ - private boolean shouldTransform(GradleDependency dependency) { - if (groupIdFilter == null && artifactIdFilter == null) { - // No filters configured, transform all dependencies - return true; - } - - String groupId = dependency.getDeclaredGroupId(); - String artifactId = dependency.getDeclaredArtifactId(); - - boolean groupMatches = groupIdFilter == null || - (groupId != null && StringUtils.matchesGlob(groupId, groupIdFilter)); - boolean artifactMatches = artifactIdFilter == null || - (artifactId != null && StringUtils.matchesGlob(artifactId, artifactIdFilter)); - - return groupMatches && artifactMatches; - } - - /** - * Finds the GradleDependency corresponding to the argument at the given index. - */ - @Nullable - private GradleDependency findDependencyForArg(int index) { + public GradleMultiDependency map(Function mapper) { J.MethodInvocation m = cursor.getValue(); - int literalIndex = 0; - for (int i = 0; i <= index && i < m.getArguments().size(); i++) { - Expression arg = m.getArguments().get(i); - if (arg instanceof J.Literal) { - if (i == index) { - return literalIndex < dependencies.size() ? dependencies.get(literalIndex) : null; - } - literalIndex++; - } else if (i == index) { + if (isVarargs()) { - return null; - } + } else { + new GradleDependency.Matcher() + .get(cursor) + .map(mapper) + .get() } - return null; + return this; } public static class Matcher extends GradleTraitMatcher { @@ -209,23 +111,32 @@ public static class Matcher extends GradleTraitMatcher { protected String configuration; @Nullable - protected String groupId; + protected DependencyMatcher matcher; - @Nullable - protected String artifactId; + public Matcher(@Nullable DependencyMatcher matcher) { + this.matcher = matcher; + } public Matcher configuration(@Nullable String configuration) { this.configuration = configuration; return this; } - public Matcher groupId(@Nullable String groupId) { - this.groupId = groupId; + public Matcher groupId(@Nullable String groupPattern) { + if(matcher == null) { + matcher = new DependencyMatcher(groupPattern, null, null); + } else { + matcher = matcher.withGroupPattern(groupPattern); + } return this; } - public Matcher artifactId(@Nullable String artifactId) { - this.artifactId = artifactId; + public Matcher artifactId(@Nullable String artifactPattern) { + if(matcher == null) { + matcher = new DependencyMatcher(null, artifactPattern, null); + } else { + matcher = matcher.withArtifactPattern(artifactPattern); + } return this; } @@ -250,66 +161,68 @@ public J visitMethodInvocation(J.MethodInvocation method, P p) { } J.MethodInvocation methodInvocation = (J.MethodInvocation) object; + List args = methodInvocation.getArguments(); + + // Check if this is a varargs invocation (multiple literal strings) + methodArgumentsContainMultipleDependencyNotations(cursor); + if (args.stream().filter(it -> Dependency.parse(it.print(cursor)) != null).count() > 1) { + return testVarargs(cursor, methodInvocation, args); + } + + return new GradleMultiDependency(cursor, matcher, ); + } + private @Nullable GradleMultiDependency testVarargs(Cursor cursor, J.MethodInvocation methodInvocation, List args) { if (!withinDependenciesBlock(cursor)) { return null; } - if (!DEPENDENCY_DSL_MATCHER.matches(methodInvocation) || "project".equals(methodInvocation.getSimpleName())) { + if (withinDependencyConstraintsBlock(cursor)) { return null; } - if (!StringUtils.isBlank(configuration) && !methodInvocation.getSimpleName().equals(configuration)) { + if (!DEPENDENCY_DSL_MATCHER.matches(methodInvocation) || "project".equals(methodInvocation.getSimpleName())) { return null; } - // Check if there are multiple literal string arguments (varargs case) - List args = methodInvocation.getArguments(); - if (args.size() <= 1) { - return null; // Not a multi-dependency + if (!StringUtils.isBlank(configuration) && !methodInvocation.getSimpleName().equals(configuration)) { + return null; } - List dependencies = new ArrayList<>(); - GradleProject gradleProject = getGradleProject(cursor); - - // Process each literal string argument as a dependency + // Parse each literal string or GString argument + List parsedDependencies = new ArrayList<>(); for (Expression arg : args) { + Dependency dep = null; if (arg instanceof J.Literal && ((J.Literal) arg).getValue() instanceof String) { String gav = (String) ((J.Literal) arg).getValue(); - Dependency dep = Dependency.parse(gav); - if (dep != null) { - // Create a synthetic method invocation wrapping just this one dependency - J.MethodInvocation synthetic = methodInvocation.withArguments(Collections.singletonList(arg)); - Cursor syntheticCursor = new Cursor(cursor.getParent(), synthetic); - - // Create a resolved dependency for this - ResolvedDependency resolvedDependency = ResolvedDependency.builder() - .depth(-1) - .gav(new ResolvedGroupArtifactVersion( - null, - dep.getGroupId() != null ? dep.getGroupId() : "", - dep.getArtifactId(), - dep.getVersion() != null ? dep.getVersion() : "", - null)) - .type(dep.getType()) - .classifier(dep.getClassifier()) - .requested(dep) - .build(); - - dependencies.add(new GradleDependency(syntheticCursor, resolvedDependency)); + dep = Dependency.parse(gav); + } else if (arg instanceof G.GString) { + // Handle GString notation: "group:artifact:$version" + G.GString gstring = (G.GString) arg; + List strings = gstring.getStrings(); + if (strings.size() >= 2 && strings.get(0) instanceof J.Literal && ((J.Literal) strings.get(0)).getValue() != null) { + String gav = (String) ((J.Literal) strings.get(0)).getValue(); + dep = Dependency.parse(gav); } } + if (dep != null) { + parsedDependencies.add(dep); + } } - if (dependencies.size() <= 1) { - return null; // Not really a multi-dependency if only one or zero dependencies found + if (parsedDependencies.size() <= 1) { + return null; // Not really a varargs if only one or zero dependencies } - return new GradleMultiDependency(cursor, dependencies, groupId, artifactId); + return new GradleMultiDependency(cursor, matcher); } private boolean withinDependenciesBlock(Cursor cursor) { return withinBlock(cursor, "dependencies"); } + + private boolean withinDependencyConstraintsBlock(Cursor cursor) { + return withinBlock(cursor, "constraints") && withinDependenciesBlock(cursor); + } } } \ No newline at end of file From b3e43482dc23a10d2d6ebb9ad7cbe36355ad59ac Mon Sep 17 00:00:00 2001 From: Sam Snyder Date: Mon, 13 Oct 2025 19:42:58 -0700 Subject: [PATCH 11/19] WIP --- .../src/main/java/org/openrewrite/Cursor.java | 4 + .../gradle/UpgradeDependencyVersion.java | 138 ++----- .../gradle/trait/GradleDependency.java | 352 ++++++++++-------- .../gradle/trait/GradleMultiDependency.java | 113 ++---- .../openrewrite/maven/tree/Dependency.java | 47 +++ .../maven/tree/DependencyTest.java | 66 +++- 6 files changed, 373 insertions(+), 347 deletions(-) diff --git a/rewrite-core/src/main/java/org/openrewrite/Cursor.java b/rewrite-core/src/main/java/org/openrewrite/Cursor.java index c174f912b0..3cf1a937df 100644 --- a/rewrite-core/src/main/java/org/openrewrite/Cursor.java +++ b/rewrite-core/src/main/java/org/openrewrite/Cursor.java @@ -15,7 +15,9 @@ */ package org.openrewrite; +import lombok.AllArgsConstructor; import lombok.EqualsAndHashCode; +import lombok.With; import org.jspecify.annotations.Nullable; import java.util.*; @@ -30,12 +32,14 @@ * A cursor is linked path of LST elements that can be used to traverse down the tree towards the root. */ @EqualsAndHashCode(exclude = "messages") +@AllArgsConstructor public class Cursor { public static final String ROOT_VALUE = "root"; @Nullable private final Cursor parent; + @With private final Object value; @Nullable diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/UpgradeDependencyVersion.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/UpgradeDependencyVersion.java index 801725f268..52010945a8 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/UpgradeDependencyVersion.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/UpgradeDependencyVersion.java @@ -168,21 +168,10 @@ public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) J.MethodInvocation m = (J.MethodInvocation) super.visitMethodInvocation(method, ctx); // Check for any dependency (single or varargs with literal strings) - GradleMultiDependency multiDependency = new GradleMultiDependency.Matcher().get(getCursor()).orElse(null); - if (multiDependency != null) { - // Scan each dependency - for (GradleDependency dep : multiDependency.getDependencies()) { - scanDependency(dep, ctx); - } - return m; - } - - // Handle other dependency notations (map notation, GStrings, etc.) - GradleDependency gradleDependency = new GradleDependency.Matcher().get(getCursor()).orElse(null); - if (gradleDependency != null) { - scanDependency(gradleDependency, ctx); - } - + GradleMultiDependency.matcher(new DependencyMatcher(groupId, artifactId, getVersionComparator())) + .get(getCursor()) + .ifPresent(multiDependency -> + multiDependency.forEach(dep -> scanDependency(dep, ctx))); return m; } /** @@ -399,37 +388,17 @@ public J postVisit(J tree, ExecutionContext ctx) { public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { J.MethodInvocation m = (J.MethodInvocation) super.visitMethodInvocation(method, ctx); - // Check for any dependency with literal strings (single or varargs) - GradleMultiDependency multiDependency = new GradleMultiDependency.Matcher() - .groupId(groupId) - .artifactId(artifactId) - .get(getCursor()).orElse(null); + GradleMultiDependency multiDependency = GradleMultiDependency.matcher(dependencyMatcher) + .get(getCursor()) + .orElse(null); if (multiDependency != null) { - // Check for exceptions before calling map() - if (!multiDependency.isVarargs()) { - // For single dependencies, check for exceptions and mark warnings - for (GradleDependency dep : multiDependency.getDependencies()) { - String depGroupId = dep.getDeclaredGroupId(); - String depArtifactId = dep.getDeclaredArtifactId(); - if (depGroupId != null && depArtifactId != null) { - Object scanResult = acc.gaToNewVersion.get(new GroupArtifact(depGroupId, depArtifactId)); - if (scanResult instanceof Exception && dep.matches(dependencyMatcher)) { - return Markup.warn(m, (Exception) scanResult); - } - } + m = multiDependency.map(dep -> { + Object scanResult = acc.gaToNewVersion.get(new GroupArtifact(dep.getGroupId(), dep.getArtifactId())); + if (scanResult instanceof Exception) { + return Markup.warn(dep.getTree(), (Exception) scanResult); } - } - // Use the map function to transform each dependency that matches our pattern - m = multiDependency.map(dependencyMatcher, dep -> updateDependency(dep, ctx)); - } else { - // Handle other dependency notations (map notation, GStrings, Kotlin templates, etc.) - GradleDependency.Matcher gradleDependencyMatcher = new GradleDependency.Matcher(); - if (gradleDependencyMatcher.get(getCursor()).isPresent()) { - GradleDependency gradleDependency = gradleDependencyMatcher.get(getCursor()).get(); - if (gradleDependency.matches(dependencyMatcher)) { - m = updateNonLiteralDependency(gradleDependency, ctx); - } - } + return updateDependency(dep, ctx); + }); } if ("ext".equals(method.getSimpleName()) && getCursor().firstEnclosingOrThrow(SourceFile.class).getSourcePath().endsWith("settings.gradle")) { @@ -465,6 +434,7 @@ public J.Assignment visitAssignment(J.Assignment assignment, ExecutionContext ex VersionComparator versionComparator = getVersionComparator(); if (versionComparator != null) { String currentVersion = (String) l.getValue(); + //noinspection DataFlowIssue Optional finalVersion = versionComparator.upgrade(currentVersion, singletonList(newVersion)); if (!finalVersion.isPresent()) { // Would be a downgrade, don't change @@ -493,37 +463,34 @@ public J.Assignment visitAssignment(J.Assignment assignment, ExecutionContext ex /** * Updates a dependency and returns the transformed GradleDependency. - * This method is used by the GradleMultiDependency.map() function. - * For single dependencies, the real cursor is used directly. - * For varargs, synthetic wrappers are created and unwrapped within map(). */ - private GradleDependency updateDependency(GradleDependency dependency, ExecutionContext ctx) { + private J.MethodInvocation updateDependency(GradleDependency dependency, ExecutionContext ctx) { // Get the current version and coordinates String currentVersion = dependency.getDeclaredVersion(); String groupId = dependency.getDeclaredGroupId(); String artifactId = dependency.getDeclaredArtifactId(); if (groupId == null || artifactId == null) { - return dependency; + return dependency.getTree(); } // Check if we have a new version for this dependency Object scanResult = acc.gaToNewVersion.get(new GroupArtifact(groupId, artifactId)); if (scanResult instanceof Exception) { // Can't mark warning on a synthetic tree, so just return unchanged - return dependency; + return dependency.getTree(); } // Handle variable references String versionVariable = dependency.getVersionVariable(); if (versionVariable != null) { // Variable updates are handled separately - return dependency; + return dependency.getTree(); } // If version starts with $, it's a variable reference we can't handle directly if (currentVersion != null && currentVersion.startsWith("$")) { - return dependency; + return dependency.getTree(); } // Select the new version @@ -535,7 +502,7 @@ private GradleDependency updateDependency(GradleDependency dependency, Execution // Only handle dependencies without versions if they are platform dependencies // Regular dependencies without versions are governed by constraints and should not be upgraded if (!dependency.isPlatform()) { - return dependency; + return dependency.getTree(); } GroupArtifact ga = new GroupArtifact(groupId, artifactId); selectedVersion = new DependencyVersionSelector(metadataFailures, gradleProject, null) @@ -546,75 +513,14 @@ private GradleDependency updateDependency(GradleDependency dependency, Execution .select(gav, configName, newVersion, versionPattern, ctx); } } catch (MavenDownloadingException e) { - return dependency; - } - - if (selectedVersion == null || (currentVersion != null && currentVersion.equals(selectedVersion))) { - return dependency; - } - - // Update the dependency version using the trait's method - return dependency.withDeclaredVersion(selectedVersion); - } - - /** - * Updates a dependency that uses non-literal notation (map notation, GStrings, Kotlin templates, etc.) - * This handles dependencies that aren't represented as simple string literals. - */ - private J.MethodInvocation updateNonLiteralDependency(GradleDependency dependency, ExecutionContext ctx) { - // Get the current version and coordinates - String declaredVersion = dependency.getDeclaredVersion(); - String groupId = dependency.getGroupId(); - String artifactId = dependency.getArtifactId(); - - // Check if we have a new version for this dependency - Object scanResult = acc.gaToNewVersion.get(new GroupArtifact(groupId, artifactId)); - if (scanResult instanceof Exception) { - return Markup.warn(dependency.getTree(), (Exception) scanResult); - } - - // Handle variable references - String versionVariable = dependency.getVersionVariable(); - if (versionVariable != null) { - replaceVariableValue(versionVariable, dependency.getTree(), groupId, artifactId); return dependency.getTree(); } - // If version starts with $, it's a variable reference we can't process directly - if (declaredVersion != null && declaredVersion.startsWith("$")) { - return dependency.getTree(); - } - - // Select the new version - String selectedVersion; - try { - String configName = dependency.getConfigurationName(); - - if (declaredVersion == null) { - // Only handle dependencies without versions if they are platform dependencies - return dependency.getTree(); - } else { - GroupArtifactVersion gav = new GroupArtifactVersion(groupId, artifactId, declaredVersion); - selectedVersion = new DependencyVersionSelector(metadataFailures, gradleProject, null) - .select(gav, configName, newVersion, versionPattern, ctx); - } - } catch (MavenDownloadingException e) { - return Markup.warn(dependency.getTree(), e); - } - - if (selectedVersion == null || (declaredVersion != null && declaredVersion.equals(selectedVersion))) { + if (selectedVersion == null || (currentVersion != null && currentVersion.equals(selectedVersion))) { return dependency.getTree(); } - // Update the dependency version using the trait's method - GradleDependency updated = dependency.withDeclaredVersion(selectedVersion); - return updated.getTree(); - } - - private void replaceVariableValue(String versionVariableName, J.MethodInvocation m, String groupId, String artifactId) { - acc.variableNames.computeIfAbsent(versionVariableName, it -> new HashMap<>()) - .computeIfAbsent(new GroupArtifact(groupId, artifactId), it -> new HashSet<>()) - .add(m.getSimpleName()); + return dependency.withDeclaredVersion(selectedVersion).getTree(); } } 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 8d48e5a843..2d7870586c 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 @@ -17,8 +17,10 @@ import lombok.Getter; import lombok.Value; +import lombok.With; import org.jspecify.annotations.Nullable; import org.openrewrite.Cursor; +import org.openrewrite.SourceFile; import org.openrewrite.Tree; import org.openrewrite.TreeVisitor; import org.openrewrite.gradle.internal.ChangeStringLiteral; @@ -43,6 +45,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.Optional; import java.util.stream.Collectors; import static java.util.Collections.singletonList; @@ -52,10 +55,79 @@ public class GradleDependency implements Trait { private static final MethodMatcher DEPENDENCY_DSL_MATCHER = new MethodMatcher("DependencyHandlerSpec *(..)"); - public static boolean isDependencyDeclaration(J.MethodInvocation methodInvocation) { + + private static @Nullable GradleProject getGradleProject(Cursor cursor) { + SourceFile sourceFile = cursor.firstEnclosing(SourceFile.class); + if (sourceFile == null) { + return null; + } + + Optional maybeGp = sourceFile.getMarkers().findFirst(GradleProject.class); + return maybeGp.orElse(null); + } + + private static boolean withinBlock(Cursor cursor, String name) { + Cursor parentCursor = cursor.getParent(); + while (parentCursor != null) { + if (parentCursor.getValue() instanceof J.MethodInvocation) { + J.MethodInvocation m = parentCursor.getValue(); + if (m.getSimpleName().equals(name)) { + return true; + } + } + parentCursor = parentCursor.getParent(); + } + + return false; + } + + private static boolean withinDependenciesBlock(Cursor cursor) { + return withinBlock(cursor, "dependencies"); + } + + private static boolean withinDependencyConstraintsBlock(Cursor cursor) { + return withinBlock(cursor, "constraints") && withinDependenciesBlock(cursor); + } + + private static @Nullable GradleDependencyConfiguration getConfiguration(@Nullable GradleProject gradleProject, J.MethodInvocation methodInvocation) { + if (gradleProject == null) { + return null; + } + + String methodName = methodInvocation.getSimpleName(); + if ("classpath".equals(methodName)) { + return gradleProject.getBuildscript().getConfiguration(methodName); + } else { + return gradleProject.getConfiguration(methodName); + } + } + + public static boolean isDependencyDeclaration(Cursor cursor) { + Object object = cursor.getValue(); + if (!(object instanceof J.MethodInvocation)) { + return false; + } + J.MethodInvocation methodInvocation = (J.MethodInvocation) object; + + if (!withinDependenciesBlock(cursor)) { + return false; + } + + if (withinDependencyConstraintsBlock(cursor)) { + // A dependency constraint is different from an actual dependency + return false; + } + + GradleProject gradleProject = getGradleProject(cursor); + GradleDependencyConfiguration gdc = getConfiguration(gradleProject, methodInvocation); + if (gdc == null && !(DEPENDENCY_DSL_MATCHER.matches(methodInvocation) && !"project".equals(methodInvocation.getSimpleName()))) { + return false; + } + return DEPENDENCY_DSL_MATCHER.matches(methodInvocation); } + @With Cursor cursor; @Getter @@ -81,6 +153,7 @@ public String getArtifactId() { /** * Gets the resolved version of the dependency + * * @return the resolved version of the dependency, after any variable substitutions have taken place */ public String getVersion() { @@ -143,7 +216,7 @@ public String getConfigurationName() { // String literal notation: "group:artifact:version" if (arg instanceof J.Literal && ((J.Literal) arg).getValue() instanceof String) { Dependency dep = - Dependency.parse((String) ((J.Literal) arg).getValue()); + Dependency.parse((String) ((J.Literal) arg).getValue()); return dep != null ? dep.getGroupId() : null; } @@ -152,7 +225,7 @@ public String getConfigurationName() { List strings = ((G.GString) arg).getStrings(); if (!strings.isEmpty() && strings.get(0) instanceof J.Literal) { Dependency dep = - Dependency.parse((String) ((J.Literal) strings.get(0)).getValue()); + Dependency.parse((String) ((J.Literal) strings.get(0)).getValue()); return dep != null ? dep.getGroupId() : null; } } @@ -162,7 +235,7 @@ public String getConfigurationName() { List strings = ((K.StringTemplate) arg).getStrings(); if (!strings.isEmpty() && strings.get(0) instanceof J.Literal) { Dependency dep = - Dependency.parse((String) ((J.Literal) strings.get(0)).getValue()); + Dependency.parse((String) ((J.Literal) strings.get(0)).getValue()); return dep != null ? dep.getGroupId() : null; } } @@ -173,13 +246,13 @@ public String getConfigurationName() { if (e instanceof G.MapEntry) { G.MapEntry entry = (G.MapEntry) e; if (entry.getKey() instanceof J.Literal && - "group".equals(((J.Literal) entry.getKey()).getValue())) { + "group".equals(((J.Literal) entry.getKey()).getValue())) { return extractValueAsString(entry.getValue()); } } else if (e instanceof J.Assignment) { J.Assignment assignment = (J.Assignment) e; if (assignment.getVariable() instanceof J.Identifier && - "group".equals(((J.Identifier) assignment.getVariable()).getSimpleName())) { + "group".equals(((J.Identifier) assignment.getVariable()).getSimpleName())) { return extractValueAsString(assignment.getAssignment()); } } @@ -190,7 +263,7 @@ public String getConfigurationName() { if (arg instanceof G.MapLiteral) { for (G.MapEntry entry : ((G.MapLiteral) arg).getElements()) { if (entry.getKey() instanceof J.Literal && - "group".equals(((J.Literal) entry.getKey()).getValue())) { + "group".equals(((J.Literal) entry.getKey()).getValue())) { return extractValueAsString(entry.getValue()); } } @@ -334,7 +407,7 @@ public String getConfigurationName() { // String literal notation: "group:artifact:version" if (arg instanceof J.Literal && ((J.Literal) arg).getValue() instanceof String) { Dependency dep = - Dependency.parse((String) ((J.Literal) arg).getValue()); + Dependency.parse((String) ((J.Literal) arg).getValue()); return dep != null ? dep.getVersion() : null; } @@ -344,13 +417,13 @@ public String getConfigurationName() { if (e instanceof G.MapEntry) { G.MapEntry entry = (G.MapEntry) e; if (entry.getKey() instanceof J.Literal && - "version".equals(((J.Literal) entry.getKey()).getValue())) { + "version".equals(((J.Literal) entry.getKey()).getValue())) { return extractValueAsString(entry.getValue()); } } else if (e instanceof J.Assignment) { J.Assignment assignment = (J.Assignment) e; if (assignment.getVariable() instanceof J.Identifier && - "version".equals(((J.Identifier) assignment.getVariable()).getSimpleName())) { + "version".equals(((J.Identifier) assignment.getVariable()).getSimpleName())) { return extractValueAsString(assignment.getAssignment()); } } @@ -361,7 +434,7 @@ public String getConfigurationName() { if (arg instanceof G.MapLiteral) { for (G.MapEntry entry : ((G.MapLiteral) arg).getElements()) { if (entry.getKey() instanceof J.Literal && - "version".equals(((J.Literal) entry.getKey()).getValue())) { + "version".equals(((J.Literal) entry.getKey()).getValue())) { return extractValueAsString(entry.getValue()); } } @@ -387,8 +460,8 @@ public String getConfigurationName() { J.MethodInvocation mi = (J.MethodInvocation) value; String methodName = mi.getSimpleName(); if (("property".equals(methodName) || "findProperty".equals(methodName)) && - mi.getArguments().size() == 1 && - mi.getArguments().get(0) instanceof J.Literal) { + mi.getArguments().size() == 1 && + mi.getArguments().get(0) instanceof J.Literal) { Object arg = ((J.Literal) mi.getArguments().get(0)).getValue(); if (arg instanceof String) { return (String) arg; @@ -397,10 +470,10 @@ public String getConfigurationName() { // Also handle project.property('name') and project.findProperty('name') if (mi.getSelect() instanceof J.Identifier && - "project".equals(((J.Identifier) mi.getSelect()).getSimpleName()) && - ("property".equals(methodName) || "findProperty".equals(methodName)) && - mi.getArguments().size() == 1 && - mi.getArguments().get(0) instanceof J.Literal) { + "project".equals(((J.Identifier) mi.getSelect()).getSimpleName()) && + ("property".equals(methodName) || "findProperty".equals(methodName)) && + mi.getArguments().size() == 1 && + mi.getArguments().get(0) instanceof J.Literal) { Object arg = ((J.Literal) mi.getArguments().get(0)).getValue(); if (arg instanceof String) { return (String) arg; @@ -714,7 +787,7 @@ private static String extractPropertyNameFromGBinary(G.Binary binary) { * operates on the inner dependency declaration. * * @return A new GradleDependency with the version removed from the cursor's method invocation, - * or the original GradleDependency if the version cannot be removed + * or the original GradleDependency if the version cannot be removed */ public GradleDependency removeVersion() { J.MethodInvocation m = cursor.getValue(); @@ -729,7 +802,7 @@ public GradleDependency removeVersion() { if (updatedPlatformDep != platformDep) { // The inner dependency was modified, so update the outer method invocation updated = m.withArguments(ListUtils.mapFirst(m.getArguments(), arg -> - platformMethod.withArguments(updatedPlatformDep.getTree().getArguments()) + platformMethod.withArguments(updatedPlatformDep.getTree().getArguments()) )); return new GradleDependency(new Cursor(cursor.getParent(), updated), resolvedDependency); } @@ -801,8 +874,8 @@ public GradleDependency withDeclaredGroupId(String newGroupId) { GradleDependency updated = platformDep.withDeclaredGroupId(newGroupId); if (updated != platformDep) { return new GradleDependency(new Cursor(cursor.getParent(), - m.withArguments(ListUtils.mapFirst(m.getArguments(), arg -> updated.getTree()))), - resolvedDependency); + m.withArguments(ListUtils.mapFirst(m.getArguments(), arg -> updated.getTree()))), + resolvedDependency); } } return this; @@ -818,7 +891,7 @@ public GradleDependency withDeclaredGroupId(String newGroupId) { if (dep != null && !newGroupId.equals(dep.getGroupId())) { Dependency updatedDep = dep.withGroupId(newGroupId); updated = m.withArguments(ListUtils.mapFirst(m.getArguments(), - arg -> ChangeStringLiteral.withStringValue((J.Literal) arg, updatedDep.toStringNotation()))); + arg -> ChangeStringLiteral.withStringValue((J.Literal) arg, updatedDep.toStringNotation()))); } } } else if (firstArg instanceof G.GString) { @@ -837,28 +910,28 @@ public GradleDependency withDeclaredGroupId(String newGroupId) { } } else if (firstArg instanceof G.MapEntry || firstArg instanceof G.MapLiteral) { List entries = firstArg instanceof G.MapLiteral ? - ((G.MapLiteral) firstArg).getElements() : m.getArguments().stream() + ((G.MapLiteral) firstArg).getElements() : m.getArguments().stream() .filter(G.MapEntry.class::isInstance) .map(G.MapEntry.class::cast) .collect(Collectors.toList()); for (G.MapEntry entry : entries) { if (entry.getKey() instanceof J.Literal && - "group".equals(((J.Literal) entry.getKey()).getValue()) && - entry.getValue() instanceof J.Literal) { + "group".equals(((J.Literal) entry.getKey()).getValue()) && + entry.getValue() instanceof J.Literal) { String currentGroup = (String) ((J.Literal) entry.getValue()).getValue(); if (!newGroupId.equals(currentGroup)) { G.MapEntry updatedEntry = entry.withValue( - ChangeStringLiteral.withStringValue((J.Literal) entry.getValue(), newGroupId)); + ChangeStringLiteral.withStringValue((J.Literal) entry.getValue(), newGroupId)); if (firstArg instanceof G.MapLiteral) { G.MapLiteral mapLiteral = (G.MapLiteral) firstArg; updated = m.withArguments(ListUtils.mapFirst(m.getArguments(), arg -> - mapLiteral.withElements(ListUtils.map(mapLiteral.getElements(), - e -> e == entry ? updatedEntry : e)))); + mapLiteral.withElements(ListUtils.map(mapLiteral.getElements(), + e -> e == entry ? updatedEntry : e)))); } else { updated = m.withArguments(ListUtils.map(m.getArguments(), - arg -> arg == entry ? updatedEntry : arg)); + arg -> arg == entry ? updatedEntry : arg)); } } break; @@ -900,7 +973,7 @@ public GradleDependency withDeclaredGroupId(String newGroupId) { } return updated == m ? this : - new GradleDependency(new Cursor(cursor.getParent(), updated), resolvedDependency); + new GradleDependency(new Cursor(cursor.getParent(), updated), resolvedDependency); } /** @@ -927,8 +1000,8 @@ public GradleDependency withDeclaredArtifactId(String newArtifactId) { GradleDependency updated = platformDep.withDeclaredArtifactId(newArtifactId); if (updated != platformDep) { return new GradleDependency(new Cursor(cursor.getParent(), - m.withArguments(ListUtils.mapFirst(m.getArguments(), arg -> updated.getTree()))), - resolvedDependency); + m.withArguments(ListUtils.mapFirst(m.getArguments(), arg -> updated.getTree()))), + resolvedDependency); } } return this; @@ -944,7 +1017,7 @@ public GradleDependency withDeclaredArtifactId(String newArtifactId) { if (dep != null && !newArtifactId.equals(dep.getArtifactId())) { Dependency updatedDep = dep.withArtifactId(newArtifactId); updated = m.withArguments(ListUtils.mapFirst(m.getArguments(), - arg -> ChangeStringLiteral.withStringValue((J.Literal) arg, updatedDep.toStringNotation()))); + arg -> ChangeStringLiteral.withStringValue((J.Literal) arg, updatedDep.toStringNotation()))); } } } else if (firstArg instanceof G.GString) { @@ -963,28 +1036,28 @@ public GradleDependency withDeclaredArtifactId(String newArtifactId) { } } else if (firstArg instanceof G.MapEntry || firstArg instanceof G.MapLiteral) { List entries = firstArg instanceof G.MapLiteral ? - ((G.MapLiteral) firstArg).getElements() : m.getArguments().stream() + ((G.MapLiteral) firstArg).getElements() : m.getArguments().stream() .filter(G.MapEntry.class::isInstance) .map(G.MapEntry.class::cast) .collect(Collectors.toList()); for (G.MapEntry entry : entries) { if (entry.getKey() instanceof J.Literal && - "name".equals(((J.Literal) entry.getKey()).getValue()) && - entry.getValue() instanceof J.Literal) { + "name".equals(((J.Literal) entry.getKey()).getValue()) && + entry.getValue() instanceof J.Literal) { String currentArtifact = (String) ((J.Literal) entry.getValue()).getValue(); if (!newArtifactId.equals(currentArtifact)) { G.MapEntry updatedEntry = entry.withValue( - ChangeStringLiteral.withStringValue((J.Literal) entry.getValue(), newArtifactId)); + ChangeStringLiteral.withStringValue((J.Literal) entry.getValue(), newArtifactId)); if (firstArg instanceof G.MapLiteral) { G.MapLiteral mapLiteral = (G.MapLiteral) firstArg; updated = m.withArguments(ListUtils.mapFirst(m.getArguments(), arg -> - mapLiteral.withElements(ListUtils.map(mapLiteral.getElements(), - e -> e == entry ? updatedEntry : e)))); + mapLiteral.withElements(ListUtils.map(mapLiteral.getElements(), + e -> e == entry ? updatedEntry : e)))); } else { updated = m.withArguments(ListUtils.map(m.getArguments(), - arg -> arg == entry ? updatedEntry : arg)); + arg -> arg == entry ? updatedEntry : arg)); } } break; @@ -1026,7 +1099,7 @@ public GradleDependency withDeclaredArtifactId(String newArtifactId) { } return updated == m ? this : - new GradleDependency(new Cursor(cursor.getParent(), updated), resolvedDependency); + new GradleDependency(new Cursor(cursor.getParent(), updated), resolvedDependency); } /** @@ -1056,8 +1129,8 @@ public GradleDependency withDeclaredVersion(@Nullable String newVersion) { GradleDependency updated = platformDep.withDeclaredVersion(newVersion); if (updated != platformDep) { return new GradleDependency(new Cursor(cursor.getParent(), - m.withArguments(ListUtils.mapFirst(m.getArguments(), arg -> updated.getTree()))), - resolvedDependency); + m.withArguments(ListUtils.mapFirst(m.getArguments(), arg -> updated.getTree()))), + resolvedDependency); } } return this; @@ -1073,7 +1146,7 @@ public GradleDependency withDeclaredVersion(@Nullable String newVersion) { if (dep != null && !newVersion.equals(dep.getVersion())) { Dependency updatedDep = dep.withVersion(newVersion); updated = m.withArguments(ListUtils.mapFirst(m.getArguments(), - arg -> ChangeStringLiteral.withStringValue((J.Literal) arg, updatedDep.toStringNotation()))); + arg -> ChangeStringLiteral.withStringValue((J.Literal) arg, updatedDep.toStringNotation()))); } } } else if (firstArg instanceof G.GString) { @@ -1093,30 +1166,30 @@ public GradleDependency withDeclaredVersion(@Nullable String newVersion) { } } else if (firstArg instanceof G.MapEntry || firstArg instanceof G.MapLiteral) { List entries = firstArg instanceof G.MapLiteral ? - new ArrayList<>(((G.MapLiteral) firstArg).getElements()) : - m.getArguments().stream() - .filter(G.MapEntry.class::isInstance) - .map(G.MapEntry.class::cast) - .collect(Collectors.toList()); + new ArrayList<>(((G.MapLiteral) firstArg).getElements()) : + m.getArguments().stream() + .filter(G.MapEntry.class::isInstance) + .map(G.MapEntry.class::cast) + .collect(Collectors.toList()); boolean versionFound = false; for (G.MapEntry entry : entries) { if (entry.getKey() instanceof J.Literal && - "version".equals(((J.Literal) entry.getKey()).getValue()) && - entry.getValue() instanceof J.Literal) { + "version".equals(((J.Literal) entry.getKey()).getValue()) && + entry.getValue() instanceof J.Literal) { String currentVersion = (String) ((J.Literal) entry.getValue()).getValue(); if (!newVersion.equals(currentVersion)) { G.MapEntry updatedEntry = entry.withValue( - ChangeStringLiteral.withStringValue((J.Literal) entry.getValue(), newVersion)); + ChangeStringLiteral.withStringValue((J.Literal) entry.getValue(), newVersion)); if (firstArg instanceof G.MapLiteral) { G.MapLiteral mapLiteral = (G.MapLiteral) firstArg; updated = m.withArguments(ListUtils.mapFirst(m.getArguments(), arg -> - mapLiteral.withElements(ListUtils.map(mapLiteral.getElements(), - e -> e == entry ? updatedEntry : e)))); + mapLiteral.withElements(ListUtils.map(mapLiteral.getElements(), + e -> e == entry ? updatedEntry : e)))); } else { updated = m.withArguments(ListUtils.map(m.getArguments(), - arg -> arg == entry ? updatedEntry : arg)); + arg -> arg == entry ? updatedEntry : arg)); } } versionFound = true; @@ -1176,9 +1249,7 @@ public static class Matcher extends GradleTraitMatcher { @Nullable protected DependencyMatcher matcher; - public Matcher(@Nullable DependencyMatcher matcher) { - this.matcher = matcher; - } + public Matcher() {} public Matcher configuration(@Nullable String configuration) { this.configuration = configuration; @@ -1186,7 +1257,7 @@ public Matcher configuration(@Nullable String configuration) { } public Matcher groupId(@Nullable String groupPattern) { - if(matcher == null) { + if (matcher == null) { matcher = new DependencyMatcher(groupPattern, null, null); } else { matcher = matcher.withGroupPattern(groupPattern); @@ -1195,7 +1266,7 @@ public Matcher groupId(@Nullable String groupPattern) { } public Matcher artifactId(@Nullable String artifactPattern) { - if(matcher == null) { + if (matcher == null) { matcher = new DependencyMatcher(null, artifactPattern, null); } else { matcher = matcher.withArtifactPattern(artifactPattern); @@ -1203,6 +1274,11 @@ public Matcher artifactId(@Nullable String artifactPattern) { return this; } + public Matcher matcher(@Nullable DependencyMatcher matcher) { + this.matcher = matcher; + return this; + } + @Override public

TreeVisitor asVisitor(VisitFunction2 visitor) { return new JavaVisitor

() { @@ -1218,93 +1294,83 @@ public J visitMethodInvocation(J.MethodInvocation method, P p) { @Override protected @Nullable GradleDependency test(Cursor cursor) { - Object object = cursor.getValue(); - if (object instanceof J.MethodInvocation) { - J.MethodInvocation methodInvocation = (J.MethodInvocation) object; - - if (!withinDependenciesBlock(cursor)) { - return null; - } - - if (withinDependencyConstraintsBlock(cursor)) { - // A dependency constraint is different from an actual dependency - return null; - } + if(!isDependencyDeclaration(cursor)) { + return null; + } + J.MethodInvocation methodInvocation = cursor.getValue(); + GradleProject gradleProject = getGradleProject(cursor); + GradleDependencyConfiguration gdc = getConfiguration(gradleProject, methodInvocation); + if (gdc == null && !(DEPENDENCY_DSL_MATCHER.matches(methodInvocation) && !"project".equals(methodInvocation.getSimpleName()))) { + return null; + } - GradleProject gradleProject = getGradleProject(cursor); - GradleDependencyConfiguration gdc = getConfiguration(gradleProject, methodInvocation); - if (gdc == null && !(DEPENDENCY_DSL_MATCHER.matches(methodInvocation) && !"project".equals(methodInvocation.getSimpleName()))) { - return null; - } + if (!StringUtils.isBlank(configuration) && !methodInvocation.getSimpleName().equals(configuration)) { + return null; + } - if (!StringUtils.isBlank(configuration) && !methodInvocation.getSimpleName().equals(configuration)) { + Dependency dependency = null; + Expression argument = methodInvocation.getArguments().get(0); + if (argument instanceof J.Literal || argument instanceof G.GString || argument instanceof G.MapEntry || argument instanceof G.MapLiteral || argument instanceof J.Assignment || argument instanceof K.StringTemplate) { + dependency = parseDependency(methodInvocation.getArguments()); + } else if (argument instanceof J.Binary && ((J.Binary) argument).getLeft() instanceof J.Literal) { + dependency = parseDependency(singletonList(((J.Binary) argument).getLeft())); + } else if (argument instanceof J.MethodInvocation) { + if ("platform".equals(((J.MethodInvocation) argument).getSimpleName()) || + "enforcedPlatform".equals(((J.MethodInvocation) argument).getSimpleName())) { + dependency = parseDependency(((J.MethodInvocation) argument).getArguments()); + } else if ("project".equals(((J.MethodInvocation) argument).getSimpleName())) { + // project dependencies are not yet supported return null; } + } - Dependency dependency = null; - Expression argument = methodInvocation.getArguments().get(0); - if (argument instanceof J.Literal || argument instanceof G.GString || argument instanceof G.MapEntry || argument instanceof G.MapLiteral || argument instanceof J.Assignment || argument instanceof K.StringTemplate) { - dependency = parseDependency(methodInvocation.getArguments()); - } else if (argument instanceof J.Binary && ((J.Binary) argument).getLeft() instanceof J.Literal) { - dependency = parseDependency(singletonList(((J.Binary) argument).getLeft())); - } else if (argument instanceof J.MethodInvocation) { - if ("platform".equals(((J.MethodInvocation) argument).getSimpleName()) || - "enforcedPlatform".equals(((J.MethodInvocation) argument).getSimpleName())) { - dependency = parseDependency(((J.MethodInvocation) argument).getArguments()); - } else if ("project".equals(((J.MethodInvocation) argument).getSimpleName())) { - // project dependencies are not yet supported - return null; - } - } - - if (dependency == null) { - return null; - } + if (dependency == null) { + return null; + } - if (gdc != null) { - if (gdc.isCanBeResolved()) { - for (ResolvedDependency resolvedDependency : gdc.getResolved()) { - if(matcher == null || matcher.matches(resolvedDependency.getGroupId(), resolvedDependency.getArtifactId())) { - Dependency req = resolvedDependency.getRequested(); - if ((req.getGroupId() == null || req.getGroupId().equals(dependency.getGroupId())) && - req.getArtifactId().equals(dependency.getArtifactId())) { - return new GradleDependency(cursor, withRequested(resolvedDependency, dependency)); - } + if (gdc != null) { + if (gdc.isCanBeResolved()) { + for (ResolvedDependency resolvedDependency : gdc.getResolved()) { + if (matcher == null || matcher.matches(resolvedDependency.getGroupId(), resolvedDependency.getArtifactId())) { + Dependency req = resolvedDependency.getRequested(); + if ((req.getGroupId() == null || req.getGroupId().equals(dependency.getGroupId())) && + req.getArtifactId().equals(dependency.getArtifactId())) { + return new GradleDependency(cursor, withRequested(resolvedDependency, dependency)); } } - } else { - for (GradleDependencyConfiguration transitiveConfiguration : gradleProject.configurationsExtendingFrom(gdc, true)) { - if (!transitiveConfiguration.isCanBeResolved()) { - for (ResolvedDependency resolvedDependency : transitiveConfiguration.getResolved()) { - if(matcher == null || matcher.matches(resolvedDependency.getGroupId(), resolvedDependency.getArtifactId())) { - Dependency req = resolvedDependency.getRequested(); - if ((req.getGroupId() == null || req.getGroupId().equals(dependency.getGroupId())) && - req.getArtifactId().equals(dependency.getArtifactId())) { - return new GradleDependency(cursor, withRequested(resolvedDependency, dependency)); - } + } + } else { + for (GradleDependencyConfiguration transitiveConfiguration : gradleProject.configurationsExtendingFrom(gdc, true)) { + if (!transitiveConfiguration.isCanBeResolved()) { + for (ResolvedDependency resolvedDependency : transitiveConfiguration.getResolved()) { + if (matcher == null || matcher.matches(resolvedDependency.getGroupId(), resolvedDependency.getArtifactId())) { + Dependency req = resolvedDependency.getRequested(); + if ((req.getGroupId() == null || req.getGroupId().equals(dependency.getGroupId())) && + req.getArtifactId().equals(dependency.getArtifactId())) { + return new GradleDependency(cursor, withRequested(resolvedDependency, dependency)); } } } } } } + } - if(matcher == null || matcher.matches(dependency.getGroupId(), dependency.getArtifactId())) { - // Couldn't find the actual resolved dependency, return a synthetic one instead - ResolvedDependency resolvedDependency = ResolvedDependency.builder() - .depth(-1) - .gav(new ResolvedGroupArtifactVersion(null, dependency.getGroupId() == null ? "" : dependency.getGroupId(), dependency.getArtifactId(), dependency.getVersion() != null ? dependency.getVersion() : "", null)) - .classifier(dependency.getClassifier()) - .type(dependency.getType()) - .requested(Dependency.builder() - .scope(methodInvocation.getSimpleName()) - .type(dependency.getType()) - .gav(dependency.getGav()) - .classifier(dependency.getClassifier()) - .build()) - .build(); - return new GradleDependency(cursor, withRequested(resolvedDependency, dependency)); - } + if (matcher == null || matcher.matches(dependency.getGroupId(), dependency.getArtifactId())) { + // Couldn't find the actual resolved dependency, return a synthetic one instead + ResolvedDependency resolvedDependency = ResolvedDependency.builder() + .depth(-1) + .gav(new ResolvedGroupArtifactVersion(null, dependency.getGroupId() == null ? "" : dependency.getGroupId(), dependency.getArtifactId(), dependency.getVersion() != null ? dependency.getVersion() : "", null)) + .classifier(dependency.getClassifier()) + .type(dependency.getType()) + .requested(Dependency.builder() + .scope(methodInvocation.getSimpleName()) + .type(dependency.getType()) + .gav(dependency.getGav()) + .classifier(dependency.getClassifier()) + .build()) + .build(); + return new GradleDependency(cursor, withRequested(resolvedDependency, dependency)); } return null; @@ -1319,28 +1385,6 @@ private static ResolvedDependency withRequested(ResolvedDependency resolved, Dep return resolved.withRequested(resolved.getRequested().withGav(requested.getGav())); } - - private static @Nullable GradleDependencyConfiguration getConfiguration(@Nullable GradleProject gradleProject, J.MethodInvocation methodInvocation) { - if (gradleProject == null) { - return null; - } - - String methodName = methodInvocation.getSimpleName(); - if ("classpath".equals(methodName)) { - return gradleProject.getBuildscript().getConfiguration(methodName); - } else { - return gradleProject.getConfiguration(methodName); - } - } - - private boolean withinDependenciesBlock(Cursor cursor) { - return withinBlock(cursor, "dependencies"); - } - - private boolean withinDependencyConstraintsBlock(Cursor cursor) { - return withinBlock(cursor, "constraints") && withinDependenciesBlock(cursor); - } - private @Nullable Dependency parseDependency(List arguments) { Expression argument = arguments.get(0); if (argument instanceof J.Literal) { diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/trait/GradleMultiDependency.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/trait/GradleMultiDependency.java index 6fb6fa90ff..e0367a7815 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/trait/GradleMultiDependency.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/trait/GradleMultiDependency.java @@ -17,25 +17,26 @@ import lombok.Getter; import lombok.Value; +import lombok.With; import org.jspecify.annotations.Nullable; import org.openrewrite.Cursor; import org.openrewrite.Tree; import org.openrewrite.TreeVisitor; -import org.openrewrite.groovy.tree.G; import org.openrewrite.internal.StringUtils; import org.openrewrite.java.JavaVisitor; import org.openrewrite.java.MethodMatcher; -import org.openrewrite.java.tree.Expression; import org.openrewrite.java.tree.J; import org.openrewrite.maven.tree.Dependency; import org.openrewrite.semver.DependencyMatcher; import org.openrewrite.trait.Trait; import org.openrewrite.trait.VisitFunction2; -import java.util.ArrayList; -import java.util.List; +import java.util.Optional; +import java.util.function.Consumer; import java.util.function.Function; +import static org.openrewrite.gradle.trait.GradleDependency.isDependencyDeclaration; + /** * Represents one or more Gradle dependencies declared in a single method invocation. * Gradle's groovy DLS allows invocations like: @@ -44,6 +45,7 @@ */ @Value public class GradleMultiDependency implements Trait { + @With Cursor cursor; /** @@ -62,6 +64,7 @@ public class GradleMultiDependency implements Trait { * * @return The configuration name */ + @SuppressWarnings("unused") public String getConfigurationName() { return getTree().getSimpleName(); } @@ -73,7 +76,7 @@ public String getConfigurationName() { */ private static boolean methodArgumentsContainMultipleDependencyNotations(Cursor methodCursor) { Object maybeMi = methodCursor.getValue(); - if(!(maybeMi instanceof J.MethodInvocation)) { + if (!(maybeMi instanceof J.MethodInvocation)) { return false; } J.MethodInvocation m = (J.MethodInvocation) maybeMi; @@ -91,21 +94,36 @@ private static boolean methodArgumentsContainMultipleDependencyNotations(Cursor * @param mapper Function to transform each matching GradleDependency * @return The updated J.MethodInvocation if any changes were made, or the original if not */ - public GradleMultiDependency map(Function mapper) { - J.MethodInvocation m = cursor.getValue(); - if (isVarargs()) { + public J.MethodInvocation map(Function mapper) { + if (isVarargs()) { + + } else { + Optional dep = new GradleDependency.Matcher() + .matcher(matcher) + .get(cursor); + if (dep.isPresent()) { + return mapper.apply(dep.get()); + } + } + return getTree(); + } + + public void forEach(Consumer consumer) { + if (isVarargs()) { } else { new GradleDependency.Matcher() + .matcher(matcher) .get(cursor) - .map(mapper) - .get() + .ifPresent(consumer); } - return this; + } + + public static Matcher matcher(DependencyMatcher matcher) { + return new Matcher().matcher(matcher); } public static class Matcher extends GradleTraitMatcher { - private static final MethodMatcher DEPENDENCY_DSL_MATCHER = new MethodMatcher("DependencyHandlerSpec *(..)"); @Nullable protected String configuration; @@ -113,8 +131,9 @@ public static class Matcher extends GradleTraitMatcher { @Nullable protected DependencyMatcher matcher; - public Matcher(@Nullable DependencyMatcher matcher) { + public Matcher matcher(@Nullable DependencyMatcher matcher) { this.matcher = matcher; + return this; } public Matcher configuration(@Nullable String configuration) { @@ -123,7 +142,7 @@ public Matcher configuration(@Nullable String configuration) { } public Matcher groupId(@Nullable String groupPattern) { - if(matcher == null) { + if (matcher == null) { matcher = new DependencyMatcher(groupPattern, null, null); } else { matcher = matcher.withGroupPattern(groupPattern); @@ -132,7 +151,7 @@ public Matcher groupId(@Nullable String groupPattern) { } public Matcher artifactId(@Nullable String artifactPattern) { - if(matcher == null) { + if (matcher == null) { matcher = new DependencyMatcher(null, artifactPattern, null); } else { matcher = matcher.withArtifactPattern(artifactPattern); @@ -155,74 +174,16 @@ public J visitMethodInvocation(J.MethodInvocation method, P p) { @Override protected @Nullable GradleMultiDependency test(Cursor cursor) { - Object object = cursor.getValue(); - if (!(object instanceof J.MethodInvocation)) { - return null; - } - - J.MethodInvocation methodInvocation = (J.MethodInvocation) object; - List args = methodInvocation.getArguments(); - - // Check if this is a varargs invocation (multiple literal strings) - methodArgumentsContainMultipleDependencyNotations(cursor); - if (args.stream().filter(it -> Dependency.parse(it.print(cursor)) != null).count() > 1) { - return testVarargs(cursor, methodInvocation, args); - } - - return new GradleMultiDependency(cursor, matcher, ); - } - - private @Nullable GradleMultiDependency testVarargs(Cursor cursor, J.MethodInvocation methodInvocation, List args) { - if (!withinDependenciesBlock(cursor)) { - return null; - } - - if (withinDependencyConstraintsBlock(cursor)) { - return null; - } - - if (!DEPENDENCY_DSL_MATCHER.matches(methodInvocation) || "project".equals(methodInvocation.getSimpleName())) { + if (!isDependencyDeclaration(cursor)) { return null; } + J.MethodInvocation methodInvocation = cursor.getValue(); if (!StringUtils.isBlank(configuration) && !methodInvocation.getSimpleName().equals(configuration)) { return null; } - // Parse each literal string or GString argument - List parsedDependencies = new ArrayList<>(); - for (Expression arg : args) { - Dependency dep = null; - if (arg instanceof J.Literal && ((J.Literal) arg).getValue() instanceof String) { - String gav = (String) ((J.Literal) arg).getValue(); - dep = Dependency.parse(gav); - } else if (arg instanceof G.GString) { - // Handle GString notation: "group:artifact:$version" - G.GString gstring = (G.GString) arg; - List strings = gstring.getStrings(); - if (strings.size() >= 2 && strings.get(0) instanceof J.Literal && ((J.Literal) strings.get(0)).getValue() != null) { - String gav = (String) ((J.Literal) strings.get(0)).getValue(); - dep = Dependency.parse(gav); - } - } - if (dep != null) { - parsedDependencies.add(dep); - } - } - - if (parsedDependencies.size() <= 1) { - return null; // Not really a varargs if only one or zero dependencies - } - - return new GradleMultiDependency(cursor, matcher); - } - - private boolean withinDependenciesBlock(Cursor cursor) { - return withinBlock(cursor, "dependencies"); - } - - private boolean withinDependencyConstraintsBlock(Cursor cursor) { - return withinBlock(cursor, "constraints") && withinDependenciesBlock(cursor); + return new GradleMultiDependency(cursor, matcher, methodArgumentsContainMultipleDependencyNotations(cursor)); } } } \ No newline at end of file diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/tree/Dependency.java b/rewrite-maven/src/main/java/org/openrewrite/maven/tree/Dependency.java index ff782b3cb1..6d1d73f2ef 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/tree/Dependency.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/tree/Dependency.java @@ -179,6 +179,21 @@ public Dependency withExt(@Nullable String ext) { if (notation == null) { return null; } + notation = notation.trim(); + if (notation.isEmpty()) { + return null; + } + + if (notation.indexOf('"') >= 0 || + notation.indexOf('\'') >= 0) { + return null; + } + + // Reject notation with spaces (which would indicate Groovy map notation or other invalid format) + if (notation.indexOf(' ') >= 0) { + return null; + } + int idx = notation.lastIndexOf('@'); if (idx == -1) { return parse(notation, null); @@ -251,6 +266,15 @@ public Dependency withExt(@Nullable String ext) { return null; } + // Validate groupId and artifactId contain only valid characters [A-Za-z0-9_.-]+ + // GroupId can be empty string (for notations like ":artifact") + if (groupId != null && !groupId.isEmpty() && !isValidIdentifier(groupId)) { + return null; + } + if (!isValidIdentifier(artifactId)) { + return null; + } + // Handle empty strings in version and classifier if ("".equals(version)) { version = ""; // Preserve empty strings @@ -265,4 +289,27 @@ public Dependency withExt(@Nullable String ext) { .type(type) .build(); } + + /** + * Validates that a string contains only valid Maven/Gradle identifier characters. + * Valid characters are: A-Z, a-z, 0-9, underscore (_), dot (.), and hyphen (-). + * + * @param str the string to validate + * @return true if the string contains only valid characters, false otherwise + */ + private static boolean isValidIdentifier(@Nullable String str) { + if (str == null || str.isEmpty()) { + return false; + } + for (int i = 0; i < str.length(); i++) { + char c = str.charAt(i); + if (!((c >= 'A' && c <= 'Z') || + (c >= 'a' && c <= 'z') || + (c >= '0' && c <= '9') || + c == '_' || c == '.' || c == '-')) { + return false; + } + } + return true; + } } diff --git a/rewrite-maven/src/test/java/org/openrewrite/maven/tree/DependencyTest.java b/rewrite-maven/src/test/java/org/openrewrite/maven/tree/DependencyTest.java index 9a492e4d44..653dac42da 100644 --- a/rewrite-maven/src/test/java/org/openrewrite/maven/tree/DependencyTest.java +++ b/rewrite-maven/src/test/java/org/openrewrite/maven/tree/DependencyTest.java @@ -82,7 +82,6 @@ void toStringNotationWithOnlyArtifact() { assertThat(dep.toStringNotation()).isEqualTo(":artifact"); } - // Tests for parse() @Test void parseWithAllFieldsPresent() { Dependency dep = Dependency.parse("com.example:artifact:1.0.0:sources@jar"); @@ -274,4 +273,69 @@ void parseWithAllFieldsPresentUsingFluentAssertions() { .extracting(Dependency::toStringNotation) .isEqualTo("com.example:artifact:1.0.0:sources@jar"); } + + @Test + void parseRejectsGroovyMapNotation() { + assertThat(Dependency.parse("group : \"com.google.guava\"")).isNull(); + assertThat(Dependency.parse("name : \"guava\"")).isNull(); + assertThat(Dependency.parse(" group: \"value\" ")).isNull(); + assertThat(Dependency.parse("version: '29.0-jre'")).isNull(); + } + + @Test + void parseRejectsNotationWithQuotedValues() { + // These look like partial Groovy notation, not valid dependency strings + assertThat(Dependency.parse("'com.example':'artifact'")).isNull(); + } + + @Test + void parseRejectsNotationWithTrailingSpaces() { + // Valid dependency notation should not have spaces around colons + assertThat(Dependency.parse("com.example : artifact")).isNull(); + assertThat(Dependency.parse("com.example: artifact")).isNull(); + assertThat(Dependency.parse("com.example :artifact")).isNull(); + } + + @Test + void parseRejectsEmptyString() { + assertThat(Dependency.parse("")).isNull(); + } + + @Test + void parseRejectsOnlyColons() { + assertThat(Dependency.parse(":")).isNull(); + assertThat(Dependency.parse("::")).isNull(); + assertThat(Dependency.parse(":::")).isNull(); + } + + @Test + void parseRejectsInvalidCharactersInGroupId() { + // GroupIds must match [A-Za-z0-9_.-]+ + assertThat(Dependency.parse("com/example:artifact")).isNull(); + assertThat(Dependency.parse("com example:artifact")).isNull(); + assertThat(Dependency.parse("com*example:artifact")).isNull(); + assertThat(Dependency.parse("com@example:artifact")).isNull(); + assertThat(Dependency.parse("com+example:artifact")).isNull(); + assertThat(Dependency.parse("com[example]:artifact")).isNull(); + } + + @Test + void parseRejectsInvalidCharactersInArtifactId() { + // ArtifactIds must match [A-Za-z0-9_.-]+ + assertThat(Dependency.parse("com.example:my/artifact")).isNull(); + assertThat(Dependency.parse("com.example:my artifact")).isNull(); + assertThat(Dependency.parse("com.example:my*artifact")).isNull(); + assertThat(Dependency.parse("com.example:my+artifact")).isNull(); + assertThat(Dependency.parse("com.example:my[artifact]")).isNull(); + assertThat(Dependency.parse("com.example:my#artifact")).isNull(); + } + + @Test + void parseAcceptsValidCharactersInGroupAndArtifact() { + // Valid characters: A-Z, a-z, 0-9, _, ., - + Dependency dep = Dependency.parse("com.example-test_123:my-artifact_456.test"); + assertThat(dep).isNotNull(); + assertThat(dep.getGroupId()).isEqualTo("com.example-test_123"); + assertThat(dep.getArtifactId()).isEqualTo("my-artifact_456.test"); + } } \ No newline at end of file From 97e559285f13caa207da56dd4a0b7f0a2564cdc8 Mon Sep 17 00:00:00 2001 From: Sam Snyder Date: Tue, 14 Oct 2025 17:56:42 -0700 Subject: [PATCH 12/19] Remove now-unnecessary null checks before using StringUtils.matchesGlob --- .../org/openrewrite/internal/StringUtils.java | 6 +- .../RemoveRedundantDependencyVersions.java | 165 +++++++----------- .../gradle/UpgradeDependencyVersion.java | 6 +- .../gradle/trait/GradleDependency.java | 45 +++-- .../gradle/trait/GradleMultiDependency.java | 46 ++++- ...SpringDependencyManagementPluginEntry.java | 4 +- .../org/openrewrite/maven/MavenVisitor.java | 4 +- .../maven/tree/ResolvedDependency.java | 4 +- 8 files changed, 140 insertions(+), 140 deletions(-) diff --git a/rewrite-core/src/main/java/org/openrewrite/internal/StringUtils.java b/rewrite-core/src/main/java/org/openrewrite/internal/StringUtils.java index bb1261b5d8..93de10b2d4 100644 --- a/rewrite-core/src/main/java/org/openrewrite/internal/StringUtils.java +++ b/rewrite-core/src/main/java/org/openrewrite/internal/StringUtils.java @@ -391,15 +391,13 @@ public static String repeat(String s, int count) { * which properly interprets '*' and '**' wildcards for file paths. * * @param str the input string to match against the pattern, can be null - * @param pattern the glob pattern to evaluate, can be null + * @param pattern the glob pattern to evaluate. null and "*" both match everything. * @return true if the input string matches the glob pattern, false otherwise * @see org.openrewrite.PathUtils#matchesGlob(Path, String) */ public static boolean matchesGlob(@Nullable String str, @Nullable String pattern) { - if ("*".equals(pattern)) { + if ("*".equals(pattern) || pattern == null) { return true; - } else if (pattern == null) { - return false; } if (str == null) { diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/RemoveRedundantDependencyVersions.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/RemoveRedundantDependencyVersions.java index c2948591f8..e12911e4d8 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/RemoveRedundantDependencyVersions.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/RemoveRedundantDependencyVersions.java @@ -19,14 +19,13 @@ import lombok.Value; import org.jspecify.annotations.Nullable; import org.openrewrite.*; -import org.openrewrite.maven.tree.Dependency; import org.openrewrite.gradle.marker.GradleDependencyConfiguration; import org.openrewrite.gradle.marker.GradleDependencyConstraint; import org.openrewrite.gradle.marker.GradleProject; import org.openrewrite.gradle.trait.GradleDependencies; import org.openrewrite.gradle.trait.GradleDependency; +import org.openrewrite.gradle.trait.GradleMultiDependency; import org.openrewrite.gradle.trait.SpringDependencyManagementPluginEntry; -import org.openrewrite.groovy.tree.G; import org.openrewrite.internal.StringUtils; import org.openrewrite.java.JavaIsoVisitor; import org.openrewrite.java.JavaVisitor; @@ -36,10 +35,7 @@ import org.openrewrite.maven.MavenDownloadingExceptions; import org.openrewrite.maven.internal.MavenPomDownloader; import org.openrewrite.maven.tree.*; -import org.openrewrite.semver.ExactVersion; -import org.openrewrite.semver.LatestIntegration; -import org.openrewrite.semver.Semver; -import org.openrewrite.semver.VersionComparator; +import org.openrewrite.semver.*; import org.openrewrite.trait.Trait; import java.util.*; @@ -117,12 +113,12 @@ public String getDescription() { @Override public TreeVisitor getVisitor() { return Preconditions.check(new IsBuildGradle<>(), new JavaIsoVisitor() { + @SuppressWarnings("NotNullFieldNotInitialized") GradleProject gp; final Map> platforms = new HashMap<>(); final Map> directDependencies = new HashMap<>(); final Map springPluginManagedDependencies = new HashMap<>(); - @Override public @Nullable J visit(@Nullable Tree tree, ExecutionContext ctx) { if (tree instanceof JavaSourceFile) { @@ -176,73 +172,32 @@ public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, Execu }.visit(tree, ctx); } - new JavaIsoVisitor() { - @Override - public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { - J.MethodInvocation m = super.visitMethodInvocation(method, ctx); - - new GradleDependency.Matcher() - .groupId(groupPattern) - .artifactId(artifactPattern) - .get(getCursor()) - .ifPresent(it -> - directDependencies.computeIfAbsent(m.getSimpleName(), k -> new ArrayList<>()).add(it.getResolvedDependency())); - - if (!"platform".equals(m.getSimpleName()) && !"enforcedPlatform".equals(m.getSimpleName())) { - return m; - } - - GroupArtifactVersion gav = null; - if (m.getArguments().get(0) instanceof J.Literal) { - J.Literal l = (J.Literal) m.getArguments().get(0); - if (l.getType() == JavaType.Primitive.String) { - Dependency dependency = Dependency.parse((String) l.getValue()); - gav = new GroupArtifactVersion(dependency.getGroupId(), dependency.getArtifactId(), dependency.getVersion()); + MavenPomDownloader mpd = new MavenPomDownloader(ctx); + GradleMultiDependency.matcher() + .groupId(groupPattern) + .artifactId(artifactPattern) + .asVisitor(gmd -> gmd.map(gradleDependency -> { + directDependencies.computeIfAbsent(gradleDependency.getConfigurationName(), k -> new ArrayList<>()).add(gradleDependency.getResolvedDependency()); + if(!gradleDependency.isPlatform()) { + return gradleDependency.getTree(); } - } else if (m.getArguments().get(0) instanceof G.MapEntry) { - String groupId = null; - String artifactId = null; - String version = null; - - for (Expression arg : m.getArguments()) { - if (!(arg instanceof G.MapEntry && - ((G.MapEntry) arg).getKey().getType() == JavaType.Primitive.String && - ((G.MapEntry) arg).getValue().getType() == JavaType.Primitive.String)) { - continue; - } - - Object key = ((J.Literal) ((G.MapEntry) arg).getKey()).getValue(); - if ("group".equals(key)) { - groupId = (String) ((J.Literal) ((G.MapEntry) arg).getValue()).getValue(); - } else if ("name".equals(key)) { - artifactId = (String) ((J.Literal) ((G.MapEntry) arg).getValue()).getValue(); - } else if ("version".equals(key)) { - version = (String) ((J.Literal) ((G.MapEntry) arg).getValue()).getValue(); - } - } - - if (groupId != null && artifactId != null && version != null) { - gav = new GroupArtifactVersion(groupId, artifactId, version); - } - } - if (gav != null) { - MavenPomDownloader mpd = new MavenPomDownloader(ctx); + GroupArtifactVersion gav = gradleDependency.getGav(); try { ResolvedPom platformPom = mpd.download(gav, null, null, gp.getMavenRepositories()) .resolve(emptyList(), mpd, ctx); - platforms.computeIfAbsent(getCursor().getParentOrThrow(1).firstEnclosingOrThrow(J.MethodInvocation.class).getSimpleName(), k -> new ArrayList<>()).add(platformPom); - } catch (MavenDownloadingException ignored) { + platforms.computeIfAbsent(gradleDependency.getConfigurationName(), k -> new ArrayList<>()).add(platformPom); + } catch (MavenDownloadingException e) { + throw new RuntimeException(e); } - } - return m; - } - }.visit(tree, ctx); + return gradleDependency.getTree(); + })) + .visit(tree, ctx, getCursor()); Set statementsToRemove = new HashSet<>(); tree = new JavaIsoVisitor() { @Override - public J.@Nullable MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { + public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { J.MethodInvocation m = super.visitMethodInvocation(method, ctx); if ("dependencies".equals(m.getSimpleName())) { // Gradle tolerates multiple declarations of the same dependency, but only the one with the newest version is used @@ -298,6 +253,7 @@ public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, Execu try { getCursor().dropParentUntil(obj -> obj instanceof J.MethodInvocation && "constraints".equals(((J.MethodInvocation) obj).getSimpleName())).getValue(); if (shouldRemoveRedundantConstraint(dependency, gp.getConfiguration(m.getSimpleName()))) { + //noinspection DataFlowIssue return null; } return m; @@ -317,9 +273,10 @@ public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, Execu } @Override - public J.@Nullable Return visitReturn(J.Return _return, ExecutionContext ctx) { + public J.Return visitReturn(J.Return _return, ExecutionContext ctx) { J.Return r = super.visitReturn(_return, ctx); if (r.getExpression() == null) { + //noinspection DataFlowIssue return null; } return r; @@ -330,8 +287,7 @@ private boolean isDependencyManagementMethod(String methodName) { } private boolean shouldRemoveRedundantDependency(@Nullable Dependency dependency, String configurationName, List repositories, ExecutionContext ctx) throws MavenDownloadingException { - if (dependency == null || ((groupPattern != null && !matchesGlob(dependency.getGroupId(), groupPattern)) || - (artifactPattern != null && !matchesGlob(dependency.getArtifactId(), artifactPattern)))) { + if (dependency == null || !matchesGlob(dependency.getGroupId(), groupPattern) || !matchesGlob(dependency.getArtifactId(), artifactPattern)) { return false; } @@ -399,8 +355,8 @@ boolean shouldRemoveRedundantConstraint(@Nullable Dependency constraint, @Nullab // https://docs.gradle.org/current/userguide/dependency_versions.html#sec:strict-version return false; } - if ((groupPattern != null && !matchesGlob(constraint.getGroupId(), groupPattern)) || - (artifactPattern != null && !matchesGlob(constraint.getArtifactId(), artifactPattern))) { + if (!matchesGlob(constraint.getGroupId(), groupPattern) || + !matchesGlob(constraint.getArtifactId(), artifactPattern)) { return false; } @@ -408,6 +364,7 @@ boolean shouldRemoveRedundantConstraint(@Nullable Dependency constraint, @Nullab Stream.of(c), gp.configurationsExtendingFrom(c, true).stream() ) + .filter(Objects::nonNull) .filter(GradleDependencyConfiguration::isCanBeResolved) .distinct() .map(conf -> conf.findResolvedDependency(requireNonNull(constraint.getGroupId()), constraint.getArtifactId())) @@ -433,49 +390,45 @@ public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) @Override public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { J.MethodInvocation m = super.visitMethodInvocation(method, ctx); - - Optional maybeGradleDependency = new GradleDependency.Matcher() + m = GradleMultiDependency.matcher() .groupId(groupPattern) .artifactId(artifactPattern) - .get(getCursor()); - if (!maybeGradleDependency.isPresent()) { - return m; - } - - GradleDependency gradleDependency = maybeGradleDependency.get(); - ResolvedDependency dep = gradleDependency.getResolvedDependency(); - if (StringUtils.isBlank(dep.getVersion())) { - return m; - } - - if (springPluginManagedDependencies.containsKey(dep.getGav().asGroupArtifact())) { - if (matchesComparator(springPluginManagedDependencies.get(dep.getGav().asGroupArtifact()), dep.getVersion())) { - return gradleDependency.removeVersion().getTree(); - } - } + .get(getCursor()) + .map(gmd -> gmd.map(gradleDependency -> { + ResolvedDependency dep = gradleDependency.getResolvedDependency(); + if (StringUtils.isBlank(dep.getVersion())) { + return gradleDependency.getTree(); + } - if (platforms.containsKey(m.getSimpleName())) { - for (ResolvedPom platform : platforms.get(m.getSimpleName())) { - String managedVersion = platform.getManagedVersion(dep.getGroupId(), dep.getArtifactId(), null, dep.getRequested().getClassifier()); - if (matchesComparator(managedVersion, dep.getVersion())) { - return gradleDependency.removeVersion().getTree(); - } - } - } - GradleDependencyConfiguration gdc = gp.getConfiguration(m.getSimpleName()); - if (gdc != null) { - for (GradleDependencyConfiguration configuration : gdc.allExtendsFrom()) { - if (platforms.containsKey(configuration.getName())) { - for (ResolvedPom platform : platforms.get(configuration.getName())) { - String managedVersion = platform.getManagedVersion(dep.getGroupId(), dep.getArtifactId(), null, dep.getRequested().getClassifier()); - if (matchesComparator(managedVersion, dep.getVersion())) { - return gradleDependency.removeVersion().getTree(); + if (springPluginManagedDependencies.containsKey(dep.getGav().asGroupArtifact())) { + if (matchesComparator(springPluginManagedDependencies.get(dep.getGav().asGroupArtifact()), dep.getVersion())) { + return gradleDependency.withDeclaredVersion(null).getTree(); } } - } - } - } + if (platforms.containsKey(gradleDependency.getConfigurationName())) { + for (ResolvedPom platform : platforms.get(gradleDependency.getConfigurationName())) { + String managedVersion = platform.getManagedVersion(dep.getGroupId(), dep.getArtifactId(), null, dep.getRequested().getClassifier()); + if (matchesComparator(managedVersion, dep.getVersion())) { + return gradleDependency.withDeclaredVersion(null).getTree(); + } + } + } + GradleDependencyConfiguration gdc = gp.getConfiguration(gradleDependency.getConfigurationName()); + if (gdc != null) { + for (GradleDependencyConfiguration configuration : gdc.allExtendsFrom()) { + if (platforms.containsKey(configuration.getName())) { + for (ResolvedPom platform : platforms.get(configuration.getName())) { + String managedVersion = platform.getManagedVersion(dep.getGroupId(), dep.getArtifactId(), null, dep.getRequested().getClassifier()); + if (matchesComparator(managedVersion, dep.getVersion())) { + return gradleDependency.withDeclaredVersion(null).getTree(); + } + } + } + } + } + return gradleDependency.getTree(); + })).orElse(m); return m; } diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/UpgradeDependencyVersion.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/UpgradeDependencyVersion.java index 52010945a8..5d5e3d0437 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/UpgradeDependencyVersion.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/UpgradeDependencyVersion.java @@ -168,7 +168,8 @@ public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) J.MethodInvocation m = (J.MethodInvocation) super.visitMethodInvocation(method, ctx); // Check for any dependency (single or varargs with literal strings) - GradleMultiDependency.matcher(new DependencyMatcher(groupId, artifactId, getVersionComparator())) + GradleMultiDependency.matcher() + .matcher(new DependencyMatcher(groupId, artifactId, getVersionComparator())) .get(getCursor()) .ifPresent(multiDependency -> multiDependency.forEach(dep -> scanDependency(dep, ctx))); @@ -388,7 +389,8 @@ public J postVisit(J tree, ExecutionContext ctx) { public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { J.MethodInvocation m = (J.MethodInvocation) super.visitMethodInvocation(method, ctx); - GradleMultiDependency multiDependency = GradleMultiDependency.matcher(dependencyMatcher) + GradleMultiDependency multiDependency = GradleMultiDependency.matcher() + .matcher(dependencyMatcher) .get(getCursor()) .orElse(null); if (multiDependency != null) { 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 2d7870586c..56746a9174 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 @@ -124,7 +124,14 @@ public static boolean isDependencyDeclaration(Cursor cursor) { return false; } - return DEPENDENCY_DSL_MATCHER.matches(methodInvocation); + // If the DSL matcher matches, this is definitely a dependency (Groovy with type attribution) + if (DEPENDENCY_DSL_MATCHER.matches(methodInvocation)) { + return true; + } + + // If we have a valid configuration, this is a dependency even without type attribution (Kotlin DSL) + // This handles cases where methodType is null but Gradle resolved the configuration + return gdc != null; } @With @@ -160,6 +167,14 @@ public String getVersion() { return resolvedDependency.getVersion(); } + public GroupArtifactVersion getGav() { + return resolvedDependency.getGav().asGroupArtifactVersion(); + } + + public ResolvedGroupArtifactVersion getResolvedGav() { + return resolvedDependency.getGav(); + } + /** * Gets the configuration name for this dependency. * For example, "implementation", "testImplementation", "api", etc. @@ -959,7 +974,7 @@ public GradleDependency withDeclaredGroupId(String newGroupId) { } else if (firstArg instanceof K.StringTemplate) { K.StringTemplate template = (K.StringTemplate) firstArg; List strings = template.getStrings(); - if (strings.size() >= 2 && strings.get(0) instanceof J.Literal) { + if (!strings.isEmpty() && strings.get(0) instanceof J.Literal) { J.Literal literal = (J.Literal) strings.get(0); Dependency dep = Dependency.parse((String) literal.getValue()); if (dep != null && !newGroupId.equals(dep.getGroupId())) { @@ -1085,7 +1100,7 @@ public GradleDependency withDeclaredArtifactId(String newArtifactId) { } else if (firstArg instanceof K.StringTemplate) { K.StringTemplate template = (K.StringTemplate) firstArg; List strings = template.getStrings(); - if (strings.size() >= 2 && strings.get(0) instanceof J.Literal) { + if (!strings.isEmpty() && strings.get(0) instanceof J.Literal) { J.Literal literal = (J.Literal) strings.get(0); Dependency dep = Dependency.parse((String) literal.getValue()); if (dep != null && !newArtifactId.equals(dep.getArtifactId())) { @@ -1113,10 +1128,7 @@ public GradleDependency withDeclaredArtifactId(String newArtifactId) { * @return A new GradleDependency with the updated version, or the original if no change was made */ public GradleDependency withDeclaredVersion(@Nullable String newVersion) { - if (newVersion == null) { - return this; - } - if (newVersion.isEmpty()) { + if (newVersion == null || newVersion.isEmpty()) { return removeVersion(); } @@ -1197,9 +1209,8 @@ public GradleDependency withDeclaredVersion(@Nullable String newVersion) { } } - // If no version entry exists, we need to add one if (!versionFound && firstArg instanceof G.MapLiteral) { - // TODO Add version entry to map literal - this is complex and may need special handling + // TODO Add version entry to map literal } } else if (firstArg instanceof J.Assignment) { List updatedArgs = m.getArguments(); @@ -1225,7 +1236,7 @@ public GradleDependency withDeclaredVersion(@Nullable String newVersion) { // For StringTemplate, we convert to a simple string literal with the new version K.StringTemplate template = (K.StringTemplate) firstArg; List strings = template.getStrings(); - if (strings.size() >= 2 && strings.get(0) instanceof J.Literal) { + if (!strings.isEmpty() && strings.get(0) instanceof J.Literal) { J.Literal literal = (J.Literal) strings.get(0); Dependency dep = Dependency.parse((String) literal.getValue()); if (dep != null) { @@ -1341,7 +1352,7 @@ public J visitMethodInvocation(J.MethodInvocation method, P p) { } } else { for (GradleDependencyConfiguration transitiveConfiguration : gradleProject.configurationsExtendingFrom(gdc, true)) { - if (!transitiveConfiguration.isCanBeResolved()) { + if (transitiveConfiguration.isCanBeResolved()) { for (ResolvedDependency resolvedDependency : transitiveConfiguration.getResolved()) { if (matcher == null || matcher.matches(resolvedDependency.getGroupId(), resolvedDependency.getArtifactId())) { Dependency req = resolvedDependency.getRequested(); @@ -1406,6 +1417,7 @@ private static ResolvedDependency withRequested(ResolvedDependency resolved, Dep } else if (argument instanceof J.Assignment) { String group = null; String artifact = null; + String version = null; for (Expression e : arguments) { if (!(e instanceof J.Assignment)) { @@ -1425,6 +1437,8 @@ private static ResolvedDependency withRequested(ResolvedDependency resolved, Dep group = (String) value.getValue(); } else if ("name".equals(name)) { artifact = (String) value.getValue(); + } else if ("version".equals(name)) { + version = (String) value.getValue(); } } @@ -1433,12 +1447,12 @@ private static ResolvedDependency withRequested(ResolvedDependency resolved, Dep } return Dependency.builder() - .gav(new GroupArtifactVersion(group, artifact, null)) + .gav(new GroupArtifactVersion(group, artifact, version)) .build(); } else if (argument instanceof K.StringTemplate) { K.StringTemplate template = (K.StringTemplate) argument; List strings = template.getStrings(); - if (strings.size() >= 2 && strings.get(0) instanceof J.Literal && ((J.Literal) strings.get(0)).getValue() != null) { + if (!strings.isEmpty() && strings.get(0) instanceof J.Literal && ((J.Literal) strings.get(0)).getValue() != null) { return Dependency.parse((String) ((J.Literal) strings.get(0)).getValue()); } } @@ -1449,6 +1463,7 @@ private static ResolvedDependency withRequested(ResolvedDependency resolved, Dep private static @Nullable Dependency getMapEntriesDependency(List arguments) { String group = null; String artifact = null; + String version = null; for (Expression e : arguments) { if (!(e instanceof G.MapEntry)) { @@ -1468,6 +1483,8 @@ private static ResolvedDependency withRequested(ResolvedDependency resolved, Dep group = (String) value.getValue(); } else if ("name".equals(keyValue)) { artifact = (String) value.getValue(); + } else if ("version".equals(keyValue)) { + version = (String) value.getValue(); } } @@ -1476,7 +1493,7 @@ private static ResolvedDependency withRequested(ResolvedDependency resolved, Dep } return org.openrewrite.maven.tree.Dependency.builder() - .gav(new GroupArtifactVersion(group, artifact, null)) + .gav(new GroupArtifactVersion(group, artifact, version)) .build(); } } diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/trait/GradleMultiDependency.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/trait/GradleMultiDependency.java index e0367a7815..dedff1a7af 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/trait/GradleMultiDependency.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/trait/GradleMultiDependency.java @@ -22,15 +22,17 @@ import org.openrewrite.Cursor; import org.openrewrite.Tree; import org.openrewrite.TreeVisitor; +import org.openrewrite.internal.ListUtils; import org.openrewrite.internal.StringUtils; import org.openrewrite.java.JavaVisitor; -import org.openrewrite.java.MethodMatcher; +import org.openrewrite.java.tree.Expression; import org.openrewrite.java.tree.J; import org.openrewrite.maven.tree.Dependency; import org.openrewrite.semver.DependencyMatcher; import org.openrewrite.trait.Trait; import org.openrewrite.trait.VisitFunction2; +import java.util.Collections; import java.util.Optional; import java.util.function.Consumer; import java.util.function.Function; @@ -80,10 +82,19 @@ private static boolean methodArgumentsContainMultipleDependencyNotations(Cursor return false; } J.MethodInvocation m = (J.MethodInvocation) maybeMi; - return m.getArguments().size() > 1 && m.getArguments().stream() - .filter(it -> Dependency.parse(it.print(methodCursor).trim()) != null) + long count = m.getArguments().stream() + .filter(it -> { + String printed = it.print(methodCursor).trim(); + // Strip surrounding quotes if present (from Groovy/Kotlin string literals) + if ((printed.startsWith("'") && printed.endsWith("'")) || + (printed.startsWith("\"") && printed.endsWith("\""))) { + printed = printed.substring(1, printed.length() - 1); + } + return Dependency.parse(printed) != null; + }) .limit(2) - .count() > 1; + .count(); + return m.getArguments().size() > 1 && count > 1; } /** @@ -96,7 +107,20 @@ private static boolean methodArgumentsContainMultipleDependencyNotations(Cursor */ public J.MethodInvocation map(Function mapper) { if (isVarargs()) { - + return getTree().withArguments( + ListUtils.map(getTree().getArguments(), argument -> { + // Make a synthetic GradleDependency representing wrapping a single dependency notation + J.MethodInvocation m = getTree().withArguments(Collections.singletonList(argument)); + Optional dep = new GradleDependency.Matcher() + .matcher(matcher) + .get(new Cursor(getCursor().getParent(), m)); + if (dep.isPresent()) { + J.MethodInvocation result = mapper.apply(dep.get()); + return result.getArguments().get(0); + } + return argument; + }) + ); } else { Optional dep = new GradleDependency.Matcher() .matcher(matcher) @@ -110,7 +134,13 @@ public J.MethodInvocation map(Function map public void forEach(Consumer consumer) { if (isVarargs()) { - + for (Expression argument : getTree().getArguments()) { + J.MethodInvocation m = getTree().withArguments(Collections.singletonList(argument)); + new GradleDependency.Matcher() + .matcher(matcher) + .get(new Cursor(getCursor().getParent(), m)) + .ifPresent(consumer); + } } else { new GradleDependency.Matcher() .matcher(matcher) @@ -119,8 +149,8 @@ public void forEach(Consumer consumer) { } } - public static Matcher matcher(DependencyMatcher matcher) { - return new Matcher().matcher(matcher); + public static Matcher matcher() { + return new Matcher(); } public static class Matcher extends GradleTraitMatcher { diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/trait/SpringDependencyManagementPluginEntry.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/trait/SpringDependencyManagementPluginEntry.java index 1ee57b782b..ccc8e9fc4b 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/trait/SpringDependencyManagementPluginEntry.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/trait/SpringDependencyManagementPluginEntry.java @@ -128,7 +128,7 @@ public Matcher artifactId(@Nullable String artifactId) { } } } - if (dependencies.stream().anyMatch(dependency -> (groupId != null && !matchesGlob(dependency.getGroupId(), groupId)) || (artifactId != null && !matchesGlob(dependency.getArtifactId(), artifactId)))) { + if (dependencies.stream().anyMatch(dependency -> !matchesGlob(dependency.getGroupId(), groupId) || !matchesGlob(dependency.getArtifactId(), artifactId))) { dependencies.clear(); } } @@ -139,7 +139,7 @@ public Matcher artifactId(@Nullable String artifactId) { if (argument instanceof J.Literal || argument instanceof G.GString || argument instanceof G.MapEntry || argument instanceof G.MapLiteral || argument instanceof J.Assignment || argument instanceof K.StringTemplate) { importedBom = parseDependency(methodInvocation.getArguments()); } - if (importedBom != null && ((groupId != null && !matchesGlob(importedBom.getGroupId(), groupId)) || (artifactId != null && !matchesGlob(importedBom.getArtifactId(), artifactId)))) { + if (importedBom != null && (!matchesGlob(importedBom.getGroupId(), groupId) || !matchesGlob(importedBom.getArtifactId(), artifactId))) { importedBom = null; } } diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/MavenVisitor.java b/rewrite-maven/src/main/java/org/openrewrite/maven/MavenVisitor.java index af460183a8..d5113148b6 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/MavenVisitor.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/MavenVisitor.java @@ -247,7 +247,7 @@ private boolean hasPluginGroupId(String groupId) { if (tag.getChildValue("groupId").isPresent() && tag.getChildValue("groupId").get().trim().startsWith("${")) { String propertyKey = tag.getChildValue("groupId").get().trim(); String value = getResolutionResult().getPom().getValue(propertyKey); - isGroupIdFound = value != null && matchesGlob(value, groupId); + isGroupIdFound = matchesGlob(value, groupId); } } return isGroupIdFound; @@ -262,7 +262,7 @@ private boolean hasPluginArtifactId(@Nullable String artifactId) { if (tag.getChildValue("artifactId").isPresent() && tag.getChildValue("artifactId").get().trim().startsWith("${")) { String propertyKey = tag.getChildValue("artifactId").get().trim(); String value = getResolutionResult().getPom().getValue(propertyKey); - isArtifactIdFound = value != null && matchesGlob(value, artifactId); + isArtifactIdFound = matchesGlob(value, artifactId); } } return isArtifactIdFound; diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/tree/ResolvedDependency.java b/rewrite-maven/src/main/java/org/openrewrite/maven/tree/ResolvedDependency.java index 9a34b97ef3..9683bceba2 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/tree/ResolvedDependency.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/tree/ResolvedDependency.java @@ -148,11 +148,11 @@ private List findDependencies0(String groupId, String artifa return dependencies; } - public @Nullable ResolvedDependency findDependency(String groupId, String artifactId) { + public @Nullable ResolvedDependency findDependency(@Nullable String groupId, @Nullable String artifactId) { return findDependency0(groupId, artifactId, newSetFromMap(new IdentityHashMap<>())); } - private @Nullable ResolvedDependency findDependency0(String groupId, String artifactId, Set visited) { + private @Nullable ResolvedDependency findDependency0(@Nullable String groupId, @Nullable String artifactId, Set visited) { if (matchesGlob(getGroupId(), groupId) && matchesGlob(getArtifactId(), artifactId)) { return this; } else if (!visited.add(this)) { From 7f200b7e959e2504aa8dab3dfa51bb30ddfcdadf Mon Sep 17 00:00:00 2001 From: Sam Snyder Date: Tue, 14 Oct 2025 18:12:18 -0700 Subject: [PATCH 13/19] Use trait to add support for kotlin gradle scripts to FindDependencyHandler. Incidentally resolves an OOM I was getting running locally due to ClasspathScanningLoader. There has to be something we can do to not have classgraph use all the memory. --- .../gradle/search/FindDependencyHandler.java | 25 ++++++++++ .../gradle/trait/GradleDependencies.java | 2 +- .../resources/META-INF/rewrite/gradle.yml | 9 ---- .../search/FindDependencyHandlerTest.java | 46 ++++++++++++++++++- 4 files changed, 70 insertions(+), 12 deletions(-) create mode 100644 rewrite-gradle/src/main/java/org/openrewrite/gradle/search/FindDependencyHandler.java diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/search/FindDependencyHandler.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/search/FindDependencyHandler.java new file mode 100644 index 0000000000..b722a33ba9 --- /dev/null +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/search/FindDependencyHandler.java @@ -0,0 +1,25 @@ +package org.openrewrite.gradle.search; + +import org.openrewrite.ExecutionContext; +import org.openrewrite.Recipe; +import org.openrewrite.TreeVisitor; +import org.openrewrite.gradle.trait.GradleDependencies; +import org.openrewrite.marker.SearchResult; + +public class FindDependencyHandler extends Recipe { + @Override + public String getDisplayName() { + return "Find Gradle `dependencies` blocks"; + } + + @Override + public String getDescription() { + return "Find the dependency handler containing any number of dependency definitions."; + } + + @Override + public TreeVisitor getVisitor() { + return new GradleDependencies.Matcher() + .asVisitor(gd -> SearchResult.found(gd.getTree())); + } +} diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/trait/GradleDependencies.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/trait/GradleDependencies.java index c3f37d7862..894535e7ff 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/trait/GradleDependencies.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/trait/GradleDependencies.java @@ -44,7 +44,7 @@ @RequiredArgsConstructor public class GradleDependencies implements Trait { - private static final MethodMatcher DEPENDENCY_DSL_MATCHER = new MethodMatcher("DependencyHandlerSpec *(..)"); + private static final MethodMatcher DEPENDENCY_DSL_MATCHER = new MethodMatcher("RewriteGradleProject dependencies(..)"); @Getter private final Cursor cursor; diff --git a/rewrite-gradle/src/main/resources/META-INF/rewrite/gradle.yml b/rewrite-gradle/src/main/resources/META-INF/rewrite/gradle.yml index 4f6d8f3da1..22c0c3b3d9 100644 --- a/rewrite-gradle/src/main/resources/META-INF/rewrite/gradle.yml +++ b/rewrite-gradle/src/main/resources/META-INF/rewrite/gradle.yml @@ -24,15 +24,6 @@ recipeList: - org.openrewrite.gradle.EnableGradleParallelExecution --- type: specs.openrewrite.org/v1beta/recipe -name: org.openrewrite.gradle.search.FindDependencyHandler -displayName: Find Gradle `dependencies` blocks -description: Find the dependency handler containing any number of dependency definitions. -recipeList: - - org.openrewrite.java.search.FindMethods: - methodPattern: RewriteGradleProject dependencies(groovy.lang.Closure) - matchOverrides: false ---- -type: specs.openrewrite.org/v1beta/recipe name: org.openrewrite.gradle.EnableGradleBuildCache displayName: Enable Gradle build cache description: Enable the Gradle build cache. By enabling build cache the build outputs are stored externally and fetched from the cache when it is determined that those inputs have no changed, avoiding the expensive work of regenerating them. See the [Gradle Build Cache](https://docs.gradle.org/current/userguide/build_cache.html) for more information. diff --git a/rewrite-gradle/src/test/java/org/openrewrite/gradle/search/FindDependencyHandlerTest.java b/rewrite-gradle/src/test/java/org/openrewrite/gradle/search/FindDependencyHandlerTest.java index 9f33d357ba..2179ef2cae 100644 --- a/rewrite-gradle/src/test/java/org/openrewrite/gradle/search/FindDependencyHandlerTest.java +++ b/rewrite-gradle/src/test/java/org/openrewrite/gradle/search/FindDependencyHandlerTest.java @@ -17,17 +17,25 @@ import org.junit.jupiter.api.Test; import org.openrewrite.DocumentExample; +import org.openrewrite.test.RecipeSpec; import org.openrewrite.test.RewriteTest; import static org.openrewrite.gradle.Assertions.buildGradle; -import static org.openrewrite.test.RewriteTest.fromRuntimeClasspath; +import static org.openrewrite.gradle.Assertions.buildGradleKts; +import static org.openrewrite.gradle.toolingapi.Assertions.withToolingApi; class FindDependencyHandlerTest implements RewriteTest { + + @Override + public void defaults(RecipeSpec spec) { + spec.recipe(new FindDependencyHandler()) + .beforeRecipe(withToolingApi()); + } + @DocumentExample @Test void findDependenciesBlock() { rewriteRun( - spec -> spec.recipe(fromRuntimeClasspath("org.openrewrite.gradle.search.FindDependencyHandler")), buildGradle( """ plugins { @@ -58,4 +66,38 @@ void findDependenciesBlock() { ) ); } + + @Test + void findDependenciesBlockKotlin() { + rewriteRun( + buildGradleKts( + """ + plugins { + `java-library` + } + + repositories { + mavenCentral() + } + + dependencies { + api("com.google.guava:guava:23.0") + } + """, + """ + plugins { + `java-library` + } + + repositories { + mavenCentral() + } + + /*~~>*/dependencies { + api("com.google.guava:guava:23.0") + } + """ + ) + ); + } } From e765f9775cc6954db44b90726d6ae2021c489077 Mon Sep 17 00:00:00 2001 From: Sam Snyder Date: Tue, 14 Oct 2025 22:51:58 -0700 Subject: [PATCH 14/19] Use trait for ChangeDependencyArtifactId and ChangeDependencyGroupId --- .../gradle/ChangeDependencyArtifactId.java | 164 +--- .../gradle/ChangeDependencyGroupId.java | 164 +--- .../gradle/trait/GradleDependency.java | 27 +- rewrite-javascript/rewrite/client.txt | 144 ---- rewrite-javascript/rewrite/server.txt | 744 ------------------ 5 files changed, 35 insertions(+), 1208 deletions(-) delete mode 100644 rewrite-javascript/rewrite/client.txt delete mode 100644 rewrite-javascript/rewrite/server.txt diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependencyArtifactId.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependencyArtifactId.java index 5d9afce64d..778da063d4 100755 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependencyArtifactId.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependencyArtifactId.java @@ -19,21 +19,17 @@ import lombok.Value; import org.jspecify.annotations.Nullable; import org.openrewrite.*; -import org.openrewrite.gradle.internal.ChangeStringLiteral; -import org.openrewrite.maven.tree.Dependency; import org.openrewrite.gradle.marker.GradleDependencyConfiguration; import org.openrewrite.gradle.marker.GradleProject; -import org.openrewrite.gradle.trait.GradleDependency; +import org.openrewrite.gradle.trait.GradleMultiDependency; import org.openrewrite.groovy.GroovyIsoVisitor; import org.openrewrite.groovy.tree.G; import org.openrewrite.internal.ListUtils; import org.openrewrite.internal.StringUtils; -import org.openrewrite.java.tree.Expression; import org.openrewrite.java.tree.J; import org.openrewrite.semver.DependencyMatcher; import java.util.HashMap; -import java.util.List; import java.util.Map; import java.util.Optional; @@ -89,6 +85,7 @@ public TreeVisitor getVisitor() { return Preconditions.check(new IsBuildGradle<>(), new GroovyIsoVisitor() { final DependencyMatcher depMatcher = requireNonNull(DependencyMatcher.build(groupId + ":" + artifactId).getValue()); + @SuppressWarnings("NotNullFieldNotInitialized") GradleProject gradleProject; @Override @@ -111,160 +108,13 @@ public G.CompilationUnit visitCompilationUnit(G.CompilationUnit cu, ExecutionCon public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { J.MethodInvocation m = super.visitMethodInvocation(method, ctx); - GradleDependency.Matcher gradleDependencyMatcher = new GradleDependency.Matcher() + m = GradleMultiDependency.matcher() .configuration(configuration) .groupId(groupId) - .artifactId(artifactId); - - if (!gradleDependencyMatcher.get(getCursor()).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) { - m = updateDependency(m); - } 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.map(depArgs, platform -> updateDependency((J.MethodInvocation) platform))); - } - - return m; - } - - private J.MethodInvocation updateDependency(J.MethodInvocation m) { - List depArgs = m.getArguments(); - if (depArgs.get(0) instanceof J.Literal) { - String gav = (String) ((J.Literal) depArgs.get(0)).getValue(); - if (gav != null) { - Dependency dependency = Dependency.parse(gav); - if (dependency != null && !newArtifactId.equals(dependency.getArtifactId())) { - Dependency newDependency = dependency.withArtifactId(newArtifactId); - m = m.withArguments(ListUtils.mapFirst(m.getArguments(), arg -> ChangeStringLiteral.withStringValue((J.Literal) arg, newDependency.toStringNotation()))); - } - } - } else if (depArgs.get(0) instanceof G.GString) { - List strings = ((G.GString) depArgs.get(0)).getStrings(); - if (strings.size() >= 2 && - strings.get(0) instanceof J.Literal) { - J.Literal firstLiteral = (J.Literal) strings.get(0); - String literalValue = (String) firstLiteral.getValue(); - if (literalValue != null) { - // Preserve trailing colon if present (for version interpolation) - boolean hasTrailingColon = literalValue.endsWith(":"); - String toParse = hasTrailingColon ? literalValue.substring(0, literalValue.length() - 1) : literalValue; - - // If there's no version part, add a dummy one for parsing - String[] parts = toParse.split(":"); - if (parts.length == 2) { - toParse = toParse + ":1.0"; // Add dummy version for parsing - } - - Dependency dependency = Dependency.parse(toParse); - if (dependency != null && !newArtifactId.equals(dependency.getArtifactId())) { - // Reconstruct with new artifact ID, preserving original structure - String replacementStr = dependency.getGroupId() + ":" + newArtifactId; - if (hasTrailingColon) { - replacementStr += ":"; - } - final String replacement = replacementStr; - m = m.withArguments(ListUtils.mapFirst(depArgs, arg -> { - G.GString gString = (G.GString) arg; - return gString.withStrings(ListUtils.mapFirst(gString.getStrings(), l -> ((J.Literal) l).withValue(replacement).withValueSource(replacement))); - })); - } - } - } - } else if (depArgs.get(0) instanceof G.MapEntry) { - G.MapEntry artifactEntry = null; - String groupId = null; - String artifactId = null; - - String versionStringDelimiter = "'"; - 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(); - if ("group".equals(keyValue)) { - groupId = valueValue; - } else if ("name".equals(keyValue) && !newArtifactId.equals(valueValue)) { - if (value.getValueSource() != null) { - versionStringDelimiter = value.getValueSource().substring(0, value.getValueSource().indexOf(valueValue)); - } - artifactEntry = arg; - artifactId = valueValue; - } - } - if (groupId == null || artifactId == null) { - return m; - } - String delimiter = versionStringDelimiter; - G.MapEntry finalArtifact = artifactEntry; - m = m.withArguments(ListUtils.map(m.getArguments(), arg -> { - if (arg == finalArtifact) { - return finalArtifact.withValue(((J.Literal) finalArtifact.getValue()) - .withValue(newArtifactId) - .withValueSource(delimiter + newArtifactId + delimiter)); - } - return arg; - })); - } else if (depArgs.get(0) instanceof G.MapLiteral) { - G.MapLiteral map = (G.MapLiteral) depArgs.get(0); - G.MapEntry artifactEntry = null; - String groupId = null; - String artifactId = null; - - String versionStringDelimiter = "'"; - 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(); - if ("group".equals(keyValue)) { - groupId = valueValue; - } else if ("name".equals(keyValue) && !newArtifactId.equals(valueValue)) { - if (value.getValueSource() != null) { - versionStringDelimiter = value.getValueSource().substring(0, value.getValueSource().indexOf(valueValue)); - } - artifactEntry = arg; - artifactId = valueValue; - } - } - if (groupId == null || artifactId == null) { - return m; - } - String delimiter = versionStringDelimiter; - G.MapEntry finalArtifact = artifactEntry; - m = m.withArguments(ListUtils.mapFirst(m.getArguments(), arg -> { - G.MapLiteral mapLiteral = (G.MapLiteral) arg; - return mapLiteral.withElements(ListUtils.map(mapLiteral.getElements(), e -> { - if (e == finalArtifact) { - return finalArtifact.withValue(((J.Literal) finalArtifact.getValue()) - .withValue(newArtifactId) - .withValueSource(delimiter + newArtifactId + delimiter)); - } - return e; - })); - })); - } - + .artifactId(artifactId) + .get(getCursor()) + .map(gmd -> gmd.map(gd -> gd.withDeclaredArtifactId(newArtifactId).getTree())) + .orElse(m); return m; } diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependencyGroupId.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependencyGroupId.java index 9468183f65..1af2699e66 100755 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependencyGroupId.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependencyGroupId.java @@ -19,21 +19,17 @@ import lombok.Value; import org.jspecify.annotations.Nullable; import org.openrewrite.*; -import org.openrewrite.gradle.internal.ChangeStringLiteral; -import org.openrewrite.maven.tree.Dependency; import org.openrewrite.gradle.marker.GradleDependencyConfiguration; import org.openrewrite.gradle.marker.GradleProject; -import org.openrewrite.gradle.trait.GradleDependency; +import org.openrewrite.gradle.trait.GradleMultiDependency; import org.openrewrite.groovy.GroovyIsoVisitor; import org.openrewrite.groovy.tree.G; import org.openrewrite.internal.ListUtils; import org.openrewrite.internal.StringUtils; -import org.openrewrite.java.tree.Expression; import org.openrewrite.java.tree.J; import org.openrewrite.semver.DependencyMatcher; import java.util.HashMap; -import java.util.List; import java.util.Map; import java.util.Optional; @@ -89,6 +85,7 @@ public TreeVisitor getVisitor() { return Preconditions.check(new IsBuildGradle<>(), new GroovyIsoVisitor() { final DependencyMatcher depMatcher = requireNonNull(DependencyMatcher.build(groupId + ":" + artifactId).getValue()); + @SuppressWarnings("NotNullFieldNotInitialized") GradleProject gradleProject; @Override @@ -111,160 +108,13 @@ public G.CompilationUnit visitCompilationUnit(G.CompilationUnit cu, ExecutionCon public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { J.MethodInvocation m = super.visitMethodInvocation(method, ctx); - GradleDependency.Matcher gradleDependencyMatcher = new GradleDependency.Matcher() + m = GradleMultiDependency.matcher() .configuration(configuration) .groupId(groupId) - .artifactId(artifactId); - - if (!gradleDependencyMatcher.get(getCursor()).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) { - m = updateDependency(m); - } 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))); - } - - return m; - } - - private J.MethodInvocation updateDependency(J.MethodInvocation m) { - List depArgs = m.getArguments(); - if (depArgs.get(0) instanceof J.Literal) { - String gav = (String) ((J.Literal) depArgs.get(0)).getValue(); - if (gav != null) { - Dependency dependency = Dependency.parse(gav); - if (dependency != null && !newGroupId.equals(dependency.getGroupId())) { - Dependency newDependency = dependency.withGroupId(newGroupId); - m = m.withArguments(ListUtils.mapFirst(m.getArguments(), arg -> ChangeStringLiteral.withStringValue((J.Literal) arg, newDependency.toStringNotation()))); - } - } - } else if (depArgs.get(0) instanceof G.GString) { - List strings = ((G.GString) depArgs.get(0)).getStrings(); - if (strings.size() >= 2 && - strings.get(0) instanceof J.Literal) { - J.Literal firstLiteral = (J.Literal) strings.get(0); - String literalValue = (String) firstLiteral.getValue(); - if (literalValue != null) { - // Preserve trailing colon if present (for version interpolation) - boolean hasTrailingColon = literalValue.endsWith(":"); - String toParse = hasTrailingColon ? literalValue.substring(0, literalValue.length() - 1) : literalValue; - - // If there's no version part, add a dummy one for parsing - String[] parts = toParse.split(":"); - if (parts.length == 2) { - toParse = toParse + ":1.0"; // Add dummy version for parsing - } - - Dependency dependency = Dependency.parse(toParse); - if (dependency != null && !newGroupId.equals(dependency.getGroupId())) { - // Reconstruct with new group ID, preserving original structure - String replacementStr = newGroupId + ":" + dependency.getArtifactId(); - if (hasTrailingColon) { - replacementStr += ":"; - } - final String replacement = replacementStr; - m = m.withArguments(ListUtils.mapFirst(depArgs, arg -> { - G.GString gString = (G.GString) arg; - return gString.withStrings(ListUtils.mapFirst(gString.getStrings(), l -> ((J.Literal) l).withValue(replacement).withValueSource(replacement))); - })); - } - } - } - } else if (depArgs.get(0) instanceof G.MapEntry) { - G.MapEntry groupEntry = null; - String groupId = null; - String artifactId = null; - - String versionStringDelimiter = "'"; - 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(); - if ("group".equals(keyValue) && !newGroupId.equals(valueValue)) { - if (value.getValueSource() != null) { - versionStringDelimiter = value.getValueSource().substring(0, value.getValueSource().indexOf(valueValue)); - } - groupEntry = arg; - groupId = valueValue; - } else if ("name".equals(keyValue)) { - artifactId = valueValue; - } - } - if (groupId == null || artifactId == null) { - return m; - } - String delimiter = versionStringDelimiter; - G.MapEntry finalGroup = groupEntry; - m = m.withArguments(ListUtils.map(m.getArguments(), arg -> { - if (arg == finalGroup) { - return finalGroup.withValue(((J.Literal) finalGroup.getValue()) - .withValue(newGroupId) - .withValueSource(delimiter + newGroupId + delimiter)); - } - return arg; - })); - } else if (depArgs.get(0) instanceof G.MapLiteral) { - G.MapLiteral map = (G.MapLiteral) depArgs.get(0); - G.MapEntry groupEntry = null; - String groupId = null; - String artifactId = null; - - String versionStringDelimiter = "'"; - 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(); - if ("group".equals(keyValue) && !newGroupId.equals(valueValue)) { - if (value.getValueSource() != null) { - versionStringDelimiter = value.getValueSource().substring(0, value.getValueSource().indexOf(valueValue)); - } - groupEntry = arg; - groupId = valueValue; - } else if ("name".equals(keyValue)) { - artifactId = valueValue; - } - } - if (groupId == null || artifactId == null) { - return m; - } - String delimiter = versionStringDelimiter; - G.MapEntry finalGroup = groupEntry; - 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(((J.Literal) finalGroup.getValue()) - .withValue(newGroupId) - .withValueSource(delimiter + newGroupId + delimiter)); - } - return e; - })); - })); - } - + .artifactId(artifactId) + .get(getCursor()) + .map(gmd -> gmd.map(gd -> gd.withDeclaredGroupId(newGroupId).getTree())) + .orElse(m); return m; } 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 56746a9174..d5267d2296 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 @@ -171,6 +171,7 @@ public GroupArtifactVersion getGav() { return resolvedDependency.getGav().asGroupArtifactVersion(); } + @SuppressWarnings("unused") public ResolvedGroupArtifactVersion getResolvedGav() { return resolvedDependency.getGav(); } @@ -914,13 +915,20 @@ public GradleDependency withDeclaredGroupId(String newGroupId) { List strings = gstring.getStrings(); if (strings.size() >= 2 && strings.get(0) instanceof J.Literal) { J.Literal literal = (J.Literal) strings.get(0); - Dependency dep = Dependency.parse((String) literal.getValue()); + String originalValue = (String) literal.getValue(); + Dependency dep = Dependency.parse(originalValue); if (dep != null && !newGroupId.equals(dep.getGroupId())) { Dependency updatedDep = dep.withGroupId(newGroupId); String replacement = updatedDep.toStringNotation(); + // Preserve trailing colon if present (for version interpolation) + if (originalValue.endsWith(":")) { + replacement = replacement + ":"; + } + // Update only the literal part, preserve the GString structure with the rest of the elements J.Literal newLiteral = literal.withValue(replacement) - .withValueSource(gstring.getDelimiter() + replacement + gstring.getDelimiter()); - updated = m.withArguments(singletonList(newLiteral)); + .withValueSource(replacement); + G.GString updatedGString = gstring.withStrings(ListUtils.mapFirst(strings, s -> newLiteral)); + updated = m.withArguments(singletonList(updatedGString)); } } } else if (firstArg instanceof G.MapEntry || firstArg instanceof G.MapLiteral) { @@ -1040,13 +1048,20 @@ public GradleDependency withDeclaredArtifactId(String newArtifactId) { List strings = gstring.getStrings(); if (strings.size() >= 2 && strings.get(0) instanceof J.Literal) { J.Literal literal = (J.Literal) strings.get(0); - Dependency dep = Dependency.parse((String) literal.getValue()); + String originalValue = (String) literal.getValue(); + Dependency dep = Dependency.parse(originalValue); if (dep != null && !newArtifactId.equals(dep.getArtifactId())) { Dependency updatedDep = dep.withArtifactId(newArtifactId); String replacement = updatedDep.toStringNotation(); + // Preserve trailing colon if present (for version interpolation) + if (originalValue.endsWith(":")) { + replacement = replacement + ":"; + } + // Update only the literal part, preserve the GString structure with the rest of the elements J.Literal newLiteral = literal.withValue(replacement) - .withValueSource(gstring.getDelimiter() + replacement + gstring.getDelimiter()); - updated = m.withArguments(singletonList(newLiteral)); + .withValueSource(replacement); + G.GString updatedGString = gstring.withStrings(ListUtils.mapFirst(strings, s -> newLiteral)); + updated = m.withArguments(singletonList(updatedGString)); } } } else if (firstArg instanceof G.MapEntry || firstArg instanceof G.MapLiteral) { diff --git a/rewrite-javascript/rewrite/client.txt b/rewrite-javascript/rewrite/client.txt deleted file mode 100644 index 7a382d6f8f..0000000000 --- a/rewrite-javascript/rewrite/client.txt +++ /dev/null @@ -1,144 +0,0 @@ -{"state":"NO_CHANGE"} - JavaScriptSender:42 => await q.getAndSend(j, j2 => j2.id); - JavaScriptReceiver:567 => draft.id = await q.receive(j.id); -{"state":"NO_CHANGE"} - JavaScriptSender:43 => await q.getAndSend(j, j2 => j2.prefix, space => this.visitSpace(space, q)); - JavaScriptReceiver:568 => draft.prefix = await q.receive(j.prefix, space => this.visitSpace(space, q)); -{"state":"NO_CHANGE"} - JavaScriptSender:44 => await q.getAndSend(j, j2 => j2.markers); - JavaScriptReceiver:569 => draft.markers = await q.receive(j.markers); -{"state":"NO_CHANGE"} - JavaScriptSender:49 => await q.getAndSend(cu, c => c.sourcePath); - JavaScriptReceiver:577 => draft.sourcePath = await q.receive(cu.sourcePath); -{"state":"NO_CHANGE"} - JavaScriptSender:50 => await q.getAndSend(cu, c => c.charsetName); - JavaScriptReceiver:578 => draft.charsetName = await q.receive(cu.charsetName); -{"state":"NO_CHANGE"} - JavaScriptSender:51 => await q.getAndSend(cu, c => c.charsetBomMarked); - JavaScriptReceiver:579 => draft.charsetBomMarked = await q.receive(cu.charsetBomMarked); -{"state":"NO_CHANGE"} - JavaScriptSender:52 => await q.getAndSend(cu, c => c.checksum); - JavaScriptReceiver:580 => draft.checksum = await q.receive(cu.checksum); -{"state":"NO_CHANGE"} - JavaScriptSender:53 => await q.getAndSend(cu, c => c.fileAttributes); - JavaScriptReceiver:581 => draft.fileAttributes = await q.receive(cu.fileAttributes); -{"state":"CHANGE"} - JavaScriptSender:54 => await q.getAndSendList(cu, c => c.statements, stmt => stmt.element.id, stmt => this.visitRightPadded(stmt, q)); - JavaScriptReceiver:582 => draft.statements = await q.receiveListDefined(cu.statements, stmt => this.visitRightPadded(stmt, q)); -{"state":"ADD","valueType":"org.openrewrite.java.tree.JRightPadded"} - JavaScriptSender:54 => await q.getAndSendList(cu, c => c.statements, stmt => stmt.element.id, stmt => this.visitRightPadded(stmt, q)); - undefined -{"state":"ADD","valueType":"org.openrewrite.javascript.tree.JS$Import"} - JavaScriptSender:510 => return this.javaSender.visitRightPadded(right, q); - JavaScriptReceiver:1106 => return this.javaReceiverDelegate.visitRightPadded(right, q) -{"state":"ADD","value":"49502576-da0b-46ec-afae-c763626588c5"} - JavaScriptSender:42 => await q.getAndSend(j, j2 => j2.id); - JavaScriptReceiver:567 => draft.id = await q.receive(j.id); -{"state":"ADD","valueType":"org.openrewrite.java.tree.Space"} - JavaScriptSender:43 => await q.getAndSend(j, j2 => j2.prefix, space => this.visitSpace(space, q)); - JavaScriptReceiver:568 => draft.prefix = await q.receive(j.prefix, space => this.visitSpace(space, q)); -{"state":"ADD"} - JavaScriptSender:522 => return this.javaSender.visitSpace(space, q); - JavaScriptReceiver:1118 => return this.javaReceiverDelegate.visitSpace(space, q); -{"state":"ADD","valueType":"org.openrewrite.marker.Markers"} - JavaScriptSender:44 => await q.getAndSend(j, j2 => j2.markers); - JavaScriptReceiver:569 => draft.markers = await q.receive(j.markers); -{"state":"ADD","value":"c80d2c98-f36a-4f09-aff9-fcada004e5d4"} - JavaScriptSender:44 => await q.getAndSend(j, j2 => j2.markers); - undefined -{"state":"ADD"} - JavaScriptSender:137 => await q.getAndSendList(jsImport, el => el.modifiers, el => el.id, el => this.visit(el, q)); - JavaScriptReceiver:680 => draft.modifiers = await q.receiveListDefined(draft.modifiers, el => this.visitDefined(el, q)); -{"state":"ADD","valueType":"org.openrewrite.javascript.tree.JS$ImportClause"} - JavaScriptSender:138 => await q.getAndSend(jsImport, el => el.importClause, el => this.visit(el, q)); - JavaScriptReceiver:681 => draft.importClause = await q.receive(draft.importClause, el => this.visitDefined(el, q)); -{"state":"ADD","value":"3d34b6c1-5d85-47e5-bf94-e406dce1c7bd"} - JavaScriptSender:42 => await q.getAndSend(j, j2 => j2.id); - JavaScriptReceiver:567 => draft.id = await q.receive(j.id); -{"state":"ADD","valueType":"org.openrewrite.java.tree.Space"} - JavaScriptSender:43 => await q.getAndSend(j, j2 => j2.prefix, space => this.visitSpace(space, q)); - JavaScriptReceiver:568 => draft.prefix = await q.receive(j.prefix, space => this.visitSpace(space, q)); -{"state":"ADD"} - JavaScriptSender:522 => return this.javaSender.visitSpace(space, q); - JavaScriptReceiver:1118 => return this.javaReceiverDelegate.visitSpace(space, q); -{"state":"ADD","valueType":"org.openrewrite.marker.Markers"} - JavaScriptSender:44 => await q.getAndSend(j, j2 => j2.markers); - JavaScriptReceiver:569 => draft.markers = await q.receive(j.markers); -{"state":"ADD","value":"c80d2c98-f36a-4f09-aff9-fcada004e5d4"} - JavaScriptSender:44 => await q.getAndSend(j, j2 => j2.markers); - undefined -{"state":"ADD","value":false} - JavaScriptSender:146 => await q.getAndSend(jsImportClause, el => el.typeOnly); - JavaScriptReceiver:690 => draft.typeOnly = await q.receive(draft.typeOnly); -{"state":"ADD","valueType":"org.openrewrite.java.tree.JRightPadded"} - JavaScriptSender:147 => await q.getAndSend(jsImportClause, el => el.name, el => this.visitRightPadded(el, q)); - JavaScriptReceiver:691 => draft.name = await q.receive(draft.name, el => this.visitRightPadded(el, q)); -{"state":"ADD","valueType":"org.openrewrite.java.tree.J$Identifier"} - JavaScriptSender:510 => return this.javaSender.visitRightPadded(right, q); - JavaScriptReceiver:1106 => return this.javaReceiverDelegate.visitRightPadded(right, q) -{"state":"ADD","value":"7fa7b9a9-1825-493f-abe5-e9888ebced4e"} - JavaScriptSender:510 => return this.javaSender.visitRightPadded(right, q); - undefined -{"state":"NO_CHANGE"} - JavaScriptSender:148 => await q.getAndSend(jsImportClause, el => el.namedBindings, el => this.visit(el, q)); - JavaScriptReceiver:692 => draft.namedBindings = await q.receive(draft.namedBindings, el => this.visitDefined(el, q)); -{"state":"ADD","valueType":"org.openrewrite.java.tree.JLeftPadded"} - JavaScriptSender:139 => await q.getAndSend(jsImport, el => el.moduleSpecifier, el => this.visitLeftPadded(el, q)); - JavaScriptReceiver:682 => draft.moduleSpecifier = await q.receive(draft.moduleSpecifier, el => this.visitLeftPadded(el, q)); -{"state":"ADD","valueType":"org.openrewrite.java.tree.Space"} - JavaScriptSender:514 => return this.javaSender.visitLeftPadded(left, q); - JavaScriptReceiver:1110 => return this.javaReceiverDelegate.visitLeftPadded(left, q); -{"state":"ADD"} - JavaScriptSender:514 => return this.javaSender.visitLeftPadded(left, q); - undefined -{"state":"NO_CHANGE"} - JavaScriptSender:140 => await q.getAndSend(jsImport, el => el.attributes, el => this.visit(el, q)); - JavaScriptReceiver:683 => draft.attributes = await q.receive(draft.attributes, el => this.visitDefined(el, q)); -{"state":"NO_CHANGE"} - JavaScriptSender:141 => await q.getAndSend(jsImport, el => el.initializer, el => this.visitLeftPadded(el, q)); - JavaScriptReceiver:684 => draft.initializer = await q.receive(draft.initializer, el => this.visitLeftPadded(el, q)); -{"state":"CHANGE"} - JavaScriptSender:54 => await q.getAndSendList(cu, c => c.statements, stmt => stmt.element.id, stmt => this.visitRightPadded(stmt, q)); - undefined -{"state":"CHANGE"} - JavaScriptSender:510 => return this.javaSender.visitRightPadded(right, q); - JavaScriptReceiver:1106 => return this.javaReceiverDelegate.visitRightPadded(right, q) -{"state":"NO_CHANGE"} - JavaScriptSender:510 => return this.javaSender.visitRightPadded(right, q); - undefined -{"state":"ADD","value":"6c66a7bd-3424-426e-9462-443b62b98a01"} - JavaScriptSender:42 => await q.getAndSend(j, j2 => j2.id); - JavaScriptReceiver:567 => draft.id = await q.receive(j.id); -{"state":"ADD","valueType":"org.openrewrite.java.tree.Space"} - JavaScriptSender:43 => await q.getAndSend(j, j2 => j2.prefix, space => this.visitSpace(space, q)); - JavaScriptReceiver:568 => draft.prefix = await q.receive(j.prefix, space => this.visitSpace(space, q)); -{"state":"ADD"} - JavaScriptSender:522 => return this.javaSender.visitSpace(space, q); - JavaScriptReceiver:1118 => return this.javaReceiverDelegate.visitSpace(space, q); -{"state":"ADD","valueType":"org.openrewrite.marker.Markers"} - JavaScriptSender:44 => await q.getAndSend(j, j2 => j2.markers); - JavaScriptReceiver:569 => draft.markers = await q.receive(j.markers); -{"state":"ADD","value":"c80d2c98-f36a-4f09-aff9-fcada004e5d4"} - JavaScriptSender:44 => await q.getAndSend(j, j2 => j2.markers); - undefined -{"state":"ADD"} - JavaScriptSender:66 => await q.getAndSendList(arrowFunction, el => el.leadingAnnotations, el => el.id, el => this.visit(el, q)); - JavaScriptReceiver:597 => draft.leadingAnnotations = await q.receiveListDefined(draft.leadingAnnotations, el => this.visitDefined(el, q)); -{"state":"ADD"} - JavaScriptSender:67 => await q.getAndSendList(arrowFunction, el => el.modifiers, el => el.id, el => this.visit(el, q)); - JavaScriptReceiver:598 => draft.modifiers = await q.receiveListDefined(draft.modifiers, el => this.visitDefined(el, q)); -{"state":"NO_CHANGE"} - JavaScriptSender:68 => await q.getAndSend(arrowFunction, el => el.typeParameters, el => this.visit(el, q)); - JavaScriptReceiver:599 => draft.typeParameters = await q.receive(draft.typeParameters, el => this.visitDefined(el, q)); -{"state":"ADD","valueType":"org.openrewrite.java.tree.J$Lambda"} - JavaScriptSender:69 => await q.getAndSend(arrowFunction, el => el.lambda, el => this.visit(el, q)); - JavaScriptReceiver:600 => draft.lambda = await q.receive(draft.lambda, el => this.visitDefined(el, q)); -{"state":"ADD","value":"afa2a3d0-844a-4691-805f-c9348a45c9a8"} - JavaScriptSender:38 => return this.javaSender.visit(tree, p, parent); - JavaScriptReceiver:561 => return this.javaReceiverDelegate.visit(tree, p, parent); -{"state":"NO_CHANGE"} - JavaScriptSender:70 => await q.getAndSend(arrowFunction, el => el.returnTypeExpression, el => this.visit(el, q)); - JavaScriptReceiver:601 => draft.returnTypeExpression = await q.receive(draft.returnTypeExpression, el => this.visitDefined(el, q)); -{"state":"NO_CHANGE"} - JavaScriptSender:55 => await q.getAndSend(cu, c => c.eof, space => this.visitSpace(space, q)); - JavaScriptReceiver:583 => draft.eof = await q.receive(cu.eof, space => this.visitSpace(space, q)); diff --git a/rewrite-javascript/rewrite/server.txt b/rewrite-javascript/rewrite/server.txt deleted file mode 100644 index fd0c04f055..0000000000 --- a/rewrite-javascript/rewrite/server.txt +++ /dev/null @@ -1,744 +0,0 @@ -{"state":"ADD","value":"5f2eaede-135f-4ab4-bab6-3cb5e8e8d03e"} - JsonSender:25 => await q.getAndSend(j, j2 => j2.id); - JsonReceiver:102 => draft.id = await q.receive(j.id); -{"state":"ADD","valueType":"org.openrewrite.json.tree.Space","ref":0} - JsonSender:26 => await q.getAndSend(j, j2 => asRef(j2.prefix), - JsonReceiver:103 => draft.prefix = await q.receive(j.prefix, async space => await this.visitSpace(space, q)); -{"state":"ADD"} - JsonSender:79 => await q.getAndSendList(space, s => s.comments, c => c.text + c.suffix, async c => { - JsonReceiver:162 => draft.comments = await q.receiveListDefined(space.comments, async c => { -{"state":"ADD","value":""} - JsonSender:85 => await q.getAndSend(space, s => s.whitespace); - JsonReceiver:170 => draft.whitespace = await q.receive(space.whitespace); -{"state":"ADD","valueType":"org.openrewrite.marker.Markers","ref":1} - JsonSender:28 => await q.getAndSend(j, j2 => j2.markers); - JsonReceiver:104 => draft.markers = await q.receive(j.markers); -{"state":"ADD","value":"c80d2c98-f36a-4f09-aff9-fcada004e5d4"} - JsonSender:28 => await q.getAndSend(j, j2 => j2.markers); - undefined -{"state":"ADD","value":"package.json"} - JsonSender:33 => await q.getAndSend(document, d => d.sourcePath); - JsonReceiver:110 => draft.sourcePath = await q.receive(document.sourcePath); -{"state":"NO_CHANGE"} - JsonSender:34 => await q.getAndSend(document, d => d.charsetName); - JsonReceiver:111 => draft.charsetName = await q.receive(document.charsetName); -{"state":"NO_CHANGE"} - JsonSender:35 => await q.getAndSend(document, d => d.charsetBomMarked); - JsonReceiver:112 => draft.charsetBomMarked = await q.receive(document.charsetBomMarked); -{"state":"NO_CHANGE"} - JsonSender:36 => await q.getAndSend(document, d => d.checksum); - JsonReceiver:113 => draft.checksum = await q.receive(document.checksum); -{"state":"NO_CHANGE"} - JsonSender:37 => await q.getAndSend(document, d => d.fileAttributes); - JsonReceiver:114 => draft.fileAttributes = await q.receive(document.fileAttributes); -{"state":"ADD","valueType":"org.openrewrite.json.tree.Json$JsonObject"} - JsonSender:38 => await q.getAndSend(document, d => d.value, - JsonReceiver:115 => draft.value = await q.receive(document.value, async j => await this.visit(j, q)!); -{"state":"ADD","value":"57fc51b1-d8e8-4317-9ae3-2ea0ef1c9b1c"} - JsonSender:25 => await q.getAndSend(j, j2 => j2.id); - JsonReceiver:102 => draft.id = await q.receive(j.id); -{"state":"ADD","valueType":"org.openrewrite.json.tree.Space","ref":2} - JsonSender:26 => await q.getAndSend(j, j2 => asRef(j2.prefix), - JsonReceiver:103 => draft.prefix = await q.receive(j.prefix, async space => await this.visitSpace(space, q)); -{"state":"ADD"} - JsonSender:79 => await q.getAndSendList(space, s => s.comments, c => c.text + c.suffix, async c => { - JsonReceiver:162 => draft.comments = await q.receiveListDefined(space.comments, async c => { -{"state":"ADD","value":""} - JsonSender:85 => await q.getAndSend(space, s => s.whitespace); - JsonReceiver:170 => draft.whitespace = await q.receive(space.whitespace); -{"state":"ADD","ref":1} - JsonSender:28 => await q.getAndSend(j, j2 => j2.markers); - JsonReceiver:104 => draft.markers = await q.receive(j.markers); -{"state":"ADD"} - JsonSender:73 => await q.getAndSendList(obj, o => o.members, j => j.element.id, - JsonReceiver:155 => draft.members = await q.receiveListDefined(obj.members, -{"state":"ADD","valueType":"org.openrewrite.json.tree.RightPadded"} - JsonSender:73 => await q.getAndSendList(obj, o => o.members, j => j.element.id, - undefined -{"state":"ADD","valueType":"org.openrewrite.json.tree.Json$Member"} - JsonSender:90 => await q.getAndSend(right, r => r.element, j => this.visit(j, q)); - JsonReceiver:179 => draft.element = await p.receive(right.element, async j => await this.visit(j, p)!) as Draft; -{"state":"ADD","value":"86243acf-31a2-422c-a381-ae5f76765610"} - JsonSender:25 => await q.getAndSend(j, j2 => j2.id); - JsonReceiver:102 => draft.id = await q.receive(j.id); -{"state":"ADD","valueType":"org.openrewrite.json.tree.Space","ref":3} - JsonSender:26 => await q.getAndSend(j, j2 => asRef(j2.prefix), - JsonReceiver:103 => draft.prefix = await q.receive(j.prefix, async space => await this.visitSpace(space, q)); -{"state":"ADD"} - JsonSender:79 => await q.getAndSendList(space, s => s.comments, c => c.text + c.suffix, async c => { - JsonReceiver:162 => draft.comments = await q.receiveListDefined(space.comments, async c => { -{"state":"ADD","value":""} - JsonSender:85 => await q.getAndSend(space, s => s.whitespace); - JsonReceiver:170 => draft.whitespace = await q.receive(space.whitespace); -{"state":"ADD","ref":1} - JsonSender:28 => await q.getAndSend(j, j2 => j2.markers); - JsonReceiver:104 => draft.markers = await q.receive(j.markers); -{"state":"ADD","valueType":"org.openrewrite.json.tree.RightPadded"} - JsonSender:67 => await q.getAndSend(member, m => m.key, async j => await this.visitRightPadded(j, q)); - JsonReceiver:146 => draft.key = await q.receive(member.key, -{"state":"ADD","valueType":"org.openrewrite.json.tree.Json$Literal"} - JsonSender:90 => await q.getAndSend(right, r => r.element, j => this.visit(j, q)); - JsonReceiver:179 => draft.element = await p.receive(right.element, async j => await this.visit(j, p)!) as Draft; -{"state":"ADD","value":"e96fdf63-8d71-4eaa-96cb-23c65ee9bc8d"} - JsonSender:25 => await q.getAndSend(j, j2 => j2.id); - JsonReceiver:102 => draft.id = await q.receive(j.id); -{"state":"ADD","valueType":"org.openrewrite.json.tree.Space","ref":4} - JsonSender:26 => await q.getAndSend(j, j2 => asRef(j2.prefix), - JsonReceiver:103 => draft.prefix = await q.receive(j.prefix, async space => await this.visitSpace(space, q)); -{"state":"ADD"} - JsonSender:79 => await q.getAndSendList(space, s => s.comments, c => c.text + c.suffix, async c => { - JsonReceiver:162 => draft.comments = await q.receiveListDefined(space.comments, async c => { -{"state":"ADD","value":"\n "} - JsonSender:85 => await q.getAndSend(space, s => s.whitespace); - JsonReceiver:170 => draft.whitespace = await q.receive(space.whitespace); -{"state":"ADD","ref":1} - JsonSender:28 => await q.getAndSend(j, j2 => j2.markers); - JsonReceiver:104 => draft.markers = await q.receive(j.markers); -{"state":"ADD","value":"\"name\""} - JsonSender:61 => await q.getAndSend(literal, lit => lit.source); - JsonReceiver:139 => draft.source = await q.receive(literal.source); -{"state":"ADD","value":"name"} - JsonSender:62 => await q.getAndSend(literal, lit => lit.value); - JsonReceiver:140 => draft.value = await q.receive(literal.value); -{"state":"ADD","valueType":"org.openrewrite.json.tree.Space","ref":5} - JsonSender:91 => await q.getAndSend(right, r => asRef(r.after), async space => await this.visitSpace(space, q)); - JsonReceiver:180 => draft.after = await p.receive(right.after, async space => await this.visitSpace(space, p)); -{"state":"ADD"} - JsonSender:79 => await q.getAndSendList(space, s => s.comments, c => c.text + c.suffix, async c => { - JsonReceiver:162 => draft.comments = await q.receiveListDefined(space.comments, async c => { -{"state":"ADD","value":""} - JsonSender:85 => await q.getAndSend(space, s => s.whitespace); - JsonReceiver:170 => draft.whitespace = await q.receive(space.whitespace); -{"state":"ADD","ref":1} - JsonSender:92 => await q.getAndSend(right, r => r.markers); - JsonReceiver:181 => draft.markers = await p.receiveMarkers(right.markers); -{"state":"ADD","valueType":"org.openrewrite.json.tree.Json$Literal"} - JsonSender:68 => await q.getAndSend(member, m => m.value, async j => await this.visit(j, q)); - JsonReceiver:148 => draft.value = await q.receive(member.value, -{"state":"ADD","value":"d3391974-e787-498b-89ea-b18e36b03a25"} - JsonSender:25 => await q.getAndSend(j, j2 => j2.id); - JsonReceiver:102 => draft.id = await q.receive(j.id); -{"state":"ADD","valueType":"org.openrewrite.json.tree.Space","ref":6} - JsonSender:26 => await q.getAndSend(j, j2 => asRef(j2.prefix), - JsonReceiver:103 => draft.prefix = await q.receive(j.prefix, async space => await this.visitSpace(space, q)); -{"state":"ADD"} - JsonSender:79 => await q.getAndSendList(space, s => s.comments, c => c.text + c.suffix, async c => { - JsonReceiver:162 => draft.comments = await q.receiveListDefined(space.comments, async c => { -{"state":"ADD","value":" "} - JsonSender:85 => await q.getAndSend(space, s => s.whitespace); - JsonReceiver:170 => draft.whitespace = await q.receive(space.whitespace); -{"state":"ADD","ref":1} - JsonSender:28 => await q.getAndSend(j, j2 => j2.markers); - JsonReceiver:104 => draft.markers = await q.receive(j.markers); -{"state":"ADD","value":"\"test-project\""} - JsonSender:61 => await q.getAndSend(literal, lit => lit.source); - JsonReceiver:139 => draft.source = await q.receive(literal.source); -{"state":"ADD","value":"test-project"} - JsonSender:62 => await q.getAndSend(literal, lit => lit.value); - JsonReceiver:140 => draft.value = await q.receive(literal.value); -{"state":"ADD","valueType":"org.openrewrite.json.tree.Space","ref":7} - JsonSender:91 => await q.getAndSend(right, r => asRef(r.after), async space => await this.visitSpace(space, q)); - JsonReceiver:180 => draft.after = await p.receive(right.after, async space => await this.visitSpace(space, p)); -{"state":"ADD"} - JsonSender:79 => await q.getAndSendList(space, s => s.comments, c => c.text + c.suffix, async c => { - JsonReceiver:162 => draft.comments = await q.receiveListDefined(space.comments, async c => { -{"state":"ADD","value":""} - JsonSender:85 => await q.getAndSend(space, s => s.whitespace); - JsonReceiver:170 => draft.whitespace = await q.receive(space.whitespace); -{"state":"ADD","ref":1} - JsonSender:92 => await q.getAndSend(right, r => r.markers); - JsonReceiver:181 => draft.markers = await p.receiveMarkers(right.markers); -{"state":"ADD","valueType":"org.openrewrite.json.tree.RightPadded"} - JsonSender:73 => await q.getAndSendList(obj, o => o.members, j => j.element.id, - undefined -{"state":"ADD","valueType":"org.openrewrite.json.tree.Json$Member"} - JsonSender:90 => await q.getAndSend(right, r => r.element, j => this.visit(j, q)); - JsonReceiver:179 => draft.element = await p.receive(right.element, async j => await this.visit(j, p)!) as Draft; -{"state":"ADD","value":"39b72eaf-336a-447d-a8f2-d50f3180c838"} - JsonSender:25 => await q.getAndSend(j, j2 => j2.id); - JsonReceiver:102 => draft.id = await q.receive(j.id); -{"state":"ADD","ref":3} - JsonSender:26 => await q.getAndSend(j, j2 => asRef(j2.prefix), - JsonReceiver:103 => draft.prefix = await q.receive(j.prefix, async space => await this.visitSpace(space, q)); -{"state":"ADD","ref":1} - JsonSender:28 => await q.getAndSend(j, j2 => j2.markers); - JsonReceiver:104 => draft.markers = await q.receive(j.markers); -{"state":"ADD","valueType":"org.openrewrite.json.tree.RightPadded"} - JsonSender:67 => await q.getAndSend(member, m => m.key, async j => await this.visitRightPadded(j, q)); - JsonReceiver:146 => draft.key = await q.receive(member.key, -{"state":"ADD","valueType":"org.openrewrite.json.tree.Json$Literal"} - JsonSender:90 => await q.getAndSend(right, r => r.element, j => this.visit(j, q)); - JsonReceiver:179 => draft.element = await p.receive(right.element, async j => await this.visit(j, p)!) as Draft; -{"state":"ADD","value":"45af3471-03f3-4557-88f4-79b82b6e1914"} - JsonSender:25 => await q.getAndSend(j, j2 => j2.id); - JsonReceiver:102 => draft.id = await q.receive(j.id); -{"state":"ADD","valueType":"org.openrewrite.json.tree.Space","ref":8} - JsonSender:26 => await q.getAndSend(j, j2 => asRef(j2.prefix), - JsonReceiver:103 => draft.prefix = await q.receive(j.prefix, async space => await this.visitSpace(space, q)); -{"state":"ADD"} - JsonSender:79 => await q.getAndSendList(space, s => s.comments, c => c.text + c.suffix, async c => { - JsonReceiver:162 => draft.comments = await q.receiveListDefined(space.comments, async c => { -{"state":"ADD","value":"\n "} - JsonSender:85 => await q.getAndSend(space, s => s.whitespace); - JsonReceiver:170 => draft.whitespace = await q.receive(space.whitespace); -{"state":"ADD","ref":1} - JsonSender:28 => await q.getAndSend(j, j2 => j2.markers); - JsonReceiver:104 => draft.markers = await q.receive(j.markers); -{"state":"ADD","value":"\"version\""} - JsonSender:61 => await q.getAndSend(literal, lit => lit.source); - JsonReceiver:139 => draft.source = await q.receive(literal.source); -{"state":"ADD","value":"version"} - JsonSender:62 => await q.getAndSend(literal, lit => lit.value); - JsonReceiver:140 => draft.value = await q.receive(literal.value); -{"state":"ADD","valueType":"org.openrewrite.json.tree.Space","ref":9} - JsonSender:91 => await q.getAndSend(right, r => asRef(r.after), async space => await this.visitSpace(space, q)); - JsonReceiver:180 => draft.after = await p.receive(right.after, async space => await this.visitSpace(space, p)); -{"state":"ADD"} - JsonSender:79 => await q.getAndSendList(space, s => s.comments, c => c.text + c.suffix, async c => { - JsonReceiver:162 => draft.comments = await q.receiveListDefined(space.comments, async c => { -{"state":"ADD","value":""} - JsonSender:85 => await q.getAndSend(space, s => s.whitespace); - JsonReceiver:170 => draft.whitespace = await q.receive(space.whitespace); -{"state":"ADD","ref":1} - JsonSender:92 => await q.getAndSend(right, r => r.markers); - JsonReceiver:181 => draft.markers = await p.receiveMarkers(right.markers); -{"state":"ADD","valueType":"org.openrewrite.json.tree.Json$Literal"} - JsonSender:68 => await q.getAndSend(member, m => m.value, async j => await this.visit(j, q)); - JsonReceiver:148 => draft.value = await q.receive(member.value, -{"state":"ADD","value":"f2c6f962-7408-44b0-8082-cf2784b5b540"} - JsonSender:25 => await q.getAndSend(j, j2 => j2.id); - JsonReceiver:102 => draft.id = await q.receive(j.id); -{"state":"ADD","valueType":"org.openrewrite.json.tree.Space","ref":10} - JsonSender:26 => await q.getAndSend(j, j2 => asRef(j2.prefix), - JsonReceiver:103 => draft.prefix = await q.receive(j.prefix, async space => await this.visitSpace(space, q)); -{"state":"ADD"} - JsonSender:79 => await q.getAndSendList(space, s => s.comments, c => c.text + c.suffix, async c => { - JsonReceiver:162 => draft.comments = await q.receiveListDefined(space.comments, async c => { -{"state":"ADD","value":" "} - JsonSender:85 => await q.getAndSend(space, s => s.whitespace); - JsonReceiver:170 => draft.whitespace = await q.receive(space.whitespace); -{"state":"ADD","ref":1} - JsonSender:28 => await q.getAndSend(j, j2 => j2.markers); - JsonReceiver:104 => draft.markers = await q.receive(j.markers); -{"state":"ADD","value":"\"1.0.0\""} - JsonSender:61 => await q.getAndSend(literal, lit => lit.source); - JsonReceiver:139 => draft.source = await q.receive(literal.source); -{"state":"ADD","value":"1.0.0"} - JsonSender:62 => await q.getAndSend(literal, lit => lit.value); - JsonReceiver:140 => draft.value = await q.receive(literal.value); -{"state":"ADD","valueType":"org.openrewrite.json.tree.Space","ref":11} - JsonSender:91 => await q.getAndSend(right, r => asRef(r.after), async space => await this.visitSpace(space, q)); - JsonReceiver:180 => draft.after = await p.receive(right.after, async space => await this.visitSpace(space, p)); -{"state":"ADD"} - JsonSender:79 => await q.getAndSendList(space, s => s.comments, c => c.text + c.suffix, async c => { - JsonReceiver:162 => draft.comments = await q.receiveListDefined(space.comments, async c => { -{"state":"ADD","value":""} - JsonSender:85 => await q.getAndSend(space, s => s.whitespace); - JsonReceiver:170 => draft.whitespace = await q.receive(space.whitespace); -{"state":"ADD","ref":1} - JsonSender:92 => await q.getAndSend(right, r => r.markers); - JsonReceiver:181 => draft.markers = await p.receiveMarkers(right.markers); -{"state":"ADD","valueType":"org.openrewrite.json.tree.RightPadded"} - JsonSender:73 => await q.getAndSendList(obj, o => o.members, j => j.element.id, - undefined -{"state":"ADD","valueType":"org.openrewrite.json.tree.Json$Member"} - JsonSender:90 => await q.getAndSend(right, r => r.element, j => this.visit(j, q)); - JsonReceiver:179 => draft.element = await p.receive(right.element, async j => await this.visit(j, p)!) as Draft; -{"state":"ADD","value":"3073e8a3-0590-4600-945c-f1b783654496"} - JsonSender:25 => await q.getAndSend(j, j2 => j2.id); - JsonReceiver:102 => draft.id = await q.receive(j.id); -{"state":"ADD","ref":3} - JsonSender:26 => await q.getAndSend(j, j2 => asRef(j2.prefix), - JsonReceiver:103 => draft.prefix = await q.receive(j.prefix, async space => await this.visitSpace(space, q)); -{"state":"ADD","ref":1} - JsonSender:28 => await q.getAndSend(j, j2 => j2.markers); - JsonReceiver:104 => draft.markers = await q.receive(j.markers); -{"state":"ADD","valueType":"org.openrewrite.json.tree.RightPadded"} - JsonSender:67 => await q.getAndSend(member, m => m.key, async j => await this.visitRightPadded(j, q)); - JsonReceiver:146 => draft.key = await q.receive(member.key, -{"state":"ADD","valueType":"org.openrewrite.json.tree.Json$Literal"} - JsonSender:90 => await q.getAndSend(right, r => r.element, j => this.visit(j, q)); - JsonReceiver:179 => draft.element = await p.receive(right.element, async j => await this.visit(j, p)!) as Draft; -{"state":"ADD","value":"258d9339-f02e-49a5-a04b-d920f2de6522"} - JsonSender:25 => await q.getAndSend(j, j2 => j2.id); - JsonReceiver:102 => draft.id = await q.receive(j.id); -{"state":"ADD","valueType":"org.openrewrite.json.tree.Space","ref":12} - JsonSender:26 => await q.getAndSend(j, j2 => asRef(j2.prefix), - JsonReceiver:103 => draft.prefix = await q.receive(j.prefix, async space => await this.visitSpace(space, q)); -{"state":"ADD"} - JsonSender:79 => await q.getAndSendList(space, s => s.comments, c => c.text + c.suffix, async c => { - JsonReceiver:162 => draft.comments = await q.receiveListDefined(space.comments, async c => { -{"state":"ADD","value":"\n "} - JsonSender:85 => await q.getAndSend(space, s => s.whitespace); - JsonReceiver:170 => draft.whitespace = await q.receive(space.whitespace); -{"state":"ADD","ref":1} - JsonSender:28 => await q.getAndSend(j, j2 => j2.markers); - JsonReceiver:104 => draft.markers = await q.receive(j.markers); -{"state":"ADD","value":"\"dependencies\""} - JsonSender:61 => await q.getAndSend(literal, lit => lit.source); - JsonReceiver:139 => draft.source = await q.receive(literal.source); -{"state":"ADD","value":"dependencies"} - JsonSender:62 => await q.getAndSend(literal, lit => lit.value); - JsonReceiver:140 => draft.value = await q.receive(literal.value); -{"state":"ADD","valueType":"org.openrewrite.json.tree.Space","ref":13} - JsonSender:91 => await q.getAndSend(right, r => asRef(r.after), async space => await this.visitSpace(space, q)); - JsonReceiver:180 => draft.after = await p.receive(right.after, async space => await this.visitSpace(space, p)); -{"state":"ADD"} - JsonSender:79 => await q.getAndSendList(space, s => s.comments, c => c.text + c.suffix, async c => { - JsonReceiver:162 => draft.comments = await q.receiveListDefined(space.comments, async c => { -{"state":"ADD","value":""} - JsonSender:85 => await q.getAndSend(space, s => s.whitespace); - JsonReceiver:170 => draft.whitespace = await q.receive(space.whitespace); -{"state":"ADD","ref":1} - JsonSender:92 => await q.getAndSend(right, r => r.markers); - JsonReceiver:181 => draft.markers = await p.receiveMarkers(right.markers); -{"state":"ADD","valueType":"org.openrewrite.json.tree.Json$JsonObject"} - JsonSender:68 => await q.getAndSend(member, m => m.value, async j => await this.visit(j, q)); - JsonReceiver:148 => draft.value = await q.receive(member.value, -{"state":"ADD","value":"6710bf6f-ae24-4f91-a8a6-16c9c1f436a6"} - JsonSender:25 => await q.getAndSend(j, j2 => j2.id); - JsonReceiver:102 => draft.id = await q.receive(j.id); -{"state":"ADD","valueType":"org.openrewrite.json.tree.Space","ref":14} - JsonSender:26 => await q.getAndSend(j, j2 => asRef(j2.prefix), - JsonReceiver:103 => draft.prefix = await q.receive(j.prefix, async space => await this.visitSpace(space, q)); -{"state":"ADD"} - JsonSender:79 => await q.getAndSendList(space, s => s.comments, c => c.text + c.suffix, async c => { - JsonReceiver:162 => draft.comments = await q.receiveListDefined(space.comments, async c => { -{"state":"ADD","value":" "} - JsonSender:85 => await q.getAndSend(space, s => s.whitespace); - JsonReceiver:170 => draft.whitespace = await q.receive(space.whitespace); -{"state":"ADD","ref":1} - JsonSender:28 => await q.getAndSend(j, j2 => j2.markers); - JsonReceiver:104 => draft.markers = await q.receive(j.markers); -{"state":"ADD"} - JsonSender:73 => await q.getAndSendList(obj, o => o.members, j => j.element.id, - JsonReceiver:155 => draft.members = await q.receiveListDefined(obj.members, -{"state":"ADD","valueType":"org.openrewrite.json.tree.RightPadded"} - JsonSender:73 => await q.getAndSendList(obj, o => o.members, j => j.element.id, - undefined -{"state":"ADD","valueType":"org.openrewrite.json.tree.Json$Member"} - JsonSender:90 => await q.getAndSend(right, r => r.element, j => this.visit(j, q)); - JsonReceiver:179 => draft.element = await p.receive(right.element, async j => await this.visit(j, p)!) as Draft; -{"state":"ADD","value":"d4ba7eb8-932a-4a92-ad98-f458eb866be9"} - JsonSender:25 => await q.getAndSend(j, j2 => j2.id); - JsonReceiver:102 => draft.id = await q.receive(j.id); -{"state":"ADD","ref":3} - JsonSender:26 => await q.getAndSend(j, j2 => asRef(j2.prefix), - JsonReceiver:103 => draft.prefix = await q.receive(j.prefix, async space => await this.visitSpace(space, q)); -{"state":"ADD","ref":1} - JsonSender:28 => await q.getAndSend(j, j2 => j2.markers); - JsonReceiver:104 => draft.markers = await q.receive(j.markers); -{"state":"ADD","valueType":"org.openrewrite.json.tree.RightPadded"} - JsonSender:67 => await q.getAndSend(member, m => m.key, async j => await this.visitRightPadded(j, q)); - JsonReceiver:146 => draft.key = await q.receive(member.key, -{"state":"ADD","valueType":"org.openrewrite.json.tree.Json$Literal"} - JsonSender:90 => await q.getAndSend(right, r => r.element, j => this.visit(j, q)); - JsonReceiver:179 => draft.element = await p.receive(right.element, async j => await this.visit(j, p)!) as Draft; -{"state":"ADD","value":"ef15efb5-cffa-423e-8e93-a9833e9f4bfd"} - JsonSender:25 => await q.getAndSend(j, j2 => j2.id); - JsonReceiver:102 => draft.id = await q.receive(j.id); -{"state":"ADD","valueType":"org.openrewrite.json.tree.Space","ref":15} - JsonSender:26 => await q.getAndSend(j, j2 => asRef(j2.prefix), - JsonReceiver:103 => draft.prefix = await q.receive(j.prefix, async space => await this.visitSpace(space, q)); -{"state":"ADD"} - JsonSender:79 => await q.getAndSendList(space, s => s.comments, c => c.text + c.suffix, async c => { - JsonReceiver:162 => draft.comments = await q.receiveListDefined(space.comments, async c => { -{"state":"ADD","value":"\n "} - JsonSender:85 => await q.getAndSend(space, s => s.whitespace); - JsonReceiver:170 => draft.whitespace = await q.receive(space.whitespace); -{"state":"ADD","ref":1} - JsonSender:28 => await q.getAndSend(j, j2 => j2.markers); - JsonReceiver:104 => draft.markers = await q.receive(j.markers); -{"state":"ADD","value":"\"lodash\""} - JsonSender:61 => await q.getAndSend(literal, lit => lit.source); - JsonReceiver:139 => draft.source = await q.receive(literal.source); -{"state":"ADD","value":"lodash"} - JsonSender:62 => await q.getAndSend(literal, lit => lit.value); - JsonReceiver:140 => draft.value = await q.receive(literal.value); -{"state":"ADD","valueType":"org.openrewrite.json.tree.Space","ref":16} - JsonSender:91 => await q.getAndSend(right, r => asRef(r.after), async space => await this.visitSpace(space, q)); - JsonReceiver:180 => draft.after = await p.receive(right.after, async space => await this.visitSpace(space, p)); -{"state":"ADD"} - JsonSender:79 => await q.getAndSendList(space, s => s.comments, c => c.text + c.suffix, async c => { - JsonReceiver:162 => draft.comments = await q.receiveListDefined(space.comments, async c => { -{"state":"ADD","value":""} - JsonSender:85 => await q.getAndSend(space, s => s.whitespace); - JsonReceiver:170 => draft.whitespace = await q.receive(space.whitespace); -{"state":"ADD","ref":1} - JsonSender:92 => await q.getAndSend(right, r => r.markers); - JsonReceiver:181 => draft.markers = await p.receiveMarkers(right.markers); -{"state":"ADD","valueType":"org.openrewrite.json.tree.Json$Literal"} - JsonSender:68 => await q.getAndSend(member, m => m.value, async j => await this.visit(j, q)); - JsonReceiver:148 => draft.value = await q.receive(member.value, -{"state":"ADD","value":"d156576d-41cd-4f8b-acc3-0feb68985821"} - JsonSender:25 => await q.getAndSend(j, j2 => j2.id); - JsonReceiver:102 => draft.id = await q.receive(j.id); -{"state":"ADD","valueType":"org.openrewrite.json.tree.Space","ref":17} - JsonSender:26 => await q.getAndSend(j, j2 => asRef(j2.prefix), - JsonReceiver:103 => draft.prefix = await q.receive(j.prefix, async space => await this.visitSpace(space, q)); -{"state":"ADD"} - JsonSender:79 => await q.getAndSendList(space, s => s.comments, c => c.text + c.suffix, async c => { - JsonReceiver:162 => draft.comments = await q.receiveListDefined(space.comments, async c => { -{"state":"ADD","value":" "} - JsonSender:85 => await q.getAndSend(space, s => s.whitespace); - JsonReceiver:170 => draft.whitespace = await q.receive(space.whitespace); -{"state":"ADD","ref":1} - JsonSender:28 => await q.getAndSend(j, j2 => j2.markers); - JsonReceiver:104 => draft.markers = await q.receive(j.markers); -{"state":"ADD","value":"\"^4.17.21\""} - JsonSender:61 => await q.getAndSend(literal, lit => lit.source); - JsonReceiver:139 => draft.source = await q.receive(literal.source); -{"state":"ADD","value":"^4.17.21"} - JsonSender:62 => await q.getAndSend(literal, lit => lit.value); - JsonReceiver:140 => draft.value = await q.receive(literal.value); -{"state":"ADD","valueType":"org.openrewrite.json.tree.Space","ref":18} - JsonSender:91 => await q.getAndSend(right, r => asRef(r.after), async space => await this.visitSpace(space, q)); - JsonReceiver:180 => draft.after = await p.receive(right.after, async space => await this.visitSpace(space, p)); -{"state":"ADD"} - JsonSender:79 => await q.getAndSendList(space, s => s.comments, c => c.text + c.suffix, async c => { - JsonReceiver:162 => draft.comments = await q.receiveListDefined(space.comments, async c => { -{"state":"ADD","value":"\n "} - JsonSender:85 => await q.getAndSend(space, s => s.whitespace); - JsonReceiver:170 => draft.whitespace = await q.receive(space.whitespace); -{"state":"ADD","ref":1} - JsonSender:92 => await q.getAndSend(right, r => r.markers); - JsonReceiver:181 => draft.markers = await p.receiveMarkers(right.markers); -{"state":"ADD","valueType":"org.openrewrite.json.tree.Space","ref":19} - JsonSender:91 => await q.getAndSend(right, r => asRef(r.after), async space => await this.visitSpace(space, q)); - JsonReceiver:180 => draft.after = await p.receive(right.after, async space => await this.visitSpace(space, p)); -{"state":"ADD"} - JsonSender:79 => await q.getAndSendList(space, s => s.comments, c => c.text + c.suffix, async c => { - JsonReceiver:162 => draft.comments = await q.receiveListDefined(space.comments, async c => { -{"state":"ADD","value":""} - JsonSender:85 => await q.getAndSend(space, s => s.whitespace); - JsonReceiver:170 => draft.whitespace = await q.receive(space.whitespace); -{"state":"ADD","ref":1} - JsonSender:92 => await q.getAndSend(right, r => r.markers); - JsonReceiver:181 => draft.markers = await p.receiveMarkers(right.markers); -{"state":"ADD","valueType":"org.openrewrite.json.tree.RightPadded"} - JsonSender:73 => await q.getAndSendList(obj, o => o.members, j => j.element.id, - undefined -{"state":"ADD","valueType":"org.openrewrite.json.tree.Json$Member"} - JsonSender:90 => await q.getAndSend(right, r => r.element, j => this.visit(j, q)); - JsonReceiver:179 => draft.element = await p.receive(right.element, async j => await this.visit(j, p)!) as Draft; -{"state":"ADD","value":"f371ec73-f998-462f-adaf-23c3b4c64939"} - JsonSender:25 => await q.getAndSend(j, j2 => j2.id); - JsonReceiver:102 => draft.id = await q.receive(j.id); -{"state":"ADD","ref":3} - JsonSender:26 => await q.getAndSend(j, j2 => asRef(j2.prefix), - JsonReceiver:103 => draft.prefix = await q.receive(j.prefix, async space => await this.visitSpace(space, q)); -{"state":"ADD","ref":1} - JsonSender:28 => await q.getAndSend(j, j2 => j2.markers); - JsonReceiver:104 => draft.markers = await q.receive(j.markers); -{"state":"ADD","valueType":"org.openrewrite.json.tree.RightPadded"} - JsonSender:67 => await q.getAndSend(member, m => m.key, async j => await this.visitRightPadded(j, q)); - JsonReceiver:146 => draft.key = await q.receive(member.key, -{"state":"ADD","valueType":"org.openrewrite.json.tree.Json$Literal"} - JsonSender:90 => await q.getAndSend(right, r => r.element, j => this.visit(j, q)); - JsonReceiver:179 => draft.element = await p.receive(right.element, async j => await this.visit(j, p)!) as Draft; -{"state":"ADD","value":"efff142d-5ee2-4da8-b9dc-bee6d97b920a"} - JsonSender:25 => await q.getAndSend(j, j2 => j2.id); - JsonReceiver:102 => draft.id = await q.receive(j.id); -{"state":"ADD","valueType":"org.openrewrite.json.tree.Space","ref":20} - JsonSender:26 => await q.getAndSend(j, j2 => asRef(j2.prefix), - JsonReceiver:103 => draft.prefix = await q.receive(j.prefix, async space => await this.visitSpace(space, q)); -{"state":"ADD"} - JsonSender:79 => await q.getAndSendList(space, s => s.comments, c => c.text + c.suffix, async c => { - JsonReceiver:162 => draft.comments = await q.receiveListDefined(space.comments, async c => { -{"state":"ADD","value":"\n "} - JsonSender:85 => await q.getAndSend(space, s => s.whitespace); - JsonReceiver:170 => draft.whitespace = await q.receive(space.whitespace); -{"state":"ADD","ref":1} - JsonSender:28 => await q.getAndSend(j, j2 => j2.markers); - JsonReceiver:104 => draft.markers = await q.receive(j.markers); -{"state":"ADD","value":"\"devDependencies\""} - JsonSender:61 => await q.getAndSend(literal, lit => lit.source); - JsonReceiver:139 => draft.source = await q.receive(literal.source); -{"state":"ADD","value":"devDependencies"} - JsonSender:62 => await q.getAndSend(literal, lit => lit.value); - JsonReceiver:140 => draft.value = await q.receive(literal.value); -{"state":"ADD","valueType":"org.openrewrite.json.tree.Space","ref":21} - JsonSender:91 => await q.getAndSend(right, r => asRef(r.after), async space => await this.visitSpace(space, q)); - JsonReceiver:180 => draft.after = await p.receive(right.after, async space => await this.visitSpace(space, p)); -{"state":"ADD"} - JsonSender:79 => await q.getAndSendList(space, s => s.comments, c => c.text + c.suffix, async c => { - JsonReceiver:162 => draft.comments = await q.receiveListDefined(space.comments, async c => { -{"state":"ADD","value":""} - JsonSender:85 => await q.getAndSend(space, s => s.whitespace); - JsonReceiver:170 => draft.whitespace = await q.receive(space.whitespace); -{"state":"ADD","ref":1} - JsonSender:92 => await q.getAndSend(right, r => r.markers); - JsonReceiver:181 => draft.markers = await p.receiveMarkers(right.markers); -{"state":"ADD","valueType":"org.openrewrite.json.tree.Json$JsonObject"} - JsonSender:68 => await q.getAndSend(member, m => m.value, async j => await this.visit(j, q)); - JsonReceiver:148 => draft.value = await q.receive(member.value, -{"state":"ADD","value":"2055fbf9-9873-493c-9f9b-5a7bcff45762"} - JsonSender:25 => await q.getAndSend(j, j2 => j2.id); - JsonReceiver:102 => draft.id = await q.receive(j.id); -{"state":"ADD","valueType":"org.openrewrite.json.tree.Space","ref":22} - JsonSender:26 => await q.getAndSend(j, j2 => asRef(j2.prefix), - JsonReceiver:103 => draft.prefix = await q.receive(j.prefix, async space => await this.visitSpace(space, q)); -{"state":"ADD"} - JsonSender:79 => await q.getAndSendList(space, s => s.comments, c => c.text + c.suffix, async c => { - JsonReceiver:162 => draft.comments = await q.receiveListDefined(space.comments, async c => { -{"state":"ADD","value":" "} - JsonSender:85 => await q.getAndSend(space, s => s.whitespace); - JsonReceiver:170 => draft.whitespace = await q.receive(space.whitespace); -{"state":"ADD","ref":1} - JsonSender:28 => await q.getAndSend(j, j2 => j2.markers); - JsonReceiver:104 => draft.markers = await q.receive(j.markers); -{"state":"ADD"} - JsonSender:73 => await q.getAndSendList(obj, o => o.members, j => j.element.id, - JsonReceiver:155 => draft.members = await q.receiveListDefined(obj.members, -{"state":"ADD","valueType":"org.openrewrite.json.tree.RightPadded"} - JsonSender:73 => await q.getAndSendList(obj, o => o.members, j => j.element.id, - undefined -{"state":"ADD","valueType":"org.openrewrite.json.tree.Json$Member"} - JsonSender:90 => await q.getAndSend(right, r => r.element, j => this.visit(j, q)); - JsonReceiver:179 => draft.element = await p.receive(right.element, async j => await this.visit(j, p)!) as Draft; -{"state":"ADD","value":"1c7cdf63-d593-44da-9a4e-ac3ac01e344d"} - JsonSender:25 => await q.getAndSend(j, j2 => j2.id); - JsonReceiver:102 => draft.id = await q.receive(j.id); -{"state":"ADD","ref":3} - JsonSender:26 => await q.getAndSend(j, j2 => asRef(j2.prefix), - JsonReceiver:103 => draft.prefix = await q.receive(j.prefix, async space => await this.visitSpace(space, q)); -{"state":"ADD","ref":1} - JsonSender:28 => await q.getAndSend(j, j2 => j2.markers); - JsonReceiver:104 => draft.markers = await q.receive(j.markers); -{"state":"ADD","valueType":"org.openrewrite.json.tree.RightPadded"} - JsonSender:67 => await q.getAndSend(member, m => m.key, async j => await this.visitRightPadded(j, q)); - JsonReceiver:146 => draft.key = await q.receive(member.key, -{"state":"ADD","valueType":"org.openrewrite.json.tree.Json$Literal"} - JsonSender:90 => await q.getAndSend(right, r => r.element, j => this.visit(j, q)); - JsonReceiver:179 => draft.element = await p.receive(right.element, async j => await this.visit(j, p)!) as Draft; -{"state":"ADD","value":"79c2ed6c-0201-49e5-bef3-91945bf2ff48"} - JsonSender:25 => await q.getAndSend(j, j2 => j2.id); - JsonReceiver:102 => draft.id = await q.receive(j.id); -{"state":"ADD","valueType":"org.openrewrite.json.tree.Space","ref":23} - JsonSender:26 => await q.getAndSend(j, j2 => asRef(j2.prefix), - JsonReceiver:103 => draft.prefix = await q.receive(j.prefix, async space => await this.visitSpace(space, q)); -{"state":"ADD"} - JsonSender:79 => await q.getAndSendList(space, s => s.comments, c => c.text + c.suffix, async c => { - JsonReceiver:162 => draft.comments = await q.receiveListDefined(space.comments, async c => { -{"state":"ADD","value":"\n "} - JsonSender:85 => await q.getAndSend(space, s => s.whitespace); - JsonReceiver:170 => draft.whitespace = await q.receive(space.whitespace); -{"state":"ADD","ref":1} - JsonSender:28 => await q.getAndSend(j, j2 => j2.markers); - JsonReceiver:104 => draft.markers = await q.receive(j.markers); -{"state":"ADD","value":"\"@types/lodash\""} - JsonSender:61 => await q.getAndSend(literal, lit => lit.source); - JsonReceiver:139 => draft.source = await q.receive(literal.source); -{"state":"ADD","value":"@types/lodash"} - JsonSender:62 => await q.getAndSend(literal, lit => lit.value); - JsonReceiver:140 => draft.value = await q.receive(literal.value); -{"state":"ADD","valueType":"org.openrewrite.json.tree.Space","ref":24} - JsonSender:91 => await q.getAndSend(right, r => asRef(r.after), async space => await this.visitSpace(space, q)); - JsonReceiver:180 => draft.after = await p.receive(right.after, async space => await this.visitSpace(space, p)); -{"state":"ADD"} - JsonSender:79 => await q.getAndSendList(space, s => s.comments, c => c.text + c.suffix, async c => { - JsonReceiver:162 => draft.comments = await q.receiveListDefined(space.comments, async c => { -{"state":"ADD","value":""} - JsonSender:85 => await q.getAndSend(space, s => s.whitespace); - JsonReceiver:170 => draft.whitespace = await q.receive(space.whitespace); -{"state":"ADD","ref":1} - JsonSender:92 => await q.getAndSend(right, r => r.markers); - JsonReceiver:181 => draft.markers = await p.receiveMarkers(right.markers); -{"state":"ADD","valueType":"org.openrewrite.json.tree.Json$Literal"} - JsonSender:68 => await q.getAndSend(member, m => m.value, async j => await this.visit(j, q)); - JsonReceiver:148 => draft.value = await q.receive(member.value, -{"state":"ADD","value":"cbb5231a-06a8-4a6f-ada0-243163f63ecf"} - JsonSender:25 => await q.getAndSend(j, j2 => j2.id); - JsonReceiver:102 => draft.id = await q.receive(j.id); -{"state":"ADD","valueType":"org.openrewrite.json.tree.Space","ref":25} - JsonSender:26 => await q.getAndSend(j, j2 => asRef(j2.prefix), - JsonReceiver:103 => draft.prefix = await q.receive(j.prefix, async space => await this.visitSpace(space, q)); -{"state":"ADD"} - JsonSender:79 => await q.getAndSendList(space, s => s.comments, c => c.text + c.suffix, async c => { - JsonReceiver:162 => draft.comments = await q.receiveListDefined(space.comments, async c => { -{"state":"ADD","value":" "} - JsonSender:85 => await q.getAndSend(space, s => s.whitespace); - JsonReceiver:170 => draft.whitespace = await q.receive(space.whitespace); -{"state":"ADD","ref":1} - JsonSender:28 => await q.getAndSend(j, j2 => j2.markers); - JsonReceiver:104 => draft.markers = await q.receive(j.markers); -{"state":"ADD","value":"\"^4.14.195\""} - JsonSender:61 => await q.getAndSend(literal, lit => lit.source); - JsonReceiver:139 => draft.source = await q.receive(literal.source); -{"state":"ADD","value":"^4.14.195"} - JsonSender:62 => await q.getAndSend(literal, lit => lit.value); - JsonReceiver:140 => draft.value = await q.receive(literal.value); -{"state":"ADD","valueType":"org.openrewrite.json.tree.Space","ref":26} - JsonSender:91 => await q.getAndSend(right, r => asRef(r.after), async space => await this.visitSpace(space, q)); - JsonReceiver:180 => draft.after = await p.receive(right.after, async space => await this.visitSpace(space, p)); -{"state":"ADD"} - JsonSender:79 => await q.getAndSendList(space, s => s.comments, c => c.text + c.suffix, async c => { - JsonReceiver:162 => draft.comments = await q.receiveListDefined(space.comments, async c => { -{"state":"ADD","value":"\n "} - JsonSender:85 => await q.getAndSend(space, s => s.whitespace); - JsonReceiver:170 => draft.whitespace = await q.receive(space.whitespace); -{"state":"ADD","ref":1} - JsonSender:92 => await q.getAndSend(right, r => r.markers); - JsonReceiver:181 => draft.markers = await p.receiveMarkers(right.markers); -{"state":"ADD","valueType":"org.openrewrite.json.tree.Space","ref":27} - JsonSender:91 => await q.getAndSend(right, r => asRef(r.after), async space => await this.visitSpace(space, q)); - JsonReceiver:180 => draft.after = await p.receive(right.after, async space => await this.visitSpace(space, p)); -{"state":"ADD"} - JsonSender:79 => await q.getAndSendList(space, s => s.comments, c => c.text + c.suffix, async c => { - JsonReceiver:162 => draft.comments = await q.receiveListDefined(space.comments, async c => { -{"state":"ADD","value":"\n"} - JsonSender:85 => await q.getAndSend(space, s => s.whitespace); - JsonReceiver:170 => draft.whitespace = await q.receive(space.whitespace); -{"state":"ADD","ref":1} - JsonSender:92 => await q.getAndSend(right, r => r.markers); - JsonReceiver:181 => draft.markers = await p.receiveMarkers(right.markers); -{"state":"ADD","valueType":"org.openrewrite.json.tree.Space","ref":28} - JsonSender:40 => await q.getAndSend(document, d => asRef(d.eof), - JsonReceiver:116 => draft.eof = await q.receive(document.eof, async space => await this.visitSpace(space, q)); -{"state":"ADD"} - JsonSender:79 => await q.getAndSendList(space, s => s.comments, c => c.text + c.suffix, async c => { - JsonReceiver:162 => draft.comments = await q.receiveListDefined(space.comments, async c => { -{"state":"ADD","value":""} - JsonSender:85 => await q.getAndSend(space, s => s.whitespace); - JsonReceiver:170 => draft.whitespace = await q.receive(space.whitespace); -{"state":"ADD","value":"2410ff3b-d200-48f4-a923-91a12602bb00"} - JavaScriptSender:42 => await q.getAndSend(j, j2 => j2.id); - JavaScriptReceiver:567 => draft.id = await q.receive(j.id); -{"state":"ADD","valueType":"org.openrewrite.java.tree.Space"} - JavaScriptSender:43 => await q.getAndSend(j, j2 => j2.prefix, space => this.visitSpace(space, q)); - JavaScriptReceiver:568 => draft.prefix = await q.receive(j.prefix, space => this.visitSpace(space, q)); -{"state":"ADD"} - JavaScriptSender:522 => return this.javaSender.visitSpace(space, q); - JavaScriptReceiver:1118 => return this.javaReceiverDelegate.visitSpace(space, q); -{"state":"ADD","ref":1} - JavaScriptSender:44 => await q.getAndSend(j, j2 => j2.markers); - JavaScriptReceiver:569 => draft.markers = await q.receive(j.markers); -{"state":"ADD","value":"680709506811822080.ts"} - JavaScriptSender:49 => await q.getAndSend(cu, c => c.sourcePath); - JavaScriptReceiver:577 => draft.sourcePath = await q.receive(cu.sourcePath); -{"state":"NO_CHANGE"} - JavaScriptSender:50 => await q.getAndSend(cu, c => c.charsetName); - JavaScriptReceiver:578 => draft.charsetName = await q.receive(cu.charsetName); -{"state":"ADD","value":false} - JavaScriptSender:51 => await q.getAndSend(cu, c => c.charsetBomMarked); - JavaScriptReceiver:579 => draft.charsetBomMarked = await q.receive(cu.charsetBomMarked); -{"state":"NO_CHANGE"} - JavaScriptSender:52 => await q.getAndSend(cu, c => c.checksum); - JavaScriptReceiver:580 => draft.checksum = await q.receive(cu.checksum); -{"state":"NO_CHANGE"} - JavaScriptSender:53 => await q.getAndSend(cu, c => c.fileAttributes); - JavaScriptReceiver:581 => draft.fileAttributes = await q.receive(cu.fileAttributes); -{"state":"ADD"} - JavaScriptSender:54 => await q.getAndSendList(cu, c => c.statements, stmt => stmt.element.id, stmt => this.visitRightPadded(stmt, q)); - JavaScriptReceiver:582 => draft.statements = await q.receiveListDefined(cu.statements, stmt => this.visitRightPadded(stmt, q)); -{"state":"ADD","valueType":"org.openrewrite.java.tree.JRightPadded"} - JavaScriptSender:54 => await q.getAndSendList(cu, c => c.statements, stmt => stmt.element.id, stmt => this.visitRightPadded(stmt, q)); - undefined -{"state":"ADD","valueType":"org.openrewrite.javascript.tree.JS$Import"} - JavaScriptSender:510 => return this.javaSender.visitRightPadded(right, q); - JavaScriptReceiver:1106 => return this.javaReceiverDelegate.visitRightPadded(right, q) -{"state":"ADD","value":"49502576-da0b-46ec-afae-c763626588c5"} - JavaScriptSender:42 => await q.getAndSend(j, j2 => j2.id); - JavaScriptReceiver:567 => draft.id = await q.receive(j.id); -{"state":"ADD","valueType":"org.openrewrite.java.tree.Space"} - JavaScriptSender:43 => await q.getAndSend(j, j2 => j2.prefix, space => this.visitSpace(space, q)); - JavaScriptReceiver:568 => draft.prefix = await q.receive(j.prefix, space => this.visitSpace(space, q)); -{"state":"ADD"} - JavaScriptSender:522 => return this.javaSender.visitSpace(space, q); - JavaScriptReceiver:1118 => return this.javaReceiverDelegate.visitSpace(space, q); -{"state":"ADD","ref":1} - JavaScriptSender:44 => await q.getAndSend(j, j2 => j2.markers); - JavaScriptReceiver:569 => draft.markers = await q.receive(j.markers); -{"state":"ADD"} - JavaScriptSender:137 => await q.getAndSendList(jsImport, el => el.modifiers, el => el.id, el => this.visit(el, q)); - JavaScriptReceiver:680 => draft.modifiers = await q.receiveListDefined(draft.modifiers, el => this.visitDefined(el, q)); -{"state":"ADD","valueType":"org.openrewrite.javascript.tree.JS$ImportClause"} - JavaScriptSender:138 => await q.getAndSend(jsImport, el => el.importClause, el => this.visit(el, q)); - JavaScriptReceiver:681 => draft.importClause = await q.receive(draft.importClause, el => this.visitDefined(el, q)); -{"state":"ADD","value":"3d34b6c1-5d85-47e5-bf94-e406dce1c7bd"} - JavaScriptSender:42 => await q.getAndSend(j, j2 => j2.id); - JavaScriptReceiver:567 => draft.id = await q.receive(j.id); -{"state":"ADD","valueType":"org.openrewrite.java.tree.Space"} - JavaScriptSender:43 => await q.getAndSend(j, j2 => j2.prefix, space => this.visitSpace(space, q)); - JavaScriptReceiver:568 => draft.prefix = await q.receive(j.prefix, space => this.visitSpace(space, q)); -{"state":"ADD"} - JavaScriptSender:522 => return this.javaSender.visitSpace(space, q); - JavaScriptReceiver:1118 => return this.javaReceiverDelegate.visitSpace(space, q); -{"state":"ADD","ref":1} - JavaScriptSender:44 => await q.getAndSend(j, j2 => j2.markers); - JavaScriptReceiver:569 => draft.markers = await q.receive(j.markers); -{"state":"ADD","value":false} - JavaScriptSender:146 => await q.getAndSend(jsImportClause, el => el.typeOnly); - JavaScriptReceiver:690 => draft.typeOnly = await q.receive(draft.typeOnly); -{"state":"ADD","valueType":"org.openrewrite.java.tree.JRightPadded"} - JavaScriptSender:147 => await q.getAndSend(jsImportClause, el => el.name, el => this.visitRightPadded(el, q)); - JavaScriptReceiver:691 => draft.name = await q.receive(draft.name, el => this.visitRightPadded(el, q)); -{"state":"ADD","valueType":"org.openrewrite.java.tree.J$Identifier"} - JavaScriptSender:510 => return this.javaSender.visitRightPadded(right, q); - JavaScriptReceiver:1106 => return this.javaReceiverDelegate.visitRightPadded(right, q) -{"state":"ADD","value":"7fa7b9a9-1825-493f-abe5-e9888ebced4e"} - JavaScriptSender:510 => return this.javaSender.visitRightPadded(right, q); - undefined -{"state":"NO_CHANGE"} - JavaScriptSender:148 => await q.getAndSend(jsImportClause, el => el.namedBindings, el => this.visit(el, q)); - JavaScriptReceiver:692 => draft.namedBindings = await q.receive(draft.namedBindings, el => this.visitDefined(el, q)); -{"state":"ADD","valueType":"org.openrewrite.java.tree.JLeftPadded"} - JavaScriptSender:139 => await q.getAndSend(jsImport, el => el.moduleSpecifier, el => this.visitLeftPadded(el, q)); - JavaScriptReceiver:682 => draft.moduleSpecifier = await q.receive(draft.moduleSpecifier, el => this.visitLeftPadded(el, q)); -{"state":"ADD","valueType":"org.openrewrite.java.tree.Space"} - JavaScriptSender:514 => return this.javaSender.visitLeftPadded(left, q); - JavaScriptReceiver:1110 => return this.javaReceiverDelegate.visitLeftPadded(left, q); -{"state":"ADD"} - JavaScriptSender:514 => return this.javaSender.visitLeftPadded(left, q); - undefined -{"state":"NO_CHANGE"} - JavaScriptSender:140 => await q.getAndSend(jsImport, el => el.attributes, el => this.visit(el, q)); - JavaScriptReceiver:683 => draft.attributes = await q.receive(draft.attributes, el => this.visitDefined(el, q)); -{"state":"NO_CHANGE"} - JavaScriptSender:141 => await q.getAndSend(jsImport, el => el.initializer, el => this.visitLeftPadded(el, q)); - JavaScriptReceiver:684 => draft.initializer = await q.receive(draft.initializer, el => this.visitLeftPadded(el, q)); -{"state":"ADD","valueType":"org.openrewrite.java.tree.JRightPadded"} - JavaScriptSender:54 => await q.getAndSendList(cu, c => c.statements, stmt => stmt.element.id, stmt => this.visitRightPadded(stmt, q)); - undefined -{"state":"ADD","valueType":"org.openrewrite.java.tree.J$VariableDeclarations"} - JavaScriptSender:510 => return this.javaSender.visitRightPadded(right, q); - JavaScriptReceiver:1106 => return this.javaReceiverDelegate.visitRightPadded(right, q) -{"state":"ADD","value":"c386461a-3b33-4e6a-bb6c-f0356caea42e"} - JavaScriptSender:510 => return this.javaSender.visitRightPadded(right, q); - undefined -{"state":"ADD","value":"6c66a7bd-3424-426e-9462-443b62b98a01"} - JavaScriptSender:42 => await q.getAndSend(j, j2 => j2.id); - JavaScriptReceiver:567 => draft.id = await q.receive(j.id); -{"state":"ADD","valueType":"org.openrewrite.java.tree.Space"} - JavaScriptSender:43 => await q.getAndSend(j, j2 => j2.prefix, space => this.visitSpace(space, q)); - JavaScriptReceiver:568 => draft.prefix = await q.receive(j.prefix, space => this.visitSpace(space, q)); -{"state":"ADD"} - JavaScriptSender:522 => return this.javaSender.visitSpace(space, q); - JavaScriptReceiver:1118 => return this.javaReceiverDelegate.visitSpace(space, q); -{"state":"ADD","ref":1} - JavaScriptSender:44 => await q.getAndSend(j, j2 => j2.markers); - JavaScriptReceiver:569 => draft.markers = await q.receive(j.markers); -{"state":"ADD"} - JavaScriptSender:66 => await q.getAndSendList(arrowFunction, el => el.leadingAnnotations, el => el.id, el => this.visit(el, q)); - JavaScriptReceiver:597 => draft.leadingAnnotations = await q.receiveListDefined(draft.leadingAnnotations, el => this.visitDefined(el, q)); -{"state":"ADD"} - JavaScriptSender:67 => await q.getAndSendList(arrowFunction, el => el.modifiers, el => el.id, el => this.visit(el, q)); - JavaScriptReceiver:598 => draft.modifiers = await q.receiveListDefined(draft.modifiers, el => this.visitDefined(el, q)); -{"state":"NO_CHANGE"} - JavaScriptSender:68 => await q.getAndSend(arrowFunction, el => el.typeParameters, el => this.visit(el, q)); - JavaScriptReceiver:599 => draft.typeParameters = await q.receive(draft.typeParameters, el => this.visitDefined(el, q)); -{"state":"ADD","valueType":"org.openrewrite.java.tree.J$Lambda"} - JavaScriptSender:69 => await q.getAndSend(arrowFunction, el => el.lambda, el => this.visit(el, q)); - JavaScriptReceiver:600 => draft.lambda = await q.receive(draft.lambda, el => this.visitDefined(el, q)); -{"state":"ADD","value":"afa2a3d0-844a-4691-805f-c9348a45c9a8"} - JavaScriptSender:38 => return this.javaSender.visit(tree, p, parent); - JavaScriptReceiver:561 => return this.javaReceiverDelegate.visit(tree, p, parent); -{"state":"NO_CHANGE"} - JavaScriptSender:70 => await q.getAndSend(arrowFunction, el => el.returnTypeExpression, el => this.visit(el, q)); - JavaScriptReceiver:601 => draft.returnTypeExpression = await q.receive(draft.returnTypeExpression, el => this.visitDefined(el, q)); -{"state":"ADD","valueType":"org.openrewrite.java.tree.Space"} - JavaScriptSender:55 => await q.getAndSend(cu, c => c.eof, space => this.visitSpace(space, q)); - JavaScriptReceiver:583 => draft.eof = await q.receive(cu.eof, space => this.visitSpace(space, q)); -{"state":"ADD"} - JavaScriptSender:522 => return this.javaSender.visitSpace(space, q); - JavaScriptReceiver:1118 => return this.javaReceiverDelegate.visitSpace(space, q); From 31611afa9a4eb815daaebecbf1d90cc7dd1cbd49 Mon Sep 17 00:00:00 2001 From: Sam Snyder Date: Tue, 14 Oct 2025 22:58:00 -0700 Subject: [PATCH 15/19] License --- .../gradle/search/FindDependencyHandler.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/search/FindDependencyHandler.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/search/FindDependencyHandler.java index b722a33ba9..368752146c 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/search/FindDependencyHandler.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/search/FindDependencyHandler.java @@ -1,3 +1,18 @@ +/* + * Copyright 2025 the original author or authors. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.openrewrite.gradle.search; import org.openrewrite.ExecutionContext; From 96a7dd4b997906fad05bd2515cf2a5178878fc49 Mon Sep 17 00:00:00 2001 From: Sam Snyder Date: Wed, 15 Oct 2025 11:34:34 -0700 Subject: [PATCH 16/19] Match test expectation to updated behavior --- .../src/test/java/org/openrewrite/internal/StringUtilsTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rewrite-core/src/test/java/org/openrewrite/internal/StringUtilsTest.java b/rewrite-core/src/test/java/org/openrewrite/internal/StringUtilsTest.java index 81428035ac..cefbf35d24 100644 --- a/rewrite-core/src/test/java/org/openrewrite/internal/StringUtilsTest.java +++ b/rewrite-core/src/test/java/org/openrewrite/internal/StringUtilsTest.java @@ -89,7 +89,7 @@ void occurrenceCount() { @Test void globMatching() { // exact matches - assertThat(matchesGlob("test", null)).isFalse(); + assertThat(matchesGlob("test", null)).isTrue(); assertThat(matchesGlob("test", "")).isFalse(); assertThat(matchesGlob("", "")).isTrue(); assertThat(matchesGlob("test", "test")).isTrue(); From 9f3a397c655655baa2da63445d51a1ded5bc879f Mon Sep 17 00:00:00 2001 From: Sam Snyder Date: Wed, 15 Oct 2025 12:29:58 -0700 Subject: [PATCH 17/19] Apply suggestions from code review Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .../openrewrite/gradle/ChangeDependencyArtifactId.java | 3 +-- .../org/openrewrite/gradle/ChangeDependencyGroupId.java | 3 +-- .../openrewrite/gradle/trait/GradleMultiDependency.java | 8 ++++---- .../java/org/openrewrite/maven/tree/DependencyTest.java | 2 +- 4 files changed, 7 insertions(+), 9 deletions(-) diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependencyArtifactId.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependencyArtifactId.java index 778da063d4..3cb110af9a 100755 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependencyArtifactId.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependencyArtifactId.java @@ -108,8 +108,7 @@ public G.CompilationUnit visitCompilationUnit(G.CompilationUnit cu, ExecutionCon public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { J.MethodInvocation m = super.visitMethodInvocation(method, ctx); - m = GradleMultiDependency.matcher() - .configuration(configuration) + return GradleMultiDependency.matcher() .groupId(groupId) .artifactId(artifactId) .get(getCursor()) diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependencyGroupId.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependencyGroupId.java index 1af2699e66..030f7662f1 100755 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependencyGroupId.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependencyGroupId.java @@ -108,8 +108,7 @@ public G.CompilationUnit visitCompilationUnit(G.CompilationUnit cu, ExecutionCon public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { J.MethodInvocation m = super.visitMethodInvocation(method, ctx); - m = GradleMultiDependency.matcher() - .configuration(configuration) + return GradleMultiDependency.matcher() .groupId(groupId) .artifactId(artifactId) .get(getCursor()) diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/trait/GradleMultiDependency.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/trait/GradleMultiDependency.java index dedff1a7af..c4b283429f 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/trait/GradleMultiDependency.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/trait/GradleMultiDependency.java @@ -32,7 +32,7 @@ import org.openrewrite.trait.Trait; import org.openrewrite.trait.VisitFunction2; -import java.util.Collections; +import static java.util.Collections.singletonList; import java.util.Optional; import java.util.function.Consumer; import java.util.function.Function; @@ -110,7 +110,7 @@ public J.MethodInvocation map(Function map return getTree().withArguments( ListUtils.map(getTree().getArguments(), argument -> { // Make a synthetic GradleDependency representing wrapping a single dependency notation - J.MethodInvocation m = getTree().withArguments(Collections.singletonList(argument)); + J.MethodInvocation m = getTree().withArguments(singletonList(argument)); Optional dep = new GradleDependency.Matcher() .matcher(matcher) .get(new Cursor(getCursor().getParent(), m)); @@ -135,7 +135,7 @@ public J.MethodInvocation map(Function map public void forEach(Consumer consumer) { if (isVarargs()) { for (Expression argument : getTree().getArguments()) { - J.MethodInvocation m = getTree().withArguments(Collections.singletonList(argument)); + J.MethodInvocation m = getTree().withArguments(singletonList(argument)); new GradleDependency.Matcher() .matcher(matcher) .get(new Cursor(getCursor().getParent(), m)) @@ -216,4 +216,4 @@ public J visitMethodInvocation(J.MethodInvocation method, P p) { return new GradleMultiDependency(cursor, matcher, methodArgumentsContainMultipleDependencyNotations(cursor)); } } -} \ No newline at end of file +} diff --git a/rewrite-maven/src/test/java/org/openrewrite/maven/tree/DependencyTest.java b/rewrite-maven/src/test/java/org/openrewrite/maven/tree/DependencyTest.java index 653dac42da..671ad87cef 100644 --- a/rewrite-maven/src/test/java/org/openrewrite/maven/tree/DependencyTest.java +++ b/rewrite-maven/src/test/java/org/openrewrite/maven/tree/DependencyTest.java @@ -338,4 +338,4 @@ void parseAcceptsValidCharactersInGroupAndArtifact() { assertThat(dep.getGroupId()).isEqualTo("com.example-test_123"); assertThat(dep.getArtifactId()).isEqualTo("my-artifact_456.test"); } -} \ No newline at end of file +} From 1ef367ed1eaa87aa693eeddacc9faf20a44cdfa5 Mon Sep 17 00:00:00 2001 From: Sam Snyder Date: Wed, 15 Oct 2025 12:34:53 -0700 Subject: [PATCH 18/19] Fix code suggestion --- .../java/org/openrewrite/gradle/ChangeDependencyArtifactId.java | 1 - .../java/org/openrewrite/gradle/ChangeDependencyGroupId.java | 1 - 2 files changed, 2 deletions(-) diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependencyArtifactId.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependencyArtifactId.java index 3cb110af9a..8139fa0e6a 100755 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependencyArtifactId.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependencyArtifactId.java @@ -114,7 +114,6 @@ public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, Execu .get(getCursor()) .map(gmd -> gmd.map(gd -> gd.withDeclaredArtifactId(newArtifactId).getTree())) .orElse(m); - return m; } private GradleProject updateGradleModel(GradleProject gp) { diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependencyGroupId.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependencyGroupId.java index 030f7662f1..079dbac425 100755 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependencyGroupId.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependencyGroupId.java @@ -114,7 +114,6 @@ public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, Execu .get(getCursor()) .map(gmd -> gmd.map(gd -> gd.withDeclaredGroupId(newGroupId).getTree())) .orElse(m); - return m; } private GradleProject updateGradleModel(GradleProject gp) { From 6354285c302e3b7851493b20eac1380a760106ba Mon Sep 17 00:00:00 2001 From: Sam Snyder Date: Tue, 21 Oct 2025 17:30:31 -0700 Subject: [PATCH 19/19] code review feedback --- .../gradle/trait/GradleDependency.java | 58 +++++++++---------- 1 file changed, 26 insertions(+), 32 deletions(-) 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 d5267d2296..aab2b73f70 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 @@ -35,6 +35,7 @@ import org.openrewrite.java.tree.J; import org.openrewrite.java.tree.JavaType; import org.openrewrite.kotlin.tree.K; +import org.openrewrite.marker.Markers; import org.openrewrite.maven.tree.Dependency; import org.openrewrite.maven.tree.GroupArtifactVersion; import org.openrewrite.maven.tree.ResolvedDependency; @@ -49,6 +50,7 @@ import java.util.stream.Collectors; import static java.util.Collections.singletonList; +import static java.util.stream.Collectors.*; import static java.util.stream.Collectors.toList; @Value @@ -460,20 +462,21 @@ public String getConfigurationName() { } /** - * Helper method to extract a string value from various expression types. + * Helper method to extract a string v from various expression types. * Handles literals, identifiers, field access, method invocations, and GStrings. */ private @Nullable String extractValueAsString(Expression value) { - if (value instanceof J.Literal) { - Object literalValue = ((J.Literal) value).getValue(); + Expression v = value.withMarkers(Markers.EMPTY); + if (v instanceof J.Literal) { + Object literalValue = ((J.Literal) v).getValue(); return literalValue instanceof String ? (String) literalValue : null; - } else if (value instanceof J.Identifier) { - return ((J.Identifier) value).getSimpleName(); - } else if (value instanceof J.FieldAccess) { - return value.printTrimmed(cursor); - } else if (value instanceof J.MethodInvocation) { + } else if (v instanceof J.Identifier) { + return ((J.Identifier) v).getSimpleName(); + } else if (v instanceof J.FieldAccess) { + return v.printTrimmed(cursor); + } else if (v instanceof J.MethodInvocation) { // Handle property('name') or findProperty('name') patterns - J.MethodInvocation mi = (J.MethodInvocation) value; + J.MethodInvocation mi = (J.MethodInvocation) v; String methodName = mi.getSimpleName(); if (("property".equals(methodName) || "findProperty".equals(methodName)) && mi.getArguments().size() == 1 && @@ -495,12 +498,10 @@ public String getConfigurationName() { return (String) arg; } } - // For other method invocations, return the full expression - return value.printTrimmed(cursor); - } else if (value instanceof G.Binary) { - G.Binary binary = (G.Binary) value; - + return v.printTrimmed(cursor); + } else if (v instanceof G.Binary) { + G.Binary binary = (G.Binary) v; // Handle project.properties['name'] pattern (G.Binary with Access operator) if (binary.getOperator() == G.Binary.Type.Access && binary.getRight() instanceof J.Literal) { J.Literal right = (J.Literal) binary.getRight(); @@ -508,7 +509,6 @@ public String getConfigurationName() { return (String) right.getValue(); } } - // Handle binary expressions like "$guavaVersion" + "-jre" if (binary.getLeft() instanceof G.GString) { G.GString left = (G.GString) binary.getLeft(); @@ -523,11 +523,10 @@ public String getConfigurationName() { } else if (binary.getLeft() instanceof J.Identifier) { return ((J.Identifier) binary.getLeft()).getSimpleName(); } - // For other binary expressions, return the full expression - return value.printTrimmed(cursor); - } else if (value instanceof G.GString) { - G.GString gString = (G.GString) value; + return v.printTrimmed(cursor); + } else if (v instanceof G.GString) { + G.GString gString = (G.GString) v; List strings = gString.getStrings(); if (!strings.isEmpty() && strings.get(0) instanceof G.GString.Value) { G.GString.Value gStringValue = (G.GString.Value) strings.get(0); @@ -544,8 +543,8 @@ public String getConfigurationName() { return extractValueAsString((G.Binary) tree); } } - } else if (value instanceof K.StringTemplate) { - K.StringTemplate template = (K.StringTemplate) value; + } else if (v instanceof K.StringTemplate) { + K.StringTemplate template = (K.StringTemplate) v; List strings = template.getStrings(); if (!strings.isEmpty() && strings.get(0) instanceof K.StringTemplate.Expression) { K.StringTemplate.Expression templateExp = (K.StringTemplate.Expression) strings.get(0); @@ -767,9 +766,8 @@ public boolean matches(DependencyMatcher matcher) { * Extract property name from method invocation patterns like property('guavaVersion') * or findProperty('guavaVersion'). */ - @Nullable - private static String extractPropertyNameFromMethodInvocation(J.MethodInvocation mi) { - if (mi.getSimpleName().equals("property") || mi.getSimpleName().equals("findProperty")) { + private static @Nullable String extractPropertyNameFromMethodInvocation(J.MethodInvocation mi) { + if ("property".equals(mi.getSimpleName()) || "findProperty".equals(mi.getSimpleName())) { if (!mi.getArguments().isEmpty() && mi.getArguments().get(0) instanceof J.Literal) { J.Literal literal = (J.Literal) mi.getArguments().get(0); if (literal.getValue() instanceof String) { @@ -784,8 +782,7 @@ private static String extractPropertyNameFromMethodInvocation(J.MethodInvocation * Handle Groovy binary access like project.properties['guavaVersion'] * where the binary operator is Access (bracket notation). */ - @Nullable - private static String extractPropertyNameFromGBinary(G.Binary binary) { + private static @Nullable String extractPropertyNameFromGBinary(G.Binary binary) { if (binary.getOperator() == G.Binary.Type.Access && binary.getRight() instanceof J.Literal) { J.Literal right = (J.Literal) binary.getRight(); if (right.getValue() instanceof String) { @@ -936,7 +933,7 @@ public GradleDependency withDeclaredGroupId(String newGroupId) { ((G.MapLiteral) firstArg).getElements() : m.getArguments().stream() .filter(G.MapEntry.class::isInstance) .map(G.MapEntry.class::cast) - .collect(Collectors.toList()); + .collect(toList()); for (G.MapEntry entry : entries) { if (entry.getKey() instanceof J.Literal && @@ -1069,7 +1066,7 @@ public GradleDependency withDeclaredArtifactId(String newArtifactId) { ((G.MapLiteral) firstArg).getElements() : m.getArguments().stream() .filter(G.MapEntry.class::isInstance) .map(G.MapEntry.class::cast) - .collect(Collectors.toList()); + .collect(toList()); for (G.MapEntry entry : entries) { if (entry.getKey() instanceof J.Literal && @@ -1197,7 +1194,7 @@ public GradleDependency withDeclaredVersion(@Nullable String newVersion) { m.getArguments().stream() .filter(G.MapEntry.class::isInstance) .map(G.MapEntry.class::cast) - .collect(Collectors.toList()); + .collect(toList()); boolean versionFound = false; for (G.MapEntry entry : entries) { @@ -1326,9 +1323,6 @@ public J visitMethodInvocation(J.MethodInvocation method, P p) { J.MethodInvocation methodInvocation = cursor.getValue(); GradleProject gradleProject = getGradleProject(cursor); GradleDependencyConfiguration gdc = getConfiguration(gradleProject, methodInvocation); - if (gdc == null && !(DEPENDENCY_DSL_MATCHER.matches(methodInvocation) && !"project".equals(methodInvocation.getSimpleName()))) { - return null; - } if (!StringUtils.isBlank(configuration) && !methodInvocation.getSimpleName().equals(configuration)) { return null;