Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
import org.openrewrite.java.VariableNameUtils;
import org.openrewrite.java.search.UsesMethod;
import org.openrewrite.java.tree.*;
import org.openrewrite.kotlin.KotlinTemplate;
import org.openrewrite.kotlin.tree.K;
import org.openrewrite.staticanalysis.LambdaBlockToExpression;

import java.util.*;
Expand Down Expand Up @@ -52,6 +54,13 @@ public TreeVisitor<?, ExecutionContext> getVisitor() {
MethodMatcher assertThrowsMatcher = new MethodMatcher(
"org.junit.jupiter.api.Assertions assertThrows(java.lang.Class, org.junit.jupiter.api.function.Executable, ..)");
return Preconditions.check(new UsesMethod<>(assertThrowsMatcher), new JavaIsoVisitor<ExecutionContext>() {
@Override
public boolean isAcceptable(SourceFile sourceFile, ExecutionContext ctx) {
// Only Java and Kotlin are supported, as the extracted variable declaration is rendered per language
return (sourceFile instanceof J.CompilationUnit || sourceFile instanceof K.CompilationUnit) &&
super.isAcceptable(sourceFile, ctx);
}

@Override
public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration methodDecl, ExecutionContext ctx) {
J.MethodDeclaration m = super.visitMethodDeclaration(methodDecl, ctx);
Expand Down Expand Up @@ -142,13 +151,30 @@ public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration methodDecl

private Statement extractExpressionArguments(J.Block body, Statement lambdaStatement, List<Statement> precedingVars, Space varPrefix) {
if (lambdaStatement instanceof J.MethodInvocation) {
boolean kotlin = getCursor().firstEnclosing(K.CompilationUnit.class) != null;
J.MethodInvocation mi = (J.MethodInvocation) lambdaStatement;
Map<String, Integer> generatedVariableSuffixes = new HashMap<>();
return mi.withArguments(ListUtils.map(mi.getArguments(), e -> {
if (e instanceof J.Identifier || e instanceof J.Literal || e instanceof J.Empty || e instanceof J.Lambda || e instanceof J.TypeCast || e instanceof J.FieldAccess) {
return e;
}

String variableName = getVariableName(e, generatedVariableSuffixes);

// Kotlin infers the type; add `val name = expr` as a statement on the method body, since replacing
// the expression in place would wrap the template as `var o = <template>` and fail to parse
if (kotlin) {
J.Block methodBody = getCursor().firstEnclosingOrThrow(J.MethodDeclaration.class).getBody();
Cursor bodyCursor = new Cursor(getCursor(), methodBody);
J.Block applied = KotlinTemplate.builder("val #{} = #{any()}")
.build()
.apply(bodyCursor, methodBody.getCoordinates().lastStatement(), variableName, e);
List<Statement> appliedStatements = applied.getStatements();
J.VariableDeclarations varDecl = (J.VariableDeclarations) appliedStatements.get(appliedStatements.size() - 1);
precedingVars.add(varDecl.withPrefix(varPrefix).withType(e.getType()));
return varDecl.getVariables().get(0).getName().withPrefix(e.getPrefix()).withType(e.getType());
}

Object variableTypeShort = "Object";
JavaType variableTypeFqn = null;
if (e.getType() instanceof JavaType.Primitive) {
Expand All @@ -174,7 +200,7 @@ private Statement extractExpressionArguments(J.Block body, Statement lambdaState

Cursor blockCursor = new Cursor(getCursor(), body);
Cursor c = new Cursor(blockCursor, lambdaStatement);
J.VariableDeclarations varDecl = JavaTemplate.apply("#{} #{} = #{any()};", c, lambdaStatement.getCoordinates().replace(), variableTypeShort, getVariableName(e, generatedVariableSuffixes), e);
J.VariableDeclarations varDecl = JavaTemplate.apply("#{} #{} = #{any()};", c, lambdaStatement.getCoordinates().replace(), variableTypeShort, variableName, e);
precedingVars.add(varDecl.withPrefix(varPrefix).withType(variableTypeFqn));
return varDecl.getVariables().get(0).getName().withPrefix(e.getPrefix()).withType(variableTypeFqn);
}));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,12 @@
import org.openrewrite.InMemoryExecutionContext;
import org.openrewrite.Issue;
import org.openrewrite.java.JavaParser;
import org.openrewrite.kotlin.KotlinParser;
import org.openrewrite.test.RecipeSpec;
import org.openrewrite.test.RewriteTest;

import static org.openrewrite.java.Assertions.java;
import static org.openrewrite.kotlin.Assertions.kotlin;

class AssertThrowsOnLastStatementTest implements RewriteTest {

Expand All @@ -33,9 +35,253 @@ public void defaults(RecipeSpec spec) {
.parser(JavaParser.fromJavaVersion()
//.logCompilationWarningsAndErrors(true)
.classpathFromResources(new InMemoryExecutionContext(), "junit-jupiter-api-5"))
.parser(KotlinParser.builder()
.classpathFromResources(new InMemoryExecutionContext(), "junit-jupiter-api-5"))
.recipe(new AssertThrowsOnLastStatement());
}

@Test
void kotlinExtractsArgumentAsInferredVal() {
rewriteRun(
//language=kotlin
kotlin(
"""
import org.junit.jupiter.api.Assertions.assertThrows

class MyTest {
fun test() {
val exception = assertThrows(IllegalArgumentException::class.java) {
foo()
bar(baz())
}
}
fun foo() {}
fun bar(s: String) {}
fun baz(): String = ""
}
""",
"""
import org.junit.jupiter.api.Assertions.assertThrows

class MyTest {
fun test() {
foo()
val baz = baz()
val exception = assertThrows(IllegalArgumentException::class.java) {
bar(baz)
}
}
fun foo() {}
fun bar(s: String) {}
fun baz(): String = ""
}
"""
)
);
}

@Test
void kotlinHoistsLeadingStatements() {
rewriteRun(
//language=kotlin
kotlin(
"""
import org.junit.jupiter.api.Assertions.assertThrows

class MyTest {
fun test() {
assertThrows(IllegalArgumentException::class.java) {
foo()
foo()
}
}
fun foo() {}
}
""",
"""
import org.junit.jupiter.api.Assertions.assertThrows

class MyTest {
fun test() {
foo()
assertThrows(IllegalArgumentException::class.java) {
foo()
}
}
fun foo() {}
}
"""
)
);
}

@Test
void kotlinMultipleExtractedArgumentsGetUniqueNames() {
rewriteRun(
//language=kotlin
kotlin(
"""
import org.junit.jupiter.api.Assertions.assertThrows

class MyTest {
fun test() {
assertThrows(IllegalArgumentException::class.java) {
foo()
bar(baz(), baz())
}
}
fun foo() {}
fun bar(a: String, b: String) {}
fun baz(): String = ""
}
""",
"""
import org.junit.jupiter.api.Assertions.assertThrows

class MyTest {
fun test() {
foo()
val baz = baz()
val baz1 = baz()
assertThrows(IllegalArgumentException::class.java) {
bar(baz, baz1)
}
}
fun foo() {}
fun bar(a: String, b: String) {}
fun baz(): String = ""
}
"""
)
);
}

@Test
void kotlinReifiedAssertThrowsIsNotChanged() {
rewriteRun(
//language=kotlin
kotlin(
"""
import org.junit.jupiter.api.assertThrows

class MyTest {
fun test() {
assertThrows<IllegalArgumentException> {
foo()
bar(baz())
}
}
fun foo() {}
fun bar(s: String) {}
fun baz(): String = ""
}
"""
)
);
}

@Test
void javaNestedAssertThrows() {
// Only assertThrows calls that are direct method-body statements are rewritten; the inner nested one is left untouched
rewriteRun(
//language=java
java(
"""
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertThrows;

class MyTest {

@Test
void test() {
assertThrows(RuntimeException.class, () -> {
foo();
assertThrows(IllegalArgumentException.class, () -> {
foo();
bar(baz());
});
});
}
void foo() {}
void bar(String s) {}
String baz() { return ""; }
}
""",
"""
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertThrows;

class MyTest {

@Test
void test() {
foo();
assertThrows(RuntimeException.class, () ->
assertThrows(IllegalArgumentException.class, () -> {
foo();
bar(baz());
}));
}
void foo() {}
void bar(String s) {}
String baz() { return ""; }
}
"""
)
);
}

@Test
void javaFactoryMethodArgumentUsesTypeBasedName() {
rewriteRun(
//language=java
java(
"""
import org.junit.jupiter.api.Test;

import java.util.List;

import static org.junit.jupiter.api.Assertions.assertThrows;

class MyTest {

@Test
void test() {
assertThrows(RuntimeException.class, () -> {
doA();
testThing(List.of("a"));
});
}
void doA() {}
void testThing(List<String> values) {}
}
""",
"""
import org.junit.jupiter.api.Test;

import java.util.List;

import static org.junit.jupiter.api.Assertions.assertThrows;

class MyTest {

@Test
void test() {
doA();
List<String> list = List.of("a");
assertThrows(RuntimeException.class, () ->
testThing(list));
}
void doA() {}
void testThing(List<String> values) {}
}
"""
)
);
}

@DocumentExample
@Test
void applyToLastStatementWithDeclaringVariableThreeLines() {
Expand Down
Loading