From 1dd7ed3abd536ceba7663a8f3310d4ffe4c79906 Mon Sep 17 00:00:00 2001 From: martinfrancois Date: Wed, 12 Aug 2026 19:53:33 +0200 Subject: [PATCH 1/2] Preserve string-specific sequenced assertions --- ...SimplifySequencedCollectionAssertions.java | 37 +++++++++++ ...lifySequencedCollectionAssertionsTest.java | 66 ++++++++++++++++++- 2 files changed, 101 insertions(+), 2 deletions(-) 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..6e07dc69f 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,6 +28,7 @@ import org.openrewrite.java.search.UsesMethod; import org.openrewrite.java.tree.Expression; import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.TypeUtils; public class SimplifySequencedCollectionAssertions extends Recipe { @@ -63,9 +65,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 +81,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 ("isEqualTo".equals(assertion.getSimpleName()) || "isNotEqualTo".equals(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"); } } """ From 2be5cd74fb4816e6873a31cc850c51c82c926fc9 Mon Sep 17 00:00:00 2001 From: martinfrancois Date: Sun, 16 Aug 2026 23:04:12 +0200 Subject: [PATCH 2/2] Extract object-equality assertion names into a named constant --- .../assertj/SimplifySequencedCollectionAssertions.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) 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 6e07dc69f..0f9dd6315 100644 --- a/src/main/java/org/openrewrite/java/testing/assertj/SimplifySequencedCollectionAssertions.java +++ b/src/main/java/org/openrewrite/java/testing/assertj/SimplifySequencedCollectionAssertions.java @@ -30,8 +30,15 @@ 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()"); @@ -103,7 +110,7 @@ private boolean usesCharSequenceSpecificAssertion(J.MethodInvocation elementAcce } private boolean isCharSequenceSpecificAssertion(J.MethodInvocation assertion) { - if ("isEqualTo".equals(assertion.getSimpleName()) || "isNotEqualTo".equals(assertion.getSimpleName())) { + if (OBJECT_EQUALITY_ASSERTIONS.contains(assertion.getSimpleName())) { return false; } return assertion.getMethodType() != null && TypeUtils.isAssignableTo(