diff --git a/rewrite-java/build.gradle.kts b/rewrite-java/build.gradle.kts index f84627b4c2b..92121e1ec1b 100644 --- a/rewrite-java/build.gradle.kts +++ b/rewrite-java/build.gradle.kts @@ -64,6 +64,7 @@ dependencies { testRuntimeOnly(project(":rewrite-java-17")) testImplementation("com.tngtech.archunit:archunit:1.0.1") testImplementation("com.tngtech.archunit:archunit-junit5:1.0.1") + testImplementation("org.junit-pioneer:junit-pioneer:2.0.0") // For use in ClassGraphTypeMappingTest testRuntimeOnly("org.eclipse.persistence:org.eclipse.persistence.core:3.0.2") diff --git a/rewrite-java/src/main/java/org/openrewrite/java/RemoveMethodInvocationsVisitor.java b/rewrite-java/src/main/java/org/openrewrite/java/RemoveMethodInvocationsVisitor.java new file mode 100644 index 00000000000..7cf86789aeb --- /dev/null +++ b/rewrite-java/src/main/java/org/openrewrite/java/RemoveMethodInvocationsVisitor.java @@ -0,0 +1,256 @@ +/* + * Copyright 2023 the original author or authors. + *
+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *
+ * https://www.apache.org/licenses/LICENSE-2.0 + *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.openrewrite.java;
+
+import lombok.Value;
+import lombok.With;
+import org.jspecify.annotations.Nullable;
+import org.openrewrite.*;
+import org.openrewrite.internal.ListUtils;
+import org.openrewrite.java.tree.*;
+import org.openrewrite.marker.Marker;
+
+import java.util.*;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+import static org.openrewrite.Tree.randomId;
+
+/**
+ * This visitor removes method calls matching some criteria.
+ * Tries to intelligently remove within chains without breaking other methods in the chain.
+ */
+public class RemoveMethodInvocationsVisitor extends JavaVisitor
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.openrewrite.java;
+
+import org.junit.jupiter.api.Test;
+import org.junitpioneer.jupiter.ExpectedToFail;
+import org.openrewrite.DocumentExample;
+import org.openrewrite.Recipe;
+import org.openrewrite.test.RewriteTest;
+
+import java.util.List;
+
+import static org.openrewrite.java.Assertions.java;
+import static org.openrewrite.test.RewriteTest.toRecipe;
+
+@SuppressWarnings({"ResultOfMethodCallIgnored", "CodeBlock2Expr", "RedundantThrows", "Convert2MethodRef", "EmptyTryBlock", "CatchMayIgnoreException", "EmptyFinallyBlock", "StringBufferReplaceableByString", "UnnecessaryLocalVariable"})
+class RemoveMethodInvocationsVisitorTest implements RewriteTest {
+
+ private Recipe createRemoveMethodsRecipe(String... methods) {
+ return toRecipe(() -> new RemoveMethodInvocationsVisitor(List.of(methods)));
+ }
+
+ @DocumentExample
+ @Test
+ void removeFromEnd() {
+ rewriteRun(
+ spec -> spec.recipe(createRemoveMethodsRecipe("java.lang.StringBuilder toString()"))
+ ,
+ //language=java
+ java(
+ """
+ public class Test {
+ void method() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("Hello")
+ .append(" ")
+ .append("World")
+ .reverse()
+ .append(" ")
+ .reverse()
+ .append("Yeah")
+ .toString();
+ }
+ }
+ """,
+ """
+ public class Test {
+ void method() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("Hello")
+ .append(" ")
+ .append("World")
+ .reverse()
+ .append(" ")
+ .reverse()
+ .append("Yeah");
+ }
+ }
+ """
+ )
+ );
+ }
+
+ @Test
+ void removeMultipleMethodsFromEnd() {
+ rewriteRun(
+ spec -> spec.recipe(createRemoveMethodsRecipe("java.lang.StringBuilder toString()", "java.lang.StringBuilder append(java.lang.String)")),
+ //language=java
+ java(
+ """
+ public class Test {
+ void method() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("Hello")
+ .append(" ")
+ .append("World")
+ .reverse()
+ .append(" ")
+ .reverse()
+ .append("Yeah")
+ .toString();
+ }
+ }
+ """,
+ """
+ public class Test {
+ void method() {
+ StringBuilder sb = new StringBuilder();
+ sb.reverse()
+ .reverse();
+ }
+ }
+ """
+ )
+ );
+ }
+
+ @Test
+ void removeFromMiddle() {
+ rewriteRun(
+ spec -> spec.recipe(createRemoveMethodsRecipe("java.lang.StringBuilder reverse()")),
+ //language=java
+ java(
+ """
+ public class Test {
+ void method() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("Hello")
+ .append(" ")
+ .append("World")
+ .reverse()
+ .append(" ")
+ .reverse()
+ .append("Yeah")
+ .toString();
+ }
+ }
+ """,
+ """
+ public class Test {
+ void method() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("Hello")
+ .append(" ")
+ .append("World")
+ .append(" ")
+ .append("Yeah")
+ .toString();
+ }
+ }
+ """
+ )
+ );
+ }
+
+ @Test
+ void removeEntireStatement() {
+ rewriteRun(
+ spec -> spec.recipe(createRemoveMethodsRecipe("java.lang.StringBuilder append(java.lang.String)")),
+ //language=java
+ java(
+ """
+ public class Test {
+ void method() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("Hello");
+ }
+ }
+ """,
+ """
+ public class Test {
+ void method() {
+ StringBuilder sb = new StringBuilder();
+ }
+ }
+ """
+ )
+ );
+ }
+
+ @Test
+ @ExpectedToFail
+ void removeWithoutSelect() {
+ rewriteRun(
+ spec -> spec.recipe(createRemoveMethodsRecipe("Test foo()")),
+ //language=java
+ java(
+ """
+ public class Test {
+ void foo() {}
+ void method() {
+ StringBuilder sb = new StringBuilder();
+ foo();
+ }
+ }
+ """,
+ """
+ public class Test {
+ void foo() {}
+ void method() {
+ StringBuilder sb = new StringBuilder();
+ }
+ }
+ """
+ )
+ );
+ }
+
+ @Test
+ void removeFromWithinArguments() {
+ rewriteRun(
+ spec -> spec.recipe(createRemoveMethodsRecipe("java.lang.StringBuilder append(java.lang.String)")),
+ //language=java
+ java(
+ """
+ public class Test {
+ void method() {
+ StringBuilder sb = new StringBuilder();
+ StringBuilder sb2 = new StringBuilder();
+ sb.append(1)
+ .append(((java.util.function.Supplier> argsMatches) {
+ return matcher.matches(m) && argsMatches.test(m.getArguments());
+ }
+
+ private boolean isStatement() {
+ return getCursor().dropParentUntil(p -> p instanceof J.Block ||
+ p instanceof J.Assignment ||
+ p instanceof J.VariableDeclarations.NamedVariable ||
+ p instanceof J.Return ||
+ p instanceof JContainer ||
+ p == Cursor.ROOT_VALUE
+ ).getValue() instanceof J.Block;
+ }
+
+ private boolean isLambdaBody() {
+ if (getCursor().getParent() == null) {
+ return false;
+ }
+ Object parent = getCursor().getParent().getValue();
+ return parent instanceof J.Lambda && ((J.Lambda) parent).getBody() == getCursor().getValue();
+ }
+
+ private boolean inMethodCallChain() {
+ return getCursor().dropParentUntil(p -> !(p instanceof JRightPadded)).getValue() instanceof J.MethodInvocation;
+ }
+
+ private J.MethodInvocation inheritSelectAfter(J.MethodInvocation method, Stack
>() {
+ @Override
+ public
> isTrueArgument() {
+ return args -> args.size() == 1 && isTrue(args.get(0));
+ }
+
+ @SuppressWarnings("unused") // used in rewrite-spring / convenient for consumers
+ public static Predicate
> isFalseArgument() {
+ return args -> args.size() == 1 && isFalse(args.get(0));
+ }
+
+ public static boolean isTrue(Expression expression) {
+ return isBoolean(expression, Boolean.TRUE);
+ }
+
+ public static boolean isFalse(Expression expression) {
+ return isBoolean(expression, Boolean.FALSE);
+ }
+
+ private static boolean isBoolean(Expression expression, Boolean b) {
+ if (expression instanceof J.Literal) {
+ return expression.getType() == JavaType.Primitive.Boolean && b.equals(((J.Literal) expression).getValue());
+ }
+ return false;
+ }
+
+ @Override
+ public J.Lambda visitLambda(J.Lambda lambda, ExecutionContext ctx) {
+ lambda = (J.Lambda) super.visitLambda(lambda, ctx);
+ J body = lambda.getBody();
+ if (body instanceof J.MethodInvocation && ToBeRemoved.hasMarker(body)) {
+ Expression select = ((J.MethodInvocation) body).getSelect();
+ List