use std::collections::HashSet;
use crate::graph::traits::GraphQuery;
pub fn map_changed_nodes(
graph: &dyn GraphQuery,
changed_files: &[(String, Vec<(u32, u32)>)],
) -> HashSet<String> {
let interner = graph.interner();
let mut result = HashSet::new();
for (file_path, hunks) in changed_files {
for &(start, end) in hunks {
let mut matched_functions: HashSet<crate::graph::NodeIndex> = HashSet::new();
for line in start..=end {
if let Some(idx) = graph.function_at_idx(file_path, line) {
matched_functions.insert(idx);
}
}
for idx in matched_functions {
if let Some(node) = graph.node_idx(idx) {
result.insert(interner.resolve(node.qualified_name).to_string());
}
}
for &cls_idx in graph.classes_in_file_idx(file_path) {
if let Some(cls) = graph.node_idx(cls_idx) {
if cls.line_start <= end && cls.line_end >= start {
result.insert(interner.resolve(cls.qualified_name).to_string());
}
}
}
}
}
result
}
pub fn find_callers_of_changed(
graph: &dyn GraphQuery,
changed_qnames: &HashSet<String>,
) -> HashSet<String> {
let interner = graph.interner();
let mut callers = HashSet::new();
for qn in changed_qnames {
if let Some((idx, _)) = graph.node_by_name_idx(qn) {
for &caller_idx in graph.callers_idx(idx) {
if let Some(caller_node) = graph.node_idx(caller_idx) {
let caller_qn = interner.resolve(caller_node.qualified_name).to_string();
if !changed_qnames.contains(&caller_qn) {
callers.insert(caller_qn);
}
}
}
}
}
callers
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_input_returns_empty() {
struct EmptyGraph;
impl GraphQuery for EmptyGraph {
fn primitives(&self) -> &crate::graph::primitives::GraphPrimitives {
use std::sync::LazyLock;
static PRIMS: LazyLock<crate::graph::primitives::GraphPrimitives> =
LazyLock::new(crate::graph::primitives::GraphPrimitives::default);
&PRIMS
}
fn interner(&self) -> &crate::graph::interner::StringInterner {
crate::graph::interner::global_interner()
}
fn extra_props(
&self,
_qn: crate::graph::interner::StrKey,
) -> Option<crate::graph::store_models::ExtraProps> {
None
}
fn stats(&self) -> std::collections::BTreeMap<String, i64> {
std::collections::BTreeMap::new()
}
}
let graph = EmptyGraph;
let empty: Vec<(String, Vec<(u32, u32)>)> = vec![];
let nodes = map_changed_nodes(&graph, &empty);
assert!(nodes.is_empty(), "empty files => empty nodes");
let files = vec![("src/main.rs".to_string(), vec![(1, 10)])];
let nodes = map_changed_nodes(&graph, &files);
assert!(nodes.is_empty(), "no graph nodes => empty result");
let changed: HashSet<String> = HashSet::new();
let callers = find_callers_of_changed(&graph, &changed);
assert!(callers.is_empty(), "empty changed => empty callers");
let mut changed = HashSet::new();
changed.insert("nonexistent::func".to_string());
let callers = find_callers_of_changed(&graph, &changed);
assert!(callers.is_empty(), "missing nodes => empty callers");
}
}