7.7 KiB
7.7 KiB
OpenRewrite Recipe Development Checklist
Use this checklist to ensure you've covered all important aspects of recipe development.
Planning Phase
Recipe Type Selection
- Determined if recipe can be declarative (preferred)
- Evaluated if Refaster template would work for simple replacements
- Confirmed imperative recipe is necessary for complex logic
- Identified which LST elements need to be visited
- Reviewed existing recipes to avoid duplication
Requirements Gathering
- Clearly defined what the recipe should change
- Identified what should NOT be changed
- Documented expected input and output
- Listed any dependencies or external types needed
- Determined if multi-file analysis is required (ScanningRecipe)
Implementation Phase
Recipe Class Structure
- Used
@Valueand@EqualsAndHashCode(callSuper = false)for immutability - All fields are final (via Lombok or manual implementation)
- Added
@JsonCreatorconstructor with@JsonPropertyannotations - Defined
@Optionfields with clear descriptions and examples - Implemented
getDisplayName()with sentence-case name - Implemented
getDescription()with clear, period-ending description getVisitor()returns NEW instance (never cached)
Visitor Implementation
- Chose correct visitor type (JavaIsoVisitor vs JavaVisitor)
- Called
super.visitX()in overridden visit methods - Checked for null before accessing type information
- Implemented "do no harm" - return unchanged LST when unsure
- Used
.withX()methods instead of mutating LSTs - Used
ListUtilsinstead of stream operations on LST collections - Avoided creating new lists unnecessarily
JavaTemplate Usage (if applicable)
- Used typed substitution
#{any(Type)}for LST elements - Used untyped substitution
#{}for strings - Declared all imports with
.imports() - Declared static imports with
.staticImports() - Configured parser with
.javaParser()if referencing external types - Added classpath dependencies or stubs as needed
- Used context-free templates (default) when possible
- Only used
.contextSensitive()when necessary
Advanced Features (if applicable)
- Added preconditions with
Preconditions.check() - Used
UsesTypeorUsesMethodto filter applicable files - Implemented cursor messaging for intra-visitor communication
- For ScanningRecipe: defined accumulator with
Map<JavaProject, T> - For ScanningRecipe: implemented
getInitialValue() - For ScanningRecipe: implemented
getScanner()(no changes, only collect) - For ScanningRecipe: implemented
getVisitor()(uses accumulator data) - For ScanningRecipe: implemented
generate()if creating new files
Imports and Dependencies
- Used
maybeAddImport()for new types - Used
maybeRemoveImport()for removed types - Chained visitors with
doAfterVisit()when needed
Testing Phase
Test Structure
- Created test class implementing
RewriteTest - Implemented
defaults(RecipeSpec)with recipe configuration - Added
@DocumentExampleto primary test
Test Coverage
- Test for expected changes (before → after)
- Test for no changes when not applicable (before only)
- Test for edge cases and boundary conditions
- Test with multiple files
- Test that recipe doesn't change already-correct code
- Test with different parameter values (if applicable)
- Test with different Java versions (if version-specific)
- Added classpath dependencies with
spec.parser()
Test Quality
- Test names clearly describe what is being tested
- Used meaningful package and class names in test code
- Included comments explaining complex test scenarios
- Verified tests pass (including multi-cycle verification)
- Checked that recipe is idempotent (runs produce same result)
Code Quality Phase
Best Practices
- Recipe follows "do no harm" principle
- Recipe makes minimal, least invasive changes
- Recipe is immutable (no mutable state)
- Recipe is idempotent (same input → same output)
- Recipe never mutates LSTs
- Recipe respects existing formatting
- Used referential equality checks (same object = no change)
Naming Conventions
- Display name uses sentence case
- Display name uses backticks around code elements
- Display name ends with period (if complete sentence)
- Description is clear and concise
- Recipe class name follows
VerbNounpattern - Package name follows
com.yourorg.categorypattern
Performance
- Added preconditions to skip irrelevant files
- Used context-free JavaTemplates when possible
- Avoided unnecessary LST allocations
- Recipe completes work in single cycle (no
causesAnotherCycle) - For ScanningRecipe: minimized accumulator size
Multi-Module Support
- For ScanningRecipe: tracked data per
JavaProject - Did not assume single project per repository
- Retrieved JavaProject from markers correctly
Documentation Phase
Code Documentation
- Added class-level JavaDoc describing the recipe
- Documented all
@Optionfields clearly - Added inline comments for complex logic
- Included usage example in JavaDoc
External Documentation
- Created YAML file in
src/main/resources/META-INF/rewrite/(if distributing) - Added recipe to catalog/index (if applicable)
- Documented any known limitations
- Added tags for categorization
Distribution Phase
Build Configuration
- Recipe compiles with Java 8 target (or appropriate version)
- Added
-parameterscompiler flag for Jackson - Included rewrite-recipe-bom for dependency management
- Tests run successfully with build tool
Publishing
- Published to local Maven repository for testing (
publishToMavenLocal) - Tested recipe in separate project
- Configured publishing to artifact repository (if applicable)
- Tagged release in version control
Final Verification
Smoke Testing
- Ran recipe on sample project
- Verified changes are correct
- Verified no unwanted changes were made
- Checked that formatting is preserved
- Ran recipe multiple times (idempotence check)
Common Pitfalls Avoided
- Did not mutate LSTs directly
- Did not cache visitor instances
- Did not use ExecutionContext for visitor state
- Did not forget to call
super.visitX() - Did not create unnecessary list allocations
- Did not forget imports in JavaTemplate
- Did not skip null checks on type information
- Did not assume single project per repository
Recipe-Specific Checklists
For Declarative Recipes
- Saved in
src/main/resources/META-INF/rewrite/ - Used
type: specs.openrewrite.org/v1beta/recipe - All recipe names are fully qualified
- All parameters are correctly indented
- String values with special characters are quoted
- Recipe list is in logical order
- Tested with
spec.recipeFromResources()
For Refaster Template Recipes
- Created template class with
@RecipeDescriptor - Implemented
@BeforeTemplatemethods - Implemented single
@AfterTemplatemethod - Verified generated recipe works correctly
- Recipe name uses generated form (e.g.,
RecipesName)
Notes
Remember:
- Do no harm: If unsure, don't change
- Minimize changes: Make only necessary modifications
- Immutability: Recipes and LSTs must not be mutated
- Test thoroughly: Both positive and negative cases
- Document clearly: Future maintainers will thank you
For more information, see:
- SKILL.md in this skill directory
- OpenRewrite documentation in this repository
- Examples in examples/ directory