drft/rules/
redundant_edge.rs1use 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}