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::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}