Module 7: Refaster recipes
OpenRewrite has support for writing Refaster recipes, which are a way to write code transformations in Java and have them run as recipes.
Refaster recipes are an easy step-up to writing imperative recipes, as they are written in Java and can be run as recipes. Your compiler will help you catch syntax errors, and you can use your IDE to navigate to definitions and references. The generated recipes can also be used as a starting point for more complex recipe implementations.
Exercise 7a: Explore Refaster recipe support
Let's look at an existing Refaster recipe in the starter project, and see how it's implemented.
Goals for this exercise
- Understand how OpenRewrite supports Refaster recipes.
- See how Refaster recipes are written in Java, and how they can be run as recipes.
Steps
- Open the Refaster template src/main/java/com/yourorg/SimplifyTernary.java
- Read through the template, and see how it matches a ternary expression that can be simplified.
- Note that we're only using a limited subset of Refaster's capabilities, as not everything is supported yet.
- Open the unit test src/test/java/com/yourorg/SimplifyTernaryTest.java
- Read through the test, and see how each ternary is simplified, and wrapped as necessary.
- Click through on the
com.yourorg.SimplifyTernaryRecipes
class that is referenced in thedefaults
method of the test to see the generated recipe.- Note how the
SimplifyTernaryRecipes
extendsRecipe
and overridesgetRecipeList()
to return two recipes. - Each inner recipe returns a visitor that extends
AbstractRefasterJavaVisitor
. - There are before and after
JavaTemplates
that are used to match and replace the ternary expressions. - The
visitTernary
method is overridden to match the ternary expressions, and replace them with the simplified version.
- Note how the
- Click through on the
org.openrewrite.java.template.internal.AbstractRefasterJavaVisitor
.- Notice the embedding options, and how those call out to subsequent visitors for cleanups and simplifications.
- Now look back at the visitors in
SimplifyTernaryRecipes
to see which embedding options are enabled there.
- Open the build files
pom.xml
andbuild.gradle
to see how the Refaster recipes are generated.- Notice the
rewrite-templating
dependency and annotation processor. This is what enables generating the Refaster template recipes.
- Notice the
Takeaways
- Refaster templates are converted into regular OpenRewrite recipes, and can be run as such.
- Common base classes, and embedding options lighten the load in implementing Refaster templates.
- Some 400+ recipes from Picnic's ErrorProne Support have made it into rewrite-third-party and the app.moderne.io marketplace.
Exercise 7b: Create a Refaster recipe
Let's create a Refaster recipe that standardizes various ways to check if a String is empty or not.
Goals for this exercise
- Explore IDE support for generating Refaster recipes.
- Write a Refaster template that matches various ways to check if a String is empty.
- Customize the generated recipe, using the tests to cover the various aspects.
Steps
- Open the unit test src/test/java/com/yourorg/StringIsEmptyTest.java
- Read through the test, to get a feel for the cases you should cover.
- Remove the
@Disabled
annotation, and run the test to see that it fails. - Uncomment the
spec.recipe(new StringIsEmptyRecipe());
line, and see that the class is missing. - In the
recipeDocumentation
method, replacenull
withnew StringIsEmptyRecipe()
.
- If you have the Moderne plugin for IntelliJ IDEA installed, you can generate Refaster recipes directly from the IDE.
- A scratch file will be created that you can customize, and add to your recipe module.
- Open the Refaster template src/main/java/com/yourorg/StringIsEmpty.java
- Using the knowledge gained in Exercise 7a, and the requirements from the test, write a Refaster recipe that matches various ways to check if a String is empty.
- Think about if your methods should take in any argument, and what the type of that argument should be.
- Add your first
@BeforeTemplate
and@AfterTemplate
annotated methods to match and replace the first way to check for an empty string.
- Trigger an explicit build of your project to generate the Recipe class with Ctrl + F9 or equivalent.
- Notice how the unit test now compiles; compare the generated recipe with the template you wrote.
- Run the test to see where you stand and add additional
@BeforeTemplate
annotated methods to cover all cases. (If you get stuck, look at the OpenRewrite documentation for some hints.)
- Follow the instructions in the tests to add a name and description to your recipe.
- These will be visible in any generated documentation, when folks run and discover recipes, and in Moderne.
- In case you get completely stuck or just need a reference, here's an example of a completed
StringIsEmpty.java
file.
Takeaways
- Refaster templates can be generated from the IDE, and used as a starting point for more complex recipe implementations.
- A Refaster rule can contain more than one before template, to match different ways to check for an empty string.
- You can customize the Recipe name and description, with the help of the
@RecipeDescriptor
annotation.