drft/rules/
fragmentation.rs1use crate::diagnostic::Diagnostic;
2use crate::rules::{Rule, RuleContext};
3
4pub struct FragmentationRule;
5
6impl Rule for FragmentationRule {
7 fn name(&self) -> &str {
8 "fragmentation"
9 }
10
11 fn evaluate(&self, ctx: &RuleContext) -> Vec<Diagnostic> {
12 let result = &ctx.graph.connected_components;
13
14 if result.component_count <= 1 {
15 return Vec::new();
16 }
17
18 result
19 .components
20 .iter()
21 .skip(1)
22 .map(|c| {
23 let members = c.members.join(", ");
24 Diagnostic {
25 rule: "fragmentation".into(),
26 message: format!("disconnected component ({} nodes)", c.members.len()),
27 node: c.members.first().cloned(),
28 fix: Some(format!(
29 "these nodes are disconnected from the main graph: {members} \u{2014} add links to connect them"
30 )),
31 ..Default::default()
32 }
33 })
34 .collect()
35 }
36}
37
38#[cfg(test)]
39mod tests {
40 use super::*;
41 use crate::graph::Graph;
42 use crate::graph::test_helpers::{make_edge, make_enriched, make_node};
43 use crate::rules::RuleContext;
44
45 #[test]
46 fn no_diagnostic_when_connected() {
47 let mut graph = Graph::new();
48 graph.add_node(make_node("a.md"));
49 graph.add_node(make_node("b.md"));
50 graph.add_edge(make_edge("a.md", "b.md"));
51
52 let enriched = make_enriched(graph);
53 let ctx = RuleContext {
54 graph: &enriched,
55 options: None,
56 };
57 let diagnostics = FragmentationRule.evaluate(&ctx);
58 assert!(diagnostics.is_empty());
59 }
60
61 #[test]
62 fn detects_fragmentation() {
63 let mut graph = Graph::new();
64 graph.add_node(make_node("a.md"));
65 graph.add_node(make_node("b.md"));
66 graph.add_node(make_node("c.md"));
67 graph.add_edge(make_edge("a.md", "b.md"));
68
69 let enriched = make_enriched(graph);
70 let ctx = RuleContext {
71 graph: &enriched,
72 options: None,
73 };
74 let diagnostics = FragmentationRule.evaluate(&ctx);
75 assert_eq!(diagnostics.len(), 1);
76 assert_eq!(diagnostics[0].rule, "fragmentation");
77 assert!(diagnostics[0].message.contains("disconnected component"));
78 }
79
80 #[test]
81 fn no_diagnostic_for_empty_graph() {
82 let graph = Graph::new();
83 let enriched = make_enriched(graph);
84 let ctx = RuleContext {
85 graph: &enriched,
86 options: None,
87 };
88 let diagnostics = FragmentationRule.evaluate(&ctx);
89 assert!(diagnostics.is_empty());
90 }
91}