Skip to main content

drft/rules/
orphan.rs

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