1use super::{Analysis, AnalysisContext};
2use std::collections::HashMap;
3
4#[derive(Debug, Clone, serde::Serialize)]
5pub struct BridgeEdge {
6 pub source: String,
7 pub target: String,
8}
9
10#[derive(Debug, Clone, serde::Serialize)]
11pub struct BridgesResult {
12 pub cut_vertices: Vec<String>,
13 pub bridges: Vec<BridgeEdge>,
14}
15
16pub struct Bridges;
17
18impl Analysis for Bridges {
19 type Output = BridgesResult;
20
21 fn name(&self) -> &str {
22 "bridges"
23 }
24
25 fn run(&self, ctx: &AnalysisContext) -> BridgesResult {
26 let graph = ctx.graph;
27 let mut adj: HashMap<&str, Vec<&str>> = HashMap::new();
29 let real_nodes: Vec<&str> = graph
30 .nodes
31 .keys()
32 .filter(|p| graph.is_included_node(p))
33 .map(|s| s.as_str())
34 .collect();
35
36 for node in &real_nodes {
37 adj.entry(node).or_default();
38 }
39
40 for edge in &graph.edges {
41 if graph.is_internal_edge(edge) && edge.source != edge.target {
42 adj.entry(edge.source.as_str())
43 .or_default()
44 .push(edge.target.as_str());
45 adj.entry(edge.target.as_str())
46 .or_default()
47 .push(edge.source.as_str());
48 }
49 }
50
51 for neighbors in adj.values_mut() {
53 neighbors.sort();
54 neighbors.dedup();
55 }
56
57 let mut state = TarjanBridgeState {
58 adj: &adj,
59 timer: 0,
60 disc: HashMap::new(),
61 low: HashMap::new(),
62 parent: HashMap::new(),
63 cut_vertices: Vec::new(),
64 bridges: Vec::new(),
65 };
66
67 let mut sorted_nodes = real_nodes;
69 sorted_nodes.sort();
70
71 for &node in &sorted_nodes {
72 if !state.disc.contains_key(node) {
73 state.dfs(node);
74 }
75 }
76
77 let mut cut_vertices = state.cut_vertices;
78 cut_vertices.sort();
79 cut_vertices.dedup();
80
81 let mut bridges: Vec<BridgeEdge> = state
82 .bridges
83 .into_iter()
84 .map(|(a, b)| {
85 if a < b {
87 BridgeEdge {
88 source: a.to_string(),
89 target: b.to_string(),
90 }
91 } else {
92 BridgeEdge {
93 source: b.to_string(),
94 target: a.to_string(),
95 }
96 }
97 })
98 .collect();
99 bridges.sort_by(|a, b| {
100 a.source
101 .cmp(&b.source)
102 .then_with(|| a.target.cmp(&b.target))
103 });
104 bridges.dedup_by(|a, b| a.source == b.source && a.target == b.target);
105
106 BridgesResult {
107 cut_vertices: cut_vertices.into_iter().map(|s| s.to_string()).collect(),
108 bridges,
109 }
110 }
111}
112
113struct TarjanBridgeState<'a> {
114 adj: &'a HashMap<&'a str, Vec<&'a str>>,
115 timer: usize,
116 disc: HashMap<&'a str, usize>,
117 low: HashMap<&'a str, usize>,
118 parent: HashMap<&'a str, &'a str>,
119 cut_vertices: Vec<&'a str>,
120 bridges: Vec<(&'a str, &'a str)>,
121}
122
123impl<'a> TarjanBridgeState<'a> {
124 fn dfs(&mut self, u: &'a str) {
125 self.disc.insert(u, self.timer);
126 self.low.insert(u, self.timer);
127 self.timer += 1;
128 let mut child_count = 0;
129
130 let neighbors = self.adj.get(u).cloned().unwrap_or_default();
131 for v in neighbors {
132 if !self.disc.contains_key(v) {
133 child_count += 1;
134 self.parent.insert(v, u);
135 self.dfs(v);
136
137 let v_low = self.low[v];
138 let u_low = self.low[u];
139 if v_low < u_low {
140 self.low.insert(u, v_low);
141 }
142
143 let is_root = !self.parent.contains_key(u);
146 if is_root && child_count > 1 {
147 self.cut_vertices.push(u);
148 }
149 if !is_root && self.low[v] >= self.disc[u] {
151 self.cut_vertices.push(u);
152 }
153
154 if self.low[v] > self.disc[u] {
156 self.bridges.push((u, v));
157 }
158 } else if self.parent.get(u) != Some(&v) {
159 let v_disc = self.disc[v];
161 let u_low = self.low[u];
162 if v_disc < u_low {
163 self.low.insert(u, v_disc);
164 }
165 }
166 }
167 }
168}
169
170#[cfg(test)]
171mod tests {
172 use super::*;
173 use crate::analyses::AnalysisContext;
174 use crate::config::Config;
175 use crate::graph::Graph;
176 use crate::graph::test_helpers::{make_edge, make_node};
177 use std::path::Path;
178
179 fn make_ctx<'a>(graph: &'a Graph, config: &'a Config) -> AnalysisContext<'a> {
180 AnalysisContext {
181 graph,
182 root: Path::new("."),
183 config,
184 lockfile: None,
185 }
186 }
187
188 #[test]
189 fn linear_chain() {
190 let mut graph = Graph::new();
191 graph.add_node(make_node("a.md"));
192 graph.add_node(make_node("b.md"));
193 graph.add_node(make_node("c.md"));
194 graph.add_edge(make_edge("a.md", "b.md"));
195 graph.add_edge(make_edge("b.md", "c.md"));
196
197 let config = Config::defaults();
198 let result = Bridges.run(&make_ctx(&graph, &config));
199 assert_eq!(result.bridges.len(), 2);
200 assert_eq!(result.cut_vertices, vec!["b.md"]);
201 }
202
203 #[test]
204 fn cycle_has_no_bridges() {
205 let mut graph = Graph::new();
206 graph.add_node(make_node("a.md"));
207 graph.add_node(make_node("b.md"));
208 graph.add_node(make_node("c.md"));
209 graph.add_edge(make_edge("a.md", "b.md"));
210 graph.add_edge(make_edge("b.md", "c.md"));
211 graph.add_edge(make_edge("c.md", "a.md"));
212
213 let config = Config::defaults();
214 let result = Bridges.run(&make_ctx(&graph, &config));
215 assert!(result.bridges.is_empty());
216 assert!(result.cut_vertices.is_empty());
217 }
218
219 #[test]
220 fn star_graph() {
221 let mut graph = Graph::new();
222 graph.add_node(make_node("center.md"));
223 graph.add_node(make_node("a.md"));
224 graph.add_node(make_node("b.md"));
225 graph.add_node(make_node("c.md"));
226 graph.add_edge(make_edge("center.md", "a.md"));
227 graph.add_edge(make_edge("center.md", "b.md"));
228 graph.add_edge(make_edge("center.md", "c.md"));
229
230 let config = Config::defaults();
231 let result = Bridges.run(&make_ctx(&graph, &config));
232 assert_eq!(result.cut_vertices, vec!["center.md"]);
233 assert_eq!(result.bridges.len(), 3);
234 }
235
236 #[test]
237 fn empty_graph() {
238 let graph = Graph::new();
239 let config = Config::defaults();
240 let result = Bridges.run(&make_ctx(&graph, &config));
241 assert!(result.cut_vertices.is_empty());
242 assert!(result.bridges.is_empty());
243 }
244
245 #[test]
246 fn single_node() {
247 let mut graph = Graph::new();
248 graph.add_node(make_node("a.md"));
249
250 let config = Config::defaults();
251 let result = Bridges.run(&make_ctx(&graph, &config));
252 assert!(result.cut_vertices.is_empty());
253 assert!(result.bridges.is_empty());
254 }
255
256 #[test]
257 fn cycle_with_tail() {
258 let mut graph = Graph::new();
259 graph.add_node(make_node("a.md"));
260 graph.add_node(make_node("b.md"));
261 graph.add_node(make_node("c.md"));
262 graph.add_node(make_node("d.md"));
263 graph.add_edge(make_edge("a.md", "b.md"));
264 graph.add_edge(make_edge("b.md", "c.md"));
265 graph.add_edge(make_edge("c.md", "a.md"));
266 graph.add_edge(make_edge("c.md", "d.md"));
267
268 let config = Config::defaults();
269 let result = Bridges.run(&make_ctx(&graph, &config));
270 assert_eq!(result.bridges.len(), 1);
271 assert_eq!(result.bridges[0].source, "c.md");
272 assert_eq!(result.bridges[0].target, "d.md");
273 assert!(result.cut_vertices.contains(&"c.md".to_string()));
274 }
275}