(&mut self, v: Node<'t>, from: u32, depth: u32)
| 1259 | } |
| 1260 | |
| 1261 | fn normalize_fn_ref_value(&mut self, v: Node<'t>, from: u32, depth: u32) { |
| 1262 | stack_guard!(); |
| 1263 | if depth > 4 { |
| 1264 | return; |
| 1265 | } |
| 1266 | match v.kind() { |
| 1267 | "argument" => { |
| 1268 | for i in 0..v.named_child_count() { |
| 1269 | if let Some(c) = v.named_child(i) { |
| 1270 | self.normalize_fn_ref_value(c, from, depth + 1); |
| 1271 | } |
| 1272 | } |
| 1273 | } |
| 1274 | // String callable — trustworthy ONLY as an argument to a known |
| 1275 | // callable-taking core function; skipGate (resolution's |
| 1276 | // unique-or-drop rule takes over). Namespaced strings drop. |
| 1277 | "string" | "encapsed_string" => { |
| 1278 | let Some(callee) = php_enclosing_call_name(v).map(|f| self.text(f)) else { |
| 1279 | return; |
| 1280 | }; |
| 1281 | if !is_php_callable_hof(callee) { |
| 1282 | return; |
| 1283 | } |
| 1284 | let Some(content) = self.php_string_content(v) else { return }; |
| 1285 | if simple_callable_re().is_match(&content) || qualified_callable_re().is_match(&content) |
| 1286 | { |
| 1287 | self.push_fn_ref_cand(from, &content, v, true); |
| 1288 | } |
| 1289 | } |
| 1290 | // Array callables in ANY call's arguments: `[$this, 'm']` → |
| 1291 | // this.m; `[Foo::class, 'm']` → Foo::m; `['Cls', 'm']` → nothing. |
| 1292 | "array_creation_expression" => { |
| 1293 | if v.named_child_count() != 2 { |
| 1294 | return; |
| 1295 | } |
| 1296 | let recv = v.named_child(0).and_then(|e| e.named_child(0)); |
| 1297 | let str_el = v.named_child(1).and_then(|e| e.named_child(0)); |
| 1298 | let (Some(recv), Some(str_el)) = (recv, str_el) else { return }; |
| 1299 | if !matches!(str_el.kind(), "encapsed_string" | "string") { |
| 1300 | return; |
| 1301 | } |
| 1302 | let Some(member) = self.php_string_content(str_el) else { return }; |
| 1303 | if !simple_callable_re().is_match(&member) { |
| 1304 | return; |
| 1305 | } |
| 1306 | if recv.kind() == "variable_name" && self.text(recv) == "$this" { |
| 1307 | let name = format!("this.{member}"); |
| 1308 | self.push_fn_ref_cand(from, &name, str_el, false); |
| 1309 | } else if recv.kind() == "class_constant_access_expression" { |
| 1310 | let cls = recv.named_child(0); |
| 1311 | let kw = recv.named_child(1); |
| 1312 | if let (Some(cls), Some(kw)) = (cls, kw) { |
| 1313 | if self.text(kw) == "class" { |
| 1314 | let name = format!("{}::{member}", self.text(cls)); |
| 1315 | self.push_fn_ref_cand(from, &name, str_el, false); |
| 1316 | } |
| 1317 | } |
| 1318 | } |
no test coverage detected