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 0b73c394..469fc9df 100644 --- a/src/main/java/org/openrewrite/java/dependencies/search/ModuleHasDependency.java +++ b/src/main/java/org/openrewrite/java/dependencies/search/ModuleHasDependency.java @@ -112,12 +112,17 @@ private boolean hasDependency(Tree tree) { if (mavenResult != null) { Scope requestedScope = scope == null ? null : Scope.fromName(scope); List dependencies = mavenResult.findDependencies(groupIdPattern, artifactIdPattern, requestedScope); + Set resolvedGAs = new HashSet<>(); for (ResolvedDependency dependency : dependencies) { + resolvedGAs.add(dependency.getGroupId() + ":" + dependency.getArtifactId()); if (versionComparator == null || versionComparator.isValid(null, dependency.getVersion())) { return true; } } for (Dependency requested : mavenResult.getPom().getRequestedDependencies()) { + if (resolvedGAs.contains(requested.getGroupId() + ":" + requested.getArtifactId())) { + continue; + } if (matchesRequested(requested, requestedScope, versionComparator)) { return true; } @@ -127,16 +132,23 @@ private boolean hasDependency(Tree tree) { GradleProject gp = tree.getMarkers().findFirst(GradleProject.class).orElse(null); if (gp != null) { + Set resolvedGAs = new HashSet<>(); for (GradleDependencyConfiguration c : gp.getConfigurations()) { for (ResolvedDependency resolvedDependency : c.getDirectResolved()) { ResolvedDependency found = resolvedDependency.findDependency(groupIdPattern, artifactIdPattern); - if (found != null && (versionComparator == null || versionComparator.isValid(null, found.getVersion()))) { - return true; + if (found != null) { + resolvedGAs.add(found.getGroupId() + ":" + found.getArtifactId()); + if (versionComparator == null || versionComparator.isValid(null, found.getVersion())) { + return true; + } } } } for (GradleDependencyConfiguration c : gp.getConfigurations()) { for (Dependency requested : c.getRequested()) { + if (resolvedGAs.contains(requested.getGroupId() + ":" + requested.getArtifactId())) { + continue; + } if (matchesRequested(requested, null, versionComparator)) { return true; } @@ -166,10 +178,10 @@ private boolean matchesRequested(Dependency dep, @Nullable Scope requestedScope, } private static boolean versionMatches(@Nullable String version, @Nullable VersionComparator cmp) { - if (cmp == null || version == null) { + if (cmp == null) { return true; } - if (version.startsWith("${")) { + if (version == null || version.startsWith("${")) { return false; } return cmp.isValid(null, version); 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 b982d0e2..59c48ee8 100644 --- a/src/main/java/org/openrewrite/java/dependencies/search/RepositoryHasDependency.java +++ b/src/main/java/org/openrewrite/java/dependencies/search/RepositoryHasDependency.java @@ -31,7 +31,9 @@ import org.openrewrite.semver.Semver; import org.openrewrite.semver.VersionComparator; +import java.util.HashSet; import java.util.List; +import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; @EqualsAndHashCode(callSuper = false) @@ -107,12 +109,17 @@ private boolean hasDependency(Tree tree) { if (mavenResult != null) { Scope requestedScope = scope == null ? null : Scope.fromName(scope); List dependencies = mavenResult.findDependencies(groupIdPattern, artifactIdPattern, requestedScope); + Set resolvedGAs = new HashSet<>(); for (ResolvedDependency dependency : dependencies) { + resolvedGAs.add(dependency.getGroupId() + ":" + dependency.getArtifactId()); if (versionComparator == null || versionComparator.isValid(null, dependency.getVersion())) { return true; } } for (Dependency requested : mavenResult.getPom().getRequestedDependencies()) { + if (resolvedGAs.contains(requested.getGroupId() + ":" + requested.getArtifactId())) { + continue; + } if (matchesRequested(requested, requestedScope, versionComparator)) { return true; } @@ -122,16 +129,23 @@ private boolean hasDependency(Tree tree) { GradleProject gp = tree.getMarkers().findFirst(GradleProject.class).orElse(null); if (gp != null) { + Set resolvedGAs = new HashSet<>(); for (GradleDependencyConfiguration c : gp.getConfigurations()) { for (ResolvedDependency resolvedDependency : c.getDirectResolved()) { ResolvedDependency found = resolvedDependency.findDependency(groupIdPattern, artifactIdPattern); - if (found != null && (versionComparator == null || versionComparator.isValid(null, found.getVersion()))) { - return true; + if (found != null) { + resolvedGAs.add(found.getGroupId() + ":" + found.getArtifactId()); + if (versionComparator == null || versionComparator.isValid(null, found.getVersion())) { + return true; + } } } } for (GradleDependencyConfiguration c : gp.getConfigurations()) { for (Dependency requested : c.getRequested()) { + if (resolvedGAs.contains(requested.getGroupId() + ":" + requested.getArtifactId())) { + continue; + } if (matchesRequested(requested, null, versionComparator)) { return true; } @@ -161,10 +175,10 @@ private boolean matchesRequested(Dependency dep, @Nullable Scope requestedScope, } private static boolean versionMatches(@Nullable String version, @Nullable VersionComparator cmp) { - if (cmp == null || version == null) { + if (cmp == null) { return true; } - if (version.startsWith("${")) { + if (version == null || version.startsWith("${")) { return false; } return cmp.isValid(null, version); 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 0efe3269..a5ae2358 100644 --- a/src/test/java/org/openrewrite/java/dependencies/search/ModuleHasDependencyTest.java +++ b/src/test/java/org/openrewrite/java/dependencies/search/ModuleHasDependencyTest.java @@ -454,6 +454,100 @@ void gradleVersionRangeOnRequestedDoesNotMatchWhenOutOfRange() { ) ); } + + @Language("groovy") + private final static String GradleNoRepositoriesNoVersion = """ + plugins { + id 'java-library' + } + dependencies { + implementation 'org.springframework:spring-beans' + } + """; + + @Test + void gradleRequestedWithoutVersionAndConstraintDoesNotMatch() { + // Force resolution failure (no repositories), so the requested fallback fires. + rewriteRun( + spec -> spec.recipe(new ModuleHasDependency(GroupId, ArtifactId, null, "[1.0,)", null)), + mavenProject("project-gradle", + buildGradle(GradleNoRepositoriesNoVersion), + java(GradleJava) + ) + ); + } + } + + @Nested + class WhenResolvedVersionIsSourceOfTruth { + + @Language("groovy") + private final static String GradleForcedOutOfRange = """ + plugins { + id 'java-library' + } + repositories { + mavenCentral() + } + configurations.all { + resolutionStrategy { + force 'org.springframework:spring-beans:6.0.0' + } + } + dependencies { + implementation 'org.springframework:spring-beans:5.3.0' + } + """; + + @Test + void gradleVersionRangeDoesNotMatchDeclaredWhenResolvedVersionIsOutOfRange() { + // The declared-dependency fallback must be skipped for an already-resolved coordinate (resolutionStrategy). + rewriteRun( + spec -> spec.recipe(new ModuleHasDependency(GroupId, ArtifactId, null, "[5.0,6.0)", null)), + mavenProject("project-gradle", + buildGradle(GradleForcedOutOfRange), + java(GradleJava) + ) + ); + } + + @Language("xml") + private final static String MavenBomManagedOutOfRange = """ + + com.example + foo + 1.0.0 + + + + org.springframework.boot + spring-boot-dependencies + 3.0.0 + pom + import + + + + + + org.springframework + spring-beans + + + + """; + + @Test + void mavenVersionRangeDoesNotMatchBomManagedDependencyWhenResolvedIsOutOfRange() { + // Regression for rewrite-third-party#76: BOM-managed version (null on requested) must not match the range. + rewriteRun( + spec -> spec.recipe(new ModuleHasDependency(GroupId, ArtifactId, null, "[5.0,6.0)", null)), + mavenProject("project-maven", + pomXml(MavenBomManagedOutOfRange), + java(MavenJava) + ) + ); + } } @Nested diff --git a/src/test/java/org/openrewrite/java/dependencies/search/RepositoryHasDependencyTest.java b/src/test/java/org/openrewrite/java/dependencies/search/RepositoryHasDependencyTest.java index c3058c0b..becc7434 100644 --- a/src/test/java/org/openrewrite/java/dependencies/search/RepositoryHasDependencyTest.java +++ b/src/test/java/org/openrewrite/java/dependencies/search/RepositoryHasDependencyTest.java @@ -15,12 +15,15 @@ */ package org.openrewrite.java.dependencies.search; +import org.intellij.lang.annotations.Language; 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.gradle.toolingapi.Assertions.withToolingApi; +import static org.openrewrite.java.Assertions.java; import static org.openrewrite.java.Assertions.mavenProject; import static org.openrewrite.maven.Assertions.pomXml; @@ -84,4 +87,92 @@ void usedAsDeclarativePrecondition() { ) ); } + + @Language("java") + private final static String GradleJava = """ + public class AGradle {} + """; + + @Test + void gradleVersionRangeDoesNotMatchDeclaredWhenResolvedVersionIsOutOfRange() { + // The declared-dependency fallback must be skipped for an already-resolved coordinate (resolutionStrategy). + rewriteRun( + spec -> spec.recipe(new RepositoryHasDependency("org.springframework", "spring-beans", null, "[5.0,6.0)")), + mavenProject("project-gradle", + //language=groovy + buildGradle(""" + plugins { + id 'java-library' + } + repositories { + mavenCentral() + } + configurations.all { + resolutionStrategy { + force 'org.springframework:spring-beans:6.0.0' + } + } + dependencies { + implementation 'org.springframework:spring-beans:5.3.0' + } + """), + java(GradleJava) + ) + ); + } + + @Test + void mavenVersionRangeDoesNotMatchBomManagedDependencyWhenResolvedIsOutOfRange() { + // Regression for rewrite-third-party#76: BOM-managed version (null on requested) must not match the range. + rewriteRun( + spec -> spec.recipe(new RepositoryHasDependency("org.springframework", "spring-beans", null, "[5.0,6.0)")), + mavenProject("project-maven", + //language=xml + pomXml(""" + + com.example + foo + 1.0.0 + + + + org.springframework.boot + spring-boot-dependencies + 3.0.0 + pom + import + + + + + + org.springframework + spring-beans + + + + """) + ) + ); + } + + @Test + void gradleRequestedWithoutVersionAndConstraintDoesNotMatch() { + // Force resolution failure (no repositories), so the requested fallback fires. + rewriteRun( + spec -> spec.recipe(new RepositoryHasDependency("org.springframework", "spring-beans", null, "[1.0,)")), + mavenProject("project-gradle", + //language=groovy + buildGradle(""" + plugins { + id 'java-library' + } + dependencies { + implementation 'org.springframework:spring-beans' + } + """), + java(GradleJava) + ) + ); + } }