use super::utils::find_nearest_entity_by_line;
use crate::models::{ParsedEntity, ReferenceIntent};
use crate::pipeline::parser::utils::node_text;
use tree_sitter::Node;
pub(crate) fn collect_rust_call_references(
root: Node<'_>,
source: &[u8],
entities: &mut [ParsedEntity],
_file_path: &str,
_repo_name: &str,
) {
let mut call_intents: Vec<(usize, String, Option<String>)> = Vec::new();
collect_call_nodes(&root, source, &mut call_intents);
for (line, func_name, receiver) in call_intents {
let target_idx = find_nearest_entity_by_line(entities, line);
if target_idx < entities.len() {
let receiver = if receiver.as_deref() == Some("Self")
&& let Some(enclosing) = entities[target_idx].enclosing_class.clone()
{
Some(enclosing)
} else {
receiver
};
entities[target_idx]
.reference_intents
.push(ReferenceIntent::Call {
method: func_name,
receiver,
line,
arg_count: None,
});
}
}
}
pub(crate) fn collect_call_nodes(
node: &Node<'_>,
source: &[u8],
calls: &mut Vec<(usize, String, Option<String>)>,
) {
if node.kind() == "call_expression" {
let line = node.start_position().row + 1;
if let Some((func_name, receiver)) = extract_call_details(*node, source) {
calls.push((line, func_name, receiver));
}
} else if node.kind() == "token_tree" {
let mut child = node.child(0);
while let Some(c) = child {
if c.kind() == "identifier" {
if let Some(next) = c.next_sibling()
&& next.kind() == "token_tree"
{
let next_text = node_text(next, source);
if next_text.starts_with('(') || next_text.starts_with('[') {
let line = c.start_position().row + 1;
let func_name = node_text(c, source).to_string();
calls.push((line, func_name, None));
}
}
} else if c.kind() == "scoped_identifier"
&& let Some(next) = c.next_sibling()
&& next.kind() == "token_tree"
{
let next_text = node_text(next, source);
if next_text.starts_with('(') || next_text.starts_with('[') {
let line = c.start_position().row + 1;
if let Some((func_name, receiver)) = extract_from_scoped_identifier(c, source) {
calls.push((line, func_name, receiver));
}
}
}
child = c.next_sibling();
}
}
let mut child = node.child(0);
while let Some(c) = child {
collect_call_nodes(&c, source, calls);
child = c.next_sibling();
}
}
fn extract_call_details(node: Node<'_>, source: &[u8]) -> Option<(String, Option<String>)> {
let mut child = node.child(0);
while let Some(c) = child {
match c.kind() {
"identifier" => {
let func_name = node_text(c, source).to_string();
return Some((func_name, None));
}
"field_expression" => {
if let Some((method_name, receiver)) = extract_from_field_expression(c, source) {
return Some((method_name, Some(receiver)));
}
}
"scoped_identifier" => {
let (func_name, receiver) = extract_from_scoped_identifier(c, source)?;
return Some((func_name, receiver));
}
_ => {}
}
child = c.next_sibling();
}
None
}
fn extract_from_field_expression(node: Node<'_>, source: &[u8]) -> Option<(String, String)> {
let mut method_name = String::new();
let mut receiver = String::new();
let mut found_method = false;
let mut found_receiver = false;
let mut child = node.child(0);
while let Some(c) = child {
match c.kind() {
"field_identifier" => {
method_name = node_text(c, source).to_string();
found_method = true;
}
"identifier" => {
receiver = node_text(c, source).to_string();
found_receiver = true;
}
_ => {}
}
child = c.next_sibling();
}
if found_method && found_receiver {
Some((method_name, receiver))
} else {
None
}
}
fn extract_from_scoped_identifier(
node: Node<'_>,
source: &[u8],
) -> Option<(String, Option<String>)> {
let mut identifiers: Vec<String> = Vec::new();
collect_scoped_identifiers(node, source, &mut identifiers);
let last = identifiers.pop()?;
let method_name = last;
let receiver = if let Some(prev) = identifiers.last() {
let first = prev.chars().next();
if first.is_some_and(|c| c.is_ascii_uppercase()) {
Some(prev.clone())
} else {
None
}
} else {
None
};
Some((method_name, receiver))
}
fn collect_scoped_identifiers(node: Node<'_>, source: &[u8], out: &mut Vec<String>) {
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
match child.kind() {
"identifier" | "type_identifier" => {
out.push(node_text(child, source).to_string());
}
"scoped_identifier" => {
collect_scoped_identifiers(child, source, out);
}
_ => {}
}
}
}