diff --git a/src/main/java/org/openrewrite/java/dependencies/RemoveRedundantDependencies.java b/src/main/java/org/openrewrite/java/dependencies/RemoveRedundantDependencies.java index 43e936f2..a265b41d 100644 --- a/src/main/java/org/openrewrite/java/dependencies/RemoveRedundantDependencies.java +++ b/src/main/java/org/openrewrite/java/dependencies/RemoveRedundantDependencies.java @@ -52,7 +52,9 @@ public class RemoveRedundantDependencies extends ScanningRecipe declaredExclusions(ResolvedDependency dep) { return exclusions == null || exclusions.isEmpty() ? emptySet() : new HashSet<>(exclusions); } + private static boolean constrainsResolution(ResolvedDependency dep) { + String requestedVersion = dep.getRequested().getVersion(); + return requestedVersion != null && + (requestedVersion.startsWith("[") || requestedVersion.startsWith("(") || + requestedVersion.endsWith("+") || requestedVersion.startsWith("latest.")); + } + private static List withMavenCentral(List repositories) { List effectiveRepos = new ArrayList<>(repositories); if (effectiveRepos.stream().noneMatch(r -> r.getUri().contains("repo.maven.apache.org") || @@ -286,6 +295,9 @@ public TreeVisitor getVisitor(Accumulator acc) { } private boolean isRedundant(ResolvedDependency dep, Set transitives) { + if (constrainsResolution(dep)) { + return false; + } Set depExclusions = declaredExclusions(dep); for (TransitiveDependency transitive : transitives) { ResolvedGroupArtifactVersion gav = transitive.getGav(); diff --git a/src/main/resources/META-INF/rewrite/recipes.csv b/src/main/resources/META-INF/rewrite/recipes.csv index 4c54eb4e..3f51f310 100644 --- a/src/main/resources/META-INF/rewrite/recipes.csv +++ b/src/main/resources/META-INF/rewrite/recipes.csv @@ -15,7 +15,7 @@ maven,org.openrewrite.recipe:rewrite-java-dependencies,org.openrewrite.java.depe This recipe makes no changes to any source file by default. Add `changeDependencies=true` to change dependencies, but note that you might need to run additional recipes to update imports and adopt other breaking changes.",1,,Dependencies,Java,,,Basic building blocks for transforming Java code.,"[{""name"":""changeDependencies"",""type"":""Boolean"",""displayName"":""Change dependencies"",""description"":""Whether to change dependencies to their relocated groupId and artifactId.""}]","[{""name"":""org.openrewrite.java.dependencies.table.RelocatedDependencyReport"",""displayName"":""Relocated dependencies"",""instanceName"":""Relocated dependencies"",""description"":""A list of dependencies in use that have relocated."",""columns"":[{""name"":""dependencyGroupId"",""type"":""String"",""displayName"":""Dependency group id"",""description"":""The Group ID of the dependency in use.""},{""name"":""dependencyArtifactId"",""type"":""String"",""displayName"":""Dependency artifact id"",""description"":""The Artifact ID of the dependency in use.""},{""name"":""relocatedGroupId"",""type"":""String"",""displayName"":""Relocated dependency group id"",""description"":""The Group ID of the relocated dependency.""},{""name"":""relocatedArtifactId"",""type"":""String"",""displayName"":""Relocated ependency artifact id"",""description"":""The Artifact ID of the relocated dependency.""},{""name"":""context"",""type"":""String"",""displayName"":""Context"",""description"":""Context for the relocation, if any.""}]}]" maven,org.openrewrite.recipe:rewrite-java-dependencies,org.openrewrite.java.dependencies.RemoveDependency,Remove a Gradle or Maven dependency,"For Gradle project, removes a single dependency from the dependencies section of the `build.gradle`. For Maven project, removes a single dependency from the `` section of the pom.xml.",1,,Dependencies,Java,,,Basic building blocks for transforming Java code.,"[{""name"":""groupId"",""type"":""String"",""displayName"":""Group ID"",""description"":""The first part of a dependency coordinate `com.google.guava:guava:VERSION`. This can be a glob expression."",""example"":""com.fasterxml.jackson*"",""required"":true},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact ID"",""description"":""The second part of a dependency coordinate `com.google.guava:guava:VERSION`. This can be a glob expression."",""example"":""jackson-module*"",""required"":true},{""name"":""unlessUsing"",""type"":""String"",""displayName"":""Unless using"",""description"":""Do not remove if type is in use. Supports glob expressions."",""example"":""org.aspectj.lang.*""},{""name"":""configuration"",""type"":""String"",""displayName"":""The dependency configuration"",""description"":""The dependency configuration to remove from."",""example"":""api""},{""name"":""scope"",""type"":""String"",""displayName"":""Scope"",""description"":""Only remove dependencies if they are in this scope. If 'runtime', this willalso remove dependencies in the 'compile' scope because 'compile' dependencies are part of the runtime dependency set"",""example"":""compile"",""valid"":[""compile"",""test"",""runtime"",""provided""]}]", -maven,org.openrewrite.recipe:rewrite-java-dependencies,org.openrewrite.java.dependencies.RemoveRedundantDependencies,Remove redundant explicit dependencies,"Remove explicit dependencies that are already provided transitively by a specified dependency. This recipe downloads and resolves the parent dependency's POM to determine its true transitive dependencies, allowing it to detect redundancies even when both dependencies are explicitly declared. A direct dependency is only removed when the transitive one provides it at the exact same scope and with the same declared exclusions, so that removing it does not change the effective classpath.",1,,Dependencies,Java,,,Basic building blocks for transforming Java code.,"[{""name"":""groupId"",""type"":""String"",""displayName"":""Group ID"",""description"":""The first part of a dependency coordinate `com.google.guava:guava:VERSION` of the parent dependency. This can be a glob expression."",""example"":""com.fasterxml.jackson.core"",""required"":true},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact ID"",""description"":""The second part of a dependency coordinate `com.google.guava:guava:VERSION` of the parent dependency. This can be a glob expression."",""example"":""jackson-databind"",""required"":true}]", +maven,org.openrewrite.recipe:rewrite-java-dependencies,org.openrewrite.java.dependencies.RemoveRedundantDependencies,Remove redundant explicit dependencies,"Remove explicit dependencies that are already provided transitively by a specified dependency. This recipe downloads and resolves the parent dependency's POM to determine its true transitive dependencies, allowing it to detect redundancies even when both dependencies are explicitly declared. A direct dependency is only removed when the transitive one provides it at the exact same scope and with the same declared exclusions, so that removing it does not change the effective classpath. Declarations that constrain resolution through a version range or dynamic version, such as a Gradle `version { strictly ... }` block, are never removed.",1,,Dependencies,Java,,,Basic building blocks for transforming Java code.,"[{""name"":""groupId"",""type"":""String"",""displayName"":""Group ID"",""description"":""The first part of a dependency coordinate `com.google.guava:guava:VERSION` of the parent dependency. This can be a glob expression."",""example"":""com.fasterxml.jackson.core"",""required"":true},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact ID"",""description"":""The second part of a dependency coordinate `com.google.guava:guava:VERSION` of the parent dependency. This can be a glob expression."",""example"":""jackson-databind"",""required"":true}]", maven,org.openrewrite.recipe:rewrite-java-dependencies,org.openrewrite.java.dependencies.UpgradeDependencyVersion,Upgrade Gradle or Maven dependency versions,"For Gradle projects, upgrade the version of a dependency in a `build.gradle` file. Supports updating dependency declarations of various forms: * `String` notation: `""group:artifact:version""` * `Map` notation: `group: 'group', name: 'artifact', version: 'version'` diff --git a/src/test/java/org/openrewrite/java/dependencies/RemoveRedundantDependenciesTest.java b/src/test/java/org/openrewrite/java/dependencies/RemoveRedundantDependenciesTest.java index 787c55ee..bfb9e298 100644 --- a/src/test/java/org/openrewrite/java/dependencies/RemoveRedundantDependenciesTest.java +++ b/src/test/java/org/openrewrite/java/dependencies/RemoveRedundantDependenciesTest.java @@ -781,6 +781,37 @@ void removeRedundantGradleDependency() { ); } + @Test + void doNotRemoveGradleDependencyDeclaringVersionConstraint() { + rewriteRun( + spec -> spec.beforeRecipe(withToolingApi()) + .recipe(new RemoveRedundantDependencies( + "com.fasterxml.jackson.core", "jackson-databind")), + mavenProject("my-app", + //language=groovy + buildGradle( + """ + plugins { + id 'java-library' + } + repositories { + mavenCentral() + } + dependencies { + implementation 'com.fasterxml.jackson.core:jackson-databind:2.17.0' + implementation('com.fasterxml.jackson.core:jackson-core') { + version { + prefer '2.17.0' + strictly '[2.17.0,2.18.0)' + } + } + } + """ + ) + ) + ); + } + @Test void globGroupIdMatchesProvider() { rewriteRun(