From d0458912355f19fb9e41d9c6383a72dabb729a5b Mon Sep 17 00:00:00 2001 From: xiongjianxu <6457197+xiongjianxu@users.noreply.github.com> Date: Tue, 13 Oct 2026 11:21:37 +0800 Subject: [PATCH] feat(cross-repo): link parked PHP member calls across a repo boundary A receiver typed to a class this build declares nowhere is parked on the caller (#3152); the merge then binds it to the one declaration in another repo. PHP was absent from `_LANG_SUFFIXES`, so parked PHP entries were never language-checked and the edge was dropped. Adding PHP's suffixes lets the merge finish the call, gated to PHP declarations like every other tier. Co-Authored-By: Claude Code (cherry picked from commit e7fee26542590c48878b039f9c3506449460e707) --- graphify/cross_repo_calls.py | 1 + tests/test_cross_repo_member_calls.py | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/graphify/cross_repo_calls.py b/graphify/cross_repo_calls.py index 2c232bde2..c3e3a13d5 100644 --- a/graphify/cross_repo_calls.py +++ b/graphify/cross_repo_calls.py @@ -44,6 +44,7 @@ _LANG_SUFFIXES: dict[str, frozenset[str]] = { "go": frozenset({".go"}), "java": frozenset({".java"}), "swift": frozenset({".swift"}), + "php": frozenset({".php", ".phtml", ".php3", ".php4", ".php5", ".php7", ".phps"}), } # A declaration owns its members through a `method` edge, except in C++, where an diff --git a/tests/test_cross_repo_member_calls.py b/tests/test_cross_repo_member_calls.py index 817b29538..196b30ffd 100644 --- a/tests/test_cross_repo_member_calls.py +++ b/tests/test_cross_repo_member_calls.py @@ -155,6 +155,28 @@ def test_a_cpp_call_does_not_bind_to_a_csharp_declaration(): assert link_cross_repo_member_calls(G) == 0 +PARKED_PHP = [{"callee": "greet", "receiver_type": "Greeter", "lang": "php", "line": "L4"}] + + +def test_a_php_call_binds_to_a_php_declaration_in_another_repo(): + G = _graph( + caller=_caller("a", PARKED_PHP, source_file="src/App.php"), + declarations=[(_declaration("b", "Greeter", "src/Greeter.php"), + _method("b", ".greet()", source_file="src/Greeter.php"))], + ) + assert link_cross_repo_member_calls(G) == 1 + assert _added_calls(G) == {("a::app_run", "b::greeter_greet")} + + +def test_a_php_call_does_not_bind_to_a_java_declaration(): + G = _graph( + caller=_caller("a", PARKED_PHP, source_file="src/App.php"), + declarations=[(_declaration("b", "Greeter", "src/Greeter.java"), + _method("b", ".greet()", source_file="src/Greeter.java"))], + ) + assert link_cross_repo_member_calls(G) == 0 + + def test_a_cpp_header_declaration_answers_through_defines(): # A C++ class that only declares `void greet();` owns it through `defines`, # the relation the extractor also uses for fields, so a header-only library