cs/search/
pattern_match.rs1use crate::config::default_patterns;
2use crate::error::Result;
3use crate::parse::translation::TranslationEntry;
4use crate::search::text_search::TextSearcher;
5use regex::Regex;
6use std::path::PathBuf;
7
8#[derive(Debug, Clone, PartialEq)]
10pub struct CodeReference {
11 pub file: PathBuf,
13 pub line: usize,
15 pub pattern: String,
17 pub context: String,
19 pub key_path: String,
21}
22
23pub struct PatternMatcher {
25 searcher: TextSearcher,
26 patterns: Vec<Regex>,
27}
28
29impl PatternMatcher {
30 pub fn new(base_dir: PathBuf) -> Self {
32 Self {
33 searcher: TextSearcher::new(base_dir),
34 patterns: default_patterns(),
35 }
36 }
37
38 pub fn with_patterns(patterns: Vec<Regex>, base_dir: PathBuf) -> Self {
40 Self {
41 searcher: TextSearcher::new(base_dir),
42 patterns,
43 }
44 }
45
46 pub fn find_usages(&self, key_path: &str) -> Result<Vec<CodeReference>> {
48 let matches = self.searcher.search(key_path)?;
50
51 let mut code_refs = Vec::new();
52
53 for m in matches {
54 let file_str = m.file.to_string_lossy().to_lowercase();
56 if file_str.starts_with("src/")
57 || (file_str.starts_with("tests/") && !file_str.starts_with("tests/fixtures/"))
58 || file_str.ends_with("readme.md")
59 || file_str.ends_with("evaluation.md")
60 || file_str.ends_with(".md")
61 {
62 continue;
63 }
64
65 for pattern in &self.patterns {
67 if let Some(captures) = pattern.captures(&m.content) {
68 if let Some(captured_key) = captures.get(1) {
70 if captured_key.as_str() == key_path {
71 code_refs.push(CodeReference {
72 file: m.file.clone(),
73 line: m.line,
74 pattern: pattern.as_str().to_string(),
75 context: m.content.clone(),
76 key_path: key_path.to_string(),
77 });
78 break; }
80 }
81 }
82 }
83 }
84
85 Ok(code_refs)
86 }
87
88 pub fn find_usages_batch(&self, entries: &[TranslationEntry]) -> Result<Vec<CodeReference>> {
90 let mut all_refs = Vec::new();
91
92 for entry in entries {
93 let refs = self.find_usages(&entry.key)?;
94 all_refs.extend(refs);
95 }
96
97 Ok(all_refs)
98 }
99}
100
101impl Default for PatternMatcher {
102 fn default() -> Self {
103 Self::new(std::env::current_dir().unwrap())
104 }
105}
106
107#[cfg(test)]
108mod tests {
109 use super::*;
110
111 #[test]
112 fn test_pattern_matcher_creation() {
113 let matcher = PatternMatcher::new(std::env::current_dir().unwrap());
114 assert!(!matcher.patterns.is_empty());
115 }
116
117 #[test]
118 fn test_code_reference_creation() {
119 let code_ref = CodeReference {
120 file: PathBuf::from("test.rb"),
121 line: 10,
122 pattern: r#"I18n\.t\(['"]([^'"]+)['"]\)"#.to_string(),
123 context: r#"I18n.t('invoice.labels.add_new')"#.to_string(),
124 key_path: "invoice.labels.add_new".to_string(),
125 };
126
127 assert_eq!(code_ref.file, PathBuf::from("test.rb"));
128 assert_eq!(code_ref.line, 10);
129 assert_eq!(code_ref.key_path, "invoice.labels.add_new");
130 }
131
132 #[test]
133 fn test_pattern_matcher_with_custom_patterns() {
134 let custom_patterns = vec['"]\)"#).unwrap()];
135 let matcher =
136 PatternMatcher::with_patterns(custom_patterns, std::env::current_dir().unwrap());
137 assert_eq!(matcher.patterns.len(), 1);
138 }
139}