Declarative YAML format
OpenRewrite allows you to create recipes and styles in YAML. While doing so potentially reduces customizability, it makes up for that with development speed and portability.
To help you confidently define recipes and styles in YAML, this guide will walk you through all of the ways you can configure an OpenRewrite YAML file.
Where OpenRewrite YAML files can exist
There are two places where you can define an OpenRewrite YAML file:
- Within the
rewrite.yml
file of a project that applies rewrite recipes via the rewrite-gradle-plugin or rewrite-maven-plugin - Inside the
META-INF/rewrite
folder of a JAR (such as in the rewrite-testing-frameworks)
If you define a recipe or style in the rewrite.yml
file, they will not be included in the JARs published from your project.
If you want to distribute a recipe or a style and apply them to other projects, you'll need to create them inside of the META-INF/rewrite
folder of a JAR.
Best practices
Please keep these conventions in mind when you're creating OpenRewrite YAML files:
- A file may contain any number of recipes and styles, separated by
---
. - Within a file, recipe and style names must be fully qualified.
- Custom recipes should not be placed into the
org.openrewrite
namespace. Instead, they should follow the same reverse domain name notation used in Java packages.
Recipes
Format
You can find the full recipe schema here.
Key | Type | Description |
---|---|---|
type | const | A constant: specs.openrewrite.org/v1beta/recipe |
name | string | A fully qualified, unique name for this recipe |
displayName | string | A human-readable name for this recipe (does not end with a period) |
description | string | A human-readable description for this recipe (ends with a period) |
tags | array of strings | A list of strings that help categorize this recipe |
estimatedEffortPerOccurrence | duration | The expected amount of time saved each time this recipe fixes something |
causesAnotherCycle | boolean | Whether or not this recipe can cause another cycle (defaults to false) |
recipeList | array of recipes | The list of recipes which comprise this recipe |
Preconditions
Preconditions are used to limit which source files a recipe is run on. This is commonly used to target specific files or directories, but any recipe which is not a ScanningRecipe
can be used as a precondition.
Preconditions are a per-file check. If a file passes the precondition check, all recipes will be run on it.
If you need to check if your repository meets certain criteria, instead (e.g., ensuring that a test source set exists), then you will need to write a custom ScanningRecipe
.
When a recipe is used as a precondition, any file it would make a change to is considered to meet the precondition. When more than one recipe is used as a precondition, all of them must make a change to the file for it to be considered to meet the precondition.
Only when all preconditions are met will the recipes in the recipe list be run. When applying preconditions to ScanningRecipes
they limit both the scanning phase and the edit phase.
Changes made by preconditions are not included in the final result of the recipe. Changes made by preconditions are used only to determine if the recipe should be run.
To create these top-level preconditions, you'll need to add the preconditions
map to your declarative recipe's YAML. This object is a list of one or more recipes (formatted the same way as the recipeList).
---
type: specs.openrewrite.org/v1beta/recipe
name: org.openrewrite.PreconditionExample
preconditions:
- org.openrewrite.text.Find:
find: 1
recipeList:
- org.openrewrite.text.ChangeText:
toText: 2
On its own ChangeText
would change the contents of all text files in the project to 2
.
But because Find
is used as a precondition, ChangeText
will only be run on files that contain a 1
.
Recipes commonly used as preconditions include:
org.openrewrite.FindSourceFiles
- limits the recipe to only run on files whose path matches a glob patternorg.openrewrite.text.Find
- limits the recipe to only run on files that contain a given stringorg.openrewrite.java.search.FindTypes
- limits the recipe to run only on source code which contain a given typeorg.openrewrite.java.search.HasJavaVersion
- limits the recipe to run only on Java source code with the specified source or target compatibility versions. Allowing a recipe to be targeted only at Java 8, 11, 17, etc., code.org.openrewrite.java.search.IsLikelyTest
- limits the recipe to run only on source code which is likely to be test code.org.openrewrite.java.search.IsLikelyNotTest
- limits the recipe to run only on source code which is likely to be production code.
Creating "OR" preconditions instead of "AND"
As mentioned above, every recipe in the preconditions
section is run and must apply to a file for it to "meet the precondition". However, what if you wanted to write a recipe where you had many precondition recipes – but you wanted to check if any of them pass rather than all of them?
To do this, you'll want to create a recipe that wraps all of your preconditions up into one recipe, and then use that recipe as the precondition such as in the following example:
type: specs.openrewrite.org/v1beta/recipe
name: org.sample.DoSomething
displayName: Do Something
preconditions:
- org.sample.FindAnyJson
recipeList:
- org.openrewrite.json.ChangeKey:
qwe: qwe
---
type: specs.openrewrite.org/v1beta/recipe
name: org.sample.FindAnyJson
recipeList:
- org.openrewrite.FindSourceFiles:
filePattern: "**/my.json"
- org.openrewrite.FindSourceFiles:
filePattern: "**/your.json"
- org.openrewrite.FindSourceFiles:
filePattern: "**/our.json"
In this example, if a file matches **/my.json
OR **/your.json*
OR **/our.json
, then the precondition has passed and the ChangeKey
recipe will be applied to it.
Recipe list
A declarative recipe can be made up of one or more recipes. The recipes in the list could be other declarative recipes defined in the same file or they can be imperative recipes created elsewhere. Like imperative recipes, each recipe in this list can potentially have configuration options that need to be specified.
Recipes in the recipeList
will run in the order they are listed. That being said, a declarative recipe may include another declarative recipe declared later in the same rewrite.yml
file.
Recipe example
Consider this example declarative recipe:
---
type: specs.openrewrite.org/v1beta/recipe
name: com.yourorg.RecipeA
displayName: Recipe A
description: Applies Recipe B.
tags:
- tag1
- tag2
estimatedEffortPerOccurrence: PT15M
causesAnotherCycle: true
recipeList:
- com.yourorg.RecipeB:
exampleConfig1: foo
exampleConfig2: bar
- com.yourorg.RecipeC
If you wanted to run this recipe (but not distribute it to others), you would:
- Copy the above YAML into a
rewrite.yml
file at the root of your project - Configure the Gradle plugin or Maven plugin to have an active recipe of
com.yourorg.RecipeA
- Run the
mvn rewrite:run
or thegradle rewriteRun
command
Styles
Format
You can find the full style schema here.
Key | Type | Description |
---|---|---|
type | const | A constant: specs.openrewrite.org/v1beta/style |
name | string | A fully qualified, unique name for this style |
displayName | string | A human-readable name for this style (does not end with a period) |
description | string | A human-readable description for this style (ends with a period) |
tags | array of strings | A list of strings that help categorize this style |
styleConfigs | array of styles | The list of styles which comprise this style |
Style example
Consider this example declarative style, which specifies that tabs should be used for indentation and that at least 9999 imports from a given package should be required before collapsing them into a single star import:
---
type: specs.openrewrite.org/v1beta/style
name: com.yourorg.YesTabsNoStarImports
styleConfigs:
- org.openrewrite.java.style.TabsAndIndentsStyle:
useTabCharacter: true
- org.openrewrite.java.style.ImportLayoutStyle:
classCountToUseStarImport: 9999
To put this style in effect for any formatting performed by OpenRewrite within the current project:
- Put the above into a
rewrite.yml
file at the project root - Configure the gradle plugin or maven plugin with
com.yourorg.YesTabsNoStarImports
listed as the active style
The next time any OpenRewrite recipe is run in that project, any formatting it performs will take these styles into account.