2020import org .openrewrite .Preconditions ;
2121import org .openrewrite .Recipe ;
2222import org .openrewrite .TreeVisitor ;
23+ import org .openrewrite .java .AnnotationMatcher ;
2324import org .openrewrite .java .ChangeType ;
2425import org .openrewrite .java .JavaIsoVisitor ;
26+ import org .openrewrite .java .RemoveAnnotationVisitor ;
2527import org .openrewrite .java .search .UsesType ;
2628import org .openrewrite .java .tree .J ;
2729
30+ import java .util .LinkedHashMap ;
31+ import java .util .Map ;
32+
2833public class UpdateBeforeAfterAnnotations extends Recipe {
2934 @ Getter
3035 final String displayName = "Migrate JUnit 4 lifecycle annotations to JUnit Jupiter" ;
3136
3237 @ Getter
3338 final String description = "Replace JUnit 4's `@Before`, `@BeforeClass`, `@After`, and `@AfterClass` annotations with their JUnit Jupiter equivalents." ;
3439
40+ private static final Map <String , String > JUNIT4_TO_JUPITER = new LinkedHashMap <String , String >() {{
41+ put ("org.junit.Before" , "org.junit.jupiter.api.BeforeEach" );
42+ put ("org.junit.After" , "org.junit.jupiter.api.AfterEach" );
43+ put ("org.junit.BeforeClass" , "org.junit.jupiter.api.BeforeAll" );
44+ put ("org.junit.AfterClass" , "org.junit.jupiter.api.AfterAll" );
45+ }};
46+
3547 @ Override
3648 public TreeVisitor <?, ExecutionContext > getVisitor () {
3749 return Preconditions .check (Preconditions .or (
@@ -48,11 +60,30 @@ public static class UpdateBeforeAfterAnnotationsVisitor extends JavaIsoVisitor<E
4860 @ Override
4961 public J preVisit (J tree , ExecutionContext ctx ) {
5062 stopAfterPreVisit ();
51- doAfterVisit ( new ChangeType ( "org.junit.Before" , "org.junit.jupiter.api.BeforeEach" , true ). getVisitor ());
52- doAfterVisit ( new ChangeType ( "org.junit.After" , "org.junit.jupiter.api.AfterEach" , true ). getVisitor ());
53- doAfterVisit (new ChangeType ( "org.junit.BeforeClass" , "org.junit.jupiter.api.BeforeAll" , true ). getVisitor ());
54- doAfterVisit (new ChangeType ("org.junit.AfterClass" , "org.junit.jupiter.api.AfterAll" , true ).getVisitor ());
63+ // Remove the JUnit 4 annotation where the Jupiter equivalent is already present, to avoid creating a
64+ // duplicate annotation when the type is subsequently changed below.
65+ doAfterVisit (new RemoveRedundantJUnit4LifecycleAnnotations ());
66+ JUNIT4_TO_JUPITER . forEach (( from , to ) -> doAfterVisit (new ChangeType (from , to , true ).getVisitor () ));
5567 return tree ;
5668 }
5769 }
70+
71+ private static class RemoveRedundantJUnit4LifecycleAnnotations extends JavaIsoVisitor <ExecutionContext > {
72+
73+ @ Override
74+ public J .MethodDeclaration visitMethodDeclaration (J .MethodDeclaration method , ExecutionContext ctx ) {
75+ J .MethodDeclaration md = super .visitMethodDeclaration (method , ctx );
76+ for (Map .Entry <String , String > pair : JUNIT4_TO_JUPITER .entrySet ()) {
77+ AnnotationMatcher junit4 = new AnnotationMatcher ("@" + pair .getKey ());
78+ AnnotationMatcher jupiter = new AnnotationMatcher ("@" + pair .getValue ());
79+ boolean hasJunit4 = md .getLeadingAnnotations ().stream ().anyMatch (junit4 ::matches );
80+ boolean hasJupiter = md .getLeadingAnnotations ().stream ().anyMatch (jupiter ::matches );
81+ if (hasJunit4 && hasJupiter ) {
82+ md = (J .MethodDeclaration ) new RemoveAnnotationVisitor (junit4 )
83+ .visitNonNull (md , ctx , getCursor ().getParentOrThrow ());
84+ }
85+ }
86+ return md ;
87+ }
88+ }
5889}
0 commit comments