Returns true when the hook handled the node (mixin call or module). The DISPATCHER then runs scan_fn_ref_subtree on the handled subtree — the source of the module multiply-capture quirk (scan runs with the module already POPPED, so candidates re-attribute to the outer scope).
(&mut self, node: Node<'t>)
| 383 | /// the source of the module multiply-capture quirk (scan runs with the |
| 384 | /// module already POPPED, so candidates re-attribute to the outer scope). |
| 385 | fn try_visit_hook(&mut self, node: Node<'t>) -> bool { |
| 386 | let kind = node.kind(); |
| 387 | if kind == "call" && node.child_by_field_name("receiver").is_none() { |
| 388 | if let Some(method) = node.child_by_field_name("method") { |
| 389 | if matches!(self.text(method), "include" | "extend" | "prepend") { |
| 390 | let args = node.child_by_field_name("arguments").or_else(|| { |
| 391 | (0..node.named_child_count()) |
| 392 | .filter_map(|i| node.named_child(i)) |
| 393 | .find(|c| c.kind() == "argument_list") |
| 394 | }); |
| 395 | // (nodeStack is never empty — the file node is pushed.) |
| 396 | if let Some(args) = args { |
| 397 | let parent = self.top_row(); |
| 398 | let implements = edge_kind_index("implements").unwrap(); |
| 399 | let line = self.line_of(node); |
| 400 | let col = self.col_of(node); |
| 401 | for i in 0..args.named_child_count() { |
| 402 | let Some(arg) = args.named_child(i) else { continue }; |
| 403 | // `Mod` is constant, `Foo::Bar` is scope_resolution; |
| 404 | // `extend self` / dynamic args are skipped. Unlike |
| 405 | // every other extraction ref, the hook sets |
| 406 | // `filePath: ctx.filePath` — flagged on the wire. |
| 407 | if matches!(arg.kind(), "constant" | "scope_resolution") { |
| 408 | let name_ref = self.arena.put(self.text(arg)); |
| 409 | self.tables.push_ref_flagged( |
| 410 | &RefRow { |
| 411 | from_idx: parent, |
| 412 | kind: implements, |
| 413 | line, |
| 414 | column: col, |
| 415 | reference_name: name_ref, |
| 416 | candidates: NONE_STR, |
| 417 | from_id_str: NONE_STR, |
| 418 | }, |
| 419 | REF_FLAG_FILE_PATH, |
| 420 | ); |
| 421 | } |
| 422 | } |
| 423 | return true; |
| 424 | } |
| 425 | // no args node → hook declines (falls to extractImport → nothing) |
| 426 | } |
| 427 | } |
| 428 | } |
| 429 | |
| 430 | if kind != "module" { |
| 431 | return false; |
| 432 | } |
| 433 | let Some(name_node) = node.child_by_field_name("name") else { return false }; |
| 434 | // `module A::B` keeps the scope_resolution text verbatim as the name. |
| 435 | let name = self.text(name_node).to_string(); |
| 436 | let Some(row) = self.create_node("module", &name, node, Extra::default()) else { |
| 437 | return false; |
| 438 | }; |
| 439 | self.stack.push(Scope { row, kind: "module", name }); |
| 440 | if let Some(body) = node.child_by_field_name("body") { |
| 441 | for i in 0..body.named_child_count() { |
| 442 | if let Some(c) = body.named_child(i) { |
no test coverage detected