Skip to content

Commit

Permalink
Add description of yaml preconditions.
Browse files Browse the repository at this point in the history
  • Loading branch information
sambsnyd committed Nov 13, 2023
1 parent db3e1f5 commit 4ea79a5
Show file tree
Hide file tree
Showing 2 changed files with 48 additions and 7 deletions.
16 changes: 10 additions & 6 deletions concepts-and-explanations/recipes.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public class ChangeType extends Recipe {
// In many cases, the visitor is implemented as a private, inner class. This
// ensures that the visitor is only used via its managed, configured recipe.
private class ChangeTypeVisitor extends JavaVisitor<ExecutionContext> {
...
//...
}

}
Expand Down Expand Up @@ -145,7 +145,7 @@ public class ChangeType extends Recipe {
return "Change a given type to another.";
}
...
//...
}
```

Expand All @@ -157,14 +157,19 @@ There are a few recommended best practices when defining metadata for a recipe:

## Scanning Recipes

A recipe should be a `ScanningRecipe` if it needs to change many source files. A `ScanningRecipe` extends the normal `Recipe` and adds two key objects: an [accumulator](https://github.com/openrewrite/rewrite/blob/v8.1.1/rewrite-core/src/main/java/org/openrewrite/ScanningRecipe.java#L88-L90) and a [scanner](https://github.com/openrewrite/rewrite/blob/v8.1.1/rewrite-core/src/main/java/org/openrewrite/ScanningRecipe.java#L53). The `accumulator` object is a custom data structure defined by the recipe itself to store any information the recipe needs to function. The `scanner` object is a `visitor` which populates the `accumulator` with data.
If a recipe needs to generate new source files or needs to see all source files before making changes, it must be a `ScanningRecipe`.
A `ScanningRecipe` extends the normal `Recipe` and adds two key objects: an [accumulator](https://github.com/openrewrite/rewrite/blob/v8.1.1/rewrite-core/src/main/java/org/openrewrite/ScanningRecipe.java#L88-L90) and a [scanner](https://github.com/openrewrite/rewrite/blob/v8.1.1/rewrite-core/src/main/java/org/openrewrite/ScanningRecipe.java#L53).
The `accumulator` object is a custom data structure defined by the recipe itself to store any information the recipe needs to function.
The `scanner` object is a `visitor` which populates the `accumulator` with data.

Scanning recipes may not be used as preconditions for declarative YAML recipes.

Scanning recipes have three phases:

1. A scanning phase that collects information while making no new code changes. In this phase, the `scanner` is called for each source file and information is added to the `accumulator` that the recipe will need for future steps.
* For example, a recipe might want to detect whether a project is a Maven project or not. The `scanner` could detect a `pom.xml` file and add a flag to the `accumulator` so that future steps know this.
2. An _optional_ generating phase where new files are created (if any are needed). In this phase, the `accumulator` can be accessed to determine whether or not a file should be created.
3. An editing phase where the recipe makes changes (as it would before). Like the generating phase, an `accumulator` can be accessed to make changes – but you **cannot** randomly access other source code or files.
3. An editing phase where the recipe makes changes, same as a regular `Recipe`. Like the generating phase, the `accumulator` can be accessed to inform how changes are made. Like a regular `Recipe` this phase makes changes to files one at a time, no random access to other source files is provided.

### Example

Expand All @@ -178,7 +183,7 @@ Scanning recipes have three phases:
public class AddManagedDependency extends ScanningRecipe<AddManagedDependency.Scanned> {
// Standard methods such as displayName and description
static class Scanned {
public static class Scanned {
boolean usingType;
List<SourceFile> rootPoms = new ArrayList<>();
}
Expand Down Expand Up @@ -341,4 +346,3 @@ The successful completion of a recipe's execution pipeline produces a collection
| `getAfter()` | The modified `SourceFile`, or null if the change represents a file deletion. |
| `getRecipesThatMadeChanges()` | The recipe names that made the changes to the source file. |
| `diff()`/`diff(Path)` | A git-style diff (with an optional path to relativize file paths in the output) |

39 changes: 38 additions & 1 deletion reference/yaml-format-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,44 @@ You can find the full recipe schema [here](https://github.com/openrewrite/rewrit

### Preconditions

Declarative YAML recipes do not support preconditions (formerly named applicability tests) as of [OpenRewrite 8](https://docs.openrewrite.org/changelog/8-1-2-release#applicability-tests-have-been-replaced). If your recipe needs that functionality, it should be defined in an [imperative manner](/concepts-and-explanations/recipes.md#imperative-recipes) instead.
Preconditions are used to limit where a recipe is run.
This is commonly used to target specific files or directories, but any recipe which is not a `ScanningRecipe` can be used as a precondition.

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 are used as preconditions 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 `ScaninngRecipes` they limit both the scanning phase and the edit phase.

{% hint style="info" %}
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.
{% endhint %}

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](#recipe-list)).

```yaml
---
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 match a glob pattern
* org.openrewrite.text.Find - limits the recipe to only run on files that contain a given string
* org.openrewrite.java.search.FindTypes - limits the recipe to run only on source code which contain a given type
* org.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.

### Recipe list

Expand Down

0 comments on commit 4ea79a5

Please sign in to comment.