1use crate::codebase_scanner::ScanResult;
4use crate::models::{DetectedPattern, PatternCategory};
5use crate::ResearchError;
6
7pub struct CodingPatternDetector;
9
10impl CodingPatternDetector {
11 pub fn detect_factory_pattern(
18 scan_result: &ScanResult,
19 ) -> Result<Option<DetectedPattern>, ResearchError> {
20 let factory_count = scan_result
21 .files
22 .iter()
23 .filter(|f| {
24 f.path
25 .file_name()
26 .and_then(|n| n.to_str())
27 .map(|n| n.to_lowercase().contains("factory"))
28 .unwrap_or(false)
29 })
30 .count();
31
32 if factory_count >= 1 {
33 let confidence = 0.75;
34 let locations = scan_result
35 .files
36 .iter()
37 .filter(|f| {
38 f.path
39 .file_name()
40 .and_then(|n| n.to_str())
41 .map(|n| n.to_lowercase().contains("factory"))
42 .unwrap_or(false)
43 })
44 .map(|f| f.path.clone())
45 .collect();
46
47 return Ok(Some(DetectedPattern {
48 name: "Factory Pattern".to_string(),
49 category: PatternCategory::Design,
50 confidence,
51 locations,
52 description: "Factory pattern detected for object creation abstraction".to_string(),
53 }));
54 }
55
56 Ok(None)
57 }
58
59 pub fn detect_observer_pattern(
66 scan_result: &ScanResult,
67 ) -> Result<Option<DetectedPattern>, ResearchError> {
68 let observer_count = scan_result
69 .files
70 .iter()
71 .filter(|f| {
72 f.path
73 .file_name()
74 .and_then(|n| n.to_str())
75 .map(|n| {
76 let lower = n.to_lowercase();
77 lower.contains("observer")
78 || lower.contains("listener")
79 || lower.contains("subscriber")
80 })
81 .unwrap_or(false)
82 })
83 .count();
84
85 if observer_count >= 1 {
86 let confidence = 0.7;
87 let locations = scan_result
88 .files
89 .iter()
90 .filter(|f| {
91 f.path
92 .file_name()
93 .and_then(|n| n.to_str())
94 .map(|n| {
95 let lower = n.to_lowercase();
96 lower.contains("observer")
97 || lower.contains("listener")
98 || lower.contains("subscriber")
99 })
100 .unwrap_or(false)
101 })
102 .map(|f| f.path.clone())
103 .collect();
104
105 return Ok(Some(DetectedPattern {
106 name: "Observer Pattern".to_string(),
107 category: PatternCategory::Design,
108 confidence,
109 locations,
110 description: "Observer pattern detected for event notification".to_string(),
111 }));
112 }
113
114 Ok(None)
115 }
116
117 pub fn detect_strategy_pattern(
124 scan_result: &ScanResult,
125 ) -> Result<Option<DetectedPattern>, ResearchError> {
126 let strategy_count = scan_result
127 .files
128 .iter()
129 .filter(|f| {
130 f.path
131 .file_name()
132 .and_then(|n| n.to_str())
133 .map(|n| n.to_lowercase().contains("strategy"))
134 .unwrap_or(false)
135 })
136 .count();
137
138 if strategy_count >= 1 {
139 let confidence = 0.75;
140 let locations = scan_result
141 .files
142 .iter()
143 .filter(|f| {
144 f.path
145 .file_name()
146 .and_then(|n| n.to_str())
147 .map(|n| n.to_lowercase().contains("strategy"))
148 .unwrap_or(false)
149 })
150 .map(|f| f.path.clone())
151 .collect();
152
153 return Ok(Some(DetectedPattern {
154 name: "Strategy Pattern".to_string(),
155 category: PatternCategory::Design,
156 confidence,
157 locations,
158 description: "Strategy pattern detected for algorithm selection".to_string(),
159 }));
160 }
161
162 Ok(None)
163 }
164
165 pub fn detect_singleton_pattern(
172 scan_result: &ScanResult,
173 ) -> Result<Option<DetectedPattern>, ResearchError> {
174 let singleton_count = scan_result
175 .files
176 .iter()
177 .filter(|f| {
178 f.path
179 .file_name()
180 .and_then(|n| n.to_str())
181 .map(|n| n.to_lowercase().contains("singleton"))
182 .unwrap_or(false)
183 })
184 .count();
185
186 if singleton_count >= 1 {
187 let confidence = 0.8;
188 let locations = scan_result
189 .files
190 .iter()
191 .filter(|f| {
192 f.path
193 .file_name()
194 .and_then(|n| n.to_str())
195 .map(|n| n.to_lowercase().contains("singleton"))
196 .unwrap_or(false)
197 })
198 .map(|f| f.path.clone())
199 .collect();
200
201 return Ok(Some(DetectedPattern {
202 name: "Singleton Pattern".to_string(),
203 category: PatternCategory::Design,
204 confidence,
205 locations,
206 description: "Singleton pattern detected for single instance management"
207 .to_string(),
208 }));
209 }
210
211 Ok(None)
212 }
213
214 pub fn detect_decorator_pattern(
221 scan_result: &ScanResult,
222 ) -> Result<Option<DetectedPattern>, ResearchError> {
223 let decorator_count = scan_result
224 .files
225 .iter()
226 .filter(|f| {
227 f.path
228 .file_name()
229 .and_then(|n| n.to_str())
230 .map(|n| n.to_lowercase().contains("decorator"))
231 .unwrap_or(false)
232 })
233 .count();
234
235 if decorator_count >= 1 {
236 let confidence = 0.75;
237 let locations = scan_result
238 .files
239 .iter()
240 .filter(|f| {
241 f.path
242 .file_name()
243 .and_then(|n| n.to_str())
244 .map(|n| n.to_lowercase().contains("decorator"))
245 .unwrap_or(false)
246 })
247 .map(|f| f.path.clone())
248 .collect();
249
250 return Ok(Some(DetectedPattern {
251 name: "Decorator Pattern".to_string(),
252 category: PatternCategory::Design,
253 confidence,
254 locations,
255 description: "Decorator pattern detected for dynamic behavior addition".to_string(),
256 }));
257 }
258
259 Ok(None)
260 }
261
262 pub fn detect_adapter_pattern(
269 scan_result: &ScanResult,
270 ) -> Result<Option<DetectedPattern>, ResearchError> {
271 let adapter_count = scan_result
272 .files
273 .iter()
274 .filter(|f| {
275 f.path
276 .file_name()
277 .and_then(|n| n.to_str())
278 .map(|n| n.to_lowercase().contains("adapter"))
279 .unwrap_or(false)
280 })
281 .count();
282
283 if adapter_count >= 1 {
284 let confidence = 0.75;
285 let locations = scan_result
286 .files
287 .iter()
288 .filter(|f| {
289 f.path
290 .file_name()
291 .and_then(|n| n.to_str())
292 .map(|n| n.to_lowercase().contains("adapter"))
293 .unwrap_or(false)
294 })
295 .map(|f| f.path.clone())
296 .collect();
297
298 return Ok(Some(DetectedPattern {
299 name: "Adapter Pattern".to_string(),
300 category: PatternCategory::Design,
301 confidence,
302 locations,
303 description: "Adapter pattern detected for interface conversion".to_string(),
304 }));
305 }
306
307 Ok(None)
308 }
309
310 pub fn detect_builder_pattern(
317 scan_result: &ScanResult,
318 ) -> Result<Option<DetectedPattern>, ResearchError> {
319 let builder_count = scan_result
320 .files
321 .iter()
322 .filter(|f| {
323 f.path
324 .file_name()
325 .and_then(|n| n.to_str())
326 .map(|n| n.to_lowercase().contains("builder"))
327 .unwrap_or(false)
328 })
329 .count();
330
331 if builder_count >= 1 {
332 let confidence = 0.75;
333 let locations = scan_result
334 .files
335 .iter()
336 .filter(|f| {
337 f.path
338 .file_name()
339 .and_then(|n| n.to_str())
340 .map(|n| n.to_lowercase().contains("builder"))
341 .unwrap_or(false)
342 })
343 .map(|f| f.path.clone())
344 .collect();
345
346 return Ok(Some(DetectedPattern {
347 name: "Builder Pattern".to_string(),
348 category: PatternCategory::Design,
349 confidence,
350 locations,
351 description: "Builder pattern detected for complex object construction".to_string(),
352 }));
353 }
354
355 Ok(None)
356 }
357
358 pub fn detect_repository_pattern(
365 scan_result: &ScanResult,
366 ) -> Result<Option<DetectedPattern>, ResearchError> {
367 let repository_count = scan_result
368 .files
369 .iter()
370 .filter(|f| {
371 f.path
372 .file_name()
373 .and_then(|n| n.to_str())
374 .map(|n| n.to_lowercase().contains("repository"))
375 .unwrap_or(false)
376 })
377 .count();
378
379 if repository_count >= 1 {
380 let confidence = 0.8;
381 let locations = scan_result
382 .files
383 .iter()
384 .filter(|f| {
385 f.path
386 .file_name()
387 .and_then(|n| n.to_str())
388 .map(|n| n.to_lowercase().contains("repository"))
389 .unwrap_or(false)
390 })
391 .map(|f| f.path.clone())
392 .collect();
393
394 return Ok(Some(DetectedPattern {
395 name: "Repository Pattern".to_string(),
396 category: PatternCategory::Design,
397 confidence,
398 locations,
399 description: "Repository pattern detected for data access abstraction".to_string(),
400 }));
401 }
402
403 Ok(None)
404 }
405
406 pub fn detect_service_locator_pattern(
413 scan_result: &ScanResult,
414 ) -> Result<Option<DetectedPattern>, ResearchError> {
415 let mut locator_indicators = 0;
416 let mut locator_files = Vec::new();
417
418 for file in &scan_result.files {
419 let file_name = file
420 .path
421 .file_name()
422 .and_then(|n| n.to_str())
423 .unwrap_or("")
424 .to_lowercase();
425
426 if file_name.contains("locator") {
427 locator_indicators += 2;
428 locator_files.push(file.path.clone());
429 }
430 if file_name.contains("registry") {
431 locator_indicators += 1;
432 locator_files.push(file.path.clone());
433 }
434 if file_name.contains("container") {
435 locator_indicators += 1;
436 locator_files.push(file.path.clone());
437 }
438 }
439
440 if locator_indicators >= 2 {
441 let confidence = (locator_indicators as f32 / 5.0).min(0.85);
442 locator_files.sort();
443 locator_files.dedup();
444
445 return Ok(Some(DetectedPattern {
446 name: "Service Locator Pattern".to_string(),
447 category: PatternCategory::Design,
448 confidence,
449 locations: locator_files,
450 description: "Service locator pattern detected for dependency lookup".to_string(),
451 }));
452 }
453
454 Ok(None)
455 }
456
457 pub fn detect_dependency_injection_pattern(
464 scan_result: &ScanResult,
465 ) -> Result<Option<DetectedPattern>, ResearchError> {
466 let mut di_indicators = 0;
467 let mut di_files = Vec::new();
468
469 for file in &scan_result.files {
470 let file_name = file
471 .path
472 .file_name()
473 .and_then(|n| n.to_str())
474 .unwrap_or("")
475 .to_lowercase();
476
477 if file_name.contains("inject") {
478 di_indicators += 2;
479 di_files.push(file.path.clone());
480 }
481 if file_name.contains("container") {
482 di_indicators += 1;
483 di_files.push(file.path.clone());
484 }
485 if file_name.contains("provider") {
486 di_indicators += 1;
487 di_files.push(file.path.clone());
488 }
489 }
490
491 if di_indicators >= 2 {
492 let confidence = (di_indicators as f32 / 5.0).min(0.85);
493 di_files.sort();
494 di_files.dedup();
495
496 return Ok(Some(DetectedPattern {
497 name: "Dependency Injection Pattern".to_string(),
498 category: PatternCategory::Design,
499 confidence,
500 locations: di_files,
501 description: "Dependency injection pattern detected for inversion of control"
502 .to_string(),
503 }));
504 }
505
506 Ok(None)
507 }
508
509 pub fn detect_middleware_pattern(
516 scan_result: &ScanResult,
517 ) -> Result<Option<DetectedPattern>, ResearchError> {
518 let middleware_count = scan_result
519 .files
520 .iter()
521 .filter(|f| {
522 f.path
523 .file_name()
524 .and_then(|n| n.to_str())
525 .map(|n| n.to_lowercase().contains("middleware"))
526 .unwrap_or(false)
527 })
528 .count();
529
530 if middleware_count >= 1 {
531 let confidence = 0.8;
532 let locations = scan_result
533 .files
534 .iter()
535 .filter(|f| {
536 f.path
537 .file_name()
538 .and_then(|n| n.to_str())
539 .map(|n| n.to_lowercase().contains("middleware"))
540 .unwrap_or(false)
541 })
542 .map(|f| f.path.clone())
543 .collect();
544
545 return Ok(Some(DetectedPattern {
546 name: "Middleware Pattern".to_string(),
547 category: PatternCategory::Design,
548 confidence,
549 locations,
550 description: "Middleware pattern detected for request/response processing"
551 .to_string(),
552 }));
553 }
554
555 Ok(None)
556 }
557}
558
559#[cfg(test)]
560mod tests {
561 use super::*;
562 use crate::codebase_scanner::FileMetadata;
563 use crate::models::Language;
564 use std::path::PathBuf;
565
566 fn create_test_scan_result(files: Vec<(&str, Option<Language>)>) -> ScanResult {
567 let files = files
568 .into_iter()
569 .map(|(path, lang)| FileMetadata {
570 path: PathBuf::from(path),
571 language: lang,
572 size: 100,
573 is_test: false,
574 })
575 .collect();
576
577 ScanResult {
578 files,
579 languages: vec![],
580 frameworks: vec![],
581 source_dirs: vec![],
582 test_dirs: vec![],
583 }
584 }
585
586 #[test]
587 fn test_detect_factory_pattern() {
588 let scan_result = create_test_scan_result(vec![("src/factory.rs", Some(Language::Rust))]);
589
590 let pattern = CodingPatternDetector::detect_factory_pattern(&scan_result).unwrap();
591 assert!(pattern.is_some());
592 let pattern = pattern.unwrap();
593 assert_eq!(pattern.name, "Factory Pattern");
594 }
595
596 #[test]
597 fn test_detect_observer_pattern() {
598 let scan_result = create_test_scan_result(vec![("src/observer.rs", Some(Language::Rust))]);
599
600 let pattern = CodingPatternDetector::detect_observer_pattern(&scan_result).unwrap();
601 assert!(pattern.is_some());
602 }
603
604 #[test]
605 fn test_detect_strategy_pattern() {
606 let scan_result = create_test_scan_result(vec![("src/strategy.rs", Some(Language::Rust))]);
607
608 let pattern = CodingPatternDetector::detect_strategy_pattern(&scan_result).unwrap();
609 assert!(pattern.is_some());
610 }
611
612 #[test]
613 fn test_detect_repository_pattern() {
614 let scan_result =
615 create_test_scan_result(vec![("src/repository.rs", Some(Language::Rust))]);
616
617 let pattern = CodingPatternDetector::detect_repository_pattern(&scan_result).unwrap();
618 assert!(pattern.is_some());
619 }
620}