diff --git a/src/main/java/org/openrewrite/java/testing/assertj/SimplifySequencedCollectionAssertions.java b/src/main/java/org/openrewrite/java/testing/assertj/SimplifySequencedCollectionAssertions.java index 417d86444..0f9dd6315 100644 --- a/src/main/java/org/openrewrite/java/testing/assertj/SimplifySequencedCollectionAssertions.java +++ b/src/main/java/org/openrewrite/java/testing/assertj/SimplifySequencedCollectionAssertions.java @@ -16,6 +16,7 @@ package org.openrewrite.java.testing.assertj; import lombok.Getter; +import org.openrewrite.Cursor; import org.openrewrite.ExecutionContext; import org.openrewrite.Preconditions; import org.openrewrite.Recipe; @@ -27,9 +28,17 @@ import org.openrewrite.java.search.UsesMethod; import org.openrewrite.java.tree.Expression; import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.TypeUtils; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; public class SimplifySequencedCollectionAssertions extends Recipe { + // Object-equality assertions come from AbstractAssert, so they do not depend on CharSequence behavior + private static final Set OBJECT_EQUALITY_ASSERTIONS = new HashSet<>(Arrays.asList("isEqualTo", "isNotEqualTo")); + private static final MethodMatcher ASSERT_THAT_MATCHER = new MethodMatcher("org.assertj.core.api.Assertions assertThat(..)"); private static final MethodMatcher GET_FIRST_MATCHER = new MethodMatcher("java.util.* getFirst()"); private static final MethodMatcher GET_LAST_MATCHER = new MethodMatcher("java.util.* getLast()"); @@ -63,9 +72,15 @@ public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, Execu if (arg instanceof J.MethodInvocation) { // Check if the method is getFirst() or getLast() on a SequencedCollection if (GET_FIRST_MATCHER.matches(arg)) { + if (usesCharSequenceSpecificAssertion((J.MethodInvocation) arg)) { + return mi; + } return assertThat(mi, (J.MethodInvocation) arg, "first", ctx); } if (GET_LAST_MATCHER.matches(arg)) { + if (usesCharSequenceSpecificAssertion((J.MethodInvocation) arg)) { + return mi; + } return assertThat(mi, (J.MethodInvocation) arg, "last", ctx); } } @@ -73,6 +88,35 @@ public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, Execu return mi; } + private boolean usesCharSequenceSpecificAssertion(J.MethodInvocation elementAccess) { + if (!TypeUtils.isAssignableTo("java.lang.CharSequence", elementAccess.getType())) { + return false; + } + J.MethodInvocation selected = getCursor().getValue(); + Cursor assertionCursor = getCursor().getParentTreeCursor(); + while (assertionCursor != null && assertionCursor.getValue() instanceof J.MethodInvocation) { + J.MethodInvocation assertion = assertionCursor.getValue(); + if (!(assertion.getSelect() instanceof J.MethodInvocation) || + !selected.getId().equals(((J.MethodInvocation) assertion.getSelect()).getId())) { + break; + } + if (isCharSequenceSpecificAssertion(assertion)) { + return true; + } + selected = assertion; + assertionCursor = assertionCursor.getParentTreeCursor(); + } + return false; + } + + private boolean isCharSequenceSpecificAssertion(J.MethodInvocation assertion) { + if (OBJECT_EQUALITY_ASSERTIONS.contains(assertion.getSimpleName())) { + return false; + } + return assertion.getMethodType() != null && TypeUtils.isAssignableTo( + "org.assertj.core.api.AbstractCharSequenceAssert", assertion.getMethodType().getDeclaringType()); + } + private J.MethodInvocation assertThat(J.MethodInvocation mi, J.MethodInvocation argMethod, String dedicatedAssertion, ExecutionContext ctx) { return JavaTemplate.builder("assertThat(#{any(java.lang.Iterable)})." + dedicatedAssertion + "()") .staticImports("org.assertj.core.api.Assertions.assertThat") diff --git a/src/test/java/org/openrewrite/java/testing/assertj/SimplifySequencedCollectionAssertionsTest.java b/src/test/java/org/openrewrite/java/testing/assertj/SimplifySequencedCollectionAssertionsTest.java index 4c6520946..74e0806e3 100644 --- a/src/test/java/org/openrewrite/java/testing/assertj/SimplifySequencedCollectionAssertionsTest.java +++ b/src/test/java/org/openrewrite/java/testing/assertj/SimplifySequencedCollectionAssertionsTest.java @@ -127,8 +127,70 @@ class MyTest { void testMethod() { List list = List.of("a", "b", "c"); assertThat(list).last().isNotNull(); - assertThat(list).first().isNotEmpty(); - assertThat(list).last().contains("c"); + assertThat(list.getFirst()).isNotEmpty(); + assertThat(list.getLast()).contains("c"); + } + } + """ + ) + ); + } + + @Test + void retainsCharSequenceSpecificAssertions() { + rewriteRun( + //language=java + java( + """ + import java.util.List; + + import static org.assertj.core.api.Assertions.assertThat; + + class FirstElementTest { + void verify(List command) { + assertThat(command.getFirst()).endsWith("setsid"); + } + } + """ + ), + //language=java + java( + """ + import java.util.List; + + import static org.assertj.core.api.Assertions.assertThat; + + class NullableElementTest { + void verify(List values) { + assertThat(values.getFirst()).usingComparator(String.CASE_INSENSITIVE_ORDER).isNull(); + } + } + """ + ), + //language=java + java( + """ + import java.util.List; + + import static org.assertj.core.api.Assertions.assertThat; + + class ConfiguredAssertionTest { + void verify(List values) { + assertThat(values.getFirst()).as("value").startsWith("a"); + } + } + """ + ), + //language=java + java( + """ + import java.util.List; + + import static org.assertj.core.api.Assertions.assertThat; + + class CharSequenceElementTest { + void verify(List values) { + assertThat(values.getFirst()).startsWith("a"); } } """