# 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 `@Value` and `@EqualsAndHashCode(callSuper = false)` for immutability - [ ] All fields are final (via Lombok or manual implementation) - [ ] Added `@JsonCreator` constructor with `@JsonProperty` annotations - [ ] Defined `@Option` fields 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 `ListUtils` instead 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 `UsesType` or `UsesMethod` to filter applicable files - [ ] Implemented cursor messaging for intra-visitor communication - [ ] For ScanningRecipe: defined accumulator with `Map` - [ ] 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 `@DocumentExample` to 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 `VerbNoun` pattern - [ ] Package name follows `com.yourorg.category` pattern ### 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 `@Option` fields 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 `-parameters` compiler 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 `@BeforeTemplate` methods - [ ] Implemented single `@AfterTemplate` method - [ ] 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