ricecoder_refactoring/patterns/
matcher.rs

1//! Pattern matching for refactoring rules
2
3use crate::error::Result;
4use crate::types::{RefactoringRule, RefactoringType};
5use regex::Regex;
6use std::collections::HashMap;
7
8/// Matches refactoring patterns in code
9pub struct PatternMatcher;
10
11/// Result of pattern matching
12#[derive(Debug, Clone)]
13pub struct MatchResult {
14    /// Whether the pattern matched
15    pub matched: bool,
16    /// Matched text
17    pub matched_text: Option<String>,
18    /// Match position (line, column)
19    pub position: Option<(usize, usize)>,
20    /// Captured groups
21    pub captures: HashMap<String, String>,
22}
23
24impl PatternMatcher {
25    /// Match a rule pattern against code
26    pub fn match_rule(code: &str, rule: &RefactoringRule) -> Result<Vec<MatchResult>> {
27        let mut results = vec![];
28
29        // Try regex matching first
30        if let Ok(re) = Regex::new(&rule.pattern) {
31            for (line_num, line) in code.lines().enumerate() {
32                for mat in re.find_iter(line) {
33                    results.push(MatchResult {
34                        matched: true,
35                        matched_text: Some(mat.as_str().to_string()),
36                        position: Some((line_num + 1, mat.start())),
37                        captures: HashMap::new(),
38                    });
39                }
40            }
41        } else {
42            // Fall back to simple string matching
43            for (line_num, line) in code.lines().enumerate() {
44                if line.contains(&rule.pattern) {
45                    results.push(MatchResult {
46                        matched: true,
47                        matched_text: Some(rule.pattern.clone()),
48                        position: Some((line_num + 1, 0)),
49                        captures: HashMap::new(),
50                    });
51                }
52            }
53        }
54
55        Ok(results)
56    }
57
58    /// Match multiple rules against code
59    pub fn match_rules(code: &str, rules: &[RefactoringRule]) -> Result<HashMap<String, Vec<MatchResult>>> {
60        let mut all_matches = HashMap::new();
61
62        for rule in rules {
63            if rule.enabled {
64                let matches = Self::match_rule(code, rule)?;
65                if !matches.is_empty() {
66                    all_matches.insert(rule.name.clone(), matches);
67                }
68            }
69        }
70
71        Ok(all_matches)
72    }
73
74    /// Check if a refactoring type is applicable based on matched patterns
75    pub fn is_applicable(
76        refactoring_type: RefactoringType,
77        matched_patterns: &[&str],
78    ) -> bool {
79        match refactoring_type {
80            RefactoringType::Rename => {
81                // Rename is applicable if we found a symbol
82                !matched_patterns.is_empty()
83            }
84            RefactoringType::RemoveUnused => {
85                // RemoveUnused is applicable if we found unused code patterns
86                matched_patterns.iter().any(|p| p.contains("unused"))
87            }
88            RefactoringType::Extract => {
89                // Extract is applicable if we found extractable code
90                !matched_patterns.is_empty()
91            }
92            RefactoringType::Inline => {
93                // Inline is applicable if we found inlinable code
94                !matched_patterns.is_empty()
95            }
96            RefactoringType::Move => {
97                // Move is applicable if we found movable code
98                !matched_patterns.is_empty()
99            }
100            RefactoringType::ChangeSignature => {
101                // ChangeSignature is applicable if we found function signatures
102                matched_patterns.iter().any(|p| p.contains("fn") || p.contains("function"))
103            }
104            RefactoringType::Simplify => {
105                // Simplify is applicable if we found simplifiable patterns
106                !matched_patterns.is_empty()
107            }
108        }
109    }
110
111    /// Extract captures from a regex match
112    pub fn extract_captures(pattern: &str, text: &str) -> Result<HashMap<String, String>> {
113        let mut captures = HashMap::new();
114
115        if let Ok(re) = Regex::new(pattern) {
116            if let Some(caps) = re.captures(text) {
117                for (i, cap) in caps.iter().enumerate() {
118                    if let Some(m) = cap {
119                        captures.insert(format!("group_{}", i), m.as_str().to_string());
120                    }
121                }
122            }
123        }
124
125        Ok(captures)
126    }
127
128    /// Count matches of a pattern in code
129    pub fn count_matches(code: &str, pattern: &str) -> Result<usize> {
130        if let Ok(re) = Regex::new(pattern) {
131            Ok(re.find_iter(code).count())
132        } else {
133            Ok(code.matches(pattern).count())
134        }
135    }
136}
137
138#[cfg(test)]
139mod tests {
140    use super::*;
141
142    #[test]
143    fn test_match_rule_regex() -> Result<()> {
144        let rule = RefactoringRule {
145            name: "test".to_string(),
146            pattern: r"\bfn\s+\w+".to_string(),
147            refactoring_type: RefactoringType::Rename,
148            enabled: true,
149        };
150
151        let code = "fn main() {}\nfn helper() {}";
152        let results = PatternMatcher::match_rule(code, &rule)?;
153
154        assert_eq!(results.len(), 2);
155        assert!(results[0].matched);
156
157        Ok(())
158    }
159
160    #[test]
161    fn test_match_rule_string() -> Result<()> {
162        let rule = RefactoringRule {
163            name: "test".to_string(),
164            pattern: "unused".to_string(),
165            refactoring_type: RefactoringType::RemoveUnused,
166            enabled: true,
167        };
168
169        let code = "let unused = 5;\nlet used = 10;";
170        let results = PatternMatcher::match_rule(code, &rule)?;
171
172        assert_eq!(results.len(), 1);
173        assert!(results[0].matched);
174
175        Ok(())
176    }
177
178    #[test]
179    fn test_match_rules() -> Result<()> {
180        let rules = vec![
181            RefactoringRule {
182                name: "rule1".to_string(),
183                pattern: "fn".to_string(),
184                refactoring_type: RefactoringType::Rename,
185                enabled: true,
186            },
187            RefactoringRule {
188                name: "rule2".to_string(),
189                pattern: "unused".to_string(),
190                refactoring_type: RefactoringType::RemoveUnused,
191                enabled: true,
192            },
193        ];
194
195        let code = "fn main() {}\nlet unused = 5;";
196        let results = PatternMatcher::match_rules(code, &rules)?;
197
198        assert_eq!(results.len(), 2);
199        assert!(results.contains_key("rule1"));
200        assert!(results.contains_key("rule2"));
201
202        Ok(())
203    }
204
205    #[test]
206    fn test_is_applicable() {
207        assert!(PatternMatcher::is_applicable(
208            RefactoringType::Rename,
209            &["symbol"]
210        ));
211        assert!(PatternMatcher::is_applicable(
212            RefactoringType::RemoveUnused,
213            &["unused_var"]
214        ));
215        assert!(!PatternMatcher::is_applicable(
216            RefactoringType::RemoveUnused,
217            &["used_var"]
218        ));
219    }
220
221    #[test]
222    fn test_count_matches() -> Result<()> {
223        let count = PatternMatcher::count_matches("fn main() {}\nfn helper() {}", r"\bfn\b")?;
224        assert_eq!(count, 2);
225
226        Ok(())
227    }
228}