diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..7a7ad4c --- /dev/null +++ b/.editorconfig @@ -0,0 +1,9 @@ +root = true + +[*] +insert_final_newline = true +trim_trailing_whitespace = true + +[src/test*/java/**.java] +indent_size = 4 +ij_continuation_indent_size = 2 diff --git a/src/main/java/org/openrewrite/java/dependencies/RemoveDependency.java b/src/main/java/org/openrewrite/java/dependencies/RemoveDependency.java index 254862e..78f2756 100644 --- a/src/main/java/org/openrewrite/java/dependencies/RemoveDependency.java +++ b/src/main/java/org/openrewrite/java/dependencies/RemoveDependency.java @@ -18,38 +18,46 @@ import lombok.EqualsAndHashCode; import lombok.Value; import org.jspecify.annotations.Nullable; -import org.openrewrite.Option; -import org.openrewrite.Recipe; - -import java.util.Arrays; -import java.util.List; +import org.openrewrite.*; +import org.openrewrite.java.search.UsesType; +import java.util.concurrent.atomic.AtomicBoolean; @Value @EqualsAndHashCode(callSuper = false) -public class RemoveDependency extends Recipe { +public class RemoveDependency extends ScanningRecipe { @Option(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*") + description = "The first part of a dependency coordinate `com.google.guava:guava:VERSION`. This can be a glob expression.", + example = "com.fasterxml.jackson*") String groupId; @Option(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*") + description = "The second part of a dependency coordinate `com.google.guava:guava:VERSION`. This can be a glob expression.", + example = "jackson-module*") String artifactId; + @Option(displayName = "Unless using", + description = "Do not remove if type is in use. Supports glob expressions.", + example = "org.aspectj.lang.*", + required = false) + @Nullable + String unlessUsing; + // Gradle only parameter - @Option(displayName = "The dependency configuration", description = "The dependency configuration to remove from.", example = "api", required = false) + @Option(displayName = "The dependency configuration", + description = "The dependency configuration to remove from.", + example = "api", + required = false) @Nullable String configuration; // Maven only parameter @Option(displayName = "Scope", - description = "Only remove dependencies if they are in this scope. If 'runtime', this will" + - "also remove dependencies in the 'compile' scope because 'compile' dependencies are part of the runtime dependency set", - valid = {"compile", "test", "runtime", "provided"}, - example = "compile", - required = false) + description = "Only remove dependencies if they are in this scope. If 'runtime', this will" + + "also remove dependencies in the 'compile' scope because 'compile' dependencies are part of the runtime dependency set", + valid = {"compile", "test", "runtime", "provided"}, + example = "compile", + required = false) @Nullable String scope; @@ -64,17 +72,23 @@ public String getDescription() { "For Maven project, removes a single dependency from the section of the pom.xml."; } - org.openrewrite.gradle.@Nullable RemoveDependency removeGradleDependency; + @Override + public AtomicBoolean getInitialValue(ExecutionContext ctx) { + return new AtomicBoolean(false); + } - org.openrewrite.maven.@Nullable RemoveDependency removeMavenDependency; + org.openrewrite.gradle.RemoveDependency removeGradleDependency; + org.openrewrite.maven.RemoveDependency removeMavenDependency; public RemoveDependency( - String groupId, - String artifactId, - @Nullable String configuration, - @Nullable String scope) { + String groupId, + String artifactId, + @Nullable String unlessUsing, + @Nullable String configuration, + @Nullable String scope) { this.groupId = groupId; this.artifactId = artifactId; + this.unlessUsing = unlessUsing; this.configuration = configuration; this.scope = scope; removeGradleDependency = new org.openrewrite.gradle.RemoveDependency(groupId, artifactId, configuration); @@ -82,7 +96,46 @@ public RemoveDependency( } @Override - public List getRecipeList() { - return Arrays.asList(removeGradleDependency, removeMavenDependency); + public TreeVisitor getScanner(AtomicBoolean usageFound) { + if (unlessUsing == null) { + return TreeVisitor.noop(); + } + UsesType usesType = new UsesType<>(unlessUsing, true); + return new TreeVisitor() { + @Override + public Tree preVisit(Tree tree, ExecutionContext ctx) { + stopAfterPreVisit(); + if (!usageFound.get()) { + usageFound.set(tree != usesType.visit(tree, ctx)); + } + return tree; + } + }; + } + + @Override + public TreeVisitor getVisitor(AtomicBoolean usageFound) { + if (usageFound.get()) { + return TreeVisitor.noop(); + } + + return new TreeVisitor() { + final TreeVisitor gradleRemoveDep = removeGradleDependency.getVisitor(); + final TreeVisitor mavenRemoveDep = removeMavenDependency.getVisitor(); + + @Override + public @Nullable Tree visit(@Nullable Tree tree, ExecutionContext ctx) { + if (tree instanceof SourceFile) { + SourceFile sf = (SourceFile) tree; + if (gradleRemoveDep.isAcceptable(sf, ctx)) { + return gradleRemoveDep.visitNonNull(tree, ctx); + } + if (mavenRemoveDep.isAcceptable(sf, ctx)) { + return mavenRemoveDep.visitNonNull(tree, ctx); + } + } + return tree; + } + }; } } diff --git a/src/test/java/org/openrewrite/.editorconfig b/src/test/java/org/openrewrite/.editorconfig deleted file mode 100644 index a482493..0000000 --- a/src/test/java/org/openrewrite/.editorconfig +++ /dev/null @@ -1,5 +0,0 @@ -root = true - -[*.java] -indent_size = 4 -ij_continuation_indent_size = 2 diff --git a/src/test/java/org/openrewrite/java/dependencies/RemoveDependencyTest.java b/src/test/java/org/openrewrite/java/dependencies/RemoveDependencyTest.java index c85f1e3..f23a7e1 100644 --- a/src/test/java/org/openrewrite/java/dependencies/RemoveDependencyTest.java +++ b/src/test/java/org/openrewrite/java/dependencies/RemoveDependencyTest.java @@ -17,10 +17,13 @@ import org.junit.jupiter.api.Test; import org.openrewrite.DocumentExample; +import org.openrewrite.Issue; +import org.openrewrite.java.JavaParser; 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.*; import static org.openrewrite.maven.Assertions.pomXml; class RemoveDependencyTest implements RewriteTest { @@ -30,18 +33,18 @@ class RemoveDependencyTest implements RewriteTest { void removeGradleDependencyUsingStringNotationWithExclusion() { rewriteRun( spec -> spec.beforeRecipe(withToolingApi()) - .recipe(new RemoveDependency("org.springframework.boot", "spring-boot*", null, null)), + .recipe(new RemoveDependency("org.springframework.boot", "spring-boot*", null, null, null)), //language=groovy buildGradle( """ plugins { id 'java-library' } - + repositories { mavenCentral() } - + dependencies { implementation("org.springframework.boot:spring-boot-starter-web:2.7.0") { exclude group: "junit" @@ -53,11 +56,11 @@ void removeGradleDependencyUsingStringNotationWithExclusion() { plugins { id 'java-library' } - + repositories { mavenCentral() } - + dependencies { testImplementation "org.junit.vintage:junit-vintage-engine:5.6.2" } @@ -70,17 +73,17 @@ void removeGradleDependencyUsingStringNotationWithExclusion() { @Test void removeMavenDependency() { rewriteRun( - spec -> spec.recipe(new RemoveDependency("junit", "junit", null, null)), + spec -> spec.recipe(new RemoveDependency("junit", "junit", null, null, null)), //language=xml pomXml( """ 4.0.0 - + com.mycompany.app my-app 1 - + com.google.guava @@ -99,11 +102,11 @@ void removeMavenDependency() { """ 4.0.0 - + com.mycompany.app my-app 1 - + com.google.guava @@ -116,4 +119,111 @@ void removeMavenDependency() { ) ); } + + @Issue("https://github.com/openrewrite/rewrite-java-dependencies/issues/11") + @Test + void doNotRemoveIfInUse() { + rewriteRun( + spec -> spec + .parser(JavaParser.fromJavaVersion().dependsOn( + //language=java + """ + package org.aspectj.lang.annotation; + + import java.lang.annotation.Target; + import java.lang.annotation.ElementType; + import java.lang.annotation.Retention; + import java.lang.annotation.RetentionPolicy; + + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.TYPE) + public @interface Aspect { + } + """ + )) + .recipe(new RemoveDependency("org.aspectj", "aspectjrt", "org.aspectj.lang.annotation.*", null, null)), + mavenProject("example", + //language=java + srcMainJava( + java( + """ + import org.aspectj.lang.annotation.Aspect; + @Aspect + class MyLoggingInterceptor { + } + """ + ) + ), + //language=xml + pomXml( + """ + + 4.0.0 + + com.mycompany.app + my-app + 1 + + + + org.aspectj + aspectjrt + 1.9.22.1 + + + + """ + ) + ) + ); + } + + @Issue("https://github.com/openrewrite/rewrite-java-dependencies/issues/11") + @Test + void doRemoveIfNotInUse() { + rewriteRun( + spec -> spec.recipe(new RemoveDependency("org.aspectj", "aspectjrt", "java.lang.String", null, null)), + mavenProject("example", + //language=java + srcMainJava( + java( + """ + class MyLoggingInterceptor { + // Not using String anywhere here; the dependency should be removed + } + """ + ) + ), + //language=xml + pomXml( + """ + + 4.0.0 + + com.mycompany.app + my-app + 1 + + + + org.aspectj + aspectjrt + 1.9.22.1 + + + + """, + """ + + 4.0.0 + + com.mycompany.app + my-app + 1 + + """ + ) + ) + ); + } }