diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/search/DependencyInsight.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/search/DependencyInsight.java index b3281a471c0..10ca95c4662 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/search/DependencyInsight.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/search/DependencyInsight.java @@ -15,7 +15,9 @@ */ package org.openrewrite.gradle.search; +import lombok.Data; import lombok.EqualsAndHashCode; +import lombok.RequiredArgsConstructor; import lombok.Value; import org.openrewrite.*; import org.openrewrite.gradle.marker.GradleDependencyConfiguration; @@ -49,6 +51,7 @@ public class DependencyInsight extends Recipe { transient DependenciesInUse dependenciesInUse = new DependenciesInUse(this); private static final MethodMatcher DEPENDENCY_CONFIGURATION_MATCHER = new MethodMatcher("DependencyHandlerSpec *(..)"); + private static final MethodMatcher DEPENDENCY_CLOSURE_MATCHER = new MethodMatcher("RewriteGradleProject dependencies(..)"); @Option(displayName = "Group pattern", description = "Group glob pattern used to match dependencies.", @@ -124,15 +127,15 @@ public Tree visit(@Nullable Tree tree, ExecutionContext ctx) { } for (ResolvedDependency resolvedDependency : c.getResolved()) { ResolvedDependency dep = resolvedDependency.findDependency(groupIdPattern, artifactIdPattern); - if(dep ==null) { + if (dep == null) { continue; } - if(version != null) { + if (version != null) { VersionComparator versionComparator = Semver.validate(version, null).getValue(); - if(versionComparator == null) { + if (versionComparator == null) { sourceFile = Markup.warn(sourceFile, new IllegalArgumentException("Could not construct a valid version comparator from " + version + ".")); } else { - if(!versionComparator.isValid(null, dep.getVersion())) { + if (!versionComparator.isValid(null, dep.getVersion())) { continue; } } @@ -186,21 +189,56 @@ public Tree visit(@Nullable Tree tree, ExecutionContext ctx) { } } return new MarkIndividualDependency(configurationToDirectDependency, directDependencyToTargetDependency) - .visitNonNull(sourceFile, ctx); + .attachMarkers(sourceFile, ctx); } }; } @EqualsAndHashCode(callSuper = false) - @Value + @RequiredArgsConstructor + @Data private static class MarkIndividualDependency extends JavaIsoVisitor { + private final Map> configurationToDirectDependency; + private final Map> directDependencyToTargetDependency; + private boolean attachToDependencyClosure = false; + private boolean hasMarkerOnDependencyClosure = false; - Map> configurationToDirectDependency; - Map> directDependencyToTargetDependency; + public Tree attachMarkers(Tree before, ExecutionContext ctx) { + J after = super.visitNonNull(before, ctx); + if (!hasMarkerOnDependencyClosure) { + if (after == before) { + attachToDependencyClosure = true; + after = super.visitNonNull(before, ctx); + } + if (after == before) { + String resultText = directDependencyToTargetDependency.values().stream() + .flatMap(Set::stream) + .distinct() + .map(target -> target.getGroupId() + ":" + target.getArtifactId() + ":" + target.getVersion()) + .collect(Collectors.joining(",")); + return SearchResult.found(after, resultText); + } + } + return after; + } @Override public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { J.MethodInvocation m = super.visitMethodInvocation(method, ctx); + if (DEPENDENCY_CLOSURE_MATCHER.matches(m)) { + String resultText = directDependencyToTargetDependency.values().stream() + .flatMap(Set::stream) + .distinct() + .map(target -> target.getGroupId() + ":" + target.getArtifactId() + ":" + target.getVersion()) + .collect(Collectors.joining(",")); + J.MethodInvocation after = SearchResult.found(m, resultText); + if (after == m) { + hasMarkerOnDependencyClosure = true; + } + if (attachToDependencyClosure) { + return after; + } + } if (!DEPENDENCY_CONFIGURATION_MATCHER.matches(m) || !configurationToDirectDependency.containsKey(m.getSimpleName()) || m.getArguments().isEmpty()) { return m; diff --git a/rewrite-gradle/src/test/java/org/openrewrite/gradle/search/DependencyInsightTest.java b/rewrite-gradle/src/test/java/org/openrewrite/gradle/search/DependencyInsightTest.java index c5a0e1b990c..a001c91f86b 100644 --- a/rewrite-gradle/src/test/java/org/openrewrite/gradle/search/DependencyInsightTest.java +++ b/rewrite-gradle/src/test/java/org/openrewrite/gradle/search/DependencyInsightTest.java @@ -24,6 +24,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.openrewrite.gradle.Assertions.buildGradle; import static org.openrewrite.gradle.toolingapi.Assertions.withToolingApi; +import static org.openrewrite.groovy.Assertions.groovy; class DependencyInsightTest implements RewriteTest { @@ -67,6 +68,96 @@ void findTransitiveDependency() { ); } + @Test + void findPluginDependencyAndAddToDependencyClosure() { + rewriteRun( + buildGradle(""" + plugins { + id 'groovy-gradle-plugin' + } + repositories { + gradlePluginPortal() + } + """, spec -> spec.path("buildSrc/build.gradle")), + groovy(""" + plugins{ + id "java" + } + dependencies{ + implementation 'com.google.guava:guava:31.1-jre' + } + """, spec -> spec.path("buildSrc/src/main/groovy/convention-plugin.gradle")), + buildGradle(""" + plugins { + id 'convention-plugin' + } + + repositories { + mavenCentral() + } + + dependencies { + implementation 'org.openrewrite:rewrite-java:8.0.0' + } + """, + """ + plugins { + id 'convention-plugin' + } + + repositories { + mavenCentral() + } + + /*~~(com.google.guava:failureaccess:1.0.1)~~>*/dependencies { + implementation 'org.openrewrite:rewrite-java:8.0.0' + } + """ + ) + ); + } + + @Test + void findPluginDependencyAndAddToRoot() { + rewriteRun( + buildGradle(""" + plugins { + id 'groovy-gradle-plugin' + } + repositories { + gradlePluginPortal() + } + """, spec -> spec.path("buildSrc/build.gradle")), + groovy(""" + plugins{ + id "java" + } + dependencies{ + implementation 'com.google.guava:guava:31.1-jre' + } + """, spec -> spec.path("buildSrc/src/main/groovy/convention-plugin.gradle")), + buildGradle(""" + plugins { + id 'convention-plugin' + } + + repositories { + mavenCentral() + } + """, + """ + /*~~(com.google.guava:failureaccess:1.0.1)~~>*/plugins { + id 'convention-plugin' + } + + repositories { + mavenCentral() + } + """ + ) + ); + } + @Test void recursive() { rewriteRun(