(&mut self, node: Node<'t>)
| 987 | // --- calls --------------------------------------------------------------------- |
| 988 | |
| 989 | fn extract_call(&mut self, node: Node<'t>) { |
| 990 | if self.stack.is_empty() { |
| 991 | return; |
| 992 | } |
| 993 | let caller = self.top_row(); |
| 994 | let mut callee_name = String::new(); |
| 995 | |
| 996 | let name_field = node.child_by_field_name("name"); |
| 997 | let object_field = node |
| 998 | .child_by_field_name("object") |
| 999 | .or_else(|| node.child_by_field_name("scope")); |
| 1000 | |
| 1001 | if let (Some(name_field), Some(object_field)) = (name_field, object_field) { |
| 1002 | // member_call_expression / scoped_call_expression. |
| 1003 | let method_name = self.text(name_field); |
| 1004 | |
| 1005 | // Fluent static-factory `Cls::factory($x)->method()` — encode |
| 1006 | // `Cls::factory().method` (inner args dropped) and return; the |
| 1007 | // inner scoped call is also visited by recursion (`Cls.factory`). |
| 1008 | if !method_name.is_empty() && object_field.kind() == "scoped_call_expression" { |
| 1009 | let inner_scope = object_field.child_by_field_name("scope"); |
| 1010 | let inner_name = object_field.child_by_field_name("name"); |
| 1011 | let callee = match (inner_scope, inner_name) { |
| 1012 | (Some(s), Some(n)) => { |
| 1013 | format!("{}::{}().{method_name}", self.text(s), self.text(n)) |
| 1014 | } |
| 1015 | _ => method_name.to_string(), |
| 1016 | }; |
| 1017 | if !callee.is_empty() { |
| 1018 | self.push_ref_at(caller, &callee, edge_kind_index("calls").unwrap(), node); |
| 1019 | } |
| 1020 | return; |
| 1021 | } |
| 1022 | |
| 1023 | // receiverName = raw receiver text with ONE leading `$` stripped: |
| 1024 | // `$this->prop->m()` → `this->prop.m` (#1251 encoding — the whole |
| 1025 | // resolution machinery is TS-side); chains keep args |
| 1026 | // (`this->factory($cfg).m`); literals are NOT suppressed |
| 1027 | // (`"chain".upper`); scoped calls are DOT-joined (`UserModel.query`). |
| 1028 | let receiver_raw = self.text(object_field); |
| 1029 | let receiver = receiver_raw.strip_prefix('$').unwrap_or(receiver_raw); |
| 1030 | if !method_name.is_empty() { |
| 1031 | if matches!(receiver, "self" | "this" | "cls" | "super" | "parent" | "static") { |
| 1032 | callee_name = method_name.to_string(); |
| 1033 | } else { |
| 1034 | callee_name = format!("{receiver}.{method_name}"); |
| 1035 | } |
| 1036 | } |
| 1037 | } else { |
| 1038 | // function_call_expression: raw func text — bare `helper`, |
| 1039 | // qualified `\App\Helpers\format_id` verbatim, `$fn` for |
| 1040 | // variable callees, FCC `f(...)` → `f`. |
| 1041 | let func = node |
| 1042 | .child_by_field_name("function") |
| 1043 | .or_else(|| node.named_child(0)); |
| 1044 | if let Some(func) = func { |
| 1045 | callee_name = self.text(func).to_string(); |
| 1046 | } |
no test coverage detected