Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions graphify/extract.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"}),
Expand All @@ -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,
)
Expand Down
31 changes: 31 additions & 0 deletions tests/test_languages.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = (
"<?php\n"
"interface Identifiable {}\n"
"interface Nameable extends Identifiable {}\n"
"enum Suit: string implements Nameable {\n"
" case Hearts = 'H';\n"
" public function label(): string { return 'x'; }\n"
"}\n"
"trait Named {}\n"
"trait Greets { use Named; }\n"
)
f = tmp_path / "heritage.php"
f.write_text(src)
r = extract_php(f)
labels = _labels(r)
assert "Nameable" in labels and "Suit" in labels and "Greets" in labels
assert ("Nameable", "Identifiable") in _edge_labels(r, "inherits")
assert ("Suit", "Nameable") in _edge_labels(r, "implements")
assert ("Greets", "Named") in _edge_labels(r, "mixes_in")
# enum body (enum_declaration_list) must be walked to reach enum methods
assert ("Suit", "label") in _edge_labels(r, "method")


def test_php_property_parameter_and_return_contexts():
r = extract_php(FIXTURES / "sample.php")
assert ("DataProcessor", "Result") in _edge_labels(r, "references", "field")
Expand Down
Loading