Refaster template recipes are recipes created from Refaster templates that refactor code by doing straightforward replacements (e.g., converting StringUtils.equals(..) to Objects.equals(..)). These are more than just a string replacement, though; they offer compiler and type support. They can also be used to build more complex recipes. Please note, though, that Refaster recipes are whitespace agnostic – meaning that you can't use a Refaster recipe to add or remove whitespace.
Let's walk through everything you need to know to get started making your own.
Terminology
Refaster template
A Java class is considered to be a Refaster template if:
There are multiple methods with the same return type
All arguments in those methods are identical and have the same name
One of the methods is annotated with @AfterTemplate
Every other method is annotated with @BeforeTemplate
Here is an example of what a Refaster template looks like:
A Refaster template recipe is an imperative recipe that is created automatically when you build your Java classes that have one or more Refaster templates in them.
You can combine multiple Refaster templates into one larger recipe by creating the templates as subclasses such as in the SimplifyTernary recipe.
Refaster template recipe names
Refaster template recipe names are the class name + Recipe or Recipes depending on if there is more than one template in the class. For example, in the above SimplifyTernary recipe, there are two Refaster templates, so the final recipe name is SimplifyTernaryRecipes. On the other hand, if you look at the example in the Refaster template section, you can see that there is only one template and no wrapper class. Because of that, the recipe would be named StringIsEmptyRecipe.
How to create a Refaster recipe
Moderne provides a starter recipe repository which contains all of the below code. We'd recommend using that template repository as a getting started point.
Update your dependencies
The first thing you'll need to do is update your dependencies and add an annotation processor. Below are the minimum recommended dependencies to include in your project:
build.gradle
dependencies {// The bom version can also be set to a specific version:// https://github.com/openrewrite/rewrite-recipe-bom/releases implementation(platform("org.openrewrite.recipe:rewrite-recipe-bom:latest.release"))// If you are developing recipes in Java, you'll need to bring in rewrite-java implementation("org.openrewrite:rewrite-java") runtimeOnly("org.openrewrite:rewrite-java-17")// Refaster style recipes need the rewrite-templating annotation processor and dependency for generated recipes:// https://github.com/openrewrite/rewrite-templating/releases annotationProcessor("org.openrewrite:rewrite-templating:latest.release") implementation("org.openrewrite:rewrite-templating")// The `@BeforeTemplate` and `@AfterTemplate` annotations are needed for refaster style recipes compileOnly("com.google.errorprone:error_prone_core:2.19.1") { exclude group: "com.google.auto.service", module: "auto-service-annotations" }}
pom.xml
<dependencies> <!-- Refaster style recipes need the rewrite-templating annotation processor and dependency for generated recipes -->
<dependency> <groupId>org.openrewrite</groupId> <artifactId>rewrite-templating</artifactId> </dependency><!-- If you are developing recipes in Java, you'll need to bring in rewrite-java --> <dependency> <groupId>org.openrewrite</groupId> <artifactId>rewrite-java</artifactId> </dependency><!-- The `@BeforeTemplate` and `@AfterTemplate` annotations are needed for refaster style recipes --> <dependency> <groupId>com.google.errorprone</groupId> <artifactId>error_prone_core</artifactId> <version>2.19.1</version> <scope>provided</scope> <exclusions> <exclusion> <groupId>com.google.auto.service</groupId> <artifactId>auto-service-annotations</artifactId> </exclusion> </exclusions> </dependency></dependencies><build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.12.1</version> <configuration> <source>17</source> <target>17</target> <compilerArgs> <arg>-parameters</arg> </compilerArgs> <annotationProcessorPaths> <path> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.32</version> </path> <path> <groupId>org.openrewrite</groupId> <artifactId>rewrite-templating</artifactId> <version>1.6.3</version> </path> </annotationProcessorPaths> </configuration> </plugin> </plugins></build>
Create a Java class
Next up is creating a Java class. In the below example, you can see that we've defined two Refaster templates inside a wrapper class.
A couple of important things to note:
You should provide a RecipeDescriptor with a useful name and description. Doing so will make it easier for others to discover and understand your recipe.
Every template needs one or more @BeforeTemplate and exactly one @AfterTemplate.
You do not need a wrapper class if you want to just make a single template recipe.
packagecom.yourorg;importcom.google.errorprone.refaster.annotation.AfterTemplate;importcom.google.errorprone.refaster.annotation.BeforeTemplate;importorg.openrewrite.java.template.RecipeDescriptor;@RecipeDescriptor( name ="Simplify ternary expressions", description ="Simplifies various types of ternary expressions to improve code readability.")publicclassSimplifyTernary { @RecipeDescriptor( name ="Replace `booleanExpression ? true : false` with `booleanExpression`", description = "Replace ternary expressions like `booleanExpression ? true : false` with `booleanExpression`."
)publicstaticclassSimplifyTernaryTrueFalse { @BeforeTemplatebooleanbefore(boolean expr) {return expr ?true:false; } @AfterTemplatebooleanafter(boolean expr) {return expr; } } @RecipeDescriptor( name ="Replace `booleanExpression ? false : true` with `!booleanExpression`", description = "Replace ternary expressions like `booleanExpression ? false : true` with `!booleanExpression`."
)publicstaticclassSimplifyTernaryFalseTrue { @BeforeTemplatebooleanbefore(boolean expr) {return expr ?false:true; } @AfterTemplatebooleanafter(boolean expr) {return!(expr); } }}
The above Refaster template recipe gets translated into this imperative recipe when your project is built:
packagecom.yourorg;importorg.openrewrite.ExecutionContext;importorg.openrewrite.Preconditions;importorg.openrewrite.Recipe;importorg.openrewrite.TreeVisitor;importorg.openrewrite.internal.lang.NonNullApi;importorg.openrewrite.java.JavaTemplate;importorg.openrewrite.java.JavaVisitor;importorg.openrewrite.java.search.*;importorg.openrewrite.java.template.Primitive;importorg.openrewrite.java.template.Semantics;importorg.openrewrite.java.template.function.*;importorg.openrewrite.java.template.internal.AbstractRefasterJavaVisitor;importorg.openrewrite.java.tree.*;importjava.util.*;importstaticorg.openrewrite.java.template.internal.AbstractRefasterJavaVisitor.EmbeddingOption.*;/** * OpenRewrite recipes created for Refaster template {@code com.yourorg.SimplifyTernary}. */@SuppressWarnings("all")publicclassSimplifyTernaryRecipesextendsRecipe { /** * Instantiates a new instance. */publicSimplifyTernaryRecipes() {} @OverridepublicStringgetDisplayName() {return"Simplify ternary expressions"; } @OverridepublicStringgetDescription() {return"Simplifies various types of ternary expressions to improve code readability."; } @OverridepublicList<Recipe> getRecipeList() {returnArrays.asList(newSimplifyTernaryTrueFalseRecipe(),newSimplifyTernaryFalseTrueRecipe() ); } /** * OpenRewrite recipe created for Refaster template {@code SimplifyTernary.SimplifyTernaryTrueFalse}. */ @SuppressWarnings("all") @NonNullApipublicstaticclassSimplifyTernaryTrueFalseRecipeextendsRecipe { /** * Instantiates a new instance. */publicSimplifyTernaryTrueFalseRecipe() {} @OverridepublicStringgetDisplayName() {return"Replace `booleanExpression ? true : false` with `booleanExpression`"; } @OverridepublicStringgetDescription() {return"Replace ternary expressions like `booleanExpression ? true : false` with `booleanExpression`."; } @OverridepublicTreeVisitor<?,ExecutionContext> getVisitor() {JavaVisitor<ExecutionContext> javaVisitor =newAbstractRefasterJavaVisitor() { final JavaTemplate before = Semantics.expression(this, "before", (@Primitive Boolean expr) -> expr ? true : false).build();
final JavaTemplate after = Semantics.expression(this, "after", (@Primitive Boolean expr) -> expr).build();
@OverridepublicJvisitTernary(J.Ternary elem,ExecutionContext ctx) {JavaTemplate.Matcher matcher;if ((matcher =before.matcher(getCursor())).find()) {returnembed(after.apply(getCursor(),elem.getCoordinates().replace(),matcher.parameter(0)), getCursor(), ctx, SHORTEN_NAMES,SIMPLIFY_BOOLEANS ); }return super.visitTernary(elem, ctx); } };return javaVisitor; } } /** * OpenRewrite recipe created for Refaster template {@code SimplifyTernary.SimplifyTernaryFalseTrue}. */ @SuppressWarnings("all") @NonNullApipublicstaticclassSimplifyTernaryFalseTrueRecipeextendsRecipe { /** * Instantiates a new instance. */publicSimplifyTernaryFalseTrueRecipe() {} @OverridepublicStringgetDisplayName() {return"Replace `booleanExpression ? false : true` with `!booleanExpression`"; } @OverridepublicStringgetDescription() {return"Replace ternary expressions like `booleanExpression ? false : true` with `!booleanExpression`."; } @OverridepublicTreeVisitor<?,ExecutionContext> getVisitor() {JavaVisitor<ExecutionContext> javaVisitor =newAbstractRefasterJavaVisitor() { final JavaTemplate before = Semantics.expression(this, "before", (@Primitive Boolean expr) -> expr ? false : true).build();
final JavaTemplate after = Semantics.expression(this, "after", (@Primitive Boolean expr) -> !(expr)).build();
@OverridepublicJvisitTernary(J.Ternary elem,ExecutionContext ctx) {JavaTemplate.Matcher matcher;if ((matcher =before.matcher(getCursor())).find()) {returnembed(after.apply(getCursor(),elem.getCoordinates().replace(),matcher.parameter(0)), getCursor(), ctx, REMOVE_PARENS, SHORTEN_NAMES,SIMPLIFY_BOOLEANS ); }return super.visitTernary(elem, ctx); } };return javaVisitor; } }}
Create tests
The last step in creating a Refaster template recipe is writing tests. These tests are the same as any other recipe development so our recipe testing guide still applies here.
Here is an example of what the test class might look like for the above recipe:
Congrats! You now know how to make a Refaster template recipe. This is a great way of getting started with recipe development. If you find that you need a bit more in your recipes, remember that you can take the recipe you generated above and then write your own custom visitor. Also remember to check out our recipe conventions and best practices guide to ensure you're writing reliable and scalable recipes.