Files
2025-11-30 08:57:41 +08:00

357 lines
7.4 KiB
Markdown

# OpenRewrite Recipe Common Patterns
Quick reference for frequently used code patterns in recipe development.
## Import Management
### Adding Imports
```java
maybeAddImport("java.util.List");
maybeAddImport("java.util.Collections", "emptyList"); // Static import
```
### Removing Imports
```java
maybeRemoveImport("old.package.Type");
```
## Visitor Patterns
### Chaining Visitors
```java
doAfterVisit(new OtherRecipe().getVisitor());
```
### Cursor Messaging (Intra-visitor State)
```java
// Store state
getCursor().putMessage("key", value);
// Retrieve state
Object value = getCursor().getNearestMessage("key");
```
## Type Checking
### Check Class Type
```java
if (methodInvocation.getType() != null &&
TypeUtils.isOfClassType(methodInvocation.getType(), "com.example.Type")) {
// Type matches
}
```
### Check Method Type
```java
if (TypeUtils.isOfType(method.getMethodType(), "com.example.Type", "methodName")) {
// Method matches
}
```
### Check Assignability
```java
if (TypeUtils.isAssignableTo("java.util.Collection", someType)) {
// Type is assignable to Collection
}
```
## LST Manipulation
### Modifying Lists (Never Mutate!)
```java
// WRONG - Mutates the list
method.getArguments().remove(0);
// CORRECT - Creates new list with ListUtils
method.withArguments(ListUtils.map(method.getArguments(), (i, arg) ->
i == 0 ? null : arg // null removes the element
));
```
### Adding to Lists
```java
// Add at end
classDecl.withModifiers(
ListUtils.concat(classDecl.getModifiers(), newModifier)
);
// Add at beginning
classDecl.withModifiers(
ListUtils.concat(newModifier, classDecl.getModifiers())
);
```
### Replacing in Lists
```java
classDecl.withModifiers(
ListUtils.map(classDecl.getModifiers(), mod ->
shouldReplace(mod) ? newModifier : mod
)
);
```
## JavaTemplate Patterns
### Simple Template
```java
JavaTemplate template = JavaTemplate
.builder("new Expression()")
.build();
// Apply
expression = template.apply(getCursor(), expression.getCoordinates().replace());
```
### Template with Imports
```java
JavaTemplate template = JavaTemplate
.builder("Collections.emptyList()")
.imports("java.util.Collections")
.build();
```
### Template with Parameters
```java
JavaTemplate template = JavaTemplate
.builder("new #{any(String)}(#{})")
.build();
// Apply with parameters
expression = template.apply(
getCursor(),
expression.getCoordinates().replace(),
typeName, // #{any(String)}
constructorArg // #{}
);
```
### Template with External Classpath
```java
JavaTemplate template = JavaTemplate
.builder("new CustomType()")
.imports("com.external.CustomType")
.javaParser(JavaParser.fromJavaVersion()
.classpath("external-library"))
.build();
```
### Context-Sensitive Template
```java
// Use ONLY when referencing local variables/methods
JavaTemplate template = JavaTemplate
.builder("localVariable.toString()")
.contextSensitive()
.build();
```
## Preconditions
### Single Precondition
```java
@Override
public TreeVisitor<?, ExecutionContext> getVisitor() {
return Preconditions.check(
new UsesType<>("com.example.Type", true),
new YourVisitor()
);
}
```
### Multiple Preconditions (AND)
```java
return Preconditions.check(
Preconditions.and(
new UsesType<>("com.example.Type", true),
new UsesMethod<>("com.example.Type methodName(..)")
),
new YourVisitor()
);
```
### Multiple Preconditions (OR)
```java
return Preconditions.check(
Preconditions.or(
new UsesType<>("com.example.TypeA", true),
new UsesType<>("com.example.TypeB", true)
),
new YourVisitor()
);
```
### Java Version Check
```java
return Preconditions.check(
new UsesJavaVersion<>(17),
new YourVisitor()
);
```
## ScanningRecipe Patterns
### Basic Accumulator Structure
```java
public static class Accumulator {
// Use per-project tracking for multi-module support
Map<JavaProject, Set<String>> projectData = new HashMap<>();
}
```
### Scanner (First Pass - Collect Only)
```java
@Override
public TreeVisitor<?, ExecutionContext> getScanner(Accumulator acc) {
return new JavaIsoVisitor<>() {
@Override
public J.CompilationUnit visitCompilationUnit(J.CompilationUnit cu, ExecutionContext ctx) {
// Collect data - DO NOT MODIFY LST
JavaProject project = cu.getMarkers()
.findFirst(JavaProject.class)
.orElse(null);
if (project != null) {
acc.projectData
.computeIfAbsent(project, k -> new HashSet<>())
.add(someData);
}
return cu; // Return unchanged
}
};
}
```
### Visitor (Second Pass - Modify Using Accumulator)
```java
@Override
public TreeVisitor<?, ExecutionContext> getVisitor(Accumulator acc) {
return new JavaIsoVisitor<>() {
@Override
public J.CompilationUnit visitCompilationUnit(J.CompilationUnit cu, ExecutionContext ctx) {
JavaProject project = cu.getMarkers()
.findFirst(JavaProject.class)
.orElse(null);
if (project == null) {
return cu;
}
// Use accumulator data to make decisions
Set<String> projectData = acc.projectData.get(project);
if (projectData != null && projectData.contains(someCondition)) {
// Make changes
}
return cu;
}
};
}
```
## Formatting Preservation
### Use Space.format()
```java
// Preserve formatting when adding elements
Space.format("\n" + indent)
```
### Copy Formatting from Existing Elements
```java
newElement = newElement.withPrefix(existingElement.getPrefix());
```
## Testing Patterns
### Multi-file Tests
```java
rewriteRun(
java(
"""
package com.example;
class First { }
""",
"""
package com.example;
class First { /* modified */ }
"""
),
java(
"""
package com.example;
class Second { }
"""
// No second arg = no change expected
)
);
```
### Tests with Parser Configuration
```java
@Override
public void defaults(RecipeSpec spec) {
spec.recipe(new YourRecipe())
.parser(JavaParser.fromJavaVersion()
.classpath("external-library"));
}
```
### Tests with Different Java Versions
```java
rewriteRun(
spec -> spec.parser(JavaParser.fromJavaVersion().version("11")),
java(
// Java 11 specific code
)
);
```
## Error Handling
### Safe Type Access
```java
// Always check for null
if (element.getType() != null) {
// Safe to use type
}
```
### Safe Method Type Access
```java
JavaType.Method methodType = method.getMethodType();
if (methodType != null && methodType.getDeclaringType() != null) {
// Safe to use
}
```
## Performance Optimization
### Referential Equality Check
```java
J.ClassDeclaration cd = super.visitClassDeclaration(classDecl, ctx);
if (!shouldChange(cd)) {
return cd; // Same object = no work downstream
}
```
### Early Return Pattern
```java
// Check cheapest conditions first
if (element.getName() == null) {
return element;
}
if (element.getType() == null) {
return element;
}
// Then check more expensive conditions
if (!TypeUtils.isOfClassType(element.getType(), targetType)) {
return element;
}
// Finally, make changes
return makeChanges(element);
```