extractImport — every non-body `call` lands here (importTypes:['call']). Hook (languages/ruby.ts:123): FIRST identifier named child must read require/require_relative (a lowercase receiver is found first and declines; a constant receiver — `Kernel.require "x"` — reaches the method identifier and IS a require); argument_list → string → string_content → moduleName. Then the generic imports ref and e
(&mut self, node: Node<'t>)
| 693 | /// string_content → moduleName. Then the generic imports ref and |
| 694 | /// emitRubyRequireRefs' path ref. |
| 695 | fn extract_import(&mut self, node: Node<'t>) { |
| 696 | let ident = (0..node.named_child_count()) |
| 697 | .filter_map(|i| node.named_child(i)) |
| 698 | .find(|c| c.kind() == "identifier"); |
| 699 | let Some(ident) = ident else { return }; |
| 700 | let mname = self.text(ident); |
| 701 | if mname != "require" && mname != "require_relative" { |
| 702 | return; |
| 703 | } |
| 704 | let arg_list = (0..node.named_child_count()) |
| 705 | .filter_map(|i| node.named_child(i)) |
| 706 | .find(|c| c.kind() == "argument_list"); |
| 707 | let Some(arg_list) = arg_list else { return }; |
| 708 | let string = (0..arg_list.named_child_count()) |
| 709 | .filter_map(|i| arg_list.named_child(i)) |
| 710 | .find(|c| c.kind() == "string"); |
| 711 | let Some(string) = string else { return }; |
| 712 | let content = (0..string.named_child_count()) |
| 713 | .filter_map(|i| string.named_child(i)) |
| 714 | .find(|c| c.kind() == "string_content"); |
| 715 | let Some(content) = content else { return }; |
| 716 | |
| 717 | // Interpolated paths take the FIRST string_content only — moduleName |
| 718 | // `interp/` (+ a garbage `interp/.rb` path ref) — deterministic quirk. |
| 719 | let module_name = self.text(content).to_string(); |
| 720 | let import_text = self.text(node).trim().to_string(); |
| 721 | if module_name.is_empty() { |
| 722 | return; |
| 723 | } |
| 724 | self.create_node( |
| 725 | "import", |
| 726 | &module_name, |
| 727 | node, |
| 728 | Extra { signature: Some(import_text), ..Extra::default() }, |
| 729 | ); |
| 730 | let parent = self.top_row(); |
| 731 | let imports_kind = edge_kind_index("imports").unwrap(); |
| 732 | self.push_ref_at(parent, &module_name.clone(), imports_kind, node); |
| 733 | |
| 734 | // emitRubyRequireRefs (3532): the file-path ref. Bare gem/stdlib |
| 735 | // requires (no `/`) emit nothing; paths get `.rb` appended. |
| 736 | let req = self.text(content).trim(); |
| 737 | if req.is_empty() { |
| 738 | return; |
| 739 | } |
| 740 | let ref_path = if mname == "require_relative" { |
| 741 | let dir = match self.file_path.rfind('/') { |
| 742 | Some(i) => &self.file_path[..i], |
| 743 | None => "", |
| 744 | }; |
| 745 | let joined = if dir.is_empty() { req.to_string() } else { format!("{dir}/{req}") }; |
| 746 | posix_normalize(&joined) |
| 747 | } else { |
| 748 | req.to_string() |
| 749 | }; |
| 750 | if !ref_path.contains('/') { |
| 751 | return; |
| 752 | } |
no test coverage detected