feat(java): index Lombok-generated members so call chains resolve (#912) (#953)

Lombok generates getters/setters, builder(), equals/hashCode/toString, and
the @Slf4j log field at compile time, so they never appear in the source AST.
Static extraction missed them entirely, so a bean.getName() / User.builder() /
log.info() call resolved to nothing and call-chain analysis broke silently —
the agent would conclude the method didn't exist.

Add a synthesizeMembers hook on LanguageExtractor, called at the end of class
extraction (class still on the scope stack, real members already extracted), and
a Java implementation that synthesizes the mechanical members for @Getter,
@Setter, @Data, @Value, @Builder/@SuperBuilder, @ToString, @EqualsAndHashCode,
and the @Log* family. Each node is anchored on the field/class name-token leaf
(so it pulls in no spurious value-reference scope), marked with a `lombok`
decorator and a docstring naming the generating annotation, and never overrides
a member the source already declares. Methods and fields are deduped separately
since they're distinct namespaces in Java (a boolean field `isRunning` and its
generated getter `isRunning()` coexist).

Deliberately not synthesized: constructors (new X() already links via
instantiates, and overloaded @NoArgs/@AllArgs/@RequiredArgs ctors would collide
on a synthetic node id), fluent builder setters, and @Accessors(fluent=true).

Validated on eladmin (274 Java files, Lombok-heavy): 100% accessor precision
(878/878 map to a real field), 722 previously-broken calls now resolve;
spring-petclinic (no Lombok) control synthesizes nothing.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Colby Mchenry
2026-06-22 12:41:26 -05:00
committed by GitHub
co-authored by Claude Opus 4.8
parent 3e1547bbe1
commit 826810f128
5 changed files with 404 additions and 10 deletions
+14
View File
@@ -174,6 +174,20 @@ export interface LanguageExtractor {
*/
visitNode?: (node: SyntaxNode, ctx: ExtractorContext) => boolean;
/**
* Synthesize members that exist at compile time but not in the source AST,
* called at the end of class extraction with the class still on the scope
* stack (so `ctx.createNode` attaches containment + qualified names) and the
* class's real members already extracted (so the hook can skip a member the
* source explicitly declares). Used by Java for Lombok-generated accessors
* (`@Getter`/`@Setter`/`@Data`/`@Value`/`@Builder` → `getX`/`setX`/`builder`/
* `equals`/`hashCode`/`toString` + the `log` field), which are otherwise
* invisible and break call-chain analysis (#912). The created nodes carry a
* `lombok` decorator + a docstring naming the generating annotation, so an
* agent can tell them apart from hand-written code.
*/
synthesizeMembers?: (classNode: SyntaxNode, ctx: ExtractorContext) => void;
/**
* Classify a class_declaration node when the grammar reuses one node type
* for multiple concepts (e.g. Swift uses class_declaration for classes, structs, and enums).