Skip to content

Commit e3dcfe4

Browse files
committed
Add incubating TemporaryFolderToTempDir Junit 4->5 visitor
1 parent df54124 commit e3dcfe4

File tree

3 files changed

+311
-1
lines changed

3 files changed

+311
-1
lines changed

src/before/java/org/openrewrite/java/testing/junit5/ExampleJunitTestClass.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@
2222
import org.junit.rules.TemporaryFolder;
2323
import org.junit.rules.Timeout;
2424

25+
import java.io.File;
26+
import java.io.IOException;
27+
2528
public class ExampleJunitTestClass {
2629

2730
@Rule
@@ -37,7 +40,13 @@ public static void beforeClass() { }
3740
public static void afterClass() {}
3841

3942
@Test(expected = RuntimeException.class)
40-
public void foo() {
43+
public void foo() throws IOException {
44+
File tempFile = folder.newFile();
45+
File tempFile2 = folder.newFile("filename");
46+
File tempDir = folder.getRoot();
47+
File tempDir2 = folder.newFolder("parent", "child");
48+
File tempDir3 = folder.newFolder("subdir");
49+
File tempDir4 = folder.newFolder();
4150
String foo = "foo";
4251
throw new RuntimeException(foo);
4352
}
Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
package org.openrewrite.java.testing.junit5;
2+
3+
import org.openrewrite.Formatting;
4+
import org.openrewrite.Incubating;
5+
import org.openrewrite.java.AutoFormat;
6+
import org.openrewrite.java.JavaIsoRefactorVisitor;
7+
import org.openrewrite.java.tree.Expression;
8+
import org.openrewrite.java.tree.J;
9+
import org.openrewrite.java.tree.JavaType;
10+
import org.openrewrite.java.tree.Statement;
11+
import org.openrewrite.java.tree.TypeUtils;
12+
13+
import java.util.ArrayList;
14+
import java.util.List;
15+
import java.util.stream.Collectors;
16+
import java.util.stream.Stream;
17+
18+
import static org.openrewrite.Tree.randomId;
19+
20+
/**
21+
* Translates JUnit4's org.junit.rules.TemporaryFolder into JUnit 5's org.junit.jupiter.api.io.TempDir
22+
*/
23+
@Incubating(since = "0.1.2")
24+
// Keeping this commented out until this class is finished
25+
//@AutoConfigure
26+
public class TemporaryFolderToTempDir extends JavaIsoRefactorVisitor {
27+
28+
private static final String RuleFqn = "org.junit.Rule";
29+
private static final String TemporaryFolderFqn = "org.junit.rules.TemporaryFolder";
30+
private static final JavaType.Class TempDirType = JavaType.Class.build("org.junit.jupiter.api.io.TempDir");
31+
private static final J.Ident TempDirIdent = J.Ident.build(randomId(), "TempDir", TempDirType, Formatting.EMPTY);
32+
private static final JavaType.Class FileType = JavaType.Class.build("java.io.File");
33+
private static final J.Ident FileIdent = J.Ident.build(randomId(), "File", FileType, Formatting.EMPTY);
34+
private static final String IOExceptionFqn = "java.io.IOException";
35+
private static final JavaType.Class IOExceptionType = JavaType.Class.build(IOExceptionFqn);
36+
private static final JavaType.Class StringType = JavaType.Class.build("java.lang.String");
37+
38+
@Override
39+
public J.ClassDecl visitClassDecl(J.ClassDecl classDecl) {
40+
J.ClassDecl cd = super.visitClassDecl(classDecl);
41+
42+
if(cd.getFields().stream().filter(it -> TypeUtils.hasElementType(it.getTypeAsClass(), TemporaryFolderFqn)).findAny().isPresent()) {
43+
List<J> newStatements = cd.getBody().getStatements().stream()
44+
.map(this::convertTempFolderField)
45+
.collect(Collectors.toList());
46+
47+
cd = cd.withBody(cd.getBody().withStatements(newStatements));
48+
}
49+
50+
return cd;
51+
}
52+
53+
/**
54+
* Given a J.VariableDecls that looks like:
55+
* <pre>
56+
* @Rule
57+
* public TemporaryFolder folder = new TemporaryFolder();
58+
* </pre>
59+
* Turn it into:
60+
* <pre>
61+
* @TempDir
62+
* public File folder
63+
* </pre>
64+
*
65+
* Any parameter that doesn't match that input is returned unaltered.
66+
*
67+
* NOT a pure function. Notable side effects include:
68+
* Adding removing/imports as necessary.
69+
* Scheduling visitors to handle formatting
70+
* Scheduling visitors to update method invocations
71+
*/
72+
private J convertTempFolderField(J statement) {
73+
if(!(statement instanceof J.VariableDecls)) {
74+
return statement;
75+
}
76+
J.VariableDecls field = (J.VariableDecls) statement;
77+
if(field.getTypeAsClass() == null || !field.getTypeAsClass().getFullyQualifiedName().equals(TemporaryFolderFqn)) {
78+
return field;
79+
}
80+
// filter out the @Rule annotation and add the @TempDir annotation
81+
82+
List<J.Annotation> newAnnotations = Stream.concat(
83+
Stream.of(new J.Annotation(randomId(), TempDirIdent, null, Formatting.EMPTY)),
84+
field.getAnnotations().stream()
85+
.filter(it -> !TypeUtils.hasElementType(it.getType(), RuleFqn)))
86+
.collect(Collectors.toList());
87+
field = field.withAnnotations(newAnnotations);
88+
89+
// Remove the initializing expression for "new TemporaryFolder()"
90+
List<J.VariableDecls.NamedVar> newVars = field.getVars().stream()
91+
.map(it -> it.withInitializer(null))
92+
// Remove the space from the suffix of the name that used to proceed the `=` bit of the assignment
93+
.map(it -> it.withName(it.getName().withFormatting(Formatting.EMPTY)))
94+
.collect(Collectors.toList());
95+
field = field.withVars(newVars);
96+
97+
// Transfer the formatting from the old field Ident onto the new field Ident
98+
Formatting originalTypeFormatting = (field.getTypeExpr() == null) ? Formatting.EMPTY : field.getTypeExpr().getFormatting();
99+
field = field.withTypeExpr(FileIdent.withFormatting(originalTypeFormatting));
100+
101+
maybeAddImport(FileType);
102+
maybeAddImport(TempDirType);
103+
maybeRemoveImport(RuleFqn);
104+
maybeRemoveImport(TemporaryFolderFqn);
105+
newVars.stream().forEach(fieldVar -> andThen(new ReplaceTemporaryFolderMethods(fieldVar.getSimpleName())));
106+
andThen(new AutoFormat(field));
107+
108+
return field;
109+
}
110+
111+
/**
112+
* This visitor replaces these methods from TemporaryFolder with a JUnit5-compatible alternative:
113+
*
114+
* File newFile()
115+
* File newFile(String fileName)
116+
*
117+
* When complete it will also replace these TemporaryFolder methods:
118+
* File getRoot()
119+
* File newFolder()
120+
* File newFolder(String... folderNames)
121+
* File newFolder(String folder)
122+
*
123+
*/
124+
private static class ReplaceTemporaryFolderMethods extends JavaIsoRefactorVisitor {
125+
private final String fieldName;
126+
ReplaceTemporaryFolderMethods(String fieldName) {
127+
this.fieldName = fieldName;
128+
setCursoringOn();
129+
}
130+
131+
@Override
132+
public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method) {
133+
J.MethodInvocation m = super.visitMethodInvocation(method);
134+
if(!(m.getSelect() instanceof J.Ident)) {
135+
return m;
136+
}
137+
J.Ident receiver = (J.Ident) m.getSelect();
138+
if(receiver.getSimpleName().equals(fieldName) &&
139+
m.getType() != null &&
140+
TypeUtils.hasElementType(m.getType().getDeclaringType(), TemporaryFolderFqn)
141+
) {
142+
assert getCursor().getParent() != null;
143+
List<Expression> args = m.getArgs().getArgs();
144+
// handle TemporaryFolder.newFile() and TemporaryFolder.newFile(String)
145+
if(m.getName().getSimpleName().equals("newFile")) {
146+
if(args.size() == 1 && args.get(0) instanceof J.Empty) {
147+
m = treeBuilder.buildSnippet(
148+
getCursor().getParent(),
149+
"File.createTempFile(\"junit\", null, " + fieldName + ");",
150+
FileType
151+
).get(0).withFormatting(Formatting.format(" "));
152+
} else {
153+
andThen(new AddNewFileFunction());
154+
m = treeBuilder.buildSnippet(
155+
getCursor().getParent(),
156+
"newFile(" + fieldName + ", "+ args.get(0).printTrimmed() +")",
157+
FileType, args.get(0).getType()
158+
).get(0).withFormatting(Formatting.format(" "));
159+
}
160+
}
161+
162+
}
163+
return m;
164+
}
165+
}
166+
167+
/**
168+
* Adds a method like this one to the target class:
169+
* private File newFile(File dir, String fileName) throws IOException {
170+
* File file = new File(getRoot(), fileName);
171+
* file.createNewFile();
172+
* return file;
173+
* }
174+
*
175+
* This generated method is intended to be a substitute for TemporaryFolder.newFile(String)
176+
*/
177+
private static class AddNewFileFunction extends JavaIsoRefactorVisitor {
178+
@Override
179+
public J.ClassDecl visitClassDecl(J.ClassDecl classDecl) {
180+
J.ClassDecl cd = super.visitClassDecl(classDecl);
181+
boolean methodAlreadyExists = cd.getMethods().stream()
182+
.filter(m -> {
183+
List<Statement> params = m.getParams().getParams();
184+
185+
return m.getSimpleName().equals("newFile")
186+
&& params.size() == 2
187+
&& params.get(0).hasClassType(FileType)
188+
&& params.get(1).hasClassType(StringType);
189+
})
190+
.findAny().isPresent();
191+
if(!methodAlreadyExists) {
192+
List<J> statements = new ArrayList<>(cd.getBody().getStatements());
193+
J.MethodDecl newFileMethod = treeBuilder.buildMethodDeclaration(
194+
cd,
195+
"private File newFile(File dir, String fileName) throws IOException {\n" +
196+
" File file = new File(getRoot(), fileName);\n" +
197+
" file.createNewFile();\n" +
198+
" return file;\n" +
199+
"}\n",
200+
FileType,
201+
IOExceptionType);
202+
statements.add(newFileMethod);
203+
cd = cd.withBody(cd.getBody().withStatements(statements));
204+
andThen(new AutoFormat(newFileMethod));
205+
}
206+
return cd;
207+
}
208+
}
209+
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
package org.openrewrite.java.testing.junit5
2+
3+
import org.junit.jupiter.api.Test
4+
import org.openrewrite.RefactorVisitorTestForParser
5+
import org.openrewrite.java.JavaParser
6+
import org.openrewrite.java.tree.J
7+
8+
class TemporaryFolderToTempDirTest : RefactorVisitorTestForParser<J.CompilationUnit> {
9+
override val parser: JavaParser = JavaParser.fromJavaVersion()
10+
.classpath("junit")
11+
.build()
12+
13+
override val visitors = listOf(TemporaryFolderToTempDir())
14+
15+
@Test
16+
fun basicReplace() = assertRefactored(
17+
before = """
18+
import org.junit.Rule;
19+
import org.junit.rules.TemporaryFolder;
20+
21+
class A {
22+
23+
@Rule
24+
TemporaryFolder tempDir = new TemporaryFolder();
25+
}
26+
""",
27+
after = """
28+
import org.junit.jupiter.api.io.TempDir;
29+
30+
import java.io.File;
31+
32+
class A {
33+
34+
@TempDir
35+
File tempDir;
36+
}
37+
"""
38+
)
39+
40+
@Test
41+
fun replacesNewFileNoArgs() = assertRefactored(
42+
before = """
43+
import org.junit.Rule;
44+
import org.junit.rules.TemporaryFolder;
45+
46+
import java.io.File;
47+
import java.io.IOException;
48+
49+
class A {
50+
51+
@Rule
52+
TemporaryFolder tempDir1 = new TemporaryFolder();
53+
54+
@Rule
55+
TemporaryFolder tempDir2 = new TemporaryFolder();
56+
57+
File file2 = tempDir2.newFile("sam");
58+
59+
void foo() throws IOException {
60+
File file1 = tempDir1.newFile();
61+
}
62+
}
63+
""",
64+
after = """
65+
import org.junit.jupiter.api.io.TempDir;
66+
67+
import java.io.File;
68+
import java.io.IOException;
69+
70+
class A {
71+
72+
@TempDir
73+
File tempDir1;
74+
75+
@TempDir
76+
File tempDir2;
77+
78+
File file2 = newFile(tempDir2, "sam");
79+
80+
void foo() throws IOException {
81+
File file1 = File.createTempFile("junit", null, tempDir1);
82+
}
83+
84+
private File newFile(File dir, String fileName) throws IOException {
85+
File file = new File(getRoot(), fileName);
86+
file.createNewFile();
87+
return file;
88+
}
89+
}
90+
"""
91+
)
92+
}

0 commit comments

Comments
 (0)