Module 3: Scanning recipes
Scanning recipes are used when a recipe needs to generate new source files or inspect all source files before making any changes. This is especially useful when a transformation in one file depends on content or structure in another. For example, a scanning recipe may examine a Maven pom.xml to check for a particular dependency and, based on its presence, update a YAML configuration file. This cross-file reasoning is a powerful capability unique to scanning recipes.
Before moving on to the following exercise, you should review the OpenRewrite documentation for more details about the different phases of scanning recipes and how they work.
Exercise 3a: Explore a scanning recipe
In this exercise, you'll explore the AppendToReleaseNotes recipe. This is a scanning recipe that appends release notes to a file based on a marker found elsewhere in the project.
Goals for this exercise
- Learn how scanning recipes differ from regular transformation recipes.
- Understand how scanning recipes enable reasoning across multiple files before making changes.
- See how to manage shared state across recipe phases using an accumulator.
Steps
- In the
rewrite-recipe-starterproject, openAppendToReleaseNotes.java. Note that it extendsScanningReciperather thanRecipe, has anAccumulatormember to share state across phases, and overridesgetInitialValue(),getScanner(),generate(), andgetVisitor()(each passing theAccumulator). - Open
AppendToReleaseNotesTest.javaand look atcreateNewReleaseNotes(). It usesSourceSpecs.text(before, after, spec -> spec.path(Path.of("RELEASE.md")))to set the file path, withdoesNotExist()as the before block to indicate the file is created (pass it as the after block instead to indicate deletion).
Takeaways
- Scanning recipes enable inspection and generation across multiple files.
- A custom class can be used as an accumulator to allow state to be shared among the phases of a scanning recipe.
- Tests for scanning recipes can simulate creation or modification of files using
doesNotExist()or empty strings.
Exercise 3b: Write a scanning recipe
In this exercise, you'll write a scanning recipe to find any comments in Java source files that contain TODO and track them all together in a separate markdown file.
Goals for this exercise
- Learn how to use a scanning recipe to collect data across source files.
- Understand how to use different visitor types for analyzing and generating files.
- See how scanning recipes support conditional file creation based on project structure.
Steps
- Open the unit test
TrackJavaTodosFileTest.java, read through the cases you need to cover, then remove the@Disabledannotations and run the tests to see them fail. - Open the scanning recipe template
TrackJavaTodosFile.java. Using Exercise 3a and the test requirements, write a scanning recipe that collects allTODOcomments into a file calledTODO.md.- You'll override
getScanner(),generate(), andgetVisitor(). TheTodoCommentsaccumulator andgetInitialValue()are already defined for you.TodoCommentshas abooleanfor whetherTODO.mdexists and a list of strings for the collected comments, plus an option for aStringmarkdown header.
- You'll override
- In
getScanner(), use two visitors: aJavaIsoVisitorto visit Java source files and aTreeVisitorfor the markdown file. Since you can only return one visitor, create one and call.visit(...)on it from within thevisit(...)of the visitor you return.- The plain-text visitor checks whether
TODO.mdalready exists (as in Exercise 3a). - In the
JavaIsoVisitor, look forCommentelements and store each match in the accumulator's list. ACommentis either aTextCommentorJavadocComment; collecting onlyTextComments is fine here.
- The plain-text visitor checks whether
- In
generate(), createTODO.mdif it doesn't already exist — this is nearly identical to theAppendToReleaseNotesgenerate()from Exercise 3a. - In
getVisitor(), write the collected comments to the markdown file. Use.withText(...)to return the plain-text file, building the full markdownStringfrom the accumulator's list of comments. - Build your project and run the tests. They should all pass. If any fail, use the failure description to locate the problem.
- If you get stuck, see the completed
TrackJavaTodosFile.javafor reference.
Takeaways
- Scanning recipes can combine analysis, state collection, and file generation.
- Visitors can be nested or composed to traverse different LST types.