Skip to content

Commit 8aeb674

Browse files
authored
Add AssertEqualsIntegralDeltaToAssertEquals recipe (#950)
* Add AssertEqualsIntegralDeltaToAssertEquals recipe (#869) Remove the unnecessary delta/precision argument from assertEquals when both expected and actual are int or long types, since the delta is meaningless for exact integer comparison. * Inline JavaParser initializer instead of caching in field * Update generated recipes.csv
1 parent 135a45b commit 8aeb674

4 files changed

Lines changed: 376 additions & 18 deletions

File tree

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
/*
2+
* Copyright 2025 the original author or authors.
3+
* <p>
4+
* Licensed under the Moderne Source Available License (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
* <p>
8+
* https://docs.moderne.io/licensing/moderne-source-available-license
9+
* <p>
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.openrewrite.java.testing.cleanup;
17+
18+
import lombok.Getter;
19+
import org.openrewrite.ExecutionContext;
20+
import org.openrewrite.Preconditions;
21+
import org.openrewrite.Recipe;
22+
import org.openrewrite.TreeVisitor;
23+
import org.openrewrite.java.JavaParser;
24+
import org.openrewrite.java.JavaTemplate;
25+
import org.openrewrite.java.JavaVisitor;
26+
import org.openrewrite.java.MethodMatcher;
27+
import org.openrewrite.java.search.UsesMethod;
28+
import org.openrewrite.java.tree.Expression;
29+
import org.openrewrite.java.tree.J;
30+
import org.openrewrite.java.tree.JavaType;
31+
import org.openrewrite.java.tree.TypeUtils;
32+
33+
import java.util.List;
34+
35+
public class AssertEqualsIntegralDeltaToAssertEquals extends Recipe {
36+
private static final MethodMatcher ASSERT_EQUALS = new MethodMatcher(
37+
"org.junit.jupiter.api.Assertions assertEquals(..)");
38+
39+
@Getter
40+
final String displayName = "Remove unnecessary `assertEquals` delta argument for integral types";
41+
42+
@Getter
43+
final String description = "Remove the delta argument from `assertEquals()` when both expected and actual are " +
44+
"`int` or `long` types, since the delta is meaningless for exact integer comparison. " +
45+
"Integer arguments get unnecessarily upcasted to `double` when a delta is provided.";
46+
47+
@Override
48+
public TreeVisitor<?, ExecutionContext> getVisitor() {
49+
return Preconditions.check(new UsesMethod<>(ASSERT_EQUALS), new JavaVisitor<ExecutionContext>() {
50+
51+
@Override
52+
public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) {
53+
J.MethodInvocation mi = (J.MethodInvocation) super.visitMethodInvocation(method, ctx);
54+
if (!ASSERT_EQUALS.matches(mi)) {
55+
return mi;
56+
}
57+
58+
List<Expression> args = mi.getArguments();
59+
if (args.size() == 3 && isIntegralType(args.get(0)) && isIntegralType(args.get(1)) && isNumericType(args.get(2))) {
60+
// assertEquals(expected, actual, delta) -> assertEquals(expected, actual)
61+
StringBuilder sb = new StringBuilder();
62+
if (mi.getSelect() != null) {
63+
sb.append("Assertions.");
64+
}
65+
sb.append("assertEquals(#{any()}, #{any()})");
66+
JavaTemplate t;
67+
if (mi.getSelect() == null) {
68+
t = JavaTemplate.builder(sb.toString())
69+
.contextSensitive()
70+
.staticImports("org.junit.jupiter.api.Assertions.assertEquals")
71+
.javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "junit-jupiter-api-5"))
72+
.build();
73+
} else {
74+
t = JavaTemplate.builder(sb.toString())
75+
.contextSensitive()
76+
.imports("org.junit.jupiter.api.Assertions")
77+
.javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "junit-jupiter-api-5"))
78+
.build();
79+
}
80+
return t.apply(updateCursor(mi), mi.getCoordinates().replace(), args.get(0), args.get(1));
81+
}
82+
83+
if (args.size() == 4 && isIntegralType(args.get(0)) && isIntegralType(args.get(1)) && isNumericType(args.get(2))) {
84+
// assertEquals(expected, actual, delta, message) -> assertEquals(expected, actual, message)
85+
StringBuilder sb = new StringBuilder();
86+
if (mi.getSelect() != null) {
87+
sb.append("Assertions.");
88+
}
89+
sb.append("assertEquals(#{any()}, #{any()}, #{any()})");
90+
JavaTemplate t;
91+
if (mi.getSelect() == null) {
92+
t = JavaTemplate.builder(sb.toString())
93+
.contextSensitive()
94+
.staticImports("org.junit.jupiter.api.Assertions.assertEquals")
95+
.javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "junit-jupiter-api-5"))
96+
.build();
97+
} else {
98+
t = JavaTemplate.builder(sb.toString())
99+
.contextSensitive()
100+
.imports("org.junit.jupiter.api.Assertions")
101+
.javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "junit-jupiter-api-5"))
102+
.build();
103+
}
104+
return t.apply(updateCursor(mi), mi.getCoordinates().replace(), args.get(0), args.get(1), args.get(3));
105+
}
106+
107+
return mi;
108+
}
109+
110+
private boolean isIntegralType(Expression expression) {
111+
JavaType.FullyQualified fq = TypeUtils.asFullyQualified(expression.getType());
112+
if (fq != null) {
113+
String typeName = fq.getFullyQualifiedName();
114+
return "java.lang.Long".equals(typeName) || "java.lang.Integer".equals(typeName);
115+
}
116+
JavaType.Primitive p = TypeUtils.asPrimitive(expression.getType());
117+
return p == JavaType.Primitive.Long || p == JavaType.Primitive.Int;
118+
}
119+
120+
private boolean isNumericType(Expression expression) {
121+
JavaType.FullyQualified fq = TypeUtils.asFullyQualified(expression.getType());
122+
if (fq != null) {
123+
String typeName = fq.getFullyQualifiedName();
124+
return "java.lang.Double".equals(typeName) || "java.lang.Float".equals(typeName) ||
125+
"java.lang.Long".equals(typeName) || "java.lang.Integer".equals(typeName) ||
126+
"java.lang.Short".equals(typeName) || "java.lang.Byte".equals(typeName);
127+
}
128+
JavaType.Primitive p = TypeUtils.asPrimitive(expression.getType());
129+
return p == JavaType.Primitive.Double || p == JavaType.Primitive.Float ||
130+
p == JavaType.Primitive.Long || p == JavaType.Primitive.Int ||
131+
p == JavaType.Primitive.Short || p == JavaType.Primitive.Byte;
132+
}
133+
});
134+
}
135+
}

src/main/resources/META-INF/rewrite/junit-jupiter.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ recipeList:
3131
- org.openrewrite.java.testing.junit5.CleanupKotlinJUnit5AssertionImports
3232
- org.openrewrite.java.testing.junit5.CleanupAssertions
3333
- org.openrewrite.java.testing.junit5.CsvSourceToValueSource
34+
- org.openrewrite.java.testing.cleanup.AssertEqualsIntegralDeltaToAssertEquals
3435
- org.openrewrite.java.testing.cleanup.AssertLiteralBooleanToFailRecipes
3536
- org.openrewrite.java.testing.cleanup.AssertLiteralBooleanRemovedRecipe
3637
- org.openrewrite.java.testing.cleanup.KotlinTestMethodsShouldReturnUnit

0 commit comments

Comments
 (0)