Skip to content
Merged
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
33 changes: 21 additions & 12 deletions src/codegraphcontext/tools/languages/php.py
Original file line number Diff line number Diff line change
Expand Up @@ -230,18 +230,27 @@ def _parse_functions(self, captures: list, source_code: str, path: Path, var_typ
if params_node:
# PHP parameters: function($a, $b)
for child in params_node.children:
if "variable_name" in child.type or "simple_parameter" in child.type:
var_node = child if "variable_name" in child.type else child.child_by_field_name("name")
type_node = child.child_by_field_name("type") if "simple_parameter" in child.type else None

if var_node:
var_name = self._get_node_text(var_node)
parameters.append(var_name)
if type_node:
var_type = self._get_node_text(type_node)
# Extract actual type from union/nullable types
var_type = var_type.lstrip("?").split("|")[0].strip()
var_type_map[(func_name, var_name)] = var_type
if child.type == "variable_name":
var_node = child
type_node = None
elif child.type in (
"simple_parameter",
"property_promotion_parameter",
"variadic_parameter",
):
var_node = child.child_by_field_name("name")
type_node = child.child_by_field_name("type")
else:
continue

if var_node:
var_name = self._get_node_text(var_node)
parameters.append(var_name)
if type_node:
var_type = self._get_node_text(type_node)
# Extract actual type from union/nullable types
var_type = var_type.lstrip("?").split("|")[0].strip()
var_type_map[(func_name, var_name)] = var_type

source_text = self._get_node_text(node)

Expand Down
38 changes: 29 additions & 9 deletions tests/unit/parsers/test_php_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,16 @@
from codegraphcontext.utils.tree_sitter_manager import get_tree_sitter_manager


@pytest.fixture(scope="module")
def parser():
manager = get_tree_sitter_manager()
wrapper = MagicMock()
wrapper.language_name = "php"
wrapper.language = manager.get_language_safe("php")
wrapper.parser = manager.create_parser("php")
return PhpTreeSitterParser(wrapper)


class TestPhpImports:
"""PHP `use` handling.

Expand All @@ -13,15 +23,6 @@ class TestPhpImports:
import and labelled trait uses as imports instead.
"""

@pytest.fixture(scope="class")
def parser(self):
manager = get_tree_sitter_manager()
wrapper = MagicMock()
wrapper.language_name = "php"
wrapper.language = manager.get_language_safe("php")
wrapper.parser = manager.create_parser("php")
return PhpTreeSitterParser(wrapper)

def _imports(self, parser, tmp_path, code):
f = tmp_path / "sample.php"
f.write_text(code, encoding="utf-8")
Expand Down Expand Up @@ -82,3 +83,22 @@ def test_imports_are_ordered_by_line(self, parser, tmp_path):
)
imports = self._imports(parser, tmp_path, code)
assert [i["line_number"] for i in imports] == [2, 3, 4]


def test_promoted_and_variadic_parameters(parser, tmp_path):
code = (
"<?php\n"
"class Service {\n"
" public function __construct(private string $dsn) {}\n"
"}\n"
"function all(...$filters) {}\n"
)
f = tmp_path / "parameters.php"
f.write_text(code, encoding="utf-8")

functions = parser.parse(str(f))["functions"]

assert {function["name"]: function["parameters"] for function in functions} == {
"__construct": ["$dsn"],
"all": ["$filters"],
}
Loading