use crate::parser::LangId;
pub fn call_node_kinds(lang: LangId) -> Vec<&'static str> {
match lang {
LangId::TypeScript | LangId::JavaScript => vec!["call_expression", "new_expression"],
LangId::Tsx => vec![
"call_expression",
"new_expression",
"jsx_opening_element",
"jsx_self_closing_element",
],
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 = callee_node(node)?;
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)
}
"subscript_expression" => extract_computed_member_name(&func_node, source)
.or_else(|| 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 = callee_node(node)?;
Some(source[func_node.byte_range()].trim().to_string())
}
fn callee_node<'a>(node: &tree_sitter::Node<'a>) -> Option<tree_sitter::Node<'a>> {
match node.kind() {
"new_expression" => node
.child_by_field_name("constructor")
.or_else(|| node.named_child(0)),
"jsx_opening_element" | "jsx_self_closing_element" => node
.child_by_field_name("name")
.or_else(|| node.named_child(0)),
_ => node
.child_by_field_name("function")
.or_else(|| node.child(0)),
}
}
fn extract_computed_member_name(node: &tree_sitter::Node, source: &str) -> Option<String> {
let index = node.child_by_field_name("index")?;
let text = source[index.byte_range()].trim();
if (text.starts_with('"') && text.ends_with('"'))
|| (text.starts_with('\'') && text.ends_with('\''))
{
return Some(text[1..text.len().saturating_sub(1)].to_string());
}
None
}
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;
}
}
}
}