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