fix(extract): emit references for Java type annotations (#1487)

Annotations on Java classes/interfaces/records (e.g. @Service, @Entity) produced
no edge, so type-level framework wiring was invisible even though method-level
annotations were already captured. The class/interface/record handler now emits
`references` edges with the existing `attribute` context, reusing the renamed
`_java_annotation_names` helper (was `_java_method_annotation_names`; logic
unchanged) at both the type and method call sites.

Ported from PR #1487 by @oleksii-tumanov. Resolved an additive test conflict with
#1485 by keeping both new test functions.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
oleksii-tumanov
2026-06-27 10:19:15 +01:00
committed by safishamsi
co-authored by Claude Opus 4.8
parent 31b3752902
commit 9b49bfd9eb
2 changed files with 25 additions and 4 deletions
+10 -4
View File
@@ -684,11 +684,11 @@ def _java_collect_type_refs(node, source: bytes, generic: bool, out: list[tuple[
_java_collect_type_refs(c, source, generic, out)
def _java_method_annotation_names(method_node, source: bytes) -> list[str]:
"""Collect annotation names from a Java method's `modifiers` child."""
def _java_annotation_names(declaration_node, source: bytes) -> list[str]:
"""Collect annotation names from a Java declaration's `modifiers` child."""
names: list[str] = []
modifiers = None
for child in method_node.children:
for child in declaration_node.children:
if child.type == "modifiers":
modifiers = child
break
@@ -2792,6 +2792,12 @@ def _extract_generic(
if tid.type == "type_identifier":
_emit_java_parent(_read_text(tid, source), "inherits", line)
for anno_name in _java_annotation_names(node, source):
target_nid = ensure_named_node(anno_name, line)
if target_nid != class_nid:
add_edge(class_nid, target_nid, "references", line,
context="attribute")
# Scala: extends_clause carries `extends Base with Trait1 with Trait2`.
# The first base after `extends` is `inherits`; each subsequent
# type after `with` is `mixes_in`. Also walk class_parameters for
@@ -3206,7 +3212,7 @@ def _extract_generic(
target_nid = ensure_named_node(ref_name, line)
if target_nid != func_nid:
add_edge(func_nid, target_nid, "references", line, context=ctx)
for anno_name in _java_method_annotation_names(node, source):
for anno_name in _java_annotation_names(node, source):
target_nid = ensure_named_node(anno_name, line)
if target_nid != func_nid:
add_edge(func_nid, target_nid, "references", line, context="attribute")
+15
View File
@@ -363,6 +363,21 @@ def test_java_field_type_references_have_field_context(tmp_path):
)
def test_java_type_annotations_have_attribute_context(tmp_path):
source = tmp_path / "TypeAnnotations.java"
source.write_text(
'@Service\n'
'@Entity(name = "checkout")\n'
'class CheckoutService {}\n'
)
result = extract_java(source)
refs = _edge_labels(result, "references", "attribute")
assert ("CheckoutService", "Service") in refs
assert ("CheckoutService", "Entity") in refs
def test_csharp_field_type_references_have_field_context():
r = extract_csharp(FIXTURES / "sample.cs")
refs = _references(r)