From a7b3d2d9e876cf4541409fee5bda7422e8d8d741 Mon Sep 17 00:00:00 2001 From: Synvoya <16019863+Synvoya@users.noreply.github.com> Date: Sun, 12 Jul 2026 00:11:10 +1000 Subject: [PATCH] fix(php): capture interface/enum/trait heritage as class-like nodes Only class_declaration was registered as a PHP class type, so interface, enum, and trait declarations produced no node and their heritage edges (interface extends -> inherits, enum implements -> implements, trait use -> mixes_in) were dropped entirely. Add interface_declaration, enum_declaration, and trait_declaration to the PHP class_types. Enums wrap their members in an enum_declaration_list rather than a declaration_list, so also add it to body_fallback_child_types so enum methods/cases get walked. Adds a regression test covering interface extends, enum implements, trait use, and an enum-body method. --- graphify/extract.py | 13 +++++++++++-- tests/test_languages.py | 31 +++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/graphify/extract.py b/graphify/extract.py index 59d4f8283..e21e21705 100644 --- a/graphify/extract.py +++ b/graphify/extract.py @@ -796,7 +796,14 @@ def _get_c_func_name(node, source: bytes) -> str | None: _PHP_CONFIG = LanguageConfig( ts_module="tree_sitter_php", ts_language_fn="language_php", - class_types=frozenset({"class_declaration"}), + # interfaces/enums/traits are class-like containers whose heritage + # (extends/implements) and members must be captured, same as classes. + class_types=frozenset({ + "class_declaration", + "interface_declaration", + "enum_declaration", + "trait_declaration", + }), function_types=frozenset({"function_definition", "method_declaration"}), import_types=frozenset({"namespace_use_clause"}), call_types=frozenset({"function_call_expression", "member_call_expression", "scoped_call_expression", "class_constant_access_expression"}), @@ -808,7 +815,9 @@ def _get_c_func_name(node, source: bytes) -> str | None: call_accessor_node_types=frozenset({"member_call_expression"}), call_accessor_field="name", name_fallback_child_types=("name",), - body_fallback_child_types=("declaration_list", "compound_statement"), + # enums wrap their members in an enum_declaration_list rather than a + # declaration_list, so the body walk needs it to reach enum methods/cases. + body_fallback_child_types=("declaration_list", "compound_statement", "enum_declaration_list"), function_boundary_types=frozenset({"function_definition", "method_declaration"}), import_handler=_import_php, ) diff --git a/tests/test_languages.py b/tests/test_languages.py index f610fbc40..52a2641e5 100644 --- a/tests/test_languages.py +++ b/tests/test_languages.py @@ -816,6 +816,37 @@ def test_php_splits_inherits_implements_mixes_in(): assert ("DataProcessor", "HasName") in _edge_labels(r, "mixes_in") +def test_php_interface_enum_trait_heritage(tmp_path): + """Interfaces, enums, and traits must be captured as class-like nodes so + their heritage edges are emitted. Previously only `class_declaration` was a + class type, so interface/enum/trait declarations produced no node and their + extends/implements/use edges were dropped entirely. Enum members live under + an `enum_declaration_list` (not a `declaration_list`), so the body walk must + reach them too. + """ + src = ( + "