From 940cb53f4d69bd42be9e499f4f3c3216825d5b62 Mon Sep 17 00:00:00 2001 From: oleksii-tumanov Date: Sun, 28 Jun 2026 10:31:22 +0100 Subject: [PATCH] fix(extract): emit Java enum and annotation declarations as type nodes (#1512) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Java `enum` and `@interface` (annotation) declarations weren't in _JAVA_CONFIG's class_types, so they were never emitted as type nodes — a field typed as an enum or a class annotated with a project annotation referenced a node that didn't exist (dangling). enum_declaration and annotation_type_declaration are now extracted as first-class (sourced) type nodes, so those references resolve onto them. Ported from PR #1513 by @oleksii-tumanov. Co-Authored-By: Claude Opus 4.8 (1M context) --- graphify/extract.py | 6 +++++- tests/test_languages.py | 30 ++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/graphify/extract.py b/graphify/extract.py index fa103cb..a3f9b29 100644 --- a/graphify/extract.py +++ b/graphify/extract.py @@ -2065,7 +2065,11 @@ _JAVA_CONFIG = LanguageConfig( ts_module="tree_sitter_java", # record_declaration shares class_declaration's name/body/interfaces fields, # so it becomes a first-class type node instead of an isolated file (#1373). - class_types=frozenset({"class_declaration", "interface_declaration", "record_declaration"}), + # Enums and annotation declarations use the same name/body contract. + class_types=frozenset({ + "class_declaration", "interface_declaration", "record_declaration", + "enum_declaration", "annotation_type_declaration", + }), function_types=frozenset({"method_declaration", "constructor_declaration"}), import_types=frozenset({"import_declaration"}), # object_creation_expression (`new Foo(...)`) is handled by a dedicated Java diff --git a/tests/test_languages.py b/tests/test_languages.py index d2fd439..ba41156 100644 --- a/tests/test_languages.py +++ b/tests/test_languages.py @@ -399,6 +399,36 @@ def test_java_type_annotations_have_attribute_context(tmp_path): assert ("CheckoutService", "Entity") in refs +def test_java_enum_and_annotation_declarations_are_type_nodes(tmp_path): + source = tmp_path / "TypeDeclarations.java" + source.write_text( + "enum PaymentStatus { PENDING, PAID }\n" + "@interface Audited {}\n" + "class Order { PaymentStatus status; }\n" + "@Audited class CheckoutService {}\n" + ) + + result = extract_java(source) + + assert ("TypeDeclarations.java", "PaymentStatus") in _edge_labels( + result, "contains" + ) + assert ("TypeDeclarations.java", "Audited") in _edge_labels(result, "contains") + assert ("Order", "PaymentStatus") in _edge_labels( + result, "references", "field" + ) + assert ("CheckoutService", "Audited") in _edge_labels( + result, "references", "attribute" + ) + definitions = { + node["label"]: node + for node in result["nodes"] + if node.get("label") in {"PaymentStatus", "Audited"} + } + assert definitions["PaymentStatus"].get("source_file") == str(source) + assert definitions["Audited"].get("source_file") == str(source) + + def test_csharp_field_type_references_have_field_context(): r = extract_csharp(FIXTURES / "sample.cs") refs = _references(r)