From 10ec29e45d4b3f0915e41cc10c69eaf9779da263 Mon Sep 17 00:00:00 2001 From: Jente Sondervorst Date: Thu, 28 May 2026 18:17:07 +0200 Subject: [PATCH] Match requested dependencies in ModuleHasDependency and RepositoryHasDependency After #176 removed DependencyInsight, both recipes only inspect resolved dependencies. Add a fallback that also matches against requested (declared) dependencies, so a dep that is declared but unresolved (e.g. unit tests with no repositories block, partially resolved POMs) is still recognized. Mirrors the existing behavior of `FindDependency` (both Maven and Gradle), which matches on what is declared in the build file rather than on the resolved tree. This keeps the two families of "find" recipes consistent and unblocks a 1-on-1 refactor that swaps one for the other in cleaner recipes. Version glob handling: null version is accepted (no constraint declared); `${...}` placeholders are skipped (cannot be evaluated); otherwise compared against the version pattern. --- .../search/ModuleHasDependency.java | 43 +++++++ .../search/RepositoryHasDependency.java | 43 +++++++ .../search/ModuleHasDependencyTest.java | 110 ++++++++++++++++++ 3 files changed, 196 insertions(+) diff --git a/src/main/java/org/openrewrite/java/dependencies/search/ModuleHasDependency.java b/src/main/java/org/openrewrite/java/dependencies/search/ModuleHasDependency.java index 749b3ab7..0b73c394 100644 --- a/src/main/java/org/openrewrite/java/dependencies/search/ModuleHasDependency.java +++ b/src/main/java/org/openrewrite/java/dependencies/search/ModuleHasDependency.java @@ -21,8 +21,10 @@ import org.openrewrite.*; import org.openrewrite.gradle.marker.GradleDependencyConfiguration; import org.openrewrite.gradle.marker.GradleProject; +import org.openrewrite.internal.StringUtils; import org.openrewrite.java.marker.JavaProject; import org.openrewrite.marker.SearchResult; +import org.openrewrite.maven.tree.Dependency; import org.openrewrite.maven.tree.MavenResolutionResult; import org.openrewrite.maven.tree.ResolvedDependency; import org.openrewrite.maven.tree.Scope; @@ -115,6 +117,11 @@ private boolean hasDependency(Tree tree) { return true; } } + for (Dependency requested : mavenResult.getPom().getRequestedDependencies()) { + if (matchesRequested(requested, requestedScope, versionComparator)) { + return true; + } + } return false; } @@ -128,10 +135,46 @@ private boolean hasDependency(Tree tree) { } } } + for (GradleDependencyConfiguration c : gp.getConfigurations()) { + for (Dependency requested : c.getRequested()) { + if (matchesRequested(requested, null, versionComparator)) { + return true; + } + } + } } return false; } + private boolean matchesRequested(Dependency dep, @Nullable Scope requestedScope, @Nullable VersionComparator versionComparator) { + if (dep.getGroupId() == null || dep.getArtifactId() == null) { + return false; + } + if (!StringUtils.matchesGlob(dep.getGroupId(), groupIdPattern)) { + return false; + } + if (!StringUtils.matchesGlob(dep.getArtifactId(), artifactIdPattern)) { + return false; + } + if (requestedScope != null) { + Scope depScope = dep.getScope() == null ? Scope.Compile : Scope.fromName(dep.getScope()); + if (!depScope.isInClasspathOf(requestedScope)) { + return false; + } + } + return versionMatches(dep.getVersion(), versionComparator); + } + + private static boolean versionMatches(@Nullable String version, @Nullable VersionComparator cmp) { + if (cmp == null || version == null) { + return true; + } + if (version.startsWith("${")) { + return false; + } + return cmp.isValid(null, version); + } + @Override public TreeVisitor getVisitor(Set acc) { return new TreeVisitor() { diff --git a/src/main/java/org/openrewrite/java/dependencies/search/RepositoryHasDependency.java b/src/main/java/org/openrewrite/java/dependencies/search/RepositoryHasDependency.java index c3357780..b982d0e2 100644 --- a/src/main/java/org/openrewrite/java/dependencies/search/RepositoryHasDependency.java +++ b/src/main/java/org/openrewrite/java/dependencies/search/RepositoryHasDependency.java @@ -21,8 +21,10 @@ import org.openrewrite.*; import org.openrewrite.gradle.marker.GradleDependencyConfiguration; import org.openrewrite.gradle.marker.GradleProject; +import org.openrewrite.internal.StringUtils; import org.openrewrite.java.marker.JavaProject; import org.openrewrite.marker.SearchResult; +import org.openrewrite.maven.tree.Dependency; import org.openrewrite.maven.tree.MavenResolutionResult; import org.openrewrite.maven.tree.ResolvedDependency; import org.openrewrite.maven.tree.Scope; @@ -110,6 +112,11 @@ private boolean hasDependency(Tree tree) { return true; } } + for (Dependency requested : mavenResult.getPom().getRequestedDependencies()) { + if (matchesRequested(requested, requestedScope, versionComparator)) { + return true; + } + } return false; } @@ -123,10 +130,46 @@ private boolean hasDependency(Tree tree) { } } } + for (GradleDependencyConfiguration c : gp.getConfigurations()) { + for (Dependency requested : c.getRequested()) { + if (matchesRequested(requested, null, versionComparator)) { + return true; + } + } + } } return false; } + private boolean matchesRequested(Dependency dep, @Nullable Scope requestedScope, @Nullable VersionComparator versionComparator) { + if (dep.getGroupId() == null || dep.getArtifactId() == null) { + return false; + } + if (!StringUtils.matchesGlob(dep.getGroupId(), groupIdPattern)) { + return false; + } + if (!StringUtils.matchesGlob(dep.getArtifactId(), artifactIdPattern)) { + return false; + } + if (requestedScope != null) { + Scope depScope = dep.getScope() == null ? Scope.Compile : Scope.fromName(dep.getScope()); + if (!depScope.isInClasspathOf(requestedScope)) { + return false; + } + } + return versionMatches(dep.getVersion(), versionComparator); + } + + private static boolean versionMatches(@Nullable String version, @Nullable VersionComparator cmp) { + if (cmp == null || version == null) { + return true; + } + if (version.startsWith("${")) { + return false; + } + return cmp.isValid(null, version); + } + @Override public TreeVisitor getVisitor(AtomicBoolean acc) { if (acc.get()) { diff --git a/src/test/java/org/openrewrite/java/dependencies/search/ModuleHasDependencyTest.java b/src/test/java/org/openrewrite/java/dependencies/search/ModuleHasDependencyTest.java index 61cd0285..0efe3269 100644 --- a/src/test/java/org/openrewrite/java/dependencies/search/ModuleHasDependencyTest.java +++ b/src/test/java/org/openrewrite/java/dependencies/search/ModuleHasDependencyTest.java @@ -21,9 +21,16 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.NullSource; import org.junit.jupiter.params.provider.ValueSource; +import org.openrewrite.InMemoryExecutionContext; +import org.openrewrite.Parser; +import org.openrewrite.maven.MavenExecutionContextView; +import org.openrewrite.maven.MavenSettings; import org.openrewrite.test.RecipeSpec; import org.openrewrite.test.RewriteTest; +import java.io.ByteArrayInputStream; +import java.nio.file.Path; + import static org.assertj.core.api.Assertions.assertThat; import static org.openrewrite.gradle.Assertions.buildGradle; import static org.openrewrite.gradle.toolingapi.Assertions.withToolingApi; @@ -346,6 +353,109 @@ void whenModuleDoesNotHaveDependencyButInvertedMarkingMarks() { ); } + @Nested + class WhenDependencyIsRequestedButNotResolved { + + @Language("groovy") + private final static String GradleNoRepositories = """ + plugins { + id 'java-library' + } + dependencies { + implementation 'org.springframework:spring-beans:6.0.0' + } + """; + + @Language("xml") + private final static String MavenNoRepositories = """ + + com.example + foo + 1.0.0 + + + org.springframework + spring-beans + 6.0.0 + + + + """; + + @Test + void gradleMatchesOnRequested() { + rewriteRun( + spec -> spec.recipe(new ModuleHasDependency(GroupId, ArtifactId, null, null, null)), + mavenProject("project-gradle", + buildGradle( + GradleNoRepositories, + spec -> spec.after(actual -> + assertThat(actual) + .startsWith(GradleMarkerPositive) + .actual() + ) + ), + java( + GradleJava, + spec -> spec.after(actual -> + assertThat(actual) + .startsWith(JavaMarkerPositive) + .actual() + ) + ) + ) + ); + } + + @Test + void mavenMatchesOnRequested() { + rewriteRun( + spec -> { + MavenExecutionContextView ctx = MavenExecutionContextView.view(new InMemoryExecutionContext()); + MavenSettings emptySettings = MavenSettings.parse(new Parser.Input(Path.of("settings.xml"), () -> new ByteArrayInputStream( + //language=xml + """ + + """.getBytes())), ctx); + ctx.setMavenSettings(emptySettings); + spec.recipe(new ModuleHasDependency(GroupId, ArtifactId, null, null, null)) + .executionContext(ctx); + }, + mavenProject("project-maven", + pomXml( + MavenNoRepositories, + spec -> spec.after(actual -> + assertThat(actual) + .startsWith(MavenMarkerPositive) + .actual() + ) + ), + java( + MavenJava, + spec -> spec.after(actual -> + assertThat(actual) + .startsWith(JavaMarkerPositive) + .actual() + ) + ) + ) + ); + } + + @Test + void gradleVersionRangeOnRequestedDoesNotMatchWhenOutOfRange() { + rewriteRun( + spec -> spec.recipe(new ModuleHasDependency(GroupId, ArtifactId, null, "[7.0,)", null)), + mavenProject("project-gradle", + buildGradle(GradleNoRepositories), + java(GradleJava) + ) + ); + } + } + @Nested class WithVersionsPattern { @ParameterizedTest