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.included_nodes().map(|(s, _)| s.as_str()).collect();
30
31 for node in &real_nodes {
32 adj.entry(node).or_default();
33 }
34
35 for edge in &graph.edges {
36 if graph.is_internal_edge(edge) && edge.source != edge.target {
37 adj.entry(edge.source.as_str())
38 .or_default()
39 .push(edge.target.as_str());
40 adj.entry(edge.target.as_str())
41 .or_default()
42 .push(edge.source.as_str());
43 }
44 }
45
46 for neighbors in adj.values_mut() {
48 neighbors.sort();
49 neighbors.dedup();
50 }
51
52 let mut state = TarjanBridgeState {
53 adj: &adj,
54 timer: 0,
55 disc: HashMap::new(),
56 low: HashMap::new(),
57 parent: HashMap::new(),
58 cut_vertices: Vec::new(),
59 bridges: Vec::new(),
60 };
61
62 let mut sorted_nodes = real_nodes;
64 sorted_nodes.sort();
65
66 for &node in &sorted_nodes {
67 if !state.disc.contains_key(node) {
68 state.dfs(node);
69 }
70 }
71
72 let mut cut_vertices = state.cut_vertices;
73 cut_vertices.sort();
74 cut_vertices.dedup();
75
76 let mut bridges: Vec<BridgeEdge> = state
77 .bridges
78 .into_iter()
79 .map(|(a, b)| {
80 if a < b {
82 BridgeEdge {
83 source: a.to_string(),
84 target: b.to_string(),
85 }
86 } else {
87 BridgeEdge {
88 source: b.to_string(),
89 target: a.to_string(),
90 }
91 }
92 })
93 .collect();
94 bridges.sort_by(|a, b| {
95 a.source
96 .cmp(&b.source)
97 .then_with(|| a.target.cmp(&b.target))
98 });
99 bridges.dedup_by(|a, b| a.source == b.source && a.target == b.target);
100
101 BridgesResult {
102 cut_vertices: cut_vertices.into_iter().map(|s| s.to_string()).collect(),
103 bridges,
104 }
105 }
106}
107
108struct TarjanBridgeState<'a> {
109 adj: &'a HashMap<&'a str, Vec<&'a str>>,
110 timer: usize,
111 disc: HashMap<&'a str, usize>,
112 low: HashMap<&'a str, usize>,
113 parent: HashMap<&'a str, &'a str>,
114 cut_vertices: Vec<&'a str>,
115 bridges: Vec<(&'a str, &'a str)>,
116}
117
118impl<'a> TarjanBridgeState<'a> {
119 fn dfs(&mut self, u: &'a str) {
120 self.disc.insert(u, self.timer);
121 self.low.insert(u, self.timer);
122 self.timer += 1;
123 let mut child_count = 0;
124
125 let neighbors = self.adj.get(u).cloned().unwrap_or_default();
126 for v in neighbors {
127 if !self.disc.contains_key(v) {
128 child_count += 1;
129 self.parent.insert(v, u);
130 self.dfs(v);
131
132 let v_low = self.low[v];
133 let u_low = self.low[u];
134 if v_low < u_low {
135 self.low.insert(u, v_low);
136 }
137
138 let is_root = !self.parent.contains_key(u);
141 if is_root && child_count > 1 {
142 self.cut_vertices.push(u);
143 }
144 if !is_root && self.low[v] >= self.disc[u] {
146 self.cut_vertices.push(u);
147 }
148
149 if self.low[v] > self.disc[u] {
151 self.bridges.push((u, v));
152 }
153 } else if self.parent.get(u) != Some(&v) {
154 let v_disc = self.disc[v];
156 let u_low = self.low[u];
157 if v_disc < u_low {
158 self.low.insert(u, v_disc);
159 }
160 }
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 linear_chain() {
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 = Bridges.run(&make_ctx(&graph, &config));
194 assert_eq!(result.bridges.len(), 2);
195 assert_eq!(result.cut_vertices, vec!["b.md"]);
196 }
197
198 #[test]
199 fn cycle_has_no_bridges() {
200 let mut graph = Graph::new();
201 graph.add_node(make_node("a.md"));
202 graph.add_node(make_node("b.md"));
203 graph.add_node(make_node("c.md"));
204 graph.add_edge(make_edge("a.md", "b.md"));
205 graph.add_edge(make_edge("b.md", "c.md"));
206 graph.add_edge(make_edge("c.md", "a.md"));
207
208 let config = Config::defaults();
209 let result = Bridges.run(&make_ctx(&graph, &config));
210 assert!(result.bridges.is_empty());
211 assert!(result.cut_vertices.is_empty());
212 }
213
214 #[test]
215 fn star_graph() {
216 let mut graph = Graph::new();
217 graph.add_node(make_node("center.md"));
218 graph.add_node(make_node("a.md"));
219 graph.add_node(make_node("b.md"));
220 graph.add_node(make_node("c.md"));
221 graph.add_edge(make_edge("center.md", "a.md"));
222 graph.add_edge(make_edge("center.md", "b.md"));
223 graph.add_edge(make_edge("center.md", "c.md"));
224
225 let config = Config::defaults();
226 let result = Bridges.run(&make_ctx(&graph, &config));
227 assert_eq!(result.cut_vertices, vec!["center.md"]);
228 assert_eq!(result.bridges.len(), 3);
229 }
230
231 #[test]
232 fn empty_graph() {
233 let graph = Graph::new();
234 let config = Config::defaults();
235 let result = Bridges.run(&make_ctx(&graph, &config));
236 assert!(result.cut_vertices.is_empty());
237 assert!(result.bridges.is_empty());
238 }
239
240 #[test]
241 fn single_node() {
242 let mut graph = Graph::new();
243 graph.add_node(make_node("a.md"));
244
245 let config = Config::defaults();
246 let result = Bridges.run(&make_ctx(&graph, &config));
247 assert!(result.cut_vertices.is_empty());
248 assert!(result.bridges.is_empty());
249 }
250
251 #[test]
252 fn cycle_with_tail() {
253 let mut graph = Graph::new();
254 graph.add_node(make_node("a.md"));
255 graph.add_node(make_node("b.md"));
256 graph.add_node(make_node("c.md"));
257 graph.add_node(make_node("d.md"));
258 graph.add_edge(make_edge("a.md", "b.md"));
259 graph.add_edge(make_edge("b.md", "c.md"));
260 graph.add_edge(make_edge("c.md", "a.md"));
261 graph.add_edge(make_edge("c.md", "d.md"));
262
263 let config = Config::defaults();
264 let result = Bridges.run(&make_ctx(&graph, &config));
265 assert_eq!(result.bridges.len(), 1);
266 assert_eq!(result.bridges[0].source, "c.md");
267 assert_eq!(result.bridges[0].target, "d.md");
268 assert!(result.cut_vertices.contains(&"c.md".to_string()));
269 }
270}