Skip to main content

drft/rules/
redundant_edge.rs

1use crate::diagnostic::Diagnostic;
2use crate::rules::{Rule, RuleContext};
3
4pub struct RedundantEdgeRule;
5
6impl Rule for RedundantEdgeRule {
7    fn name(&self) -> &str {
8        "redundant-edge"
9    }
10
11    fn evaluate(&self, ctx: &RuleContext) -> Vec<Diagnostic> {
12        let result = &ctx.graph.transitive_reduction;
13
14        result
15            .redundant_edges
16            .iter()
17            .map(|re| Diagnostic {
18                rule: "redundant-edge".into(),
19                message: "transitively redundant".into(),
20                source: Some(re.source.clone()),
21                target: Some(re.target.clone()),
22                via: Some(re.via.clone()),
23                fix: Some(format!(
24                    "{} links directly to {}, but already reaches it via {} \u{2014} remove the direct link",
25                    re.source, re.target, re.via
26                )),
27                ..Default::default()
28            })
29            .collect()
30    }
31}
32
33#[cfg(test)]
34mod tests {
35    use super::*;
36    use crate::analyses::EnrichedGraph;
37    use crate::config::Config;
38    use crate::graph::{Edge, Graph, Node, NodeType};
39    use crate::rules::RuleContext;
40    use std::collections::HashMap;
41
42    fn make_node(path: &str) -> Node {
43        Node {
44            path: path.into(),
45            node_type: NodeType::File,
46            hash: None,
47            graph: None,
48            metadata: HashMap::new(),
49        }
50    }
51
52    fn make_edge(source: &str, target: &str) -> Edge {
53        Edge {
54            source: source.into(),
55            target: target.into(),
56            link: None,
57            parser: "markdown".into(),
58        }
59    }
60
61    fn make_enriched(graph: Graph) -> EnrichedGraph {
62        crate::analyses::enrich_graph(graph, std::path::Path::new("."), &Config::defaults(), None)
63    }
64
65    #[test]
66    fn produces_diagnostics_for_redundant_edges() {
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_node(make_node("c.md"));
71        graph.add_edge(make_edge("a.md", "b.md"));
72        graph.add_edge(make_edge("b.md", "c.md"));
73        graph.add_edge(make_edge("a.md", "c.md"));
74
75        let enriched = make_enriched(graph);
76        let ctx = RuleContext {
77            graph: &enriched,
78            options: None,
79        };
80        let diagnostics = RedundantEdgeRule.evaluate(&ctx);
81
82        assert_eq!(diagnostics.len(), 1);
83        assert_eq!(diagnostics[0].rule, "redundant-edge");
84        assert_eq!(diagnostics[0].source.as_deref(), Some("a.md"));
85        assert_eq!(diagnostics[0].target.as_deref(), Some("c.md"));
86        assert_eq!(diagnostics[0].via.as_deref(), Some("b.md"));
87        assert_eq!(diagnostics[0].message, "transitively redundant");
88    }
89
90    #[test]
91    fn no_diagnostics_when_no_redundancy() {
92        let mut graph = Graph::new();
93        graph.add_node(make_node("a.md"));
94        graph.add_node(make_node("b.md"));
95        graph.add_node(make_node("c.md"));
96        graph.add_edge(make_edge("a.md", "b.md"));
97        graph.add_edge(make_edge("b.md", "c.md"));
98
99        let enriched = make_enriched(graph);
100        let ctx = RuleContext {
101            graph: &enriched,
102            options: None,
103        };
104        let diagnostics = RedundantEdgeRule.evaluate(&ctx);
105        assert!(diagnostics.is_empty());
106    }
107}