Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,9 @@ public class RemoveRedundantDependencies extends ScanningRecipe<RemoveRedundantD
"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.";
"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.";

@Value
public static class Accumulator {
Expand Down Expand Up @@ -197,6 +199,13 @@ private static Set<GroupArtifact> 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<MavenRepository> withMavenCentral(List<MavenRepository> repositories) {
List<MavenRepository> effectiveRepos = new ArrayList<>(repositories);
if (effectiveRepos.stream().noneMatch(r -> r.getUri().contains("repo.maven.apache.org") ||
Expand Down Expand Up @@ -286,6 +295,9 @@ public TreeVisitor<?, ExecutionContext> getVisitor(Accumulator acc) {
}

private boolean isRedundant(ResolvedDependency dep, Set<TransitiveDependency> transitives) {
if (constrainsResolution(dep)) {
return false;
}
Set<GroupArtifact> depExclusions = declaredExclusions(dep);
for (TransitiveDependency transitive : transitives) {
ResolvedGroupArtifactVersion gav = transitive.getGav();
Expand Down
2 changes: 1 addition & 1 deletion src/main/resources/META-INF/rewrite/recipes.csv
Original file line number Diff line number Diff line change
Expand Up @@ -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 `<dependencies>` 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'`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
Loading