Skip to main content

driven/validation/
conflicts.rs

1//! Conflict detection
2
3use super::Conflict;
4use crate::{Result, parser::UnifiedRule};
5
6/// Detects conflicts between rules
7#[derive(Debug, Default)]
8pub struct ConflictDetector;
9
10impl ConflictDetector {
11    /// Create a new conflict detector
12    pub fn new() -> Self {
13        Self
14    }
15
16    /// Detect conflicts in a set of rules
17    pub fn detect(&self, rules: &[UnifiedRule]) -> Result<Vec<Conflict>> {
18        let mut conflicts = Vec::new();
19
20        // Check for conflicting naming conventions
21        conflicts.extend(self.check_naming_conflicts(rules));
22
23        // Check for contradictory standards
24        conflicts.extend(self.check_contradictions(rules));
25
26        Ok(conflicts)
27    }
28
29    fn check_naming_conflicts(&self, rules: &[UnifiedRule]) -> Vec<Conflict> {
30        let mut conflicts = Vec::new();
31        let mut naming_rules: Vec<&str> = Vec::new();
32
33        for rule in rules {
34            if let UnifiedRule::Standard { description, .. } = rule {
35                let lower = description.to_lowercase();
36                if lower.contains("snake_case")
37                    || lower.contains("camelcase")
38                    || lower.contains("pascalcase")
39                {
40                    naming_rules.push(description);
41                }
42            }
43        }
44
45        // Check for conflicting naming rules
46        if naming_rules.len() > 1 {
47            let has_snake = naming_rules
48                .iter()
49                .any(|r| r.to_lowercase().contains("snake_case"));
50            let has_camel = naming_rules
51                .iter()
52                .any(|r| r.to_lowercase().contains("camelcase"));
53
54            if has_snake && has_camel {
55                // Only conflict if they're for the same context
56                let func_snake = naming_rules.iter().any(|r| {
57                    r.to_lowercase().contains("function") && r.to_lowercase().contains("snake_case")
58                });
59                let func_camel = naming_rules.iter().any(|r| {
60                    r.to_lowercase().contains("function") && r.to_lowercase().contains("camelcase")
61                });
62
63                if func_snake && func_camel {
64                    conflicts.push(Conflict {
65                        description: "Conflicting function naming conventions".to_string(),
66                        rules: naming_rules.iter().map(|s| s.to_string()).collect(),
67                        suggestion: Some("Choose one naming convention for functions".to_string()),
68                    });
69                }
70            }
71        }
72
73        conflicts
74    }
75
76    fn check_contradictions(&self, rules: &[UnifiedRule]) -> Vec<Conflict> {
77        let mut conflicts = Vec::new();
78
79        // Look for obvious contradictions
80        let contradiction_pairs = [
81            ("always", "never"),
82            ("must", "must not"),
83            ("require", "avoid"),
84        ];
85
86        let standards: Vec<_> = rules
87            .iter()
88            .filter_map(|r| {
89                if let UnifiedRule::Standard { description, .. } = r {
90                    Some(description.as_str())
91                } else {
92                    None
93                }
94            })
95            .collect();
96
97        for (positive, negative) in contradiction_pairs {
98            for &rule1 in &standards {
99                for &rule2 in &standards {
100                    if rule1 == rule2 {
101                        continue;
102                    }
103
104                    let r1_lower = rule1.to_lowercase();
105                    let r2_lower = rule2.to_lowercase();
106
107                    // Check if same topic with contradicting verbs
108                    if (r1_lower.contains(positive) && r2_lower.contains(negative))
109                        || (r1_lower.contains(negative) && r2_lower.contains(positive))
110                    {
111                        // Check for similar topics (simple word overlap)
112                        let r1_words: std::collections::HashSet<_> = r1_lower
113                            .split_whitespace()
114                            .filter(|w| w.len() > 4)
115                            .collect();
116                        let r2_words: std::collections::HashSet<_> = r2_lower
117                            .split_whitespace()
118                            .filter(|w| w.len() > 4)
119                            .collect();
120
121                        let overlap: Vec<_> = r1_words.intersection(&r2_words).collect();
122                        if overlap.len() >= 2 {
123                            conflicts.push(Conflict {
124                                description: "Potentially contradicting rules".to_string(),
125                                rules: vec![rule1.to_string(), rule2.to_string()],
126                                suggestion: Some("Review these rules for consistency".to_string()),
127                            });
128                        }
129                    }
130                }
131            }
132        }
133
134        conflicts
135    }
136}
137
138#[cfg(test)]
139mod tests {
140    use super::*;
141
142    #[test]
143    fn test_detector_new() {
144        let detector = ConflictDetector::new();
145        let conflicts = detector.detect(&[]).unwrap();
146        assert!(conflicts.is_empty());
147    }
148}