Skip to main content

drft/rules/
fragmentation.rs

1use 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}