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::analyses::EnrichedGraph;
42 use crate::config::Config;
43 use crate::graph::Graph;
44 use crate::graph::test_helpers::{make_edge, make_node};
45 use crate::rules::RuleContext;
46
47 fn make_enriched(graph: Graph) -> EnrichedGraph {
48 crate::analyses::enrich_graph(graph, std::path::Path::new("."), &Config::defaults(), None)
49 }
50
51 #[test]
52 fn no_diagnostic_when_connected() {
53 let mut graph = Graph::new();
54 graph.add_node(make_node("a.md"));
55 graph.add_node(make_node("b.md"));
56 graph.add_edge(make_edge("a.md", "b.md"));
57
58 let enriched = make_enriched(graph);
59 let ctx = RuleContext {
60 graph: &enriched,
61 options: None,
62 };
63 let diagnostics = FragmentationRule.evaluate(&ctx);
64 assert!(diagnostics.is_empty());
65 }
66
67 #[test]
68 fn detects_fragmentation() {
69 let mut graph = Graph::new();
70 graph.add_node(make_node("a.md"));
71 graph.add_node(make_node("b.md"));
72 graph.add_node(make_node("c.md"));
73 graph.add_edge(make_edge("a.md", "b.md"));
74
75 let enriched = make_enriched(graph);
76 let ctx = RuleContext {
77 graph: &enriched,
78 options: None,
79 };
80 let diagnostics = FragmentationRule.evaluate(&ctx);
81 assert_eq!(diagnostics.len(), 1);
82 assert_eq!(diagnostics[0].rule, "fragmentation");
83 assert!(diagnostics[0].message.contains("disconnected component"));
84 }
85
86 #[test]
87 fn no_diagnostic_for_empty_graph() {
88 let graph = Graph::new();
89 let enriched = make_enriched(graph);
90 let ctx = RuleContext {
91 graph: &enriched,
92 options: None,
93 };
94 let diagnostics = FragmentationRule.evaluate(&ctx);
95 assert!(diagnostics.is_empty());
96 }
97}