Skip to main content

drft/rules/
orphan_node.rs

1use 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::graph::Graph;
36    use crate::graph::test_helpers::{make_edge, make_enriched, make_node};
37    use crate::rules::RuleContext;
38
39    #[test]
40    fn detects_orphan() {
41        let mut graph = Graph::new();
42        graph.add_node(make_node("index.md"));
43        graph.add_node(make_node("orphan.md"));
44        graph.add_edge(make_edge("index.md", "setup.md"));
45
46        let enriched = make_enriched(graph);
47        let ctx = RuleContext {
48            graph: &enriched,
49            options: None,
50        };
51        let diagnostics = OrphanNodeRule.evaluate(&ctx);
52
53        let orphan_nodes: Vec<&str> = diagnostics
54            .iter()
55            .map(|d| d.node.as_deref().unwrap())
56            .collect();
57        assert!(orphan_nodes.contains(&"orphan.md"));
58        assert!(orphan_nodes.contains(&"index.md"));
59    }
60
61    #[test]
62    fn linked_file_is_not_orphan() {
63        let mut graph = Graph::new();
64        graph.add_node(make_node("index.md"));
65        graph.add_node(make_node("setup.md"));
66        graph.add_edge(make_edge("index.md", "setup.md"));
67
68        let enriched = make_enriched(graph);
69        let ctx = RuleContext {
70            graph: &enriched,
71            options: None,
72        };
73        let diagnostics = OrphanNodeRule.evaluate(&ctx);
74
75        let orphan_nodes: Vec<&str> = diagnostics
76            .iter()
77            .map(|d| d.node.as_deref().unwrap())
78            .collect();
79        assert!(!orphan_nodes.contains(&"setup.md"));
80        assert!(orphan_nodes.contains(&"index.md"));
81    }
82}