| name | convert-openrewrite-to-jdt |
| description | Converts a Spring Boot language server quick fix in headless-services from an OpenRewrite recipe to a JDT-based refactoring, following this repo's stated goal of moving away from OpenRewrite recipes for Java quick fixes. Use when asked to convert, migrate, or port an OpenRewrite recipe (in commons-rewrite) to a JDT refactoring, or when implementing a new Java quick fix that should use JDT instead of OpenRewrite. |
Convert an OpenRewrite recipe to a JDT refactoring
Background
Every Java quick fix in spring-boot-language-server shares one diagnostic/CodeAction
pipeline (boot/java/reconcilers/JdtAstReconciler → ReconcileProblemImpl →
SimpleLanguageServer.createProblemCollector → LSP Diagnostic/CodeAction/
executeCommand → QuickfixRegistry). Only the fix descriptor and its execution
engine differ between the two mechanisms:
| OpenRewrite (old) | JDT (target) |
|---|
| Descriptor | FixDescriptor (commons-rewrite/.../java/FixDescriptor.java) | JdtFixDescriptor (boot/java/jdt/refactoring/JdtFixDescriptor.java) |
| Quickfix type id | RewriteRefactorings.REWRITE_RECIPE_QUICKFIX | JdtRefactorings.JDT_QUICKFIX |
| Fix logic | org.openrewrite.Recipe subclass; re-parses source with OpenRewrite's own parser | JdtRefactoring subclass; runs against the same JDT CompilationUnit used for reconciling |
| Scope | RecipeScope.NODE/FILE/PROJECT, generic | No generic scope concept — a JdtFixDescriptor just lists the docUris to apply to; each refactoring implements its own "multiple targets" story (e.g. int... offsets) |
Workflow
-
Read the recipe and its test. Recipe lives in
headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/java/
(or org/openrewrite/java/spring/...). Its test (sibling src/test tree) is the
ground truth for the before/after behavior you must preserve.
-
Find the reconciler wiring the recipe. Grep the recipe's class name under
spring-boot-language-server/.../boot/java/reconcilers/. Read how it detects the
problem (JDT AST + resolved bindings + AnnotationHierarchies) and what
FixDescriptor(s)/RecipeScope(s) it builds via ReconcileUtils.setRewriteFixes.
-
Design the JdtRefactoring. Look at existing examples in
boot/java/jdt/refactoring/ for the closest shape:
- Offset-anchored, single or batched fix —
ChangeMethodVisibilityRefactoring,
RemoveAnnotationRefactoring, AddAnnotationRefactoring (constructor takes the
int offset(s) the reconciler already found; apply() re-locates the node via
NodeFinder).
- Offset-anchored, re-derives related nodes —
ExtractRequestMappingParentPathRefactoring
(class + method offsets, matches annotations by simple name).
- Self-scanning, no offsets —
MovePathToRequestMappingRefactoring (used when the
fix genuinely needs to re-scan a whole compilation unit, e.g. for a "fix all in
file" quick fix without pre-collected offsets).
Prefer an offset-based design when the reconciler already found the exact node(s) —
it's cheaper and matches most of the codebase. Only self-scan when you truly need to
reprocess a whole file.
-
Match by simple name, not resolved bindings, inside the refactoring.
JdtRefactoring.apply() runs with real bindings in production (via
CompilationUnitCache), but this class's own unit tests parse with an empty
classpath (see ExtractRequestMappingParentPathRefactoringTest), so
resolveTypeBinding()/resolveBinding() return null there. Match annotation/type
names via JdtRefactorUtils.extractSimpleName(...) on the syntactic type name, the
way every existing does — don't rely on resolved bindings inside
these classes. (The , which always runs with a real classpath, is the
right place to use bindings/ for the actual diagnostic
detection — leave that binding-based logic alone.)
Key files
| Purpose | Path |
|---|
| JDT refactoring interface | spring-boot-language-server/.../boot/java/jdt/refactoring/JdtRefactoring.java |
| JDT execution engine | .../jdt/refactoring/JdtRefactorings.java |
| JDT fix descriptor | .../jdt/refactoring/JdtFixDescriptor.java |
| Shared JDT AST helpers | .../jdt/refactoring/JdtRefactorUtils.java |
| Gson polymorphic registration | .../boot/index/cache/IndexGsonTypeFactories.java (jdtRefactorings()) |
| Reconciler examples | .../boot/java/reconcilers/*Reconciler.java |
| Old-style OpenRewrite recipes | commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/java/ |