Skip to main content

dei_metrics/
graph.rs

1//! Dependency graph for analyzing coupling
2//! 
3//! Extension beyond the C# version - provides graph-based insights
4
5use petgraph::graph::{DiGraph, NodeIndex};
6use petgraph::visit::EdgeRef;
7use std::collections::HashMap;
8use std::sync::Arc;
9
10/// Represents a dependency graph between classes/methods
11#[derive(Debug, Clone)]
12pub struct DependencyGraph {
13    graph: DiGraph<Arc<str>, EdgeKind>,
14    node_map: HashMap<Arc<str>, NodeIndex>,
15}
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub enum EdgeKind {
19    Calls,
20    Inherits,
21    Uses,
22    Implements,
23}
24
25impl DependencyGraph {
26    pub fn new() -> Self {
27        Self {
28            graph: DiGraph::new(),
29            node_map: HashMap::new(),
30        }
31    }
32
33    /// Add a node (class or method)
34    pub fn add_node(&mut self, name: Arc<str>) -> NodeIndex {
35        if let Some(&idx) = self.node_map.get(&name) {
36            return idx;
37        }
38
39        let idx = self.graph.add_node(name.clone());
40        self.node_map.insert(name, idx);
41        idx
42    }
43
44    /// Add an edge between nodes
45    pub fn add_edge(&mut self, from: Arc<str>, to: Arc<str>, kind: EdgeKind) {
46        let from_idx = self.add_node(from);
47        let to_idx = self.add_node(to);
48        self.graph.add_edge(from_idx, to_idx, kind);
49    }
50
51    /// Calculate coupling metrics
52    pub fn coupling_metrics(&self, node: &Arc<str>) -> Option<CouplingMetrics> {
53        let idx = *self.node_map.get(node)?;
54
55        let incoming = self
56            .graph
57            .edges_directed(idx, petgraph::Direction::Incoming)
58            .count();
59        
60        let outgoing = self
61            .graph
62            .edges_directed(idx, petgraph::Direction::Outgoing)
63            .count();
64
65        Some(CouplingMetrics {
66            afferent: incoming,
67            efferent: outgoing,
68            instability: if incoming + outgoing > 0 {
69                outgoing as f64 / (incoming + outgoing) as f64
70            } else {
71                0.0
72            },
73        })
74    }
75
76    /// Find strongly connected components (circular dependencies)
77    pub fn find_cycles(&self) -> Vec<Vec<Arc<str>>> {
78        let sccs = petgraph::algo::kosaraju_scc(&self.graph);
79        
80        sccs.into_iter()
81            .filter(|scc| scc.len() > 1)
82            .map(|scc| {
83                scc.into_iter()
84                    .map(|idx| self.graph[idx].clone())
85                    .collect()
86            })
87            .collect()
88    }
89
90    /// Calculate graph density
91    pub fn density(&self) -> f64 {
92        let n = self.graph.node_count();
93        let e = self.graph.edge_count();
94        
95        if n <= 1 {
96            0.0
97        } else {
98            e as f64 / (n * (n - 1)) as f64
99        }
100    }
101}
102
103impl Default for DependencyGraph {
104    fn default() -> Self {
105        Self::new()
106    }
107}
108
109/// Coupling metrics for a node
110#[derive(Debug, Clone)]
111pub struct CouplingMetrics {
112    pub afferent: usize,  // Incoming dependencies
113    pub efferent: usize,  // Outgoing dependencies
114    pub instability: f64, // Efferent / (Afferent + Efferent)
115}
116