diff --git a/graphify/extract.py b/graphify/extract.py index a37298f07..9e1853706 100644 --- a/graphify/extract.py +++ b/graphify/extract.py @@ -1459,7 +1459,7 @@ _PHP_CONFIG = LanguageConfig( "enum_declaration", "trait_declaration", }), - function_types=frozenset({"function_definition", "method_declaration", "anonymous_function_creation_expression", "arrow_function"}), + function_types=frozenset({"function_definition", "method_declaration", "anonymous_function", "arrow_function"}), import_types=frozenset({"namespace_use_clause"}), # object_creation_expression joins the dispatch set so `new Foo(...)` links # the constructing method to Foo (engine has a dedicated PHP branch: the @@ -1477,7 +1477,7 @@ _PHP_CONFIG = LanguageConfig( # 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", "anonymous_function_creation_expression", "arrow_function"}), + function_boundary_types=frozenset({"function_definition", "method_declaration", "anonymous_function", "arrow_function"}), import_handler=_import_php, ) diff --git a/graphify/extractors/engine.py b/graphify/extractors/engine.py index e0e00364a..88c25a143 100644 --- a/graphify/extractors/engine.py +++ b/graphify/extractors/engine.py @@ -3471,7 +3471,7 @@ def _php_get_route_name(closure_node, src: bytes) -> str | None: if curr.type == "argument": arg_list = curr.parent - if arg_list is not None and arg_list.type == "argument_list": + if arg_list is not None and arg_list.type == "arguments": call = arg_list.parent if call is not None and call.type in ("member_call_expression", "function_call_expression", "scoped_call_expression"): name_node = call.child_by_field_name("name") @@ -3484,8 +3484,16 @@ def _php_get_route_name(closure_node, src: bytes) -> str | None: for sibling in arg_list.children: if sibling is curr: break - if sibling.type in ("string", "encapsed_string"): - path_text = _read_text(sibling, src).strip("'\"") + + target = sibling + if sibling.type == "argument": + for c in sibling.children: + if c.type in ("string", "encapsed_string"): + target = c + break + + if target.type in ("string", "encapsed_string"): + path_text = _read_text(target, src).strip("'\"") break if verb is None: @@ -3503,7 +3511,7 @@ def _php_get_route_name(closure_node, src: bytes) -> str | None: curr = call.parent continue - elif curr.type in ("anonymous_function_creation_expression", "arrow_function"): + elif curr.type in ("anonymous_function", "arrow_function"): # Jump across the closure boundary to its containing argument curr = curr.parent continue @@ -4992,7 +5000,7 @@ def _extract_generic( func_name = _read_text(name_node, source) if name_node else None if not func_name: - if t in ("anonymous_function_creation_expression", "arrow_function"): + if t in ("anonymous_function", "arrow_function"): route_name = _php_get_route_name(node, source) if route_name: func_name = route_name @@ -5445,6 +5453,10 @@ def _extract_generic( scope_parents=scope_parents, lexical_nids_by_scope=lexical_nids_by_scope, ) + if config.ts_module == "tree_sitter_php": + # Manually walk the body to find nested closures, passing the + # body node itself so `walk()` visits its children. + walk(body, parent_class_nid=parent_class_nid) if config.ts_module == "tree_sitter_kotlin": # #2347: Kotlin anonymous objects (`object : Foo { … }`, # node type `object_literal`). The function branch never