Skip to main content

Writing recipes over multiple source file types

When creating new recipes, you may find it desirable to examine multiple source files, potentially of different types, to make key decisions in your visitor. For example, you may want to look for a particular condition to be present in a Maven POM file and, if that condition is met, alter an application property in a YAML file.

In situations like that, you should create a ScanningRecipe. In there, you'll then make an accumulator which visitors can read from and write to as needed. For a more thorough explanation of scanning recipes, please see our recipe concepts doc

Below you can find an example of a ScanningRecipe that does what we described earlier:

import org.openrewrite.*;
import org.openrewrite.internal.lang.Nullable;
import org.openrewrite.yaml.YamlVisitor;

public class MavenYamlRecipe extends ScanningRecipe<MavenYamlRecipe.Accumulator> {
@Override
public String getDisplayName() {
return "Something with Maven and something with YAML";
}

@Override
public String getDescription() {
return "Something with Maven and something with YAML.";
}

@Override
public Accumulator getInitialValue(ExecutionContext ctx) {
return new Accumulator();
}

@Override
public TreeVisitor<?, ExecutionContext> getScanner(Accumulator acc) {
return new TreeVisitor<Tree, ExecutionContext>() {
@Override
public Tree visit(@Nullable Tree tree, ExecutionContext executionContext) {
if (!(tree instanceof SourceFile)) {
return tree;
}

SourceFile sourceFile = (SourceFile) tree;
String sourcePath = PathUtils.separatorsToUnix(sourceFile.getSourcePath().toString());
if (sourcePath.equals("pom.xml")) {
// it's a maven project
// have a xml visitor to check whether "pom.xml" meet some condition, assume it's true here
boolean isPomConditionMet = true;

// set it in Accumulator
acc.pomConditionMet = isPomConditionMet;
}

return tree;
}
};
}

@Override
public TreeVisitor<?, ExecutionContext> getVisitor(Accumulator acc) {
if (!acc.pomConditionMet) {
// if pom condition not met, do nothing
return TreeVisitor.noop();
}

// whatever your YAML visitor does can now respond to pomConditionMet
return new YamlVisitor<ExecutionContext>(){
// some logic
};
}

// @Data
static class Accumulator {
boolean pomConditionMet = false;
// something else if needed from scanner
}
}

To write a unit test that tests multiple source file types, use their respective parsers:

package org.openrewrite

import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.openrewrite.maven.MavenParser
import org.openrewrite.yaml.YamlParser

class MavenYamlTest {

@Test
fun mavenYaml() {
val sources =
MavenParser.builder().build().parse(
"""
<project>
<groupId>org.openrewrite</groupId>
<artifactId>test</artifactId>
<version>0.1.0</version>
</project>
"""
).plus(
YamlParser().parse("""
key: value
""")
)

val results = MavenYaml().run(sources)
assertThat(results.map { it.after!!.printAll() }).containsExactly(
"""
key: changedValue
""".trimIndent()
)
}
}