use crate::parser::LangId;
pub fn call_node_kinds(lang: LangId) -> Vec<&'static str> {
match lang {
LangId::TypeScript | LangId::Tsx | LangId::JavaScript | LangId::Go => {
vec!["call_expression"]
}
LangId::Python => vec!["call"],
LangId::Rust => vec!["call_expression", "macro_invocation"],
LangId::Solidity | LangId::Scala => vec!["call_expression"],
LangId::C
| LangId::Cpp
| LangId::Zig
| LangId::CSharp
| LangId::Bash
| LangId::Vue
| LangId::Html
| LangId::Markdown
| LangId::Json => vec![],
}
}
pub fn walk_for_calls(
node: tree_sitter::Node,
source: &str,
byte_start: usize,
byte_end: usize,
call_kinds: &[&str],
results: &mut Vec<(String, u32)>,
) {
let node_start = node.start_byte();
let node_end = node.end_byte();
if node_end <= byte_start || node_start >= byte_end {
return;
}
if call_kinds.contains(&node.kind()) && node_start >= byte_start && node_end <= byte_end {
if let Some(name) = extract_callee_name(&node, source) {
results.push((name, node.start_position().row as u32 + 1));
}
}
let mut cursor = node.walk();
if cursor.goto_first_child() {
loop {
walk_for_calls(
cursor.node(),
source,
byte_start,
byte_end,
call_kinds,
results,
);
if !cursor.goto_next_sibling() {
break;
}
}
}
}
pub fn extract_callee_name(node: &tree_sitter::Node, source: &str) -> Option<String> {
let kind = node.kind();
if kind == "macro_invocation" {
let first_child = node.child(0)?;
let text = &source[first_child.byte_range()];
return Some(format!("{}!", text));
}
let func_node = node
.child_by_field_name("function")
.or_else(|| node.child(0))?;
let func_kind = func_node.kind();
match func_kind {
"identifier" => Some(source[func_node.byte_range()].to_string()),
"member_expression" | "field_expression" | "attribute" => {
extract_last_segment(&func_node, source)
}
_ => {
let text = &source[func_node.byte_range()];
if text.contains('.') {
text.rsplit('.').next().map(|s| s.trim().to_string())
} else {
Some(text.trim().to_string())
}
}
}
}
pub fn extract_full_callee(node: &tree_sitter::Node, source: &str) -> Option<String> {
let kind = node.kind();
if kind == "macro_invocation" {
let first_child = node.child(0)?;
let text = &source[first_child.byte_range()];
return Some(format!("{}!", text));
}
let func_node = node
.child_by_field_name("function")
.or_else(|| node.child(0))?;
Some(source[func_node.byte_range()].trim().to_string())
}
pub fn extract_last_segment(node: &tree_sitter::Node, source: &str) -> Option<String> {
let child_count = node.child_count();
for i in (0..child_count).rev() {
if let Some(child) = node.child(i as u32) {
match child.kind() {
"property_identifier" | "field_identifier" | "identifier" => {
return Some(source[child.byte_range()].to_string());
}
_ => {}
}
}
}
let text = &source[node.byte_range()];
text.rsplit('.').next().map(|s| s.trim().to_string())
}
pub fn extract_calls_in_range(
source: &str,
root: tree_sitter::Node,
byte_start: usize,
byte_end: usize,
lang: LangId,
) -> Vec<(String, u32)> {
let mut results = Vec::new();
let call_kinds = call_node_kinds(lang);
walk_for_calls(
root,
source,
byte_start,
byte_end,
&call_kinds,
&mut results,
);
results
}
pub fn extract_calls_full(
source: &str,
root: tree_sitter::Node,
byte_start: usize,
byte_end: usize,
lang: LangId,
) -> Vec<(String, String, u32)> {
let mut results = Vec::new();
let call_kinds = call_node_kinds(lang);
collect_calls_full(
root,
source,
byte_start,
byte_end,
&call_kinds,
&mut results,
);
results
}
fn collect_calls_full(
node: tree_sitter::Node,
source: &str,
byte_start: usize,
byte_end: usize,
call_kinds: &[&str],
results: &mut Vec<(String, String, u32)>,
) {
let node_start = node.start_byte();
let node_end = node.end_byte();
if node_end <= byte_start || node_start >= byte_end {
return;
}
if call_kinds.contains(&node.kind()) && node_start >= byte_start && node_end <= byte_end {
if let (Some(full), Some(short)) = (
extract_full_callee(&node, source),
extract_callee_name(&node, source),
) {
results.push((full, short, node.start_position().row as u32 + 1));
}
}
let mut cursor = node.walk();
if cursor.goto_first_child() {
loop {
collect_calls_full(
cursor.node(),
source,
byte_start,
byte_end,
call_kinds,
results,
);
if !cursor.goto_next_sibling() {
break;
}
}
}
}