diff --git a/graphify/extract.py b/graphify/extract.py index a3f9b291..863ae480 100644 --- a/graphify/extract.py +++ b/graphify/extract.py @@ -2780,11 +2780,25 @@ def _extract_generic( seen_ids.add(base_nid) add_edge(class_nid, base_nid, rel, at_line) + def _emit_java_parent_type(type_node, rel: str, at_line: int) -> None: + refs: list[tuple[str, str]] = [] + _java_collect_type_refs(type_node, source, False, refs) + parent_emitted = False + for ref_name, role in refs: + if role == "type" and not parent_emitted: + _emit_java_parent(ref_name, rel, at_line) + parent_emitted = True + elif role == "generic_arg": + target_nid = ensure_named_node(ref_name, at_line) + if target_nid != class_nid: + add_edge(class_nid, target_nid, "references", at_line, + context="generic_arg") + sup = node.child_by_field_name("superclass") if sup is not None: for sub in sup.children: - if sub.type == "type_identifier": - _emit_java_parent(_read_text(sub, source), "inherits", line) + if sub.is_named: + _emit_java_parent_type(sub, "inherits", line) break ifs = node.child_by_field_name("interfaces") @@ -2792,8 +2806,8 @@ def _extract_generic( for sub in ifs.children: if sub.type == "type_list": for tid in sub.children: - if tid.type == "type_identifier": - _emit_java_parent(_read_text(tid, source), "implements", line) + if tid.is_named: + _emit_java_parent_type(tid, "implements", line) if t == "interface_declaration": for child in node.children: @@ -2801,8 +2815,8 @@ def _extract_generic( for sub in child.children: if sub.type == "type_list": for tid in sub.children: - if tid.type == "type_identifier": - _emit_java_parent(_read_text(tid, source), "inherits", line) + if tid.is_named: + _emit_java_parent_type(tid, "inherits", line) for anno_name in _java_annotation_names(node, source): target_nid = ensure_named_node(anno_name, line) diff --git a/tests/test_languages.py b/tests/test_languages.py index ba411562..65ac204e 100644 --- a/tests/test_languages.py +++ b/tests/test_languages.py @@ -357,6 +357,28 @@ def test_java_normalizes_inherits_and_implements(): assert ("DataProcessor", "Processor") in _edge_labels(result, "implements") +def test_java_generic_parents_include_type_argument_references(tmp_path): + source = tmp_path / "GenericParents.java" + source.write_text( + "class Dependency {}\n" + "interface Event {}\n" + "class Base {}\n" + "interface Handler {}\n" + "interface DerivedHandler extends Handler {}\n" + "class Service extends Base implements Handler {}\n" + ) + + result = extract_java(source) + + assert ("Service", "Base") in _edge_labels(result, "inherits") + assert ("Service", "Handler") in _edge_labels(result, "implements") + refs = _edge_labels(result, "references", "generic_arg") + assert ("Service", "Dependency") in refs + assert ("Service", "Event") in refs + assert ("DerivedHandler", "Handler") in _edge_labels(result, "inherits") + assert ("DerivedHandler", "Event") in refs + + def test_java_parameter_return_generic_and_attribute_contexts(): result = extract_java(FIXTURES / "sample.java") assert ("build", "HttpClient") in _edge_labels(result, "references", "parameter_type")