use super::CodeNode;
use crate::graph::interner::{StrKey, StringInterner};
use crate::graph::node_index::NodeIndex;
use crate::graph::store_models::ExtraProps;
use std::collections::{BTreeMap, HashMap};
use std::sync::Arc;
#[allow(dead_code)] pub trait GraphQuery: Send + Sync {
fn primitives(&self) -> &crate::graph::primitives::GraphPrimitives;
fn interner(&self) -> &StringInterner;
fn extra_props(&self, qn: StrKey) -> Option<ExtraProps>;
fn extra_props_ref(&self, _qn: StrKey) -> Option<&ExtraProps> {
None
}
fn stats(&self) -> BTreeMap<String, i64>;
fn node_idx(&self, _idx: NodeIndex) -> Option<&CodeNode> {
None
}
fn node_by_name_idx(&self, _qn: &str) -> Option<(NodeIndex, &CodeNode)> {
None
}
fn functions_idx(&self) -> &[NodeIndex] {
&[]
}
fn classes_idx(&self) -> &[NodeIndex] {
&[]
}
fn files_idx(&self) -> &[NodeIndex] {
&[]
}
fn callers_idx(&self, _idx: NodeIndex) -> &[NodeIndex] {
&[]
}
fn callees_idx(&self, _idx: NodeIndex) -> &[NodeIndex] {
&[]
}
fn importers_idx(&self, _idx: NodeIndex) -> &[NodeIndex] {
&[]
}
fn importees_idx(&self, _idx: NodeIndex) -> &[NodeIndex] {
&[]
}
fn parent_classes_idx(&self, _idx: NodeIndex) -> &[NodeIndex] {
&[]
}
fn child_classes_idx(&self, _idx: NodeIndex) -> &[NodeIndex] {
&[]
}
fn call_fan_in_idx(&self, idx: NodeIndex) -> usize {
self.callers_idx(idx).len()
}
fn call_fan_out_idx(&self, idx: NodeIndex) -> usize {
self.callees_idx(idx).len()
}
fn functions_in_file_idx(&self, _file_path: &str) -> &[NodeIndex] {
&[]
}
fn classes_in_file_idx(&self, _file_path: &str) -> &[NodeIndex] {
&[]
}
fn function_at_idx(&self, _file_path: &str, _line: u32) -> Option<NodeIndex> {
None
}
fn all_call_edges(&self) -> &[(NodeIndex, NodeIndex)] {
&[]
}
fn all_import_edges(&self) -> &[(NodeIndex, NodeIndex)] {
&[]
}
fn all_inheritance_edges(&self) -> &[(NodeIndex, NodeIndex)] {
&[]
}
fn import_cycles_idx(&self) -> &[Vec<NodeIndex>] {
&[]
}
fn edge_fingerprint_idx(&self) -> u64 {
0
}
}
#[allow(dead_code)] pub trait GraphQueryExt: GraphQuery {
fn get_functions(&self) -> Vec<CodeNode> {
self.functions_idx()
.iter()
.filter_map(|&idx| self.node_idx(idx).copied())
.collect()
}
fn get_classes(&self) -> Vec<CodeNode> {
self.classes_idx()
.iter()
.filter_map(|&idx| self.node_idx(idx).copied())
.collect()
}
fn get_files(&self) -> Vec<CodeNode> {
self.files_idx()
.iter()
.filter_map(|&idx| self.node_idx(idx).copied())
.collect()
}
fn get_functions_in_file(&self, file_path: &str) -> Vec<CodeNode> {
self.functions_in_file_idx(file_path)
.iter()
.filter_map(|&idx| self.node_idx(idx).copied())
.collect()
}
fn get_classes_in_file(&self, file_path: &str) -> Vec<CodeNode> {
self.classes_in_file_idx(file_path)
.iter()
.filter_map(|&idx| self.node_idx(idx).copied())
.collect()
}
fn get_node(&self, qn: &str) -> Option<CodeNode> {
self.node_by_name_idx(qn).map(|(_, node)| *node)
}
fn get_callers(&self, qn: &str) -> Vec<CodeNode> {
let Some((idx, _)) = self.node_by_name_idx(qn) else {
return vec![];
};
self.callers_idx(idx)
.iter()
.filter_map(|&ci| self.node_idx(ci).copied())
.collect()
}
fn get_callees(&self, qn: &str) -> Vec<CodeNode> {
let Some((idx, _)) = self.node_by_name_idx(qn) else {
return vec![];
};
self.callees_idx(idx)
.iter()
.filter_map(|&ci| self.node_idx(ci).copied())
.collect()
}
fn call_fan_in(&self, qn: &str) -> usize {
self.node_by_name_idx(qn)
.map(|(idx, _)| self.callers_idx(idx).len())
.unwrap_or(0)
}
fn call_fan_out(&self, qn: &str) -> usize {
self.node_by_name_idx(qn)
.map(|(idx, _)| self.callees_idx(idx).len())
.unwrap_or(0)
}
fn get_calls(&self) -> Vec<(StrKey, StrKey)> {
self.all_call_edges()
.iter()
.filter_map(|&(src, tgt)| {
let s = self.node_idx(src)?;
let t = self.node_idx(tgt)?;
Some((s.qualified_name, t.qualified_name))
})
.collect()
}
fn get_imports(&self) -> Vec<(StrKey, StrKey)> {
self.all_import_edges()
.iter()
.filter_map(|&(src, tgt)| {
let s = self.node_idx(src)?;
let t = self.node_idx(tgt)?;
Some((s.qualified_name, t.qualified_name))
})
.collect()
}
fn get_inheritance(&self) -> Vec<(StrKey, StrKey)> {
self.all_inheritance_edges()
.iter()
.filter_map(|&(src, tgt)| {
let s = self.node_idx(src)?;
let t = self.node_idx(tgt)?;
Some((s.qualified_name, t.qualified_name))
})
.collect()
}
fn get_child_classes(&self, qn: &str) -> Vec<CodeNode> {
let Some((idx, _)) = self.node_by_name_idx(qn) else {
return vec![];
};
self.child_classes_idx(idx)
.iter()
.filter_map(|&ci| self.node_idx(ci).copied())
.collect()
}
fn get_importers(&self, qn: &str) -> Vec<CodeNode> {
let Some((idx, _)) = self.node_by_name_idx(qn) else {
return vec![];
};
self.importers_idx(idx)
.iter()
.filter_map(|&ci| self.node_idx(ci).copied())
.collect()
}
fn find_import_cycles(&self) -> Vec<Vec<String>> {
let si = self.interner();
self.import_cycles_idx()
.iter()
.map(|cycle| {
let mut names: Vec<String> = cycle
.iter()
.filter_map(|&idx| self.node_idx(idx))
.map(|n| si.resolve(n.qualified_name).to_string())
.collect();
names.sort();
names
})
.collect()
}
fn get_functions_shared(&self) -> Arc<[CodeNode]> {
Arc::from(self.get_functions())
}
fn get_classes_shared(&self) -> Arc<[CodeNode]> {
Arc::from(self.get_classes())
}
fn get_files_shared(&self) -> Arc<[CodeNode]> {
Arc::from(self.get_files())
}
fn get_calls_shared(&self) -> Arc<[(StrKey, StrKey)]> {
Arc::from(self.get_calls())
}
fn is_in_import_cycle(&self, file_path: &str) -> bool {
let cycles = self.find_import_cycles();
cycles.iter().any(|cycle| {
cycle
.iter()
.any(|qn| qn == file_path || file_path.contains(qn.as_str()))
})
}
fn find_function_at(&self, file_path: &str, line: u32) -> Option<CodeNode> {
if let Some(idx) = self.function_at_idx(file_path, line) {
return self.node_idx(idx).copied();
}
self.get_functions_in_file(file_path)
.into_iter()
.find(|f| f.line_start <= line && f.line_end >= line)
}
fn get_complex_functions(&self, min_complexity: i64) -> Vec<CodeNode> {
self.functions_idx()
.iter()
.filter_map(|&idx| {
let node = self.node_idx(idx)?;
if node.complexity_opt().is_some_and(|c| c >= min_complexity) {
Some(*node)
} else {
None
}
})
.collect()
}
fn get_long_param_functions(&self, min_params: i64) -> Vec<CodeNode> {
self.functions_idx()
.iter()
.filter_map(|&idx| {
let node = self.node_idx(idx)?;
if node.param_count_opt().is_some_and(|p| p >= min_params) {
Some(*node)
} else {
None
}
})
.collect()
}
fn caller_file_spread(&self, qn: &str) -> usize {
let Some((idx, _)) = self.node_by_name_idx(qn) else {
return 0;
};
let files: std::collections::HashSet<StrKey> = self
.callers_idx(idx)
.iter()
.filter_map(|&ci| self.node_idx(ci))
.map(|n| n.file_path)
.collect();
files.len()
}
fn count_external_callers_of(
&self,
qn: &str,
class_file: &str,
class_start: u32,
class_end: u32,
) -> usize {
let si = self.interner();
let Some((idx, _)) = self.node_by_name_idx(qn) else {
return 0;
};
self.callers_idx(idx)
.iter()
.filter_map(|&ci| self.node_idx(ci))
.filter(|c| {
si.resolve(c.file_path) != class_file
|| c.line_start < class_start
|| c.line_end > class_end
})
.count()
}
fn caller_module_spread(&self, qn: &str) -> usize {
let si = self.interner();
let Some((idx, _)) = self.node_by_name_idx(qn) else {
return 0;
};
let modules: std::collections::HashSet<&str> = self
.callers_idx(idx)
.iter()
.filter_map(|&ci| self.node_idx(ci))
.map(|n| {
std::path::Path::new(si.resolve(n.file_path))
.parent()
.and_then(|p| p.to_str())
.unwrap_or("root")
})
.collect();
modules.len()
}
fn build_call_maps_raw(
&self,
) -> (
HashMap<StrKey, usize>,
HashMap<usize, Vec<usize>>,
HashMap<usize, Vec<usize>>,
) {
let functions = self.get_functions();
let calls = self.get_calls();
let qn_to_idx: HashMap<StrKey, usize> = functions
.iter()
.enumerate()
.map(|(i, f)| (f.qualified_name, i))
.collect();
let mut callers: HashMap<usize, Vec<usize>> = HashMap::new();
let mut callees: HashMap<usize, Vec<usize>> = HashMap::new();
for (caller, callee) in &calls {
if let (Some(&from), Some(&to)) = (qn_to_idx.get(caller), qn_to_idx.get(callee)) {
callers.entry(to).or_default().push(from);
callees.entry(from).or_default().push(to);
}
}
(qn_to_idx, callers, callees)
}
fn get_call_adjacency(&self) -> (Vec<Vec<usize>>, Vec<Vec<usize>>, HashMap<StrKey, usize>) {
let functions = self.get_functions();
let calls = self.get_calls();
let qn_to_idx: HashMap<StrKey, usize> = functions
.iter()
.enumerate()
.map(|(i, f)| (f.qualified_name, i))
.collect();
let n = functions.len();
let mut adj = vec![vec![]; n];
let mut rev_adj = vec![vec![]; n];
for (caller, callee) in &calls {
if let (Some(&from), Some(&to)) = (qn_to_idx.get(caller), qn_to_idx.get(callee)) {
adj[from].push(to);
rev_adj[to].push(from);
}
}
(adj, rev_adj, qn_to_idx)
}
}
impl<T: GraphQuery + ?Sized> GraphQueryExt for T {}