cs/trace/
function_finder.rs1use crate::error::{Result, SearchError};
2use crate::search::TextSearcher;
3use regex::Regex;
4use std::collections::HashSet;
5use std::fs;
6use std::path::PathBuf;
7
8#[derive(Debug, Clone, PartialEq, Eq, Hash)]
10pub struct FunctionDef {
11 pub name: String,
12 pub file: PathBuf,
13 pub line: usize,
14 pub body: String,
15}
16
17pub struct FunctionFinder {
19 searcher: TextSearcher,
20 patterns: Vec<Regex>,
21 base_dir: PathBuf,
22}
23
24impl FunctionFinder {
25 pub fn new(base_dir: PathBuf) -> Self {
30 Self {
31 searcher: TextSearcher::new(base_dir.clone()),
32 patterns: Self::default_patterns(),
33 base_dir,
34 }
35 }
36
37 fn default_patterns() -> Vec<Regex> {
39 vec![
40 Regex::new(r"function\s+(\w+)\s*\(").unwrap(),
42 Regex::new(r"(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s+)?\([^)]*\)\s*=>").unwrap(),
44 Regex::new(r"^\s*(?:async\s+)?(\w+)\s*\([^)]*\)\s*\{").unwrap(),
46 Regex::new(r"export\s+function\s+(\w+)").unwrap(),
48 Regex::new(r"^\s*(?:public|private|protected|static|async)\s+(\w+)\s*\(").unwrap(),
50 Regex::new(r"def\s+(\w+)").unwrap(),
52 Regex::new(r"def\s+self\.(\w+)").unwrap(),
54 Regex::new(r"def\s+(\w+)\s*\(").unwrap(),
56 Regex::new(r"fn\s+(\w+)\s*[<(]").unwrap(),
58 ]
59 }
60
61 fn generate_case_variants(func_name: &str) -> Vec<String> {
66 let mut variants = HashSet::new();
67
68 variants.insert(func_name.to_string());
70
71 let snake_case = Self::to_snake_case(func_name);
73 variants.insert(snake_case.clone());
74
75 let camel_case = Self::to_camel_case(&snake_case);
77 variants.insert(camel_case.clone());
78
79 let pascal_case = Self::to_pascal_case(&snake_case);
81 variants.insert(pascal_case);
82
83 variants.into_iter().collect()
84 }
85
86 fn to_snake_case(input: &str) -> String {
88 let mut result = String::new();
89
90 for (i, ch) in input.chars().enumerate() {
91 if ch.is_uppercase() && i > 0 {
92 result.push('_');
93 }
94 result.push(ch.to_lowercase().next().unwrap());
95 }
96
97 result
98 }
99
100 fn to_camel_case(input: &str) -> String {
102 let parts: Vec<&str> = input.split('_').collect();
103 if parts.is_empty() {
104 return String::new();
105 }
106
107 let mut result = parts[0].to_lowercase();
108 for part in parts.iter().skip(1) {
109 if !part.is_empty() {
110 let mut chars = part.chars();
111 if let Some(first) = chars.next() {
112 result.push(first.to_uppercase().next().unwrap());
113 result.push_str(&chars.as_str().to_lowercase());
114 }
115 }
116 }
117
118 result
119 }
120
121 fn to_pascal_case(input: &str) -> String {
123 let parts: Vec<&str> = input.split('_').collect();
124 let mut result = String::new();
125
126 for part in parts {
127 if !part.is_empty() {
128 let mut chars = part.chars();
129 if let Some(first) = chars.next() {
130 result.push(first.to_uppercase().next().unwrap());
131 result.push_str(&chars.as_str().to_lowercase());
132 }
133 }
134 }
135
136 result
137 }
138
139 pub fn find_function(&self, func_name: &str) -> Option<FunctionDef> {
150 if let Ok(mut defs) = self.find_definition(func_name) {
152 if let Some(def) = defs.pop() {
153 return Some(def);
154 }
155 }
156
157 let variants = Self::generate_case_variants(func_name);
159
160 for variant in variants {
161 if variant != func_name {
162 if let Ok(mut defs) = self.find_definition(&variant) {
164 if let Some(def) = defs.pop() {
165 return Some(def);
166 }
167 }
168 }
169 }
170
171 None
172 }
173
174 pub fn find_definition(&self, func_name: &str) -> Result<Vec<FunctionDef>> {
179 let mut results = Vec::new();
180
181 let matches = self.searcher.search(func_name)?;
183
184 for m in matches {
186 let relative_path_buf = match m.file.strip_prefix(&self.base_dir) {
189 Ok(rel_path) => rel_path.to_path_buf(),
190 Err(_) => m.file.clone(),
191 };
192
193 let path_components: Vec<_> = relative_path_buf
195 .components()
196 .map(|c| c.as_os_str().to_string_lossy().to_lowercase())
197 .collect();
198
199 if !path_components.is_empty() {
200 if path_components[0] == "src" {
201 continue;
202 }
203 if path_components[0] == "tests"
204 && (path_components.len() < 2 || path_components[1] != "fixtures")
205 {
206 continue;
207 }
208 }
209
210 let content = &m.content;
211
212 for pattern in &self.patterns {
214 if let Some(captures) = pattern.captures(content) {
215 if let Some(name_match) = captures.get(1) {
216 let name = name_match.as_str();
217 if name == func_name {
219 let file_content = fs::read_to_string(&m.file)?;
220 let body = file_content
221 .lines()
222 .skip(m.line - 1)
223 .collect::<Vec<_>>()
224 .join("\n");
225 results.push(FunctionDef {
226 name: name.to_string(),
227 file: m.file.clone(),
228 line: m.line,
229 body,
230 });
231 break; }
233 }
234 }
235 }
236 }
237
238 if results.is_empty() {
239 Err(SearchError::Generic(format!(
240 "Function '{}' not found in codebase",
241 func_name
242 )))
243 } else {
244 results.sort_by(|a, b| a.file.cmp(&b.file).then(a.line.cmp(&b.line)));
246 Ok(results)
247 }
248 }
249}
250
251impl Default for FunctionFinder {
252 fn default() -> Self {
253 Self::new(std::env::current_dir().unwrap())
254 }
255}
256
257#[cfg(test)]
258mod tests {
259 use super::*;
260
261 #[test]
262 fn test_function_finder_creation() {
263 let finder = FunctionFinder::new(std::env::current_dir().unwrap());
264 assert!(!finder.patterns.is_empty());
265 }
266
267 #[test]
268 fn test_patterns_compile() {
269 let patterns = FunctionFinder::default_patterns();
270 assert_eq!(patterns.len(), 9);
271 }
272
273 #[test]
274 fn test_js_function_pattern() {
275 let patterns = FunctionFinder::default_patterns();
276 let js_pattern = &patterns[0];
277
278 assert!(js_pattern.is_match("function handleClick() {"));
279 assert!(js_pattern.is_match("function processData(x, y) {"));
280 assert!(!js_pattern.is_match("const x = function() {"));
281 }
282
283 #[test]
284 fn test_arrow_function_pattern() {
285 let patterns = FunctionFinder::default_patterns();
286 let arrow_pattern = &patterns[1];
287
288 assert!(arrow_pattern.is_match("const handleClick = () => {"));
289 assert!(arrow_pattern.is_match("let processData = async (x) => {"));
290 assert!(arrow_pattern.is_match("var foo = (a, b) => {"));
291 }
292
293 #[test]
294 fn test_ruby_pattern() {
295 let patterns = FunctionFinder::default_patterns();
296 let ruby_pattern = &patterns[5]; assert!(ruby_pattern.is_match("def process_order"));
299 assert!(ruby_pattern.is_match(" def calculate_total"));
300 }
301
302 #[test]
303 fn test_python_pattern() {
304 let patterns = FunctionFinder::default_patterns();
305 let python_pattern = &patterns[7]; assert!(python_pattern.is_match("def process_data(x):"));
308 assert!(python_pattern.is_match(" def helper():"));
309 }
310
311 #[test]
312 fn test_rust_pattern() {
313 let patterns = FunctionFinder::default_patterns();
314 let rust_pattern = &patterns[8]; assert!(rust_pattern.is_match("fn main() {"));
317 assert!(rust_pattern.is_match("fn process<T>(x: T) {"));
318 assert!(rust_pattern.is_match("pub fn calculate("));
319 }
320
321 #[test]
322 fn test_javascript_export_patterns() {
323 let patterns = FunctionFinder::default_patterns();
324 let export_func_pattern = &patterns[3];
325
326 assert!(export_func_pattern.is_match("export function processData"));
327 assert!(export_func_pattern.is_match("export function calculate"));
328 }
329
330 #[test]
331 fn test_javascript_method_patterns() {
332 let patterns = FunctionFinder::default_patterns();
333 let method_pattern = &patterns[2];
334
335 assert!(method_pattern.is_match(" processData() {"));
336 assert!(method_pattern.is_match(" handleClick() {"));
337 assert!(method_pattern.is_match(" async methodName() {"));
338 }
339
340 #[test]
341 fn test_ruby_class_methods() {
342 let patterns = FunctionFinder::default_patterns();
343 let ruby_class_method_pattern = &patterns[6];
344
345 assert!(ruby_class_method_pattern.is_match("def self.create"));
346 assert!(ruby_class_method_pattern.is_match(" def self.find_by_name"));
347 }
348
349 #[test]
350 fn test_case_conversion() {
351 assert_eq!(FunctionFinder::to_snake_case("createUser"), "create_user");
353 assert_eq!(
354 FunctionFinder::to_snake_case("validateEmailAddress"),
355 "validate_email_address"
356 );
357 assert_eq!(
358 FunctionFinder::to_snake_case("XMLHttpRequest"),
359 "x_m_l_http_request"
360 );
361 assert_eq!(
362 FunctionFinder::to_snake_case("already_snake"),
363 "already_snake"
364 );
365
366 assert_eq!(FunctionFinder::to_camel_case("create_user"), "createUser");
368 assert_eq!(
369 FunctionFinder::to_camel_case("validate_email_address"),
370 "validateEmailAddress"
371 );
372 assert_eq!(FunctionFinder::to_camel_case("single"), "single");
373
374 assert_eq!(FunctionFinder::to_pascal_case("create_user"), "CreateUser");
376 assert_eq!(
377 FunctionFinder::to_pascal_case("validate_email_address"),
378 "ValidateEmailAddress"
379 );
380 assert_eq!(FunctionFinder::to_pascal_case("single"), "Single");
381 }
382
383 #[test]
384 fn test_generate_case_variants() {
385 let variants = FunctionFinder::generate_case_variants("createUser");
387 assert!(variants.contains(&"createUser".to_string()));
388 assert!(variants.contains(&"create_user".to_string()));
389 assert!(variants.contains(&"CreateUser".to_string()));
390
391 let variants = FunctionFinder::generate_case_variants("validate_email");
393 assert!(variants.contains(&"validate_email".to_string()));
394 assert!(variants.contains(&"validateEmail".to_string()));
395 assert!(variants.contains(&"ValidateEmail".to_string()));
396
397 let variants = FunctionFinder::generate_case_variants("ProcessUserData");
399 assert!(variants.contains(&"ProcessUserData".to_string()));
400 assert!(variants.contains(&"process_user_data".to_string()));
401 assert!(variants.contains(&"processUserData".to_string()));
402 }
403}