mirror of
https://github.com/safishamsi/graphify.git
synced 2026-10-16 08:59:26 +00:00
refactor(extract): hoist PHP route helper to module level (#3409)
Address maintainer nits by moving _php_get_route_name and the
_PHP_ROUTING_VERBS frozenset out of the _extract_generic body
to the module level, preventing them from being repeatedly
re-initialized per file on large codebases.
(cherry picked from commit e845f15b68)
This commit is contained in:
1 parent
8227c468c2
commit
435f5a88e9
1 file changed
+60
-60
@@ -3457,6 +3457,66 @@ def _lua_is_require_call(node, source: bytes) -> bool:
|
||||
return False
|
||||
return _read_text(name_node, source) == "require"
|
||||
|
||||
_PHP_ROUTING_VERBS = frozenset({"get", "post", "put", "patch", "delete", "options", "any", "match", "map"})
|
||||
|
||||
def _php_get_route_name(closure_node, src: bytes) -> str | None:
|
||||
"""Walk up the AST to extract grouped routing prefixes (#3409)."""
|
||||
prefixes = []
|
||||
verb = None
|
||||
|
||||
curr = closure_node.parent
|
||||
while curr is not None:
|
||||
if curr.type in ("function_definition", "method_declaration", "class_declaration"):
|
||||
break
|
||||
|
||||
if curr.type == "argument":
|
||||
arg_list = curr.parent
|
||||
if arg_list is not None and arg_list.type == "argument_list":
|
||||
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")
|
||||
if name_node is None:
|
||||
name_node = call.child_by_field_name("function")
|
||||
|
||||
raw_method = (_read_text(name_node, src) if name_node else "").lower()
|
||||
path_text = 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("'\"")
|
||||
break
|
||||
|
||||
if verb is None:
|
||||
# The innermost call must be a routing verb with a path starting with '/'
|
||||
if path_text is not None and path_text.startswith("/") and raw_method in _PHP_ROUTING_VERBS:
|
||||
verb = raw_method.upper()
|
||||
prefixes.append(path_text)
|
||||
else:
|
||||
return None # Not a valid route closure
|
||||
else:
|
||||
# Outer calls (e.g. group(), prefix()) just contribute their prefix if it starts with '/'
|
||||
if path_text is not None and path_text.startswith("/"):
|
||||
prefixes.append(path_text)
|
||||
|
||||
curr = call.parent
|
||||
continue
|
||||
|
||||
elif curr.type in ("anonymous_function_creation_expression", "arrow_function"):
|
||||
# Jump across the closure boundary to its containing argument
|
||||
curr = curr.parent
|
||||
continue
|
||||
|
||||
curr = curr.parent
|
||||
|
||||
if verb and prefixes:
|
||||
# prefixes are inside-out (innermost path is first)
|
||||
# e.g. ['/users/{id}', '/api/v1'] -> '/api/v1/users/{id}'
|
||||
full_path = "/" + "/".join(p.strip("/") for p in reversed(prefixes) if p.strip("/"))
|
||||
return f"{verb} {full_path}"
|
||||
|
||||
return None
|
||||
|
||||
def _extract_generic(
|
||||
path: Path, config: LanguageConfig, *, source_override: bytes | None = None,
|
||||
@@ -3559,66 +3619,6 @@ def _extract_generic(
|
||||
php_closure_counts: dict[str, int] = {}
|
||||
pending_listen_edges: list[tuple[str, str, int]] = []
|
||||
|
||||
def _php_get_route_name(closure_node, src: bytes) -> str | None:
|
||||
"""Walk up the AST to extract grouped routing prefixes (#3409)."""
|
||||
_routing_verbs = frozenset({"get", "post", "put", "patch", "delete", "options", "any", "match", "map"})
|
||||
prefixes = []
|
||||
verb = None
|
||||
|
||||
curr = closure_node.parent
|
||||
while curr is not None:
|
||||
if curr.type in ("function_definition", "method_declaration", "class_declaration"):
|
||||
break
|
||||
|
||||
if curr.type == "argument":
|
||||
arg_list = curr.parent
|
||||
if arg_list is not None and arg_list.type == "argument_list":
|
||||
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")
|
||||
if name_node is None:
|
||||
name_node = call.child_by_field_name("function")
|
||||
|
||||
raw_method = (_read_text(name_node, src) if name_node else "").lower()
|
||||
path_text = 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("'\"")
|
||||
break
|
||||
|
||||
if verb is None:
|
||||
# The innermost call must be a routing verb with a path starting with '/'
|
||||
if path_text is not None and path_text.startswith("/") and raw_method in _routing_verbs:
|
||||
verb = raw_method.upper()
|
||||
prefixes.append(path_text)
|
||||
else:
|
||||
return None # Not a valid route closure
|
||||
else:
|
||||
# Outer calls (e.g. group(), prefix()) just contribute their prefix if it starts with '/'
|
||||
if path_text is not None and path_text.startswith("/"):
|
||||
prefixes.append(path_text)
|
||||
|
||||
curr = call.parent
|
||||
continue
|
||||
|
||||
elif curr.type in ("anonymous_function_creation_expression", "arrow_function"):
|
||||
# Jump across the closure boundary to its containing argument
|
||||
curr = curr.parent
|
||||
continue
|
||||
|
||||
curr = curr.parent
|
||||
|
||||
if verb and prefixes:
|
||||
# prefixes are inside-out (innermost path is first)
|
||||
# e.g. ['/users/{id}', '/api/v1'] -> '/api/v1/users/{id}'
|
||||
full_path = "/" + "/".join(p.strip("/") for p in reversed(prefixes) if p.strip("/"))
|
||||
return f"{verb} {full_path}"
|
||||
|
||||
return None
|
||||
|
||||
# tree-sitter-swift parses both `class Foo` and `extension Foo` as
|
||||
# `class_declaration`. Same-file pairs collapse via seen_ids, but cross-file
|
||||
# extensions don't (file stem is part of the id), so they're collected here
|
||||
|
||||
Reference in new issue
Block a user