codelens_engine/call_graph/
api.rs1use crate::import_graph::GraphCache;
2use crate::project::ProjectRoot;
3use anyhow::Result;
4use std::collections::HashMap;
5use std::path::PathBuf;
6
7use super::extract::extract_calls;
8use super::js_imports::{build_js_import_binding_index, filter_external_import_edges};
9use super::resolve::{collect_candidate_files, maybe_import_graph, resolve_call_edges};
10use super::types::{CallEdge, CalleeEntry, CallerEntry};
11
12pub fn get_callers(
15 project: &ProjectRoot,
16 function_name: &str,
17 file_path: Option<&str>,
18 max_results: usize,
19 graph_cache: Option<&GraphCache>,
20) -> Result<Vec<CallerEntry>> {
21 let files: Vec<PathBuf> = if let Some(fp) = file_path {
22 vec![project.resolve(fp)?]
23 } else {
24 collect_candidate_files(project.as_path())?
25 };
26 let mut all_edges: Vec<CallEdge> = Vec::new();
27
28 for file in &files {
29 let mut edges = extract_calls(file);
30 for edge in &mut edges {
32 edge.caller_file = project.to_relative(file);
33 }
34 all_edges.extend(edges);
35 }
36
37 let import_bindings = build_js_import_binding_index(project, &files);
38 filter_external_import_edges(&mut all_edges, &import_bindings);
39 let import_graph = maybe_import_graph(project, &files, graph_cache);
40 resolve_call_edges(
41 &mut all_edges,
42 project,
43 import_graph.as_deref(),
44 Some(&import_bindings),
45 );
46
47 let mut seen = std::collections::HashSet::new();
49 let mut results = Vec::new();
50
51 for edge in all_edges {
52 if edge.callee_name == function_name
53 || edge.canonical_callee_name.as_deref() == Some(function_name)
54 {
55 let key = (
56 edge.caller_file.clone(),
57 edge.caller_name.clone(),
58 edge.line,
59 );
60 if seen.insert(key) {
61 results.push(CallerEntry {
62 file: edge.caller_file,
63 function: edge.caller_name,
64 line: edge.line,
65 confidence: edge.confidence,
66 resolution: edge.resolution_strategy,
67 });
68 }
69 }
70 }
71
72 results.sort_by(|a, b| {
74 b.confidence
75 .partial_cmp(&a.confidence)
76 .unwrap_or(std::cmp::Ordering::Equal)
77 });
78 if max_results > 0 && results.len() > max_results {
79 results.truncate(max_results);
80 }
81 Ok(results)
82}
83
84pub fn get_callees(
87 project: &ProjectRoot,
88 function_name: &str,
89 file_path: Option<&str>,
90 max_results: usize,
91 graph_cache: Option<&GraphCache>,
92) -> Result<Vec<CalleeEntry>> {
93 let files: Vec<PathBuf> = if let Some(fp) = file_path {
94 let resolved = project.resolve(fp)?;
95 vec![resolved]
96 } else {
97 collect_candidate_files(project.as_path())?
98 };
99
100 let mut all_edges: Vec<CallEdge> = Vec::new();
101 for file in &files {
102 let mut edges = extract_calls(file);
103 for edge in &mut edges {
104 edge.caller_file = project.to_relative(file);
105 }
106 all_edges.extend(edges);
107 }
108
109 let import_bindings = build_js_import_binding_index(project, &files);
110 filter_external_import_edges(&mut all_edges, &import_bindings);
111 let import_graph = maybe_import_graph(project, &files, graph_cache);
112 resolve_call_edges(
113 &mut all_edges,
114 project,
115 import_graph.as_deref(),
116 Some(&import_bindings),
117 );
118
119 let mut seen: HashMap<(String, usize), ()> = HashMap::new();
120 let mut results = Vec::new();
121
122 for edge in all_edges {
123 if edge.caller_name == function_name {
124 let key = (edge.callee_name.clone(), edge.line);
125 if seen.insert(key, ()).is_none() {
126 results.push(CalleeEntry {
127 name: edge.callee_name,
128 line: edge.line,
129 resolved_file: edge.resolved_file,
130 confidence: edge.confidence,
131 resolution: edge.resolution_strategy,
132 });
133 }
134 }
135 }
136
137 results.sort_by(|a, b| {
138 b.confidence
139 .partial_cmp(&a.confidence)
140 .unwrap_or(std::cmp::Ordering::Equal)
141 });
142 if max_results > 0 && results.len() > max_results {
143 results.truncate(max_results);
144 }
145 Ok(results)
146}