Skip to main content

the_code_graph_domain/analysis/
impact.rs

1use super::blast_radius::compute_blast_radius;
2use super::change_detection::find_affected_symbols;
3use crate::model::*;
4use crate::traversal::InMemoryGraph;
5
6/// Combine change detection with blast radius for a full diff impact report.
7pub fn compute_diff_impact(
8    graph: &InMemoryGraph,
9    hunks: &[DiffHunk],
10    symbols: &[SymbolNode],
11    max_depth: usize,
12) -> DiffImpactReport {
13    let changed = find_affected_symbols(hunks, symbols);
14    let targets: Vec<ImpactTarget> = changed
15        .iter()
16        .map(|s| ImpactTarget::Symbol(s.qualified_name.clone()))
17        .collect();
18    let impact = compute_blast_radius(graph, &targets, max_depth, Confidence::Structural);
19    DiffImpactReport {
20        changed_symbols: changed,
21        impact,
22    }
23}
24
25#[cfg(test)]
26mod tests {
27    use super::*;
28    use crate::traversal::InMemoryGraph;
29
30    #[test]
31    fn diff_impact_non_overlapping_returns_empty() {
32        let graph = InMemoryGraph::from_edges(vec![]);
33        let symbols: Vec<SymbolNode> = vec![];
34        let hunks = vec![DiffHunk {
35            file: "src/a.rs".into(),
36            old_start: 1,
37            old_count: 1,
38            new_start: 1,
39            new_count: 1,
40        }];
41        let report = compute_diff_impact(&graph, &hunks, &symbols, 3);
42        assert!(report.changed_symbols.is_empty());
43        assert!(report.impact.affected.is_empty());
44    }
45
46    #[test]
47    fn diff_impact_overlapping_hunk_produces_full_report() {
48        let symbols = vec![SymbolNode {
49            name: "foo".into(),
50            qualified_name: "a.rs::foo".into(),
51            kind: SymbolKind::Function,
52            location: Location {
53                file: "a.rs".into(),
54                line_start: 10,
55                line_end: 20,
56                col_start: 0,
57                col_end: 0,
58            },
59            visibility: Visibility::Public,
60            is_exported: false,
61            is_async: false,
62            is_test: false,
63            decorators: vec![],
64            signature: None,
65        }];
66        let edges = vec![Edge {
67            kind: EdgeKind::Calls,
68            source: "a.rs::foo".into(),
69            target: "b.rs::bar".into(),
70            metadata: None,
71        }];
72        let graph = InMemoryGraph::from_edges(edges);
73        let hunks = vec![DiffHunk {
74            file: "a.rs".into(),
75            old_start: 15,
76            old_count: 3,
77            new_start: 15,
78            new_count: 3,
79        }];
80        let report = compute_diff_impact(&graph, &hunks, &symbols, 3);
81        assert_eq!(report.changed_symbols.len(), 1);
82        assert_eq!(report.changed_symbols[0].name, "foo");
83        assert!(report
84            .impact
85            .affected
86            .iter()
87            .any(|n| n.qualified_name == "b.rs::bar"));
88    }
89}