From 8bffcdadd6e826140625e2ea1ddf63629747a575 Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Sun, 2 Nov 2025 23:34:26 +0100 Subject: [PATCH] Add recipe to simplify AssertJ SequencedCollection assertions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Transforms assertThat(collection.getLast()) to assertThat(collection).last() and assertThat(collection.getFirst()) to assertThat(collection).first() for cleaner and more idiomatic AssertJ assertions on SequencedCollections. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- ...SimplifySequencedCollectionAssertions.java | 89 +++++++++++ .../resources/META-INF/rewrite/assertj.yml | 1 + ...lifySequencedCollectionAssertionsTest.java | 138 ++++++++++++++++++ 3 files changed, 228 insertions(+) create mode 100644 src/main/java/org/openrewrite/java/testing/assertj/SimplifySequencedCollectionAssertions.java create mode 100644 src/test/java/org/openrewrite/java/testing/assertj/SimplifySequencedCollectionAssertionsTest.java diff --git a/src/main/java/org/openrewrite/java/testing/assertj/SimplifySequencedCollectionAssertions.java b/src/main/java/org/openrewrite/java/testing/assertj/SimplifySequencedCollectionAssertions.java new file mode 100644 index 000000000..fa4390bde --- /dev/null +++ b/src/main/java/org/openrewrite/java/testing/assertj/SimplifySequencedCollectionAssertions.java @@ -0,0 +1,89 @@ +/* + * Copyright 2025 the original author or authors. + *

+ * Licensed under the Moderne Source Available License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://docs.moderne.io/licensing/moderne-source-available-license + *

+ * 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.testing.assertj; + +import org.openrewrite.ExecutionContext; +import org.openrewrite.Preconditions; +import org.openrewrite.Recipe; +import org.openrewrite.TreeVisitor; +import org.openrewrite.java.JavaIsoVisitor; +import org.openrewrite.java.JavaParser; +import org.openrewrite.java.JavaTemplate; +import org.openrewrite.java.MethodMatcher; +import org.openrewrite.java.search.UsesMethod; +import org.openrewrite.java.tree.Expression; +import org.openrewrite.java.tree.J; + +public class SimplifySequencedCollectionAssertions extends Recipe { + + 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()"); + + @Override + public String getDisplayName() { + return "Simplify AssertJ assertions on SequencedCollection"; + } + + @Override + public String getDescription() { + return "Simplify AssertJ assertions on SequencedCollection by using dedicated assertion methods. " + + "For example, `assertThat(sequencedCollection.getLast())` can be simplified to `assertThat(sequencedCollection).last()`."; + } + + @Override + public TreeVisitor getVisitor() { + return Preconditions.check( + Preconditions.or( + new UsesMethod<>(GET_FIRST_MATCHER), + new UsesMethod<>(GET_LAST_MATCHER) + ), + new JavaIsoVisitor() { + @Override + public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { + J.MethodInvocation mi = super.visitMethodInvocation(method, ctx); + + // Check if this is an assertThat call + if (!ASSERT_THAT_MATCHER.matches(mi) || mi.getArguments().size() != 1) { + return mi; + } + + // Check if the argument is a method invocation + Expression arg = mi.getArguments().get(0); + if (arg instanceof J.MethodInvocation) { + // Check if the method is getFirst() or getLast() on a SequencedCollection + if (GET_FIRST_MATCHER.matches(arg)) { + return assertThat(mi, (J.MethodInvocation) arg, "first", ctx); + } + if (GET_LAST_MATCHER.matches(arg)) { + return assertThat(mi, (J.MethodInvocation) arg, "last", ctx); + } + } + + return mi; + } + + 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") + .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "assertj-core-3")) + .build() + .apply(getCursor(), mi.getCoordinates().replace(), argMethod.getSelect()); + } + } + ); + } +} diff --git a/src/main/resources/META-INF/rewrite/assertj.yml b/src/main/resources/META-INF/rewrite/assertj.yml index 2f21d5354..53bcd9e27 100644 --- a/src/main/resources/META-INF/rewrite/assertj.yml +++ b/src/main/resources/META-INF/rewrite/assertj.yml @@ -47,6 +47,7 @@ recipeList: - org.openrewrite.java.testing.assertj.SimplifyChainedAssertJAssertions - org.openrewrite.java.testing.assertj.SimplifyAssertJAssertions - org.openrewrite.java.testing.assertj.SimplifyHasSizeAssertion + - org.openrewrite.java.testing.assertj.SimplifySequencedCollectionAssertions - tech.picnic.errorprone.refasterrules.AssertJBigDecimalRulesRecipes - org.openrewrite.java.testing.assertj.AssertJBigIntegerRulesRecipes diff --git a/src/test/java/org/openrewrite/java/testing/assertj/SimplifySequencedCollectionAssertionsTest.java b/src/test/java/org/openrewrite/java/testing/assertj/SimplifySequencedCollectionAssertionsTest.java new file mode 100644 index 000000000..4c6520946 --- /dev/null +++ b/src/test/java/org/openrewrite/java/testing/assertj/SimplifySequencedCollectionAssertionsTest.java @@ -0,0 +1,138 @@ +/* + * Copyright 2025 the original author or authors. + *

+ * Licensed under the Moderne Source Available License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://docs.moderne.io/licensing/moderne-source-available-license + *

+ * 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.testing.assertj; + +import org.junit.jupiter.api.Test; +import org.openrewrite.DocumentExample; +import org.openrewrite.InMemoryExecutionContext; +import org.openrewrite.java.JavaParser; +import org.openrewrite.test.RecipeSpec; +import org.openrewrite.test.RewriteTest; + +import static org.openrewrite.java.Assertions.java; + +class SimplifySequencedCollectionAssertionsTest implements RewriteTest { + @Override + public void defaults(RecipeSpec spec) { + spec + .parser(JavaParser.fromJavaVersion().classpathFromResources(new InMemoryExecutionContext(), "assertj-core-3")) + .recipe(new SimplifySequencedCollectionAssertions()); + } + + @DocumentExample + @Test + void getLast() { + rewriteRun( + //language=java + java( + """ + import java.util.List; + + import static org.assertj.core.api.Assertions.assertThat; + + class MyTest { + void testMethod() { + List list = List.of("a", "b", "c"); + assertThat(list.getLast()).isEqualTo("c"); + } + } + """, + """ + import java.util.List; + + import static org.assertj.core.api.Assertions.assertThat; + + class MyTest { + void testMethod() { + List list = List.of("a", "b", "c"); + assertThat(list).last().isEqualTo("c"); + } + } + """ + ) + ); + } + + @Test + void getFirst() { + rewriteRun( + //language=java + java( + """ + import java.util.List; + + import static org.assertj.core.api.Assertions.assertThat; + + class MyTest { + void testMethod() { + List list = List.of("a", "b", "c"); + assertThat(list.getFirst()).isEqualTo("a"); + } + } + """, + """ + import java.util.List; + + import static org.assertj.core.api.Assertions.assertThat; + + class MyTest { + void testMethod() { + List list = List.of("a", "b", "c"); + assertThat(list).first().isEqualTo("a"); + } + } + """ + ) + ); + } + + @Test + void withDifferentAssertions() { + rewriteRun( + //language=java + java( + """ + import java.util.List; + + import static org.assertj.core.api.Assertions.assertThat; + + class MyTest { + void testMethod() { + List list = List.of("a", "b", "c"); + assertThat(list.getLast()).isNotNull(); + assertThat(list.getFirst()).isNotEmpty(); + assertThat(list.getLast()).contains("c"); + } + } + """, + """ + import java.util.List; + + import static org.assertj.core.api.Assertions.assertThat; + + class MyTest { + void testMethod() { + List list = List.of("a", "b", "c"); + assertThat(list).last().isNotNull(); + assertThat(list).first().isNotEmpty(); + assertThat(list).last().contains("c"); + } + } + """ + ) + ); + } +}