ricecoder_refactoring/patterns/
matcher.rs1use crate::error::Result;
4use crate::types::{RefactoringRule, RefactoringType};
5use regex::Regex;
6use std::collections::HashMap;
7
8pub struct PatternMatcher;
10
11#[derive(Debug, Clone)]
13pub struct MatchResult {
14 pub matched: bool,
16 pub matched_text: Option<String>,
18 pub position: Option<(usize, usize)>,
20 pub captures: HashMap<String, String>,
22}
23
24impl PatternMatcher {
25 pub fn match_rule(code: &str, rule: &RefactoringRule) -> Result<Vec<MatchResult>> {
27 let mut results = vec![];
28
29 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 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 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 pub fn is_applicable(
76 refactoring_type: RefactoringType,
77 matched_patterns: &[&str],
78 ) -> bool {
79 match refactoring_type {
80 RefactoringType::Rename => {
81 !matched_patterns.is_empty()
83 }
84 RefactoringType::RemoveUnused => {
85 matched_patterns.iter().any(|p| p.contains("unused"))
87 }
88 RefactoringType::Extract => {
89 !matched_patterns.is_empty()
91 }
92 RefactoringType::Inline => {
93 !matched_patterns.is_empty()
95 }
96 RefactoringType::Move => {
97 !matched_patterns.is_empty()
99 }
100 RefactoringType::ChangeSignature => {
101 matched_patterns.iter().any(|p| p.contains("fn") || p.contains("function"))
103 }
104 RefactoringType::Simplify => {
105 !matched_patterns.is_empty()
107 }
108 }
109 }
110
111 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 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}