Declarative Preconditions - Control applicability to ScanningRecipes
What problem are you trying to solve?
Suppose you want to add a dependency to a Maven project, but only if some other condition is true in the pom.xml.
You might try something like this:
type: specs.openrewrite.org/v1beta/recipe
name: org.sample.Example
displayName: Use AddDependency with a declarative precondition
preconditions:
- org.openrewrite.maven.FindDependency:
groupId: org.glassfish.jaxb
artifactId: *
recipeList:
- org.openrewrite.maven.AddDependency:
groupId: org.glassfish.jaxb
artifactId: jaxb-runtime
version: latest.release
onlyIfUsing: jakarta.xml.bind..*
...but this will never pass Preconditions, because AddDependency's scanner targets Java files, but the declarative precondition only matches on Maven files. And declarative preconditions always apply to both the scan and edit phases.
Describe the solution you'd like
Could be solved with a new, optional enum field:
type: specs.openrewrite.org/v1beta/recipe
name: org.sample.Example
displayName: Use AddDependency with a declarative precondition
preconditionPhases: edit # or "scan", or (default) "all"
preconditions:
- org.openrewrite.maven.FindDependency:
groupId: org.glassfish.jaxb
artifactId: *
recipeList:
- org.openrewrite.maven.AddDependency:
groupId: org.glassfish.jaxb
artifactId: jaxb-runtime
version: latest.release
onlyIfUsing: jakarta.xml.bind..*
Have you considered any alternatives or workarounds?
- Imperative recipes
- Individual toggle arguments (eg
preconditionsOnScanning: false,preconditionsOnEdit: false) - Separate precondition sequences per desired phase (eg
scanningPreconditions: # one or more recipes,editPreconditions: # one or more recipes), while the existingpreconditionsoption continues to mean "both scan and edit phase"
Additional context
cc @sambsnyd
Are you interested in contributing this feature to OpenRewrite?
Assuming we can bless a design, yeah, I suspect the code won't be too treacherous
Is this maybe the issue for #4004?
hmm -- I think #4004 fixes an issue which I didn't realize was happening, and brings us to the state described in this issue. ie:
- current state: scanning phases never run for recipes with declarative preconditions (which is the most-direct reason why my example doesn't work)
- with your PR: scanning phases do run, and are always "filtered" by the declarative precondition (in which case my example still won't work for the reasons I originally wrote)
- if we implement this issue: scanning phases run, and the declarative author can control whether the declarative precondition will filter the scanning phase
@nmck257 Makes sense. I was too perplexed by the fact that your issue was created within minutes of my PR, so I didn't read it carefully enough. I like your proposal however and also think there will be more use cases which require this level of control.
An alternative would be to have a scanningPreconditions property, which would allow for independent sets of preconditions. This would only make sense if the most common use case is to have the preconditions apply to the edit phase only (🤷). If it is more common to have the preconditions apply to both phases, then what you propose makes more sense, as it would avoid repetition. After all, for complex cases the developer can always implement a recipe using some Java code.
@knutwannheden yeah, that was quite the coincidence :)
I agree, I could see having three different properties which accept lists of recipes:
-
preconditions(the existing) would apply to both scans and edits -
scanningPreconditions -
editPreconditions
This thread had some context on choosing the appropriate default behavior: https://github.com/openrewrite/rewrite/pull/3643#issuecomment-1791484914
In #4004 I have now changed the implementation so that preconditions are ignored for the scanning phase. I suspect that this will in fact be turn out to be the most common usage. This should at least be an improvement over the status quo (scanning phase of owned recipes is never executed) and any further enhancements should be the subject of this issue.
@sambsnyd Does that sound good to you?
On (kind of) this topic, am I correct in understanding that there is no mechanism for a declarative conditional run of a recipe?
So for example, I have a set of 10 services and 2 different recipes, each only applicable to half of the services, so one recipe for 5 and one for the other 5.
Both of those recipes are complex - have their own recipeList and target a variety of files.
I would love to just take these 2 recipes, combine them into one and run that single one on all of my services.
The idea is that those 2 recipes would each have recipe conditions, similar to preconditions, but instead of limiting scope would have to "pass" for the actual recipeList to run. So this saves me from analyzing the metadata of the service manually, and allowing the recipe to decide if it has to run or not.
So in a way a declarative way of doing Preconditions.check(List<Recipe> conditions, List<Recipe> recipesIfConditionsMet)
The difference between preconditions is that instead of limiting the scope/files for recipes to run on, it actually allows/disallows them to run