Skip to content

Commit

Permalink
Actually support Recipe in addApplicableTest & `addSingleSourceAp…
Browse files Browse the repository at this point in the history
…plicableTest` (#2861)

Recipes used in applicability tests will have their own applicabibility tests invoked now.

This means that expensive recipes with light `singleSourceApplicableTest` can be used
without incurring a performance overhead of invoking the recipe visitor every time.
  • Loading branch information
JLLeitschuh authored Feb 21, 2023
1 parent 8685ef4 commit 9b72d9a
Show file tree
Hide file tree
Showing 14 changed files with 345 additions and 155 deletions.
22 changes: 19 additions & 3 deletions rewrite-core/src/main/java/org/openrewrite/ExecutionContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
*/
public interface ExecutionContext {
String CURRENT_RECIPE = "org.openrewrite.currentRecipe";
String PARENT_RECIPE = "org.openrewrite.parentRecipe";
String UNCAUGHT_EXCEPTION_COUNT = "org.openrewrite.uncaughtExceptionCount";
String DATA_TABLES = "org.openrewrite.dataTables";

Expand Down Expand Up @@ -88,9 +89,24 @@ default void putCurrentRecipe(Recipe recipe) {
putMessage(CURRENT_RECIPE, recipe);
}

default Recipe getCurrentRecipe() {
//noinspection ConstantConditions
return getMessage(CURRENT_RECIPE);
/**
* @return The previous parent recipe.
* @implNote For use in the {@link RecipeScheduler} only.
*/
@Nullable
@Incubating(since = "7.37.0")
default Recipe putParentRecipe(@Nullable Recipe recipe) {
Recipe previousParent = getMessage(PARENT_RECIPE);
putMessage(PARENT_RECIPE, recipe);
return previousParent;
}

/**
* The recipe that scheduled this recipe's execution via {@link Recipe#getApplicableTests()} or {@link Recipe#getSingleSourceApplicableTests()}.
*/
@Incubating(since = "7.37.0")
default Optional<Recipe> getParentRecipe() {
return Optional.ofNullable(getMessage(PARENT_RECIPE));
}

default int incrementAndGetUncaughtExceptionCount() {
Expand Down
104 changes: 96 additions & 8 deletions rewrite-core/src/main/java/org/openrewrite/Recipe.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import lombok.EqualsAndHashCode;
import lombok.Value;
import org.intellij.lang.annotations.Language;
import org.openrewrite.config.DataTableDescriptor;
import org.openrewrite.config.RecipeDescriptor;
Expand Down Expand Up @@ -72,8 +74,8 @@ public Tree visit(@Nullable Tree tree, ExecutionContext ctx) {
}
};

private transient List<TreeVisitor<?, ExecutionContext>> singleSourceApplicableTests;
private transient List<TreeVisitor<?, ExecutionContext>> applicableTests;
private transient List<Recipe> singleSourceApplicableTests;
private transient List<Recipe> applicableTests;

@Nullable
private transient List<DataTableDescriptor> dataTables;
Expand All @@ -99,6 +101,40 @@ public TreeVisitor<?, ExecutionContext> getVisitor() {
}
}

@Value
@EqualsAndHashCode(callSuper = true)
private static class AdHocRecipe extends Recipe {
@Language("markdown")
String displayName;
@Language("markdown")
String description;
TreeVisitor<?, ExecutionContext> visitor;

@Override
public String getDisplayName() {
return displayName;
}

@Override
public String getDescription() {
return description;
}

@Override
public TreeVisitor<?, ExecutionContext> getVisitor() {
return visitor;
}

@Nullable
static AdHocRecipe fromNullableVisitor(
@Language("markdown") String displayName,
@Language("markdown") String description,
@Nullable TreeVisitor<?, ExecutionContext> visitor
) {
return visitor == null ? null : new AdHocRecipe(displayName, description, visitor);
}
}

/**
* A human-readable display name for the recipe, initial capped with no period.
* For example, "Find text". The display name can be assumed to be rendered in
Expand Down Expand Up @@ -230,14 +266,33 @@ protected TreeVisitor<?, ExecutionContext> getApplicableTest() {
* particular source file. If multiple applicable tests configured, the final result of the applicable test depends
* on all conditions being met, that is, a logical 'AND' relationship.
* <p>
* To identify a {@link SourceFile} as applicable, the visitor should mark or change it at any level. Any mutation
* To identify a {@link SourceFile} as applicable, the {@link TreeVisitor} should mark or change it at any level. Any mutation
* that the applicability test visitor makes on the tree will not be included in the results.
* <p>
*
* @return This recipe.
*/
@SuppressWarnings("unused")
public Recipe addApplicableTest(TreeVisitor<?, ExecutionContext> test) {
return addApplicableTest(AdHocRecipe.fromNullableVisitor(
"Add applicable test for: " + getDisplayName(),
"Add applicable test for: " + getDescription(),
test
));
}

/**
* A recipe can be configured with any number of applicable tests that can be used to determine whether it should run on a
* particular source file. If multiple applicable tests configured, the final result of the applicable test depends
* on all conditions being met, that is, a logical 'AND' relationship.
* <p>
* To identify a {@link SourceFile} as applicable, the {@link Recipe} should mark or change it at any level. Any mutation
* that the applicability test recipe makes on the tree will not be included in the results.
* <p>
*
* @return This recipe. Not the argument passed.
*/
public Recipe addApplicableTest(Recipe test) {
if (applicableTests == null) {
applicableTests = new ArrayList<>(1);
}
Expand All @@ -252,8 +307,15 @@ public void addDataTable(DataTable<?> dataTable) {
dataTables.add(dataTableDescriptorFromDataTable(dataTable));
}

public List<TreeVisitor<?, ExecutionContext>> getApplicableTests() {
List<TreeVisitor<?, ExecutionContext>> tests = ListUtils.concat(getApplicableTest(), applicableTests);
public List<Recipe> getApplicableTests() {
List<Recipe> tests = ListUtils.concat(
AdHocRecipe.fromNullableVisitor(
"Applicable test for: " + getDisplayName(),
"Applicable test for: " + getDescription(),
getApplicableTest()
),
applicableTests
);
return tests == null ? emptyList() : tests;
}

Expand All @@ -276,21 +338,47 @@ protected TreeVisitor<?, ExecutionContext> getSingleSourceApplicableTest() {
* particular source file. If multiple applicable tests configured, the final result of the applicable test depends
* on all conditions being met, that is, a logical 'AND' relationship.
* <p>
* To identify a {@link SourceFile} as applicable, the visitor should mark or change it at any level. Any mutation
* To identify a {@link SourceFile} as applicable, the {@link TreeVisitor} should mark or change it at any level. Any mutation
* that the applicability test visitor makes on the tree will not be included in the results.
*
* @return This recipe.
*/
@SuppressWarnings("unused")
public Recipe addSingleSourceApplicableTest(TreeVisitor<?, ExecutionContext> test) {
return addSingleSourceApplicableTest(AdHocRecipe.fromNullableVisitor(
"Add single source applicable test for: " + getDisplayName(),
"Add single source applicable test for: " + getDescription(),
test
));
}

/**
* A recipe can be configured with any number of applicable tests that can be used to determine whether it should run on a
* particular source file. If multiple applicable tests configured, the final result of the applicable test depends
* on all conditions being met, that is, a logical 'AND' relationship.
* <p>
* To identify a {@link SourceFile} as applicable, the {@link Recipe} should mark or change it at any level. Any mutation
* that the applicability test recipe makes on the tree will not be included in the results.
*
* @return This recipe. Not the argument passed.
*/
public Recipe addSingleSourceApplicableTest(Recipe test) {
if (singleSourceApplicableTests == null) {
singleSourceApplicableTests = new ArrayList<>(1);
}
singleSourceApplicableTests.add(test);
return this;
}

public List<TreeVisitor<?, ExecutionContext>> getSingleSourceApplicableTests() {
List<TreeVisitor<?, ExecutionContext>> tests = ListUtils.concat(getSingleSourceApplicableTest(), singleSourceApplicableTests);
public List<Recipe> getSingleSourceApplicableTests() {
List<Recipe> tests = ListUtils.concat(
AdHocRecipe.fromNullableVisitor(
"Single Source Applicable test for: " + getDisplayName(),
"Single Source Applicable test for: " + getDescription(),
getSingleSourceApplicableTest()
),
singleSourceApplicableTests
);
return tests == null ? emptyList() : tests;
}

Expand Down
Loading

0 comments on commit 9b72d9a

Please sign in to comment.