Skip to main content

code_split_core/
hk.rs

1use crate::{Complexity, Coupling, EdgeKind, Graph, NodeId, NodeKind, PluginGraphs};
2use std::collections::{HashMap, HashSet};
3
4pub fn annotate_hk(graphs: &mut PluginGraphs) {
5    annotate_graph_hk(&mut graphs.modules);
6    annotate_graph_hk(&mut graphs.files);
7    annotate_graph_hk(&mut graphs.functions);
8}
9
10fn annotate_graph_hk(graph: &mut Graph) {
11    // If the graph has no Calls edges (sema was skipped), fn/method nodes get
12    // no coupling annotation — showing 0 would be misleading vs. genuinely
13    // isolated nodes discovered when sema did run.
14    let has_calls = graph.edges.iter().any(|e| e.kind == EdgeKind::Calls);
15
16    let mut fan_in: HashMap<NodeId, HashSet<NodeId>> = HashMap::new();
17    let mut fan_out: HashMap<NodeId, HashSet<NodeId>> = HashMap::new();
18
19    for edge in &graph.edges {
20        if edge.kind == EdgeKind::Contains {
21            continue;
22        }
23        fan_out
24            .entry(edge.from.clone())
25            .or_default()
26            .insert(edge.to.clone());
27        fan_in
28            .entry(edge.to.clone())
29            .or_default()
30            .insert(edge.from.clone());
31    }
32
33    for node in &mut graph.nodes {
34        if !has_calls && matches!(node.kind, NodeKind::Fn | NodeKind::Method) {
35            continue;
36        }
37        let fi = fan_in.get(&node.id).map(|s| s.len()).unwrap_or(0);
38        let fo = fan_out.get(&node.id).map(|s| s.len()).unwrap_or(0);
39        let loc = node.loc.unwrap_or(0) as f64;
40        let hk_term = ((fi * fo) as f64).powi(2);
41        let hk = if loc > 0.0 { loc * hk_term } else { hk_term };
42
43        let cx = node.complexity.get_or_insert_with(Complexity::default);
44        cx.coupling = Some(Coupling {
45            fan_in: fi as u32,
46            fan_out: fo as u32,
47            hk,
48        });
49    }
50}