codeowners_validation/validators/
duplicate_patterns.rs1use crate::parser::CodeOwnerRule;
2use rustc_hash::FxHashSet;
3
4pub fn validate_duplicates(rules: &[CodeOwnerRule]) -> Vec<CodeOwnerRule> {
5 let mut pattern_set = FxHashSet::default();
6 let mut original_path_set = FxHashSet::default();
7
8 pattern_set.reserve(rules.len());
9 original_path_set.reserve(rules.len());
10
11 let mut duplicates = Vec::new();
12
13 for rule in rules {
14 let is_original_path_duplicate = !original_path_set.insert(&rule.original_path);
15 let is_pattern_duplicate = !pattern_set.insert(&rule.pattern);
16
17 if is_original_path_duplicate || is_pattern_duplicate {
18 duplicates.push(rule.clone());
19 if !is_original_path_duplicate && is_pattern_duplicate {
21 println!("Warning: Duplicate pattern found in normalized pattern");
22 println!("Please raise an issue if this seems incorrect.");
23 println!("Pattern: {}", &rule.pattern);
24 println!("Original: {}", &rule.original_path);
25 }
26 }
27 }
28
29 duplicates
30}
31
32#[cfg(test)]
33mod tests {
34 use super::*;
35 use crate::parser::CodeOwnerRule;
36
37 fn rule(pattern: &str, original: &str) -> CodeOwnerRule {
38 CodeOwnerRule {
39 pattern: pattern.trim_matches('/').to_string(),
40 original_path: original.to_string(),
41 owners: vec!["@owner".to_string()],
42 }
43 }
44
45 #[test]
46 fn no_duplicates() {
47 let rules = vec![rule("a.txt", "a.txt"), rule("b.txt", "b.txt")];
48 let result = validate_duplicates(&rules);
49 assert!(result.is_empty());
50 }
51
52 #[test]
53 fn detects_exact_duplicates() {
54 let rules = vec![rule("src", "src/"), rule("src", "src/")];
55 let result = validate_duplicates(&rules);
56 assert_eq!(result.len(), 1);
57 assert_eq!(result[0].pattern, "src");
58 }
59
60 #[test]
61 fn normalized_duplicates_detected() {
62 let rules = vec![rule("docs", "/docs"), rule("docs", "docs")];
64 let result = validate_duplicates(&rules);
65 assert_eq!(result.len(), 1);
66 assert_eq!(result[0].pattern, "docs");
67 }
68
69 #[test]
70 fn different_slash_variations() {
71 let rules = vec![
73 rule("src/lib", "/src/lib/"),
74 rule("src/lib", "src/lib"),
75 rule("src/lib", "/src/lib"),
76 ];
77 let result = validate_duplicates(&rules);
78 assert_eq!(result.len(), 2);
80 }
81
82 #[test]
83 fn original_path_duplicates() {
84 let rules = vec![rule("docs", "docs/"), rule("docs", "docs/")];
86 let result = validate_duplicates(&rules);
87 assert_eq!(result.len(), 1);
88 }
89
90 #[test]
91 fn wildcard_duplicates() {
92 let rules = vec![rule("*.md", "*.md"), rule("*.md", "*.md")];
93 let result = validate_duplicates(&rules);
94 assert_eq!(result.len(), 1);
95 }
96
97 #[test]
98 fn complex_pattern_duplicates() {
99 let rules = vec![
100 rule("**/*.test.js", "**/*.test.js"),
101 rule("src/*.rs", "src/*.rs"),
102 rule("**/*.test.js", "**/*.test.js"), ];
104 let result = validate_duplicates(&rules);
105 assert_eq!(result.len(), 1);
106 assert_eq!(result[0].pattern, "**/*.test.js");
107 }
108}