Skip to content

Commit f2e6a77

Browse files
authored
Include declarative recipe preconditions in recipe descriptors (#6777)
* Include declarative recipe preconditions in recipe descriptors * Javadoc screw off * Fix test expectation
1 parent a9d51b8 commit f2e6a77

6 files changed

Lines changed: 99 additions & 34 deletions

File tree

rewrite-core/build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,5 +46,5 @@ tasks.withType<Javadoc> {
4646
// symbol: method onConstructor_()
4747
// location: @interface AllArgsConstructor
4848
// 1 error
49-
exclude("**/RpcObjectData.java")
49+
exclude("**/RpcObjectData.java", "**/RecipeDescriptor.java")
5050
}

rewrite-core/src/main/java/org/openrewrite/Recipe.java

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -203,8 +203,6 @@ public String getInstanceNameSuffix() {
203203
* A set of strings used for categorizing related recipes. For example
204204
* "testing", "junit", "spring". Any individual tag should consist of a
205205
* single word, all lowercase.
206-
*
207-
* @return The tags.
208206
*/
209207
@Getter
210208
final Set<String> tags = emptySet();
@@ -225,11 +223,21 @@ public final RecipeDescriptor getDescriptor() {
225223

226224
protected RecipeDescriptor createRecipeDescriptor() {
227225
List<OptionDescriptor> options = getOptionDescriptors();
228-
ArrayList<RecipeDescriptor> recipeList1 = new ArrayList<>();
229-
for (Recipe next : getRecipeList()) {
230-
recipeList1.add(next.getDescriptor());
226+
List<RecipeDescriptor> preconditionDescriptors = emptyList();
227+
if (this instanceof RecipePreconditions) {
228+
RecipePreconditions recipeWithPreconditions = (RecipePreconditions) this;
229+
List<Recipe> preconditions = recipeWithPreconditions.getPreconditions();
230+
preconditionDescriptors = new ArrayList<>(preconditions.size());
231+
for (Recipe precondition : preconditions) {
232+
preconditionDescriptors.add(precondition.getDescriptor());
233+
}
234+
}
235+
236+
List<Recipe> recipeList = getRecipeList();
237+
List<RecipeDescriptor> recipeDescriptors = new ArrayList<>(recipeList.size());
238+
for (Recipe next : recipeList) {
239+
recipeDescriptors.add(next.getDescriptor());
231240
}
232-
recipeList1.trimToSize();
233241

234242
URI recipeSource;
235243
try {
@@ -239,7 +247,7 @@ protected RecipeDescriptor createRecipeDescriptor() {
239247
}
240248

241249
return new RecipeDescriptor(getName(), getDisplayName(), getInstanceName(), getDescription(), getTags(),
242-
getEstimatedEffortPerOccurrence(), options, recipeList1, getDataTableDescriptors(),
250+
getEstimatedEffortPerOccurrence(), options, preconditionDescriptors, recipeDescriptors, getDataTableDescriptors(),
243251
getMaintainers(), getContributors(), getExamples(), recipeSource);
244252
}
245253

rewrite-core/src/main/java/org/openrewrite/config/DeclarativeRecipe.java

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
import static org.openrewrite.Validated.invalid;
3333

3434
@RequiredArgsConstructor
35-
public class DeclarativeRecipe extends ScanningRecipe<DeclarativeRecipe.Accumulator> {
35+
public class DeclarativeRecipe extends ScanningRecipe<DeclarativeRecipe.Accumulator> implements RecipePreconditions {
3636
@Getter
3737
private final String name;
3838

@@ -187,7 +187,6 @@ private void registerNestedScanningRecipes(Recipe recipe, Accumulator acc, Execu
187187
@Override
188188
public TreeVisitor<?, ExecutionContext> getScanner(Accumulator acc) {
189189
return new TreeVisitor<Tree, ExecutionContext>() {
190-
@SuppressWarnings({"rawtypes", "unchecked"})
191190
@Override
192191
public @Nullable Tree visit(@Nullable Tree tree, ExecutionContext ctx) {
193192
for (Recipe precondition : preconditions) {
@@ -251,7 +250,7 @@ public boolean isAcceptable(SourceFile sourceFile, ExecutionContext ctx) {
251250

252251
@EqualsAndHashCode(callSuper = false)
253252
@Value
254-
static class BellwetherDecoratedRecipe extends Recipe implements DelegatingRecipe {
253+
static class BellwetherDecoratedRecipe extends Recipe implements DelegatingRecipe, RecipePreconditions {
255254

256255
DeclarativeRecipe.PreconditionBellwether bellwether;
257256
Recipe delegate;
@@ -350,11 +349,19 @@ public Validated<Object> validate(ExecutionContext ctx) {
350349
public Collection<Validated<Object>> validateAll(ExecutionContext ctx, Collection<Validated<Object>> acc) {
351350
return delegate.validateAll(ctx, acc);
352351
}
352+
353+
@Override
354+
public List<Recipe> getPreconditions() {
355+
if (delegate instanceof RecipePreconditions) {
356+
return ((RecipePreconditions) delegate).getPreconditions();
357+
}
358+
return emptyList();
359+
}
353360
}
354361

355362
@Value
356363
@EqualsAndHashCode(callSuper = false)
357-
static class BellwetherDecoratedScanningRecipe<T> extends ScanningRecipe<T> implements DelegatingRecipe {
364+
static class BellwetherDecoratedScanningRecipe<T> extends ScanningRecipe<T> implements DelegatingRecipe, RecipePreconditions {
358365

359366
DeclarativeRecipe.PreconditionBellwether bellwether;
360367
ScanningRecipe<T> delegate;
@@ -468,6 +475,14 @@ public Validated<Object> validate(ExecutionContext ctx) {
468475
public Collection<Validated<Object>> validateAll(ExecutionContext ctx, Collection<Validated<Object>> acc) {
469476
return delegate.validateAll(ctx, acc);
470477
}
478+
479+
@Override
480+
public List<Recipe> getPreconditions() {
481+
if (delegate instanceof RecipePreconditions) {
482+
return ((RecipePreconditions) delegate).getPreconditions();
483+
}
484+
return emptyList();
485+
}
471486
}
472487

473488
@Override
@@ -589,13 +604,17 @@ private static class LazyLoadedRecipe extends Recipe {
589604

590605
@Override
591606
protected RecipeDescriptor createRecipeDescriptor() {
592-
List<RecipeDescriptor> recipeList = new ArrayList<>();
593-
for (Recipe childRecipe : this.recipeList) {
594-
recipeList.add(childRecipe.getDescriptor());
607+
List<RecipeDescriptor> preconditionDescriptors = new ArrayList<>();
608+
for (Recipe childRecipe : preconditions) {
609+
preconditionDescriptors.add(childRecipe.getDescriptor());
610+
}
611+
List<RecipeDescriptor> recipeDescriptors = new ArrayList<>();
612+
for (Recipe childRecipe : recipeList) {
613+
recipeDescriptors.add(childRecipe.getDescriptor());
595614
}
596615
return new RecipeDescriptor(getName(), getDisplayName(), getInstanceName(), getDescription() != null ? getDescription() : "",
597616
getTags(), getEstimatedEffortPerOccurrence(),
598-
emptyList(), recipeList, getDataTableDescriptors(), getMaintainers(), getContributors(),
617+
emptyList(), preconditionDescriptors, recipeDescriptors, getDataTableDescriptors(), getMaintainers(), getContributors(),
599618
getExamples(), source);
600619
}
601620

rewrite-core/src/main/java/org/openrewrite/config/RecipeDescriptor.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
*/
1616
package org.openrewrite.config;
1717

18+
import com.fasterxml.jackson.annotation.JsonCreator;
19+
import lombok.AllArgsConstructor;
1820
import lombok.EqualsAndHashCode;
1921
import lombok.Value;
2022
import lombok.With;
@@ -36,6 +38,7 @@
3638

3739
@Value
3840
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
41+
@AllArgsConstructor(onConstructor = @__(@JsonCreator))
3942
public class RecipeDescriptor {
4043
@EqualsAndHashCode.Include
4144
String name;
@@ -57,6 +60,9 @@ public class RecipeDescriptor {
5760
@EqualsAndHashCode.Include
5861
List<OptionDescriptor> options;
5962

63+
@With
64+
List<RecipeDescriptor> preconditions;
65+
6066
@With
6167
List<RecipeDescriptor> recipeList;
6268

@@ -73,6 +79,16 @@ public class RecipeDescriptor {
7379
@Deprecated
7480
URI source;
7581

82+
@Deprecated
83+
public RecipeDescriptor(String name, String displayName, String instanceName, String description,
84+
Set<String> tags, @Nullable Duration estimatedEffortPerOccurrence,
85+
List<OptionDescriptor> options, List<RecipeDescriptor> recipeList,
86+
List<DataTableDescriptor> dataTables, List<Maintainer> maintainers,
87+
List<Contributor> contributors, List<RecipeExample> examples, URI source) {
88+
this(name, displayName, instanceName, description, tags, estimatedEffortPerOccurrence,
89+
options, emptyList(), recipeList, dataTables, maintainers, contributors, examples, source);
90+
}
91+
7692
/**
7793
* @param env Provides a source of category descriptors to build category names from more
7894
* than just the name segments.
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
* Copyright 2026 the original author or authors.
3+
* <p>
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
* <p>
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
* <p>
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.openrewrite.config;
17+
18+
import org.openrewrite.Recipe;
19+
20+
import java.util.List;
21+
22+
/**
23+
* Indicates that a recipe exposes a list of recipes used as preconditions.
24+
* This is purely informational and not taken into consideration by recipe execution.
25+
* Imperative recipes interact with preconditions as implementation details of their visitor(s).
26+
* There is no reason for an imperative recipe to implement this, it will not affect the behavior of the recipe in any way.
27+
*/
28+
public interface RecipePreconditions {
29+
List<Recipe> getPreconditions();
30+
}

rewrite-core/src/test/java/org/openrewrite/config/DeclarativeRecipeTest.java

Lines changed: 10 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import static org.openrewrite.test.RewriteTest.toRecipe;
4040
import static org.openrewrite.test.SourceSpecs.text;
4141

42+
@SuppressWarnings("NullableProblems")
4243
class DeclarativeRecipeTest implements RewriteTest {
4344

4445
@DocumentExample
@@ -105,30 +106,22 @@ public PlainText visitText(PlainText text, ExecutionContext ctx) {
105106
}
106107

107108
@Test
108-
void descriptorDoesNotLeakBellwetherWhenPreconditionsPresent() {
109+
void preconditionDescriptorsIncludedInDescriptor() {
109110
DeclarativeRecipe dr = new DeclarativeRecipe("test", "test", "test", emptySet(),
110111
null, URI.create("dummy"), true, emptyList());
111-
dr.addPrecondition(
112-
toRecipe(() -> new PlainTextVisitor<>() {
113-
@Override
114-
public PlainText visitText(PlainText text, ExecutionContext ctx) {
115-
if ("1".equals(text.getText())) {
116-
return SearchResult.found(text);
117-
}
118-
return text;
119-
}
120-
})
121-
);
112+
dr.addPrecondition(new Find("precondition-marker", null, null, null, null, null, null, null));
122113
dr.addUninitialized(new ChangeText("2"));
123-
dr.addUninitialized(new ChangeText("3"));
124114
dr.initialize(List.of());
125115

126116
RecipeDescriptor descriptor = dr.getDescriptor();
117+
assertThat(descriptor.getPreconditions())
118+
.hasSize(1)
119+
.first()
120+
.satisfies(p -> assertThat(p.getName()).isEqualTo("org.openrewrite.text.Find"));
127121
assertThat(descriptor.getRecipeList())
128-
.hasSize(2)
129-
.extracting(RecipeDescriptor::getName)
130-
.noneMatch(name -> name.contains("Bellwether"))
131-
.noneMatch(name -> name.contains("BellwetherDecorated"));
122+
.hasSize(1)
123+
.first()
124+
.satisfies(r -> assertThat(r.getName()).isEqualTo("org.openrewrite.text.ChangeText"));
132125
}
133126

134127
@Test
@@ -281,7 +274,6 @@ void yamlPreconditionWithScanningRecipe() {
281274
""", "org.openrewrite.PreconditionTest")
282275
.afterRecipe(run -> assertThat(run.getChangeset().getAllResults()).anySatisfy(
283276
s -> {
284-
//noinspection DataFlowIssue
285277
assertThat(s.getAfter()).isNotNull();
286278
assertThat(s.getAfter().getSourcePath()).isEqualTo(Path.of("test.txt"));
287279
}

0 commit comments

Comments
 (0)