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::graph::Graph;
37    use crate::graph::test_helpers::{make_edge, make_enriched, make_node};
38    use crate::rules::RuleContext;
39
40    #[test]
41    fn produces_diagnostics_for_redundant_edges() {
42        let mut graph = Graph::new();
43        graph.add_node(make_node("a.md"));
44        graph.add_node(make_node("b.md"));
45        graph.add_node(make_node("c.md"));
46        graph.add_edge(make_edge("a.md", "b.md"));
47        graph.add_edge(make_edge("b.md", "c.md"));
48        graph.add_edge(make_edge("a.md", "c.md"));
49
50        let enriched = make_enriched(graph);
51        let ctx = RuleContext {
52            graph: &enriched,
53            options: None,
54        };
55        let diagnostics = RedundantEdgeRule.evaluate(&ctx);
56
57        assert_eq!(diagnostics.len(), 1);
58        assert_eq!(diagnostics[0].rule, "redundant-edge");
59        assert_eq!(diagnostics[0].source.as_deref(), Some("a.md"));
60        assert_eq!(diagnostics[0].target.as_deref(), Some("c.md"));
61        assert_eq!(diagnostics[0].via.as_deref(), Some("b.md"));
62        assert_eq!(diagnostics[0].message, "transitively redundant");
63    }
64
65    #[test]
66    fn no_diagnostics_when_no_redundancy() {
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
74        let enriched = make_enriched(graph);
75        let ctx = RuleContext {
76            graph: &enriched,
77            options: None,
78        };
79        let diagnostics = RedundantEdgeRule.evaluate(&ctx);
80        assert!(diagnostics.is_empty());
81    }
82}