Skip to main content

the_code_graph_domain/analysis/
blast_radius.rs

1use crate::model::*;
2use crate::traversal::InMemoryGraph;
3
4/// Compute blast radius from a set of impact targets.
5/// For Symbol targets: BFS forward from the symbol.
6/// For File targets: BFS forward from the file node (which typically has Contains edges to symbols).
7pub fn compute_blast_radius(
8    graph: &InMemoryGraph,
9    targets: &[ImpactTarget],
10    max_depth: usize,
11    min_confidence: Confidence,
12) -> ImpactReport {
13    let mut all_affected = Vec::new();
14
15    for target in targets {
16        let start = match target {
17            ImpactTarget::Symbol(s) => s.as_str(),
18            ImpactTarget::File(p) => p.to_str().unwrap_or_default(),
19        };
20        let results = graph.bfs_filtered(start, Direction::Forward, max_depth, min_confidence);
21        for r in results {
22            all_affected.push(AffectedNode {
23                qualified_name: r.node,
24                depth: r.depth,
25                confidence: r.edge_kind.confidence(),
26                path: r.path,
27            });
28        }
29    }
30
31    ImpactReport {
32        targets: targets.to_vec(),
33        affected: all_affected,
34        depth: max_depth,
35        min_confidence,
36    }
37}
38
39#[cfg(test)]
40mod tests {
41    use super::*;
42    use crate::traversal::InMemoryGraph;
43
44    #[test]
45    fn blast_radius_from_single_symbol() {
46        let edges = vec![
47            Edge {
48                kind: EdgeKind::Calls,
49                source: "a::foo".into(),
50                target: "b::bar".into(),
51                metadata: None,
52            },
53            Edge {
54                kind: EdgeKind::Calls,
55                source: "b::bar".into(),
56                target: "c::baz".into(),
57                metadata: None,
58            },
59        ];
60        let graph = InMemoryGraph::from_edges(edges);
61        let targets = vec![ImpactTarget::Symbol("a::foo".into())];
62        let report = compute_blast_radius(&graph, &targets, 3, Confidence::Structural);
63        assert!(!report.affected.is_empty());
64        assert!(report.affected.iter().any(|n| n.qualified_name == "b::bar"));
65        assert!(report.affected.iter().any(|n| n.qualified_name == "c::baz"));
66    }
67
68    #[test]
69    fn blast_radius_from_file_target() {
70        let edges = vec![
71            Edge {
72                kind: EdgeKind::Contains,
73                source: "a.rs".into(),
74                target: "a.rs::foo".into(),
75                metadata: None,
76            },
77            Edge {
78                kind: EdgeKind::Calls,
79                source: "a.rs::foo".into(),
80                target: "b.rs::bar".into(),
81                metadata: None,
82            },
83        ];
84        let graph = InMemoryGraph::from_edges(edges);
85        let targets = vec![ImpactTarget::File("a.rs".into())];
86        let report = compute_blast_radius(&graph, &targets, 3, Confidence::Structural);
87        assert!(!report.targets.is_empty());
88    }
89}