ricecoder_research/
coding_patterns.rs

1//! Coding pattern detection
2
3use crate::codebase_scanner::ScanResult;
4use crate::models::{DetectedPattern, PatternCategory};
5use crate::ResearchError;
6
7/// Detects coding patterns and design patterns in a codebase
8pub struct CodingPatternDetector;
9
10impl CodingPatternDetector {
11    /// Detects factory pattern
12    ///
13    /// Factory pattern is characterized by:
14    /// - Factory classes/functions
15    /// - Object creation abstraction
16    /// - Multiple concrete implementations
17    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    /// Detects observer pattern
60    ///
61    /// Observer pattern is characterized by:
62    /// - Observer/listener interfaces
63    /// - Subject/publisher classes
64    /// - Event notification mechanism
65    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    /// Detects strategy pattern
118    ///
119    /// Strategy pattern is characterized by:
120    /// - Strategy interfaces/traits
121    /// - Multiple concrete strategies
122    /// - Context that uses strategies
123    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    /// Detects singleton pattern
166    ///
167    /// Singleton pattern is characterized by:
168    /// - Singleton class definitions
169    /// - Single instance management
170    /// - Global access point
171    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    /// Detects decorator pattern
215    ///
216    /// Decorator pattern is characterized by:
217    /// - Decorator classes/functions
218    /// - Wrapping/composition of objects
219    /// - Dynamic behavior addition
220    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    /// Detects adapter pattern
263    ///
264    /// Adapter pattern is characterized by:
265    /// - Adapter classes
266    /// - Interface conversion
267    /// - Incompatible interface bridging
268    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    /// Detects builder pattern
311    ///
312    /// Builder pattern is characterized by:
313    /// - Builder classes
314    /// - Fluent interface
315    /// - Complex object construction
316    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    /// Detects repository pattern
359    ///
360    /// Repository pattern is characterized by:
361    /// - Repository classes/interfaces
362    /// - Data access abstraction
363    /// - CRUD operations
364    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    /// Detects service locator pattern
407    ///
408    /// Service locator pattern is characterized by:
409    /// - Service locator classes
410    /// - Service registry
411    /// - Dependency lookup
412    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    /// Detects dependency injection pattern
458    ///
459    /// Dependency injection pattern is characterized by:
460    /// - Dependency injection containers
461    /// - Constructor/setter injection
462    /// - Inversion of control
463    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    /// Detects middleware pattern
510    ///
511    /// Middleware pattern is characterized by:
512    /// - Middleware classes/functions
513    /// - Request/response processing chain
514    /// - Cross-cutting concerns
515    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}