Skip to main content

codelens_engine/call_graph/
api.rs

1use 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
12/// Find all functions that call `function_name` across the project.
13/// Edges are resolved via the 6-stage confidence cascade when an import graph is available.
14pub 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        // Relativize caller_file paths
31        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    // Filter to edges calling our target
48    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    // Sort by confidence descending
73    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
84/// Find all functions called by `function_name` (optionally restricted to a file).
85/// Callee names are resolved to their definition files via the 6-stage cascade.
86pub 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}