Skip to content

Commit 541e480

Browse files
Move Junit 5 assertThrows to last statement in lamdba (#590)
* Initial stab at recipe to move assertThrows to last statement in lamdba * Silence the review bot by applying recommended changes * Handle the non-assignment case already * Add a note on another case to check * Handle case where variable is declared with assertThrows * Add test case for assertThrows with error message * Update tests to account for removal of unnecessary curly braces. Also add warning of possible compilation error in description. * Update warning in description --------- Co-authored-by: Tim te Beek <[email protected]>
1 parent 8d8970d commit 541e480

File tree

2 files changed

+401
-0
lines changed

2 files changed

+401
-0
lines changed
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
/*
2+
* Copyright 2021 the original author or authors.
3+
* <p>
4+
* Licensed under the Apache License, Version 2.0 (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://www.apache.org/licenses/LICENSE-2.0
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.junit5;
17+
18+
import org.openrewrite.ExecutionContext;
19+
import org.openrewrite.Preconditions;
20+
import org.openrewrite.Recipe;
21+
import org.openrewrite.TreeVisitor;
22+
import org.openrewrite.internal.ListUtils;
23+
import org.openrewrite.java.JavaIsoVisitor;
24+
import org.openrewrite.java.MethodMatcher;
25+
import org.openrewrite.java.search.UsesMethod;
26+
import org.openrewrite.java.tree.Expression;
27+
import org.openrewrite.java.tree.J;
28+
import org.openrewrite.java.tree.Statement;
29+
import org.openrewrite.staticanalysis.LambdaBlockToExpression;
30+
31+
import java.util.Collections;
32+
import java.util.List;
33+
34+
import static java.util.Collections.singletonList;
35+
36+
37+
public class AssertThrowsOnLastStatement extends Recipe {
38+
39+
@Override
40+
public String getDisplayName() {
41+
return "Applies Junit 5 assertThrows on last statement in lamdba block only";
42+
}
43+
44+
@Override
45+
public String getDescription() {
46+
return "Applies Junit 5 assertThrows on last statement in lambda block only, in extremely rare cases may cause " +
47+
"compilation errors if lambda uses non final variables.";
48+
}
49+
50+
@Override
51+
public TreeVisitor<?, ExecutionContext> getVisitor() {
52+
MethodMatcher assertThrowsMatcher = new MethodMatcher(
53+
"org.junit.jupiter.api.Assertions assertThrows(java.lang.Class, org.junit.jupiter.api.function.Executable, ..)");
54+
return Preconditions.check(new UsesMethod<>(assertThrowsMatcher), new JavaIsoVisitor<ExecutionContext>() {
55+
@Override
56+
public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration methodDecl, ExecutionContext ctx) {
57+
J.MethodDeclaration m = super.visitMethodDeclaration(methodDecl, ctx);
58+
59+
m = m.withBody(m.getBody().withStatements(ListUtils.flatMap(m.getBody().getStatements(), methodStatement -> {
60+
J statementToCheck = methodStatement;
61+
final J.VariableDeclarations assertThrowsWithVarDec;
62+
final J.VariableDeclarations.NamedVariable assertThrowsVar;
63+
64+
if (methodStatement instanceof J.VariableDeclarations) {
65+
assertThrowsWithVarDec = (J.VariableDeclarations) methodStatement;
66+
List<J.VariableDeclarations.NamedVariable> assertThrowsNamedVars = assertThrowsWithVarDec.getVariables();
67+
if (assertThrowsNamedVars.size() != 1) {
68+
return methodStatement;
69+
}
70+
71+
// has variable declaration for assertThrows eg Throwable ex = assertThrows(....)
72+
assertThrowsVar = assertThrowsNamedVars.get(0);
73+
statementToCheck = assertThrowsVar.getInitializer();
74+
} else {
75+
assertThrowsWithVarDec = null;
76+
assertThrowsVar = null;
77+
}
78+
79+
if (!(statementToCheck instanceof J.MethodInvocation)) {
80+
return methodStatement;
81+
}
82+
83+
J.MethodInvocation methodInvocation = (J.MethodInvocation) statementToCheck;
84+
if (!assertThrowsMatcher.matches(methodInvocation)) {
85+
return methodStatement;
86+
}
87+
88+
List<Expression> arguments = methodInvocation.getArguments();
89+
if (arguments.size() <= 1) {
90+
return methodStatement;
91+
}
92+
93+
Expression arg = arguments.get(1);
94+
if (!(arg instanceof J.Lambda)) {
95+
return methodStatement;
96+
}
97+
98+
J.Lambda lambda = (J.Lambda) arg;
99+
if (!(lambda.getBody() instanceof J.Block)) {
100+
return methodStatement;
101+
}
102+
103+
J.Block body = (J.Block) lambda.getBody();
104+
if (body == null) {
105+
return methodStatement;
106+
}
107+
108+
List<Statement> lambdaStatements = body.getStatements();
109+
if (lambdaStatements.size() <= 1) {
110+
return methodStatement;
111+
}
112+
113+
// TODO Check to see if last line in lambda does not use a non-final variable
114+
115+
// move all the statements from the body into before the method invocation, except last one
116+
return ListUtils.map(lambdaStatements, (idx, lambdaStatement) -> {
117+
if (idx < lambdaStatements.size() - 1) {
118+
return lambdaStatement.withPrefix(methodStatement.getPrefix().withComments(Collections.emptyList()));
119+
}
120+
121+
J.MethodInvocation newAssertThrows = methodInvocation.withArguments(
122+
ListUtils.map(arguments, (argIdx, argument) -> {
123+
if (argIdx == 1) {
124+
// Only retain the last statement in the lambda block
125+
return lambda.withBody(body.withStatements(singletonList(lambdaStatement)));
126+
}
127+
return argument;
128+
})
129+
);
130+
131+
if (assertThrowsWithVarDec == null) {
132+
return newAssertThrows;
133+
}
134+
135+
J.VariableDeclarations.NamedVariable newAssertThrowsVar = assertThrowsVar.withInitializer(newAssertThrows);
136+
return assertThrowsWithVarDec.withVariables(singletonList(newAssertThrowsVar));
137+
});
138+
139+
})));
140+
141+
doAfterVisit(new LambdaBlockToExpression().getVisitor());
142+
return m;
143+
}
144+
});
145+
}
146+
}

0 commit comments

Comments
 (0)