drft/rules/
orphan_node.rs1use crate::diagnostic::Diagnostic;
2use crate::rules::{Rule, RuleContext};
3
4pub struct OrphanNodeRule;
5
6impl Rule for OrphanNodeRule {
7 fn name(&self) -> &str {
8 "orphan-node"
9 }
10
11 fn evaluate(&self, ctx: &RuleContext) -> Vec<Diagnostic> {
12 let result = &ctx.graph.degree;
13
14 result
15 .nodes
16 .iter()
17 .filter(|nd| nd.in_degree == 0)
18 .map(|nd| Diagnostic {
19 rule: "orphan-node".into(),
20 message: "no inbound links".into(),
21 node: Some(nd.node.clone()),
22 fix: Some(format!(
23 "{} has no inbound links — either link to it from another file or remove it",
24 nd.node
25 )),
26 ..Default::default()
27 })
28 .collect()
29 }
30}
31
32#[cfg(test)]
33mod tests {
34 use super::*;
35 use crate::analyses::EnrichedGraph;
36 use crate::config::Config;
37 use crate::graph::Graph;
38 use crate::graph::test_helpers::{make_edge, make_node};
39 use crate::rules::RuleContext;
40
41 fn make_enriched(graph: Graph) -> EnrichedGraph {
42 crate::analyses::enrich_graph(graph, std::path::Path::new("."), &Config::defaults(), None)
43 }
44
45 #[test]
46 fn detects_orphan() {
47 let mut graph = Graph::new();
48 graph.add_node(make_node("index.md"));
49 graph.add_node(make_node("orphan.md"));
50 graph.add_edge(make_edge("index.md", "setup.md"));
51
52 let enriched = make_enriched(graph);
53 let ctx = RuleContext {
54 graph: &enriched,
55 options: None,
56 };
57 let diagnostics = OrphanNodeRule.evaluate(&ctx);
58
59 let orphan_nodes: Vec<&str> = diagnostics
60 .iter()
61 .map(|d| d.node.as_deref().unwrap())
62 .collect();
63 assert!(orphan_nodes.contains(&"orphan.md"));
64 assert!(orphan_nodes.contains(&"index.md"));
65 }
66
67 #[test]
68 fn linked_file_is_not_orphan() {
69 let mut graph = Graph::new();
70 graph.add_node(make_node("index.md"));
71 graph.add_node(make_node("setup.md"));
72 graph.add_edge(make_edge("index.md", "setup.md"));
73
74 let enriched = make_enriched(graph);
75 let ctx = RuleContext {
76 graph: &enriched,
77 options: None,
78 };
79 let diagnostics = OrphanNodeRule.evaluate(&ctx);
80
81 let orphan_nodes: Vec<&str> = diagnostics
82 .iter()
83 .map(|d| d.node.as_deref().unwrap())
84 .collect();
85 assert!(!orphan_nodes.contains(&"setup.md"));
86 assert!(orphan_nodes.contains(&"index.md"));
87 }
88}