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 = ( + "