Files
gh-sjungling-claude-plugins…/skills/recipe-writer/references/common-patterns.md
2025-11-30 08:57:41 +08:00

7.4 KiB

OpenRewrite Recipe Common Patterns

Quick reference for frequently used code patterns in recipe development.

Import Management

Adding Imports

maybeAddImport("java.util.List");
maybeAddImport("java.util.Collections", "emptyList"); // Static import

Removing Imports

maybeRemoveImport("old.package.Type");

Visitor Patterns

Chaining Visitors

doAfterVisit(new OtherRecipe().getVisitor());

Cursor Messaging (Intra-visitor State)

// Store state
getCursor().putMessage("key", value);

// Retrieve state
Object value = getCursor().getNearestMessage("key");

Type Checking

Check Class Type

if (methodInvocation.getType() != null &&
    TypeUtils.isOfClassType(methodInvocation.getType(), "com.example.Type")) {
    // Type matches
}

Check Method Type

if (TypeUtils.isOfType(method.getMethodType(), "com.example.Type", "methodName")) {
    // Method matches
}

Check Assignability

if (TypeUtils.isAssignableTo("java.util.Collection", someType)) {
    // Type is assignable to Collection
}

LST Manipulation

Modifying Lists (Never Mutate!)

// 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

// Add at end
classDecl.withModifiers(
    ListUtils.concat(classDecl.getModifiers(), newModifier)
);

// Add at beginning
classDecl.withModifiers(
    ListUtils.concat(newModifier, classDecl.getModifiers())
);

Replacing in Lists

classDecl.withModifiers(
    ListUtils.map(classDecl.getModifiers(), mod ->
        shouldReplace(mod) ? newModifier : mod
    )
);

JavaTemplate Patterns

Simple Template

JavaTemplate template = JavaTemplate
    .builder("new Expression()")
    .build();

// Apply
expression = template.apply(getCursor(), expression.getCoordinates().replace());

Template with Imports

JavaTemplate template = JavaTemplate
    .builder("Collections.emptyList()")
    .imports("java.util.Collections")
    .build();

Template with Parameters

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

JavaTemplate template = JavaTemplate
    .builder("new CustomType()")
    .imports("com.external.CustomType")
    .javaParser(JavaParser.fromJavaVersion()
        .classpath("external-library"))
    .build();

Context-Sensitive Template

// Use ONLY when referencing local variables/methods
JavaTemplate template = JavaTemplate
    .builder("localVariable.toString()")
    .contextSensitive()
    .build();

Preconditions

Single Precondition

@Override
public TreeVisitor<?, ExecutionContext> getVisitor() {
    return Preconditions.check(
        new UsesType<>("com.example.Type", true),
        new YourVisitor()
    );
}

Multiple Preconditions (AND)

return Preconditions.check(
    Preconditions.and(
        new UsesType<>("com.example.Type", true),
        new UsesMethod<>("com.example.Type methodName(..)")
    ),
    new YourVisitor()
);

Multiple Preconditions (OR)

return Preconditions.check(
    Preconditions.or(
        new UsesType<>("com.example.TypeA", true),
        new UsesType<>("com.example.TypeB", true)
    ),
    new YourVisitor()
);

Java Version Check

return Preconditions.check(
    new UsesJavaVersion<>(17),
    new YourVisitor()
);

ScanningRecipe Patterns

Basic Accumulator Structure

public static class Accumulator {
    // Use per-project tracking for multi-module support
    Map<JavaProject, Set<String>> projectData = new HashMap<>();
}

Scanner (First Pass - Collect Only)

@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)

@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()

// Preserve formatting when adding elements
Space.format("\n" + indent)

Copy Formatting from Existing Elements

newElement = newElement.withPrefix(existingElement.getPrefix());

Testing Patterns

Multi-file Tests

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

@Override
public void defaults(RecipeSpec spec) {
    spec.recipe(new YourRecipe())
        .parser(JavaParser.fromJavaVersion()
            .classpath("external-library"));
}

Tests with Different Java Versions

rewriteRun(
    spec -> spec.parser(JavaParser.fromJavaVersion().version("11")),
    java(
        // Java 11 specific code
    )
);

Error Handling

Safe Type Access

// Always check for null
if (element.getType() != null) {
    // Safe to use type
}

Safe Method Type Access

JavaType.Method methodType = method.getMethodType();
if (methodType != null && methodType.getDeclaringType() != null) {
    // Safe to use
}

Performance Optimization

Referential Equality Check

J.ClassDeclaration cd = super.visitClassDeclaration(classDecl, ctx);

if (!shouldChange(cd)) {
    return cd; // Same object = no work downstream
}

Early Return Pattern

// 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);