diff --git a/strategies/extractors.go b/strategies/extractors.go index 44faae0..fc22572 100644 --- a/strategies/extractors.go +++ b/strategies/extractors.go @@ -1908,11 +1908,23 @@ func javaVisit(node *sitter.Node, filePath, blobSHA string, src []byte, imports javaMethodDecl(n, astkit.KindMethod, filePath, blobSHA, src, imports, parentClass, out) case "enum_body_declarations": javaVisit(n, filePath, blobSHA, src, imports, parentClass, out) + case "static_initializer", "block": + // `static { ... }` and instance initializer blocks: anonymous + // classes and lambdas declared inside them belong to the class. + if parentClass != "" { + javaNestedExecutables(n, filePath, blobSHA, src, imports, parentClass, out) + } case "enum_constant": if parentClass == "" { continue } javaEnumConstantDecl(n, src, parentClass, out) + // `TWEETS(new TypeToken>() {})`: anonymous classes + // and lambdas passed as constructor arguments are declarations of + // the enum (gson ParseBenchmark, 2026-10-12). + if args := n.ChildByFieldName("arguments"); args != nil { + javaNestedExecutables(args, filePath, blobSHA, src, imports, parentClass, out) + } if body := n.ChildByFieldName("body"); body != nil { javaVisit(body, filePath, blobSHA, src, imports, parentClass, out) } @@ -2245,7 +2257,11 @@ func javaLambdaDecl(n *sitter.Node, filePath, blobSHA string, src []byte, import Span: internalast.NodeSpan(n), Body: n.Content(src), Modifiers: []string{"lambda"}, CallSites: javaCallSites(body, src), }) - javaNestedExecutables(body, filePath, blobSHA, src, imports, parentClass, out) + // Walk the lambda node, not its body: an expression body can itself be + // the anonymous class or lambda (`url -> new Monitor() {...}`, dubbo; + // `() -> new SubTypeToken() {}`, gson), and the walker only + // inspects children (2026-10-12). + javaNestedExecutables(n, filePath, blobSHA, src, imports, parentClass, out) } func javaFieldDecl(n *sitter.Node, filePath, blobSHA string, src []byte, imports []string, parentClass string, out *[]astkit.Symbol) { @@ -2277,6 +2293,13 @@ func javaFieldDecl(n *sitter.Node, filePath, blobSHA string, src []byte, imports Modifiers: modifiers, Annotations: javaAnnotations(n, src), }) + // An initializer declares code too: `CLASS = new TypeAdapter<>() { + // write(...) {...} }` (gson TypeAdapters has 25 such adapters; none + // was indexed, so change_impact on TypeAdapter.write reported the + // family closed at 6 of 31 overrides, 2026-10-12). Lambdas likewise. + // Walk the declarator, not its value: the value node is often the + // anonymous class or lambda itself, and the walker inspects children. + javaNestedExecutables(decl, filePath, blobSHA, src, imports, parentClass, out) } } diff --git a/strategies/metadata_test.go b/strategies/metadata_test.go index ec7384d..d691a82 100644 --- a/strategies/metadata_test.go +++ b/strategies/metadata_test.go @@ -1412,3 +1412,70 @@ public class Outer2 { } } } + +// gson TypeAdapters.java (2026-10-12): 25 of its 31 TypeAdapter.write +// overrides live in anonymous classes assigned to static fields, and none +// was indexed. Anonymous classes and lambdas in field initializers and +// initializer blocks are declarations of the enclosing class. +func TestJava_AnonymousClassesInFieldInitializersAndBlocks(t *testing.T) { + src := `class Adapters { + static final Adapter CLASS = new Adapter() { + @Override public void write(Out out, Object v) { out.nullValue(); } + }; + final Runnable r = () -> { run(); }; + static { + REGISTRY.add(new Adapter() { public void write(Out out, Object v) {} }); + } + { helper = new Adapter() { public void write(Out out, Object v) {} }; } + abstract static class Adapter { public abstract void write(Out out, Object v); } +}` + syms, _ := extract(t, astkit.LangJava, src) + var anon, writes, lambdas int + for _, s := range syms { + switch { + case s.Kind == astkit.KindClass && strings.HasPrefix(s.Name, " new Monitor() { + public String getUrl() { return url; } + }; + void m() { run(() -> new Token() {}); } + enum Api { + TWEETS(new Token() {}, x -> x); + Api(Token t, Fn f) {} + } +}` + syms, _ := extract(t, astkit.LangJava, src) + var anon, getURL, lambdas int + for _, s := range syms { + switch { + case s.Kind == astkit.KindClass && strings.HasPrefix(s.Name, "