1use super::{Analysis, AnalysisContext};
2use crate::graph::Graph;
3use std::collections::HashMap;
4
5#[derive(Debug, Clone, serde::Serialize)]
6pub struct Scc {
7 pub id: usize,
8 pub members: Vec<String>,
9}
10
11#[derive(Debug, Clone, serde::Serialize)]
12pub struct SccResult {
13 pub scc_count: usize,
14 pub nontrivial_count: usize,
15 pub sccs: Vec<Scc>,
16 pub node_scc: HashMap<String, usize>,
17}
18
19pub struct StronglyConnectedComponents;
20
21impl Analysis for StronglyConnectedComponents {
22 type Output = SccResult;
23
24 fn name(&self) -> &str {
25 "scc"
26 }
27
28 fn run(&self, ctx: &AnalysisContext) -> SccResult {
29 let graph = ctx.graph;
30 let real_nodes: Vec<&str> = graph
31 .nodes
32 .keys()
33 .filter(|p| graph.is_included_node(p))
34 .map(|s| s.as_str())
35 .collect();
36
37 let mut state = TarjanState {
38 graph,
39 index_counter: 0,
40 stack: Vec::new(),
41 on_stack: HashMap::new(),
42 index: HashMap::new(),
43 lowlink: HashMap::new(),
44 components: Vec::new(),
45 };
46
47 let mut sorted_nodes = real_nodes;
49 sorted_nodes.sort();
50
51 for &node in &sorted_nodes {
52 if !state.index.contains_key(node) {
53 state.strongconnect(node);
54 }
55 }
56
57 let all_components = state.components;
59 let scc_count = all_components.len();
60
61 let mut has_self_loop: HashMap<&str, bool> = HashMap::new();
63 for edge in &graph.edges {
64 if edge.source == edge.target && graph.is_internal_edge(edge) {
65 has_self_loop.insert(edge.source.as_str(), true);
66 }
67 }
68
69 let nontrivial: Vec<Vec<String>> = all_components
71 .iter()
72 .filter(|c| c.len() > 1 || (c.len() == 1 && has_self_loop.contains_key(c[0].as_str())))
73 .cloned()
74 .collect();
75
76 let nontrivial_count = nontrivial.len();
77
78 let mut node_scc: HashMap<String, usize> = HashMap::new();
80 for (i, component) in all_components.iter().enumerate() {
81 for member in component {
82 node_scc.insert(member.clone(), i + 1);
83 }
84 }
85
86 let mut output_sccs = nontrivial;
88 output_sccs.sort_by(|a, b| b.len().cmp(&a.len()).then_with(|| a[0].cmp(&b[0])));
89
90 let sccs = output_sccs
91 .into_iter()
92 .enumerate()
93 .map(|(i, members)| Scc { id: i + 1, members })
94 .collect();
95
96 SccResult {
97 scc_count,
98 nontrivial_count,
99 sccs,
100 node_scc,
101 }
102 }
103}
104
105struct TarjanState<'a> {
106 graph: &'a Graph,
107 index_counter: usize,
108 stack: Vec<&'a str>,
109 on_stack: HashMap<&'a str, bool>,
110 index: HashMap<&'a str, usize>,
111 lowlink: HashMap<&'a str, usize>,
112 components: Vec<Vec<String>>,
113}
114
115impl<'a> TarjanState<'a> {
116 fn strongconnect(&mut self, v: &'a str) {
117 self.index.insert(v, self.index_counter);
118 self.lowlink.insert(v, self.index_counter);
119 self.index_counter += 1;
120 self.stack.push(v);
121 self.on_stack.insert(v, true);
122
123 if let Some(edge_indices) = self.graph.forward.get(v) {
125 for &idx in edge_indices {
126 let edge = &self.graph.edges[idx];
127 if !self.graph.is_internal_edge(edge) {
128 continue;
129 }
130 let w = edge.target.as_str();
131 if !self.index.contains_key(w) {
132 self.strongconnect(w);
133 let w_low = self.lowlink[w];
134 let v_low = self.lowlink[v];
135 if w_low < v_low {
136 self.lowlink.insert(v, w_low);
137 }
138 } else if self.on_stack.get(w) == Some(&true) {
139 let w_idx = self.index[w];
140 let v_low = self.lowlink[v];
141 if w_idx < v_low {
142 self.lowlink.insert(v, w_idx);
143 }
144 }
145 }
146 }
147
148 if self.lowlink[v] == self.index[v] {
150 let mut component = Vec::new();
151 loop {
152 let w = self.stack.pop().unwrap();
153 self.on_stack.insert(w, false);
154 component.push(w.to_string());
155 if w == v {
156 break;
157 }
158 }
159 component.sort();
160 self.components.push(component);
161 }
162 }
163}
164
165#[cfg(test)]
166mod tests {
167 use super::*;
168 use crate::analyses::AnalysisContext;
169 use crate::config::Config;
170 use crate::graph::Graph;
171 use crate::graph::test_helpers::{make_edge, make_node};
172 use std::path::Path;
173
174 fn make_ctx<'a>(graph: &'a Graph, config: &'a Config) -> AnalysisContext<'a> {
175 AnalysisContext {
176 graph,
177 root: Path::new("."),
178 config,
179 lockfile: None,
180 }
181 }
182
183 #[test]
184 fn dag_has_no_nontrivial_sccs() {
185 let mut graph = Graph::new();
186 graph.add_node(make_node("a.md"));
187 graph.add_node(make_node("b.md"));
188 graph.add_node(make_node("c.md"));
189 graph.add_edge(make_edge("a.md", "b.md"));
190 graph.add_edge(make_edge("b.md", "c.md"));
191
192 let config = Config::defaults();
193 let result = StronglyConnectedComponents.run(&make_ctx(&graph, &config));
194 assert_eq!(result.scc_count, 3);
195 assert_eq!(result.nontrivial_count, 0);
196 assert!(result.sccs.is_empty());
197 }
198
199 #[test]
200 fn simple_cycle() {
201 let mut graph = Graph::new();
202 graph.add_node(make_node("a.md"));
203 graph.add_node(make_node("b.md"));
204 graph.add_node(make_node("c.md"));
205 graph.add_edge(make_edge("a.md", "b.md"));
206 graph.add_edge(make_edge("b.md", "c.md"));
207 graph.add_edge(make_edge("c.md", "a.md"));
208
209 let config = Config::defaults();
210 let result = StronglyConnectedComponents.run(&make_ctx(&graph, &config));
211 assert_eq!(result.nontrivial_count, 1);
212 assert_eq!(result.sccs.len(), 1);
213 assert_eq!(result.sccs[0].members, vec!["a.md", "b.md", "c.md"]);
214 }
215
216 #[test]
217 fn two_separate_cycles() {
218 let mut graph = Graph::new();
219 graph.add_node(make_node("a.md"));
220 graph.add_node(make_node("b.md"));
221 graph.add_node(make_node("c.md"));
222 graph.add_node(make_node("d.md"));
223 graph.add_edge(make_edge("a.md", "b.md"));
224 graph.add_edge(make_edge("b.md", "a.md"));
225 graph.add_edge(make_edge("c.md", "d.md"));
226 graph.add_edge(make_edge("d.md", "c.md"));
227
228 let config = Config::defaults();
229 let result = StronglyConnectedComponents.run(&make_ctx(&graph, &config));
230 assert_eq!(result.nontrivial_count, 2);
231 assert_eq!(result.sccs.len(), 2);
232 }
233
234 #[test]
235 fn mixed_cyclic_and_acyclic() {
236 let mut graph = Graph::new();
237 graph.add_node(make_node("a.md"));
238 graph.add_node(make_node("b.md"));
239 graph.add_node(make_node("c.md"));
240 graph.add_edge(make_edge("a.md", "b.md"));
241 graph.add_edge(make_edge("b.md", "c.md"));
242 graph.add_edge(make_edge("c.md", "b.md"));
243
244 let config = Config::defaults();
245 let result = StronglyConnectedComponents.run(&make_ctx(&graph, &config));
246 assert_eq!(result.nontrivial_count, 1);
247 assert_eq!(result.sccs[0].members, vec!["b.md", "c.md"]);
248 assert!(result.node_scc.contains_key("a.md"));
249 }
250
251 #[test]
252 fn self_loop_is_nontrivial() {
253 let mut graph = Graph::new();
254 graph.add_node(make_node("a.md"));
255 graph.add_edge(make_edge("a.md", "a.md"));
256
257 let config = Config::defaults();
258 let result = StronglyConnectedComponents.run(&make_ctx(&graph, &config));
259 assert_eq!(result.nontrivial_count, 1);
260 assert_eq!(result.sccs[0].members, vec!["a.md"]);
261 }
262
263 #[test]
264 fn empty_graph() {
265 let graph = Graph::new();
266 let config = Config::defaults();
267 let result = StronglyConnectedComponents.run(&make_ctx(&graph, &config));
268 assert_eq!(result.scc_count, 0);
269 assert_eq!(result.nontrivial_count, 0);
270 }
271
272 #[test]
273 fn every_node_has_scc_id() {
274 let mut graph = Graph::new();
275 graph.add_node(make_node("a.md"));
276 graph.add_node(make_node("b.md"));
277 graph.add_node(make_node("c.md"));
278 graph.add_edge(make_edge("a.md", "b.md"));
279 graph.add_edge(make_edge("b.md", "c.md"));
280
281 let config = Config::defaults();
282 let result = StronglyConnectedComponents.run(&make_ctx(&graph, &config));
283 assert!(result.node_scc.contains_key("a.md"));
284 assert!(result.node_scc.contains_key("b.md"));
285 assert!(result.node_scc.contains_key("c.md"));
286 }
287}