diff --git a/rewrite-core/src/main/java/org/openrewrite/table/RewriteRecipeSource.java b/rewrite-core/src/main/java/org/openrewrite/table/RewriteRecipeSource.java index 201881218b9..3e6594d35e7 100644 --- a/rewrite-core/src/main/java/org/openrewrite/table/RewriteRecipeSource.java +++ b/rewrite-core/src/main/java/org/openrewrite/table/RewriteRecipeSource.java @@ -53,6 +53,7 @@ public static class Row { public enum RecipeType { Java, + Refaster, Yaml } } diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/recipes/FindRecipesTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/recipes/FindRecipesTest.java index 4426ea61e75..988d0b18deb 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/recipes/FindRecipesTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/recipes/FindRecipesTest.java @@ -19,6 +19,7 @@ import org.openrewrite.DocumentExample; import org.openrewrite.java.JavaParser; import org.openrewrite.table.RewriteRecipeSource; +import org.openrewrite.test.RecipeSpec; import org.openrewrite.test.RewriteTest; import static org.assertj.core.api.Assertions.assertThat; @@ -26,14 +27,16 @@ class FindRecipesTest implements RewriteTest { + @Override + public void defaults(RecipeSpec spec) { + spec.recipe(new FindRecipes()).parser(JavaParser.fromJavaVersion().classpath(JavaParser.runtimeClasspath())); + } + @DocumentExample @Test void findRecipes() { rewriteRun( spec -> spec - .recipe(new FindRecipes()) - .parser(JavaParser.fromJavaVersion() - .classpath(JavaParser.runtimeClasspath())) .dataTable(RewriteRecipeSource.Row.class, rows -> { assertThat(rows).hasSize(1); RewriteRecipeSource.Row row = rows.get(0); @@ -66,7 +69,7 @@ class MyRecipe extends Recipe { public String getDisplayName() { return "My recipe"; } - + @Override public String getDescription() { return "This is my recipe."; @@ -85,19 +88,19 @@ class /*~~>*/MyRecipe extends Recipe { description = "A method pattern that is used to find matching method declarations/invocations.", example = "org.mockito.Matchers anyVararg()") String methodPattern; - + @Option(displayName = "New access level", description = "New method access level to apply to the method, like \\"public\\".", example = "public", valid = {"private", "protected", "package", "public"}, required = false) String newAccessLevel; - + @Override public String getDisplayName() { return "My recipe"; } - + @Override public String getDescription() { return "This is my recipe."; @@ -111,7 +114,6 @@ public String getDescription() { @Test void returnInLambda() { rewriteRun( - spec -> spec.recipe(new FindRecipes()), java( """ import java.util.function.UnaryOperator; @@ -126,4 +128,50 @@ class SomeTest { ) ); } + + @Test + void findRefasterRecipe() { + rewriteRun( + spec -> spec.parser(JavaParser.fromJavaVersion().dependsOn( + """ + package org.openrewrite.java.template; + import java.lang.annotation.ElementType; + import java.lang.annotation.Target; + @Target(ElementType.TYPE) + public @interface RecipeDescriptor { + String name(); + String description(); + } + """ + )) + .dataTable(RewriteRecipeSource.Row.class, rows -> { + assertThat(rows).hasSize(1); + RewriteRecipeSource.Row row = rows.get(0); + assertThat(row.getDisplayName()).isEqualTo("Some refaster rule"); + assertThat(row.getDescription()).isEqualTo("This is a refaster rule."); + }), + java( + """ + import org.openrewrite.java.template.RecipeDescriptor; + + @RecipeDescriptor( + name = "Some refaster rule", + description = "This is a refaster rule." + ) + class SomeRefasterRule { + } + """, + """ + import org.openrewrite.java.template.RecipeDescriptor; + + /*~~>*/@RecipeDescriptor( + name = "Some refaster rule", + description = "This is a refaster rule." + ) + class SomeRefasterRule { + } + """ + ) + ); + } } diff --git a/rewrite-java/src/main/java/org/openrewrite/java/recipes/FindRecipes.java b/rewrite-java/src/main/java/org/openrewrite/java/recipes/FindRecipes.java index 9be46a8dd1e..b00f5e9c14e 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/recipes/FindRecipes.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/recipes/FindRecipes.java @@ -19,11 +19,9 @@ import com.fasterxml.jackson.databind.node.JsonNodeFactory; import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.ValueNode; +import org.jspecify.annotations.NonNull; import org.jspecify.annotations.Nullable; -import org.openrewrite.ExecutionContext; -import org.openrewrite.Preconditions; -import org.openrewrite.Recipe; -import org.openrewrite.TreeVisitor; +import org.openrewrite.*; import org.openrewrite.java.AnnotationMatcher; import org.openrewrite.java.JavaIsoVisitor; import org.openrewrite.java.MethodMatcher; @@ -55,6 +53,65 @@ public String getDescription() { @Override public TreeVisitor getVisitor() { + TreeVisitor findRefasterRecipes = findRefasterRecipes(); + TreeVisitor findImperativeRecipes = findImperativeRecipes(); + return new TreeVisitor() { + @Override + public @Nullable Tree preVisit(@NonNull Tree tree, ExecutionContext ctx) { + stopAfterPreVisit(); + tree = findRefasterRecipes.visit(tree, ctx); + tree = findImperativeRecipes.visit(tree, ctx); + return tree; + } + }; + } + + private TreeVisitor findRefasterRecipes() { + String recipeDescriptor = "org.openrewrite.java.template.RecipeDescriptor"; + AnnotationMatcher annotationMatcher = new AnnotationMatcher("@" + recipeDescriptor); + return Preconditions.check(new UsesType<>(recipeDescriptor, false), new JavaIsoVisitor() { + @Override + public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext ctx) { + J.ClassDeclaration cd = super.visitClassDeclaration(classDecl, ctx); + for (J.Annotation annotation : cd.getLeadingAnnotations()) { + if (annotationMatcher.matches(annotation)) { + String name = null; + String description = null; + for (Expression argument : annotation.getArguments()) { + if (argument instanceof J.Assignment) { + J.Assignment assignment = (J.Assignment) argument; + if (assignment.getVariable() instanceof J.Identifier) { + String simpleName = ((J.Identifier) assignment.getVariable()).getSimpleName(); + if ("name".equals(simpleName)) { + if (assignment.getAssignment() instanceof J.Literal) { + name = (String) ((J.Literal) assignment.getAssignment()).getValue(); + } + } else if ("description".equals(simpleName)) { + if (assignment.getAssignment() instanceof J.Literal) { + description = (String) ((J.Literal) assignment.getAssignment()).getValue(); + } + } + } + } + } + if (name != null && description != null) { + recipeSource.insertRow(ctx, new RewriteRecipeSource.Row( + name, + description, + RewriteRecipeSource.RecipeType.Refaster, + cd.printTrimmed(getCursor()), + "[]" + )); + return SearchResult.found(cd); + } + } + } + return cd; + } + }); + } + + private TreeVisitor findImperativeRecipes() { MethodMatcher getDisplayName = new MethodMatcher("org.openrewrite.Recipe getDisplayName()", true); MethodMatcher getDescription = new MethodMatcher("org.openrewrite.Recipe getDescription()", true); AnnotationMatcher optionAnnotation = new AnnotationMatcher("@org.openrewrite.Option");