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