use crate::diagnostic::Diagnostic;
use crate::rules::{Rule, RuleContext};
pub struct FragmentationRule;
impl Rule for FragmentationRule {
fn name(&self) -> &str {
"fragmentation"
}
fn evaluate(&self, ctx: &RuleContext) -> Vec<Diagnostic> {
let result = &ctx.graph.connected_components;
if result.component_count <= 1 {
return Vec::new();
}
result
.components
.iter()
.skip(1)
.map(|c| {
let members = c.members.join(", ");
Diagnostic {
rule: "fragmentation".into(),
message: format!("disconnected component ({} nodes)", c.members.len()),
node: c.members.first().cloned(),
fix: Some(format!(
"these nodes are disconnected from the main graph: {members} \u{2014} add links to connect them"
)),
..Default::default()
}
})
.collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::graph::Graph;
use crate::graph::test_helpers::{make_edge, make_enriched, make_node};
use crate::rules::RuleContext;
#[test]
fn no_diagnostic_when_connected() {
let mut graph = Graph::new();
graph.add_node(make_node("a.md"));
graph.add_node(make_node("b.md"));
graph.add_edge(make_edge("a.md", "b.md"));
let enriched = make_enriched(graph);
let ctx = RuleContext {
graph: &enriched,
options: None,
};
let diagnostics = FragmentationRule.evaluate(&ctx);
assert!(diagnostics.is_empty());
}
#[test]
fn detects_fragmentation() {
let mut graph = Graph::new();
graph.add_node(make_node("a.md"));
graph.add_node(make_node("b.md"));
graph.add_node(make_node("c.md"));
graph.add_edge(make_edge("a.md", "b.md"));
let enriched = make_enriched(graph);
let ctx = RuleContext {
graph: &enriched,
options: None,
};
let diagnostics = FragmentationRule.evaluate(&ctx);
assert_eq!(diagnostics.len(), 1);
assert_eq!(diagnostics[0].rule, "fragmentation");
assert!(diagnostics[0].message.contains("disconnected component"));
}
#[test]
fn no_diagnostic_for_empty_graph() {
let graph = Graph::new();
let enriched = make_enriched(graph);
let ctx = RuleContext {
graph: &enriched,
options: None,
};
let diagnostics = FragmentationRule.evaluate(&ctx);
assert!(diagnostics.is_empty());
}
}