go_brrr/patterns/
design_patterns.rs

1//! Design pattern detection module.
2//!
3//! Detects common design patterns in source code using AST analysis and heuristics.
4//!
5//! # Supported Patterns
6//!
7//! **Creational:**
8//! - **Singleton**: Private constructor, static instance, `getInstance()`
9//! - **Factory**: Methods returning interface types, `*Factory` naming, `create*` methods
10//! - **Builder**: Method chaining (`return self`), `build()` method
11//!
12//! **Structural:**
13//! - **Adapter**: Wraps another class, implements interface
14//! - **Decorator**: Wraps same interface, delegates to wrapped object
15//! - **Proxy**: Same interface as subject, controls access
16//!
17//! **Behavioral:**
18//! - **Observer**: Subscribe/notify pattern, listener collections
19//! - **Strategy**: Interface with multiple implementations, injected via constructor
20//! - **Command**: `execute()` method, encapsulates action
21//!
22//! # Language Considerations
23//!
24//! The detector accounts for language-specific idioms:
25//! - **Python**: `__new__` for singleton, no explicit interfaces
26//! - **Rust**: Traits for interfaces, no classes
27//! - **Go**: Implicit interfaces, struct embedding
28//! - **Java/TypeScript**: Standard OOP patterns
29//!
30//! # Example
31//!
32//! ```ignore
33//! use go_brrr::patterns::{detect_patterns, PatternConfig, DesignPattern};
34//!
35//! let result = detect_patterns("./src", None, None)?;
36//! for pattern_match in &result.patterns {
37//!     println!("{}: {} (confidence: {:.1}%)",
38//!         pattern_match.pattern.name(),
39//!         pattern_match.primary_location().map(|l| l.file.display().to_string())
40//!             .unwrap_or_default(),
41//!         pattern_match.confidence * 100.0);
42//! }
43//! ```
44
45use std::collections::{HashMap, HashSet};
46use std::path::{Path, PathBuf};
47use std::sync::Mutex;
48
49use fxhash::FxHashMap;
50use rayon::prelude::*;
51use serde::{Deserialize, Serialize};
52use tracing::{debug, trace};
53
54use crate::ast::{ClassInfo, FunctionInfo, ModuleInfo};
55use crate::callgraph::scanner::{ProjectScanner, ScanConfig};
56use crate::error::Result;
57use crate::lang::LanguageRegistry;
58
59// =============================================================================
60// DESIGN PATTERN TYPES
61// =============================================================================
62
63/// Categories of design patterns.
64#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
65#[serde(rename_all = "snake_case")]
66pub enum PatternCategory {
67    /// Patterns dealing with object creation mechanisms.
68    Creational,
69    /// Patterns dealing with object composition.
70    Structural,
71    /// Patterns dealing with object interaction.
72    Behavioral,
73}
74
75impl std::fmt::Display for PatternCategory {
76    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
77        match self {
78            Self::Creational => write!(f, "Creational"),
79            Self::Structural => write!(f, "Structural"),
80            Self::Behavioral => write!(f, "Behavioral"),
81        }
82    }
83}
84
85/// Detected design pattern with associated metadata.
86#[derive(Debug, Clone, Serialize, Deserialize)]
87#[serde(tag = "type", rename_all = "snake_case")]
88pub enum DesignPattern {
89    /// Singleton pattern: ensures a class has only one instance.
90    ///
91    /// Heuristics:
92    /// - Private/protected constructor
93    /// - Static instance field
94    /// - Static getter method (getInstance, instance, shared, etc.)
95    Singleton {
96        /// The singleton class name.
97        class: String,
98        /// Method name for getting the instance.
99        instance_method: String,
100        /// Instance field name (if detected).
101        instance_field: Option<String>,
102        /// Whether constructor is private.
103        private_constructor: bool,
104    },
105
106    /// Factory pattern: creates objects without specifying exact class.
107    ///
108    /// Heuristics:
109    /// - Method returns interface/abstract type
110    /// - Creates different concrete types based on input
111    /// - Named *Factory or has create* methods
112    Factory {
113        /// The factory class name.
114        class: String,
115        /// Factory method name(s).
116        create_methods: Vec<String>,
117        /// Product types that can be created.
118        products: Vec<String>,
119        /// Whether it's an abstract factory.
120        is_abstract: bool,
121    },
122
123    /// Builder pattern: constructs complex objects step by step.
124    ///
125    /// Heuristics:
126    /// - Method chaining (methods return self/Self)
127    /// - Has build() or create() method
128    /// - Setter-like methods
129    Builder {
130        /// The builder class name.
131        class: String,
132        /// The build/create method name.
133        build_method: String,
134        /// Setter/configuration methods.
135        setters: Vec<String>,
136        /// Target type being built (if detected).
137        target_type: Option<String>,
138    },
139
140    /// Adapter pattern: allows incompatible interfaces to work together.
141    ///
142    /// Heuristics:
143    /// - Wraps another class
144    /// - Implements different interface than wrapped class
145    /// - Delegates to wrapped object
146    Adapter {
147        /// The adapter class name.
148        class: String,
149        /// The adapted (wrapped) class.
150        adaptee: String,
151        /// Interface being adapted to.
152        target_interface: Option<String>,
153    },
154
155    /// Decorator pattern: adds behavior to objects dynamically.
156    ///
157    /// Heuristics:
158    /// - Implements same interface as decorated object
159    /// - Has reference to decorated object
160    /// - Delegates most calls to decorated object
161    Decorator {
162        /// The decorator class name.
163        class: String,
164        /// Base interface/class being decorated.
165        base_interface: String,
166        /// The decorated component field.
167        component_field: Option<String>,
168    },
169
170    /// Proxy pattern: provides a surrogate for another object.
171    ///
172    /// Heuristics:
173    /// - Same interface as real subject
174    /// - Controls access to real subject
175    /// - May add lazy loading, access control, logging
176    Proxy {
177        /// The proxy class name.
178        class: String,
179        /// The real subject class.
180        subject: String,
181        /// Type of proxy (lazy, protection, remote, etc.).
182        proxy_type: Option<String>,
183    },
184
185    /// Observer pattern: defines one-to-many dependency.
186    ///
187    /// Heuristics:
188    /// - Has collection of listeners/observers
189    /// - Methods like add/remove/notify
190    /// - Callback invocation pattern
191    Observer {
192        /// The subject (observable) class.
193        subject: String,
194        /// Observer interface or types.
195        observers: Vec<String>,
196        /// Method for notifying observers.
197        notify_method: String,
198        /// Methods for subscribing/unsubscribing.
199        subscribe_methods: Vec<String>,
200    },
201
202    /// Strategy pattern: defines family of interchangeable algorithms.
203    ///
204    /// Heuristics:
205    /// - Interface with multiple implementations
206    /// - Strategy injected via constructor or setter
207    /// - Context class uses strategy
208    Strategy {
209        /// The strategy interface name.
210        interface: String,
211        /// Concrete strategy implementations.
212        implementations: Vec<String>,
213        /// Context class using the strategy.
214        context: Option<String>,
215    },
216
217    /// Command pattern: encapsulates request as an object.
218    ///
219    /// Heuristics:
220    /// - Has execute() or similar method
221    /// - Encapsulates all info needed for action
222    /// - Often with undo() support
223    Command {
224        /// The command interface name.
225        interface: String,
226        /// Concrete command implementations.
227        commands: Vec<String>,
228        /// Execute method name.
229        execute_method: String,
230        /// Whether undo is supported.
231        has_undo: bool,
232    },
233
234    /// Dependency Injection pattern (modern alternative to many patterns).
235    ///
236    /// Heuristics:
237    /// - Dependencies passed via constructor
238    /// - Interface-typed parameters
239    /// - No internal instantiation of dependencies
240    DependencyInjection {
241        /// Class using dependency injection.
242        class: String,
243        /// Injected dependencies (field name -> type).
244        dependencies: Vec<(String, String)>,
245    },
246
247    /// Repository pattern: abstracts data access logic.
248    ///
249    /// Heuristics:
250    /// - Named *Repository
251    /// - Has CRUD-like methods (find, save, delete)
252    /// - Works with entity types
253    Repository {
254        /// The repository class name.
255        class: String,
256        /// Entity type managed.
257        entity_type: Option<String>,
258        /// CRUD methods present.
259        methods: Vec<String>,
260    },
261}
262
263impl DesignPattern {
264    /// Get the human-readable name of the pattern.
265    #[must_use]
266    pub fn name(&self) -> &'static str {
267        match self {
268            Self::Singleton { .. } => "Singleton",
269            Self::Factory { .. } => "Factory",
270            Self::Builder { .. } => "Builder",
271            Self::Adapter { .. } => "Adapter",
272            Self::Decorator { .. } => "Decorator",
273            Self::Proxy { .. } => "Proxy",
274            Self::Observer { .. } => "Observer",
275            Self::Strategy { .. } => "Strategy",
276            Self::Command { .. } => "Command",
277            Self::DependencyInjection { .. } => "Dependency Injection",
278            Self::Repository { .. } => "Repository",
279        }
280    }
281
282    /// Get the category of this pattern.
283    #[must_use]
284    pub fn category(&self) -> PatternCategory {
285        match self {
286            Self::Singleton { .. } | Self::Factory { .. } | Self::Builder { .. } => {
287                PatternCategory::Creational
288            }
289            Self::Adapter { .. } | Self::Decorator { .. } | Self::Proxy { .. } => {
290                PatternCategory::Structural
291            }
292            Self::Observer { .. }
293            | Self::Strategy { .. }
294            | Self::Command { .. }
295            | Self::DependencyInjection { .. }
296            | Self::Repository { .. } => PatternCategory::Behavioral,
297        }
298    }
299
300    /// Get the primary class/type name associated with this pattern.
301    #[must_use]
302    pub fn primary_class(&self) -> &str {
303        match self {
304            Self::Singleton { class, .. }
305            | Self::Factory { class, .. }
306            | Self::Builder { class, .. }
307            | Self::Adapter { class, .. }
308            | Self::Decorator { class, .. }
309            | Self::Proxy { class, .. }
310            | Self::Observer { subject: class, .. }
311            | Self::DependencyInjection { class, .. }
312            | Self::Repository { class, .. } => class,
313            Self::Strategy { interface, .. } | Self::Command { interface, .. } => interface,
314        }
315    }
316}
317
318/// Source code location for a pattern element.
319#[derive(Debug, Clone, Serialize, Deserialize)]
320pub struct Location {
321    /// File containing the pattern element.
322    pub file: PathBuf,
323    /// Starting line number (1-indexed).
324    pub line: usize,
325    /// Ending line number (1-indexed, optional).
326    pub end_line: Option<usize>,
327    /// Element name (class, method, etc.).
328    pub name: String,
329    /// Element kind (class, method, field, etc.).
330    pub kind: String,
331}
332
333impl Location {
334    /// Create a new location for a class.
335    #[must_use]
336    pub fn for_class(file: PathBuf, class: &ClassInfo) -> Self {
337        Self {
338            file,
339            line: class.line_number,
340            end_line: class.end_line_number,
341            name: class.name.clone(),
342            kind: "class".to_string(),
343        }
344    }
345
346    /// Create a new location for a function.
347    #[must_use]
348    pub fn for_function(file: PathBuf, func: &FunctionInfo) -> Self {
349        Self {
350            file,
351            line: func.line_number,
352            end_line: func.end_line_number,
353            name: func.name.clone(),
354            kind: if func.is_method { "method" } else { "function" }.to_string(),
355        }
356    }
357}
358
359/// A detected pattern match with confidence score.
360#[derive(Debug, Clone, Serialize, Deserialize)]
361pub struct PatternMatch {
362    /// The detected pattern.
363    pub pattern: DesignPattern,
364    /// Confidence score (0.0 to 1.0).
365    /// - 1.0: Very high confidence (explicit implementation)
366    /// - 0.7-0.9: High confidence (strong heuristic match)
367    /// - 0.5-0.7: Medium confidence (partial match)
368    /// - 0.3-0.5: Low confidence (weak signals)
369    pub confidence: f64,
370    /// Locations of pattern elements.
371    pub locations: Vec<Location>,
372    /// Additional notes about the detection.
373    #[serde(skip_serializing_if = "Option::is_none")]
374    pub note: Option<String>,
375    /// Evidence supporting the detection.
376    #[serde(skip_serializing_if = "Vec::is_empty")]
377    pub evidence: Vec<String>,
378}
379
380impl PatternMatch {
381    /// Create a new pattern match.
382    #[must_use]
383    pub fn new(pattern: DesignPattern, confidence: f64) -> Self {
384        Self {
385            pattern,
386            confidence,
387            locations: Vec::new(),
388            note: None,
389            evidence: Vec::new(),
390        }
391    }
392
393    /// Add a location to this match.
394    #[must_use]
395    pub fn with_location(mut self, location: Location) -> Self {
396        self.locations.push(location);
397        self
398    }
399
400    /// Add multiple locations.
401    #[must_use]
402    pub fn with_locations(mut self, locations: Vec<Location>) -> Self {
403        self.locations.extend(locations);
404        self
405    }
406
407    /// Add a note about the detection.
408    #[must_use]
409    pub fn with_note(mut self, note: impl Into<String>) -> Self {
410        self.note = Some(note.into());
411        self
412    }
413
414    /// Add evidence supporting the detection.
415    #[must_use]
416    pub fn with_evidence(mut self, evidence: Vec<String>) -> Self {
417        self.evidence = evidence;
418        self
419    }
420
421    /// Get the primary location (first location if any).
422    #[must_use]
423    pub fn primary_location(&self) -> Option<&Location> {
424        self.locations.first()
425    }
426}
427
428// =============================================================================
429// CONFIGURATION
430// =============================================================================
431
432/// Configuration for pattern detection.
433#[derive(Debug, Clone, Serialize, Deserialize)]
434pub struct PatternConfig {
435    /// Minimum confidence threshold for reporting patterns.
436    /// Default: 0.5 (medium confidence).
437    pub min_confidence: f64,
438
439    /// Patterns to detect (empty = all patterns).
440    pub patterns: Vec<String>,
441
442    /// Language filter (None = auto-detect).
443    pub language: Option<String>,
444
445    /// Maximum file size to process (bytes).
446    pub max_file_size: u64,
447
448    /// Include test files in detection.
449    pub include_tests: bool,
450
451    /// Include generated code in detection.
452    pub include_generated: bool,
453
454    /// Detect modern patterns (DI, Repository).
455    pub detect_modern_patterns: bool,
456
457    /// Language-specific pattern detection.
458    pub language_specific: bool,
459}
460
461impl Default for PatternConfig {
462    fn default() -> Self {
463        Self {
464            min_confidence: 0.5,
465            patterns: Vec::new(),
466            language: None,
467            max_file_size: 1024 * 1024, // 1MB
468            include_tests: false,
469            include_generated: false,
470            detect_modern_patterns: true,
471            language_specific: true,
472        }
473    }
474}
475
476impl PatternConfig {
477    /// Create a configuration for a specific pattern.
478    #[must_use]
479    pub fn for_pattern(pattern: impl Into<String>) -> Self {
480        Self {
481            patterns: vec![pattern.into()],
482            ..Default::default()
483        }
484    }
485
486    /// Set minimum confidence threshold.
487    #[must_use]
488    pub fn with_min_confidence(mut self, confidence: f64) -> Self {
489        self.min_confidence = confidence.clamp(0.0, 1.0);
490        self
491    }
492
493    /// Set language filter.
494    #[must_use]
495    pub fn with_language(mut self, lang: impl Into<String>) -> Self {
496        self.language = Some(lang.into());
497        self
498    }
499
500    /// Enable/disable modern pattern detection.
501    #[must_use]
502    pub fn with_modern_patterns(mut self, enable: bool) -> Self {
503        self.detect_modern_patterns = enable;
504        self
505    }
506}
507
508// =============================================================================
509// STATISTICS
510// =============================================================================
511
512/// Statistics about pattern detection.
513#[derive(Debug, Clone, Default, Serialize, Deserialize)]
514pub struct PatternStats {
515    /// Total files scanned.
516    pub files_scanned: usize,
517    /// Files with patterns detected.
518    pub files_with_patterns: usize,
519    /// Total patterns detected.
520    pub patterns_detected: usize,
521    /// Patterns by category.
522    pub by_category: HashMap<String, usize>,
523    /// Patterns by type.
524    pub by_type: HashMap<String, usize>,
525    /// Average confidence of detections.
526    pub average_confidence: f64,
527    /// Files skipped (size/exclusion).
528    pub files_skipped: usize,
529}
530
531// =============================================================================
532// ANALYSIS RESULT
533// =============================================================================
534
535/// Result of pattern detection analysis.
536#[derive(Debug, Clone, Serialize, Deserialize)]
537pub struct PatternAnalysis {
538    /// Path analyzed.
539    pub path: PathBuf,
540    /// Detected patterns.
541    pub patterns: Vec<PatternMatch>,
542    /// Detection statistics.
543    pub stats: PatternStats,
544    /// Errors encountered (non-fatal).
545    #[serde(skip_serializing_if = "Vec::is_empty")]
546    pub errors: Vec<String>,
547}
548
549impl PatternAnalysis {
550    /// Create a new analysis result.
551    #[must_use]
552    pub fn new(path: PathBuf) -> Self {
553        Self {
554            path,
555            patterns: Vec::new(),
556            stats: PatternStats::default(),
557            errors: Vec::new(),
558        }
559    }
560
561    /// Get patterns of a specific type.
562    #[must_use]
563    pub fn patterns_of_type(&self, pattern_name: &str) -> Vec<&PatternMatch> {
564        self.patterns
565            .iter()
566            .filter(|p| p.pattern.name().eq_ignore_ascii_case(pattern_name))
567            .collect()
568    }
569
570    /// Get patterns in a category.
571    #[must_use]
572    pub fn patterns_in_category(&self, category: PatternCategory) -> Vec<&PatternMatch> {
573        self.patterns
574            .iter()
575            .filter(|p| p.pattern.category() == category)
576            .collect()
577    }
578
579    /// Get high-confidence patterns (>= 0.7).
580    #[must_use]
581    pub fn high_confidence_patterns(&self) -> Vec<&PatternMatch> {
582        self.patterns
583            .iter()
584            .filter(|p| p.confidence >= 0.7)
585            .collect()
586    }
587}
588
589// =============================================================================
590// ERROR TYPE
591// =============================================================================
592
593/// Error during pattern detection.
594#[derive(Debug, Clone, thiserror::Error)]
595pub enum PatternError {
596    /// Failed to scan project files.
597    #[error("Failed to scan project: {0}")]
598    ScanError(String),
599
600    /// Failed to parse a file.
601    #[error("Failed to parse file {path}: {message}")]
602    ParseError { path: PathBuf, message: String },
603
604    /// Invalid configuration.
605    #[error("Invalid configuration: {0}")]
606    ConfigError(String),
607
608    /// IO error.
609    #[error("IO error: {0}")]
610    IoError(String),
611}
612
613impl From<std::io::Error> for PatternError {
614    fn from(e: std::io::Error) -> Self {
615        Self::IoError(e.to_string())
616    }
617}
618
619// =============================================================================
620// PATTERN DETECTOR
621// =============================================================================
622
623/// Design pattern detector using AST analysis.
624pub struct PatternDetector {
625    config: PatternConfig,
626}
627
628impl PatternDetector {
629    /// Create a new pattern detector with the given configuration.
630    #[must_use]
631    pub fn new(config: PatternConfig) -> Self {
632        Self { config }
633    }
634
635    /// Detect patterns in a file or directory.
636    pub fn detect(&self, path: impl AsRef<Path>) -> Result<PatternAnalysis> {
637        let path = path.as_ref();
638        let abs_path = if path.is_absolute() {
639            path.to_path_buf()
640        } else {
641            std::env::current_dir()?.join(path)
642        };
643
644        let mut analysis = PatternAnalysis::new(abs_path.clone());
645
646        if abs_path.is_file() {
647            self.detect_in_file(&abs_path, &mut analysis)?;
648        } else {
649            self.detect_in_directory(&abs_path, &mut analysis)?;
650        }
651
652        // Calculate statistics.
653        self.calculate_stats(&mut analysis);
654
655        // Filter by minimum confidence.
656        analysis.patterns.retain(|p| p.confidence >= self.config.min_confidence);
657
658        // Filter by requested patterns if specified.
659        if !self.config.patterns.is_empty() {
660            let patterns_lower: Vec<String> = self.config.patterns
661                .iter()
662                .map(|p| p.to_lowercase())
663                .collect();
664            analysis.patterns.retain(|p| {
665                patterns_lower.iter().any(|name| {
666                    p.pattern.name().to_lowercase().contains(name)
667                })
668            });
669        }
670
671        // Sort by confidence (highest first).
672        analysis.patterns.sort_by(|a, b| {
673            b.confidence.partial_cmp(&a.confidence).unwrap_or(std::cmp::Ordering::Equal)
674        });
675
676        Ok(analysis)
677    }
678
679    /// Detect patterns in a directory.
680    fn detect_in_directory(&self, path: &Path, analysis: &mut PatternAnalysis) -> Result<()> {
681        let path_str = path
682            .to_str()
683            .ok_or_else(|| crate::error::BrrrError::InvalidArgument("Invalid path encoding".to_string()))?;
684
685        let scanner = ProjectScanner::new(path_str)?;
686
687        let scan_config = if let Some(ref lang) = self.config.language {
688            ScanConfig::for_language(lang)
689        } else {
690            ScanConfig::default()
691        };
692
693        let scan_result = scanner.scan_with_config(&scan_config)?;
694
695        let patterns: Mutex<Vec<PatternMatch>> = Mutex::new(Vec::new());
696        let errors: Mutex<Vec<String>> = Mutex::new(Vec::new());
697        let files_scanned: Mutex<usize> = Mutex::new(0);
698        let files_skipped: Mutex<usize> = Mutex::new(0);
699
700        scan_result.files.par_iter().for_each(|file| {
701            // Skip based on size (if metadata was collected).
702            if !scan_result.metadata.is_empty() {
703                if let Some(file_meta) = scan_result.metadata.iter().find(|m| m.path == *file) {
704                    if file_meta.size > self.config.max_file_size {
705                        let mut skipped = files_skipped.lock().unwrap();
706                        *skipped += 1;
707                        return;
708                    }
709                }
710            }
711
712            // Skip test files if configured.
713            if !self.config.include_tests && is_test_file(file) {
714                let mut skipped = files_skipped.lock().unwrap();
715                *skipped += 1;
716                return;
717            }
718
719            // Skip generated files if configured.
720            if !self.config.include_generated && is_generated_file(file) {
721                let mut skipped = files_skipped.lock().unwrap();
722                *skipped += 1;
723                return;
724            }
725
726            let mut file_analysis = PatternAnalysis::new(file.clone());
727            match self.detect_in_file(file, &mut file_analysis) {
728                Ok(()) => {
729                    let mut p = patterns.lock().unwrap();
730                    p.extend(file_analysis.patterns);
731                    let mut scanned = files_scanned.lock().unwrap();
732                    *scanned += 1;
733                }
734                Err(e) => {
735                    let mut errs = errors.lock().unwrap();
736                    errs.push(format!("{}: {}", file.display(), e));
737                }
738            }
739        });
740
741        analysis.patterns = patterns.into_inner().unwrap();
742        analysis.errors = errors.into_inner().unwrap();
743        analysis.stats.files_scanned = *files_scanned.lock().unwrap();
744        analysis.stats.files_skipped = *files_skipped.lock().unwrap();
745
746        Ok(())
747    }
748
749    /// Detect patterns in a single file.
750    fn detect_in_file(&self, path: &Path, analysis: &mut PatternAnalysis) -> Result<()> {
751        let registry = LanguageRegistry::global();
752        let lang = registry.detect_language(path)
753            .or_else(|| self.config.language.as_ref().and_then(|l| registry.get_by_name(l)));
754
755        if lang.is_none() {
756            trace!("Unsupported language for file: {}", path.display());
757            return Ok(());
758        }
759
760        // Extract module info using AST.
761        let module = match crate::ast::AstExtractor::extract_file(path) {
762            Ok(m) => m,
763            Err(e) => {
764                debug!("Failed to extract AST from {}: {}", path.display(), e);
765                return Ok(());
766            }
767        };
768
769        // Detect patterns in the module.
770        self.detect_in_module(&module, path, analysis);
771
772        Ok(())
773    }
774
775    /// Detect patterns in a parsed module.
776    fn detect_in_module(&self, module: &ModuleInfo, path: &Path, analysis: &mut PatternAnalysis) {
777        let lang = module.language.as_str();
778
779        // Detect Singleton pattern.
780        for class in &module.classes {
781            if let Some(pattern_match) = self.detect_singleton(class, path, lang) {
782                analysis.patterns.push(pattern_match);
783            }
784
785            // Detect Builder pattern.
786            if let Some(pattern_match) = self.detect_builder(class, path, lang) {
787                analysis.patterns.push(pattern_match);
788            }
789
790            // Detect Factory pattern.
791            if let Some(pattern_match) = self.detect_factory(class, &module.classes, path, lang) {
792                analysis.patterns.push(pattern_match);
793            }
794
795            // Detect Observer pattern.
796            if let Some(pattern_match) = self.detect_observer(class, path, lang) {
797                analysis.patterns.push(pattern_match);
798            }
799
800            // Detect Decorator pattern.
801            if let Some(pattern_match) = self.detect_decorator(class, &module.classes, path, lang) {
802                analysis.patterns.push(pattern_match);
803            }
804
805            // Detect Adapter pattern.
806            if let Some(pattern_match) = self.detect_adapter(class, path, lang) {
807                analysis.patterns.push(pattern_match);
808            }
809
810            // Detect Proxy pattern.
811            if let Some(pattern_match) = self.detect_proxy(class, path, lang) {
812                analysis.patterns.push(pattern_match);
813            }
814
815            // Detect Command pattern.
816            if let Some(pattern_match) = self.detect_command(class, &module.classes, path, lang) {
817                analysis.patterns.push(pattern_match);
818            }
819
820            // Detect modern patterns.
821            if self.config.detect_modern_patterns {
822                // Detect Dependency Injection.
823                if let Some(pattern_match) = self.detect_dependency_injection(class, path, lang) {
824                    analysis.patterns.push(pattern_match);
825                }
826
827                // Detect Repository pattern.
828                if let Some(pattern_match) = self.detect_repository(class, path, lang) {
829                    analysis.patterns.push(pattern_match);
830                }
831            }
832        }
833
834        // Detect Strategy pattern (needs interface analysis across classes).
835        let strategies = self.detect_strategy(&module.classes, path, lang);
836        analysis.patterns.extend(strategies);
837    }
838
839    /// Detect Singleton pattern.
840    fn detect_singleton(&self, class: &ClassInfo, path: &Path, lang: &str) -> Option<PatternMatch> {
841        let mut confidence: f64 = 0.0;
842        let mut evidence = Vec::new();
843        let mut instance_method = String::new();
844        let mut instance_field = None;
845        let mut private_constructor = false;
846
847        let class_name_lower = class.name.to_lowercase();
848
849        // Check for singleton naming patterns.
850        if class_name_lower.contains("singleton") {
851            confidence += 0.3;
852            evidence.push("Class name contains 'singleton'".to_string());
853        }
854
855        // Check for private constructor.
856        let private_ctor = class.methods.iter().find(|m| {
857            let name = m.name.to_lowercase();
858            (name == "__init__" || name == "new" || name == class_name_lower ||
859             name == "init" || name == "constructor") &&
860            m.decorators.iter().any(|d| d.contains("private")) ||
861            matches!(lang, "java" | "typescript" | "csharp") &&
862                !m.params.iter().any(|p| !p.contains("self") && !p.contains("this"))
863        });
864
865        // Check for Python __new__ singleton pattern.
866        let python_new = class.methods.iter().find(|m| m.name == "__new__");
867        if python_new.is_some() && lang == "python" {
868            confidence += 0.2;
869            evidence.push("Has __new__ method (Python singleton idiom)".to_string());
870        }
871
872        if private_ctor.is_some() {
873            confidence += 0.2;
874            evidence.push("Has private constructor".to_string());
875            private_constructor = true;
876        }
877
878        // Check for static instance field.
879        let instance_field_names = ["_instance", "instance", "_singleton", "singleton",
880                                     "INSTANCE", "_INSTANCE", "shared", "sharedInstance"];
881        for field in &class.fields {
882            if instance_field_names.iter().any(|n| field.name.eq_ignore_ascii_case(n)) {
883                if field.is_static {
884                    confidence += 0.25;
885                    evidence.push(format!("Has static instance field: {}", field.name));
886                    instance_field = Some(field.name.clone());
887                } else {
888                    confidence += 0.1;
889                    evidence.push(format!("Has instance field (non-static): {}", field.name));
890                    instance_field = Some(field.name.clone());
891                }
892            }
893        }
894
895        // Check for getInstance() or similar method.
896        let getter_patterns = ["getinstance", "get_instance", "instance", "shared",
897                               "sharedinstance", "default", "current", "singleton"];
898        for method in &class.methods {
899            let method_lower = method.name.to_lowercase();
900            if getter_patterns.iter().any(|p| method_lower == *p || method_lower.contains(p)) {
901                // Check if it returns the class type or has static marker.
902                let is_static = method.decorators.iter().any(|d| {
903                    d.contains("static") || d.contains("classmethod")
904                }) || !method.params.iter().any(|p| p.contains("self"));
905
906                if is_static {
907                    confidence += 0.3;
908                    evidence.push(format!("Has static getter method: {}", method.name));
909                    instance_method = method.name.clone();
910                } else {
911                    confidence += 0.1;
912                    evidence.push(format!("Has getter method (non-static): {}", method.name));
913                    if instance_method.is_empty() {
914                        instance_method = method.name.clone();
915                    }
916                }
917            }
918        }
919
920        // Rust-specific: Check for lazy_static or once_cell patterns.
921        if lang == "rust" {
922            for field in &class.fields {
923                let field_type = field.field_type.as_deref().unwrap_or("");
924                if field_type.contains("Lazy") || field_type.contains("OnceCell") ||
925                   field_type.contains("LazyLock") {
926                    confidence += 0.3;
927                    evidence.push(format!("Uses lazy initialization: {}", field_type));
928                }
929            }
930        }
931
932        if confidence >= 0.4 {
933            if instance_method.is_empty() {
934                instance_method = "getInstance".to_string();
935            }
936
937            let pattern = DesignPattern::Singleton {
938                class: class.name.clone(),
939                instance_method,
940                instance_field,
941                private_constructor,
942            };
943
944            let note = if confidence < 0.7 {
945                Some("Partial singleton implementation detected".to_string())
946            } else {
947                None
948            };
949
950            Some(PatternMatch::new(pattern, confidence.min(1.0))
951                .with_location(Location::for_class(path.to_path_buf(), class))
952                .with_note(note.unwrap_or_default())
953                .with_evidence(evidence))
954        } else {
955            None
956        }
957    }
958
959    /// Detect Builder pattern.
960    fn detect_builder(&self, class: &ClassInfo, path: &Path, lang: &str) -> Option<PatternMatch> {
961        let mut confidence: f64 = 0.0;
962        let mut evidence = Vec::new();
963        let mut build_method = String::new();
964        let mut setters = Vec::new();
965        let mut target_type = None;
966
967        let class_name_lower = class.name.to_lowercase();
968
969        // Check for builder naming.
970        if class_name_lower.ends_with("builder") {
971            confidence += 0.35;
972            evidence.push("Class name ends with 'Builder'".to_string());
973            // Infer target type from class name.
974            let target = class.name.strip_suffix("Builder")
975                .or_else(|| class.name.strip_suffix("builder"));
976            if let Some(t) = target {
977                if !t.is_empty() {
978                    target_type = Some(t.to_string());
979                }
980            }
981        }
982
983        // Check for build() method.
984        let build_methods = ["build", "create", "make", "construct", "get_result", "getResult"];
985        for method in &class.methods {
986            let method_lower = method.name.to_lowercase();
987            if build_methods.iter().any(|b| method_lower == *b) {
988                confidence += 0.25;
989                evidence.push(format!("Has build method: {}", method.name));
990                build_method = method.name.clone();
991
992                // Check return type for target type.
993                if let Some(ref ret) = method.return_type {
994                    if !ret.contains("Self") && !ret.contains(&class.name) {
995                        target_type = Some(ret.clone());
996                    }
997                }
998            }
999        }
1000
1001        // Check for fluent setter methods (return Self).
1002        let setter_prefixes = ["with_", "set_", "add_", "set", "with", "add"];
1003        for method in &class.methods {
1004            let method_lower = method.name.to_lowercase();
1005            let is_setter = setter_prefixes.iter().any(|p| method_lower.starts_with(p));
1006
1007            // Check if method returns Self (fluent interface).
1008            let returns_self = method.return_type.as_ref().map(|r| {
1009                r.contains("Self") || r.contains(&class.name) || r == "&mut Self"
1010            }).unwrap_or(false);
1011
1012            if is_setter && returns_self {
1013                confidence += 0.05;
1014                setters.push(method.name.clone());
1015            } else if is_setter {
1016                // Non-fluent setter still counts.
1017                setters.push(method.name.clone());
1018            }
1019        }
1020
1021        // Bonus for multiple fluent setters.
1022        if setters.len() >= 3 {
1023            confidence += 0.15;
1024            evidence.push(format!("Has {} setter/configuration methods", setters.len()));
1025        }
1026
1027        // Check for step builder pattern (interfaces).
1028        if class.bases.iter().any(|b| b.to_lowercase().contains("step")) {
1029            confidence += 0.1;
1030            evidence.push("Implements step builder interface".to_string());
1031        }
1032
1033        if confidence >= 0.4 && !setters.is_empty() {
1034            if build_method.is_empty() {
1035                build_method = "build".to_string();
1036            }
1037
1038            let pattern = DesignPattern::Builder {
1039                class: class.name.clone(),
1040                build_method,
1041                setters,
1042                target_type,
1043            };
1044
1045            Some(PatternMatch::new(pattern, confidence.min(1.0))
1046                .with_location(Location::for_class(path.to_path_buf(), class))
1047                .with_evidence(evidence))
1048        } else {
1049            None
1050        }
1051    }
1052
1053    /// Detect Factory pattern.
1054    fn detect_factory(&self, class: &ClassInfo, all_classes: &[ClassInfo],
1055                      path: &Path, _lang: &str) -> Option<PatternMatch> {
1056        let mut confidence: f64 = 0.0;
1057        let mut evidence = Vec::new();
1058        let mut create_methods = Vec::new();
1059        let mut products = Vec::new();
1060        let is_abstract = class.decorators.iter().any(|d| d.contains("abstract")) ||
1061                          class.bases.iter().any(|b| b.contains("ABC") || b.contains("Abstract"));
1062
1063        let class_name_lower = class.name.to_lowercase();
1064
1065        // Check for factory naming.
1066        if class_name_lower.ends_with("factory") {
1067            confidence += 0.35;
1068            evidence.push("Class name ends with 'Factory'".to_string());
1069        }
1070
1071        // Check for create* methods.
1072        let factory_prefixes = ["create", "make", "build", "new", "get", "produce", "manufacture"];
1073        for method in &class.methods {
1074            let method_lower = method.name.to_lowercase();
1075            if factory_prefixes.iter().any(|p| method_lower.starts_with(p)) {
1076                // Check if return type is abstract or interface.
1077                if let Some(ref ret_type) = method.return_type {
1078                    let ret_lower = ret_type.to_lowercase();
1079                    // Check if return type is a known class.
1080                    let returns_concrete = all_classes.iter()
1081                        .any(|c| c.name.eq_ignore_ascii_case(ret_type));
1082
1083                    if !returns_concrete ||
1084                       ret_lower.contains("interface") ||
1085                       ret_lower.contains("abstract") ||
1086                       ret_lower.contains("protocol") {
1087                        confidence += 0.15;
1088                        create_methods.push(method.name.clone());
1089                        if !products.contains(ret_type) {
1090                            products.push(ret_type.clone());
1091                        }
1092                    }
1093                }
1094
1095                // Even without return type, factory-named methods count.
1096                if !create_methods.contains(&method.name) {
1097                    create_methods.push(method.name.clone());
1098                    confidence += 0.05;
1099                }
1100            }
1101        }
1102
1103        evidence.push(format!("Has {} factory methods", create_methods.len()));
1104
1105        // Check for abstract factory pattern (returns abstract products).
1106        if is_abstract && !create_methods.is_empty() {
1107            confidence += 0.2;
1108            evidence.push("Abstract factory (abstract class with factory methods)".to_string());
1109        }
1110
1111        // Check for parameterized factory.
1112        let has_type_param = class.methods.iter().any(|m| {
1113            m.params.iter().any(|p| {
1114                let p_lower = p.to_lowercase();
1115                p_lower.contains("type") || p_lower.contains("kind") || p_lower.contains("variant")
1116            })
1117        });
1118        if has_type_param {
1119            confidence += 0.1;
1120            evidence.push("Has type/kind parameter (parameterized factory)".to_string());
1121        }
1122
1123        if confidence >= 0.4 && !create_methods.is_empty() {
1124            let pattern = DesignPattern::Factory {
1125                class: class.name.clone(),
1126                create_methods,
1127                products,
1128                is_abstract,
1129            };
1130
1131            Some(PatternMatch::new(pattern, confidence.min(1.0))
1132                .with_location(Location::for_class(path.to_path_buf(), class))
1133                .with_evidence(evidence))
1134        } else {
1135            None
1136        }
1137    }
1138
1139    /// Detect Observer pattern.
1140    fn detect_observer(&self, class: &ClassInfo, path: &Path, _lang: &str) -> Option<PatternMatch> {
1141        let mut confidence: f64 = 0.0;
1142        let mut evidence = Vec::new();
1143        let mut observers = Vec::new();
1144        let mut notify_method = String::new();
1145        let mut subscribe_methods = Vec::new();
1146
1147        let class_name_lower = class.name.to_lowercase();
1148
1149        // Check for observable/subject naming.
1150        if class_name_lower.contains("observable") ||
1151           class_name_lower.contains("subject") ||
1152           class_name_lower.contains("publisher") ||
1153           class_name_lower.contains("emitter") {
1154            confidence += 0.25;
1155            evidence.push("Class name suggests observable pattern".to_string());
1156        }
1157
1158        // Check for listener/observer collection fields.
1159        let collection_patterns = ["listener", "observer", "subscriber", "handler", "callback"];
1160        for field in &class.fields {
1161            let field_lower = field.name.to_lowercase();
1162            let type_lower = field.field_type.as_ref()
1163                .map(|t| t.to_lowercase())
1164                .unwrap_or_default();
1165
1166            if collection_patterns.iter().any(|p| field_lower.contains(p)) ||
1167               (type_lower.contains("list") || type_lower.contains("vec") ||
1168                type_lower.contains("set") || type_lower.contains("array")) &&
1169               collection_patterns.iter().any(|p| type_lower.contains(p)) {
1170                confidence += 0.2;
1171                evidence.push(format!("Has observer collection: {}", field.name));
1172
1173                // Try to extract observer type.
1174                if let Some(ref ft) = field.field_type {
1175                    // Extract type from generic like List<Observer>.
1176                    if let Some(start) = ft.find('<') {
1177                        if let Some(end) = ft.find('>') {
1178                            let inner = &ft[start+1..end];
1179                            if !observers.contains(&inner.to_string()) {
1180                                observers.push(inner.to_string());
1181                            }
1182                        }
1183                    }
1184                }
1185            }
1186        }
1187
1188        // Check for subscribe/unsubscribe methods.
1189        let subscribe_patterns = ["add", "register", "subscribe", "attach", "on"];
1190        let unsubscribe_patterns = ["remove", "unregister", "unsubscribe", "detach", "off"];
1191
1192        for method in &class.methods {
1193            let method_lower = method.name.to_lowercase();
1194
1195            if subscribe_patterns.iter().any(|p| method_lower.contains(p)) &&
1196               collection_patterns.iter().any(|p| method_lower.contains(p)) {
1197                confidence += 0.15;
1198                subscribe_methods.push(method.name.clone());
1199                evidence.push(format!("Has subscribe method: {}", method.name));
1200            }
1201
1202            if unsubscribe_patterns.iter().any(|p| method_lower.contains(p)) &&
1203               collection_patterns.iter().any(|p| method_lower.contains(p)) {
1204                confidence += 0.1;
1205                subscribe_methods.push(method.name.clone());
1206            }
1207        }
1208
1209        // Check for notify method.
1210        let notify_patterns = ["notify", "emit", "fire", "trigger", "dispatch", "publish", "broadcast"];
1211        for method in &class.methods {
1212            let method_lower = method.name.to_lowercase();
1213            if notify_patterns.iter().any(|p| method_lower.contains(p)) {
1214                confidence += 0.2;
1215                evidence.push(format!("Has notify method: {}", method.name));
1216                notify_method = method.name.clone();
1217            }
1218        }
1219
1220        if confidence >= 0.4 && !notify_method.is_empty() {
1221            let pattern = DesignPattern::Observer {
1222                subject: class.name.clone(),
1223                observers,
1224                notify_method,
1225                subscribe_methods,
1226            };
1227
1228            Some(PatternMatch::new(pattern, confidence.min(1.0))
1229                .with_location(Location::for_class(path.to_path_buf(), class))
1230                .with_evidence(evidence))
1231        } else {
1232            None
1233        }
1234    }
1235
1236    /// Detect Decorator pattern.
1237    fn detect_decorator(&self, class: &ClassInfo, all_classes: &[ClassInfo],
1238                        path: &Path, _lang: &str) -> Option<PatternMatch> {
1239        let mut confidence: f64 = 0.0;
1240        let mut evidence = Vec::new();
1241        let mut base_interface = String::new();
1242        let mut component_field = None;
1243
1244        let class_name_lower = class.name.to_lowercase();
1245
1246        // Check for decorator naming.
1247        if class_name_lower.contains("decorator") || class_name_lower.contains("wrapper") {
1248            confidence += 0.25;
1249            evidence.push("Class name suggests decorator pattern".to_string());
1250        }
1251
1252        // Check if class implements an interface and has a field of the same interface type.
1253        for base in &class.bases {
1254            // Check if any field has the same type as a base class.
1255            for field in &class.fields {
1256                if let Some(ref field_type) = field.field_type {
1257                    if field_type == base ||
1258                       field_type.contains(base) ||
1259                       base.contains(field_type) {
1260                        confidence += 0.35;
1261                        evidence.push(format!("Has wrapped component field: {} of type {}",
1262                                              field.name, field_type));
1263                        base_interface = base.clone();
1264                        component_field = Some(field.name.clone());
1265                        break;
1266                    }
1267                }
1268            }
1269
1270            // Check constructor parameters for component injection.
1271            for method in &class.methods {
1272                let is_ctor = method.name == "__init__" || method.name == "new" ||
1273                             method.name.to_lowercase() == class_name_lower ||
1274                             method.name == "constructor";
1275                if is_ctor {
1276                    for param in &method.params {
1277                        if param.contains(base) {
1278                            confidence += 0.2;
1279                            evidence.push(format!("Constructor accepts base type: {}", base));
1280                        }
1281                    }
1282                }
1283            }
1284        }
1285
1286        // Check for delegation (methods that call same method on wrapped object).
1287        // This is a heuristic based on method matching.
1288        if !base_interface.is_empty() {
1289            if let Some(base_class) = all_classes.iter().find(|c| c.name == base_interface) {
1290                let base_method_names: HashSet<_> = base_class.methods.iter()
1291                    .map(|m| m.name.as_str())
1292                    .collect();
1293                let class_method_names: HashSet<_> = class.methods.iter()
1294                    .map(|m| m.name.as_str())
1295                    .collect();
1296
1297                let shared_methods: usize = base_method_names.intersection(&class_method_names).count();
1298                if shared_methods >= 2 {
1299                    confidence += 0.1;
1300                    evidence.push(format!("Shares {} methods with base interface", shared_methods));
1301                }
1302            }
1303        }
1304
1305        if confidence >= 0.4 && !base_interface.is_empty() {
1306            let pattern = DesignPattern::Decorator {
1307                class: class.name.clone(),
1308                base_interface,
1309                component_field,
1310            };
1311
1312            Some(PatternMatch::new(pattern, confidence.min(1.0))
1313                .with_location(Location::for_class(path.to_path_buf(), class))
1314                .with_evidence(evidence))
1315        } else {
1316            None
1317        }
1318    }
1319
1320    /// Detect Adapter pattern.
1321    fn detect_adapter(&self, class: &ClassInfo, path: &Path, _lang: &str) -> Option<PatternMatch> {
1322        let mut confidence: f64 = 0.0;
1323        let mut evidence = Vec::new();
1324        let mut adaptee = String::new();
1325        let mut target_interface = None;
1326
1327        let class_name_lower = class.name.to_lowercase();
1328
1329        // Check for adapter naming.
1330        if class_name_lower.contains("adapter") {
1331            confidence += 0.3;
1332            evidence.push("Class name contains 'Adapter'".to_string());
1333        }
1334
1335        // Check for wrapping a different type than implemented interface.
1336        // Adapter: implements Interface A, wraps Class B (different from A).
1337        for field in &class.fields {
1338            if let Some(ref field_type) = field.field_type {
1339                let field_type_lower = field_type.to_lowercase();
1340
1341                // Check if field type is different from all base classes.
1342                let is_different_from_bases = class.bases.iter()
1343                    .all(|b| !field_type.contains(b) && !b.contains(field_type));
1344
1345                if is_different_from_bases && !class.bases.is_empty() {
1346                    confidence += 0.25;
1347                    evidence.push(format!("Wraps {} while implementing different interface", field_type));
1348                    adaptee = field_type.clone();
1349                    target_interface = class.bases.first().cloned();
1350                }
1351
1352                // Common adaptee naming patterns.
1353                if field_type_lower.contains("adaptee") ||
1354                   field.name.to_lowercase().contains("adaptee") ||
1355                   field.name.to_lowercase().contains("wrapped") {
1356                    confidence += 0.2;
1357                    evidence.push(format!("Has adaptee field: {}", field.name));
1358                    adaptee = field_type.clone();
1359                }
1360            }
1361        }
1362
1363        if confidence >= 0.4 && !adaptee.is_empty() {
1364            let pattern = DesignPattern::Adapter {
1365                class: class.name.clone(),
1366                adaptee,
1367                target_interface,
1368            };
1369
1370            Some(PatternMatch::new(pattern, confidence.min(1.0))
1371                .with_location(Location::for_class(path.to_path_buf(), class))
1372                .with_evidence(evidence))
1373        } else {
1374            None
1375        }
1376    }
1377
1378    /// Detect Proxy pattern.
1379    fn detect_proxy(&self, class: &ClassInfo, path: &Path, _lang: &str) -> Option<PatternMatch> {
1380        let mut confidence: f64 = 0.0;
1381        let mut evidence = Vec::new();
1382        let mut subject = String::new();
1383        let mut proxy_type = None;
1384
1385        let class_name_lower = class.name.to_lowercase();
1386
1387        // Check for proxy naming.
1388        if class_name_lower.contains("proxy") {
1389            confidence += 0.3;
1390            evidence.push("Class name contains 'Proxy'".to_string());
1391        }
1392
1393        // Detect specific proxy types.
1394        if class_name_lower.contains("lazy") {
1395            proxy_type = Some("lazy".to_string());
1396            confidence += 0.1;
1397        } else if class_name_lower.contains("remote") {
1398            proxy_type = Some("remote".to_string());
1399            confidence += 0.1;
1400        } else if class_name_lower.contains("protection") || class_name_lower.contains("secure") {
1401            proxy_type = Some("protection".to_string());
1402            confidence += 0.1;
1403        } else if class_name_lower.contains("cache") || class_name_lower.contains("cached") {
1404            proxy_type = Some("caching".to_string());
1405            confidence += 0.1;
1406        } else if class_name_lower.contains("virtual") {
1407            proxy_type = Some("virtual".to_string());
1408            confidence += 0.1;
1409        }
1410
1411        // Check for subject field of same type as base interface.
1412        for field in &class.fields {
1413            if let Some(ref field_type) = field.field_type {
1414                // Check if field type matches any base class.
1415                for base in &class.bases {
1416                    if field_type == base || field_type.contains(base) {
1417                        confidence += 0.3;
1418                        evidence.push(format!("Has subject field: {} of type {}", field.name, field_type));
1419                        subject = field_type.clone();
1420                        break;
1421                    }
1422                }
1423            }
1424
1425            // Check for subject/real naming.
1426            let field_lower = field.name.to_lowercase();
1427            if field_lower.contains("subject") || field_lower.contains("real") ||
1428               field_lower.contains("target") || field_lower.contains("wrapped") {
1429                confidence += 0.15;
1430                if let Some(ref ft) = field.field_type {
1431                    subject = ft.clone();
1432                }
1433            }
1434        }
1435
1436        // Check for lazy loading indicators.
1437        let has_lazy = class.methods.iter().any(|m| {
1438            let name_lower = m.name.to_lowercase();
1439            name_lower.contains("lazy") || name_lower.contains("ensure") ||
1440            name_lower.contains("initialize") || name_lower.contains("load")
1441        });
1442        if has_lazy {
1443            confidence += 0.1;
1444            if proxy_type.is_none() {
1445                proxy_type = Some("lazy".to_string());
1446            }
1447        }
1448
1449        // Check for access control methods.
1450        let has_access_control = class.methods.iter().any(|m| {
1451            let name_lower = m.name.to_lowercase();
1452            name_lower.contains("check") || name_lower.contains("authorize") ||
1453            name_lower.contains("validate") || name_lower.contains("permission")
1454        });
1455        if has_access_control {
1456            confidence += 0.1;
1457            if proxy_type.is_none() {
1458                proxy_type = Some("protection".to_string());
1459            }
1460        }
1461
1462        if confidence >= 0.4 && !subject.is_empty() {
1463            let pattern = DesignPattern::Proxy {
1464                class: class.name.clone(),
1465                subject,
1466                proxy_type,
1467            };
1468
1469            Some(PatternMatch::new(pattern, confidence.min(1.0))
1470                .with_location(Location::for_class(path.to_path_buf(), class))
1471                .with_evidence(evidence))
1472        } else {
1473            None
1474        }
1475    }
1476
1477    /// Detect Command pattern.
1478    fn detect_command(&self, class: &ClassInfo, all_classes: &[ClassInfo],
1479                      path: &Path, _lang: &str) -> Option<PatternMatch> {
1480        let mut confidence: f64 = 0.0;
1481        let mut evidence = Vec::new();
1482        let mut execute_method = String::new();
1483        let mut has_undo = false;
1484
1485        let class_name_lower = class.name.to_lowercase();
1486
1487        // Check for command naming.
1488        if class_name_lower.ends_with("command") || class_name_lower.ends_with("action") ||
1489           class_name_lower.ends_with("handler") {
1490            confidence += 0.25;
1491            evidence.push("Class name suggests command pattern".to_string());
1492        }
1493
1494        // Check for execute method.
1495        let execute_patterns = ["execute", "run", "invoke", "call", "handle", "perform", "do"];
1496        for method in &class.methods {
1497            let method_lower = method.name.to_lowercase();
1498            if execute_patterns.iter().any(|p| method_lower == *p || method_lower.starts_with(p)) {
1499                confidence += 0.25;
1500                evidence.push(format!("Has execute method: {}", method.name));
1501                execute_method = method.name.clone();
1502            }
1503
1504            // Check for undo support.
1505            if method_lower == "undo" || method_lower == "revert" || method_lower == "rollback" {
1506                has_undo = true;
1507                confidence += 0.15;
1508                evidence.push(format!("Has undo method: {}", method.name));
1509            }
1510        }
1511
1512        // Check for receiver field (command encapsulates receiver).
1513        let has_receiver = class.fields.iter().any(|f| {
1514            let name_lower = f.name.to_lowercase();
1515            name_lower.contains("receiver") || name_lower.contains("target") ||
1516            name_lower.contains("handler")
1517        });
1518        if has_receiver {
1519            confidence += 0.1;
1520            evidence.push("Has receiver field".to_string());
1521        }
1522
1523        // Check if class implements Command interface.
1524        let implements_command = class.bases.iter().any(|b| {
1525            let b_lower = b.to_lowercase();
1526            b_lower.contains("command") || b_lower.contains("handler") ||
1527            b_lower.contains("action")
1528        });
1529        if implements_command {
1530            confidence += 0.2;
1531            evidence.push(format!("Implements command interface: {:?}", class.bases));
1532        }
1533
1534        if confidence >= 0.4 && !execute_method.is_empty() {
1535            // Try to find command interface and other implementations.
1536            let interface = class.bases.first().cloned().unwrap_or_else(|| "Command".to_string());
1537            let commands: Vec<String> = all_classes.iter()
1538                .filter(|c| c.name != class.name && c.bases.contains(&interface))
1539                .map(|c| c.name.clone())
1540                .collect();
1541
1542            let pattern = DesignPattern::Command {
1543                interface,
1544                commands,
1545                execute_method,
1546                has_undo,
1547            };
1548
1549            Some(PatternMatch::new(pattern, confidence.min(1.0))
1550                .with_location(Location::for_class(path.to_path_buf(), class))
1551                .with_evidence(evidence))
1552        } else {
1553            None
1554        }
1555    }
1556
1557    /// Detect Strategy pattern (multiple implementations of an interface).
1558    fn detect_strategy(&self, classes: &[ClassInfo], path: &Path, _lang: &str) -> Vec<PatternMatch> {
1559        let mut patterns = Vec::new();
1560
1561        // Group classes by base interface.
1562        let mut interface_impls: FxHashMap<String, Vec<&ClassInfo>> = FxHashMap::default();
1563        for class in classes {
1564            for base in &class.bases {
1565                interface_impls.entry(base.clone())
1566                    .or_default()
1567                    .push(class);
1568            }
1569        }
1570
1571        // Find interfaces with multiple implementations.
1572        for (interface, implementations) in interface_impls {
1573            if implementations.len() < 2 {
1574                continue;
1575            }
1576
1577            let mut confidence: f64 = 0.0;
1578            let mut evidence = Vec::new();
1579
1580            // Check if interface name suggests strategy.
1581            let interface_lower = interface.to_lowercase();
1582            if interface_lower.contains("strategy") || interface_lower.contains("policy") ||
1583               interface_lower.contains("algorithm") || interface_lower.contains("handler") {
1584                confidence += 0.3;
1585                evidence.push("Interface name suggests strategy pattern".to_string());
1586            }
1587
1588            // Multiple implementations is a strong signal.
1589            confidence += 0.1 * (implementations.len().min(5) as f64);
1590            evidence.push(format!("{} implementations found", implementations.len()));
1591
1592            // Check if implementations have similar method signatures.
1593            if implementations.len() >= 2 {
1594                let first_methods: HashSet<_> = implementations[0].methods.iter()
1595                    .map(|m| m.name.as_str())
1596                    .collect();
1597                let all_have_same_methods = implementations.iter().skip(1).all(|impl_class| {
1598                    let impl_methods: HashSet<_> = impl_class.methods.iter()
1599                        .map(|m| m.name.as_str())
1600                        .collect();
1601                    !first_methods.is_disjoint(&impl_methods)
1602                });
1603                if all_have_same_methods {
1604                    confidence += 0.2;
1605                    evidence.push("Implementations share method signatures".to_string());
1606                }
1607            }
1608
1609            // Check for context class that uses the interface.
1610            let context = classes.iter().find(|c| {
1611                c.fields.iter().any(|f| {
1612                    f.field_type.as_ref().map(|t| t == &interface).unwrap_or(false)
1613                }) ||
1614                c.methods.iter().any(|m| {
1615                    m.params.iter().any(|p| p.contains(&interface))
1616                })
1617            });
1618
1619            if context.is_some() {
1620                confidence += 0.15;
1621                evidence.push("Found context class using strategy".to_string());
1622            }
1623
1624            if confidence >= 0.4 {
1625                let impl_names: Vec<String> = implementations.iter()
1626                    .map(|c| c.name.clone())
1627                    .collect();
1628
1629                let pattern = DesignPattern::Strategy {
1630                    interface: interface.clone(),
1631                    implementations: impl_names,
1632                    context: context.map(|c| c.name.clone()),
1633                };
1634
1635                let mut locations = Vec::new();
1636                for impl_class in &implementations {
1637                    locations.push(Location::for_class(path.to_path_buf(), impl_class));
1638                }
1639
1640                patterns.push(PatternMatch::new(pattern, confidence.min(1.0))
1641                    .with_locations(locations)
1642                    .with_evidence(evidence));
1643            }
1644        }
1645
1646        patterns
1647    }
1648
1649    /// Detect Dependency Injection pattern.
1650    fn detect_dependency_injection(&self, class: &ClassInfo, path: &Path, _lang: &str) -> Option<PatternMatch> {
1651        let mut confidence: f64 = 0.0;
1652        let mut evidence = Vec::new();
1653        let mut dependencies: Vec<(String, String)> = Vec::new();
1654
1655        // Find constructor.
1656        let constructor = class.methods.iter().find(|m| {
1657            let name = m.name.to_lowercase();
1658            name == "__init__" || name == "new" || name == "constructor" ||
1659            name == class.name.to_lowercase()
1660        });
1661
1662        if let Some(ctor) = constructor {
1663            // Check for interface-typed parameters (excluding self/this).
1664            let meaningful_params: Vec<_> = ctor.params.iter()
1665                .filter(|p| !p.contains("self") && !p.contains("this"))
1666                .collect();
1667
1668            for param in &meaningful_params {
1669                // Try to extract type from parameter.
1670                // Common formats: "name: Type", "Type name", "name"
1671                let parts: Vec<&str> = param.split(':').collect();
1672                let (name, type_hint) = if parts.len() >= 2 {
1673                    (parts[0].trim().to_string(), Some(parts[1].trim().to_string()))
1674                } else {
1675                    (param.trim().to_string(), None)
1676                };
1677
1678                if let Some(ref t) = type_hint {
1679                    // Check if type looks like an interface (not primitive).
1680                    let t_lower = t.to_lowercase();
1681                    let is_primitive = ["int", "str", "string", "bool", "boolean", "float",
1682                                        "double", "void", "none", "null"].iter()
1683                        .any(|p| t_lower == *p);
1684
1685                    if !is_primitive && !t.starts_with(char::is_lowercase) {
1686                        dependencies.push((name, t.clone()));
1687                        confidence += 0.15;
1688                    }
1689                }
1690            }
1691        }
1692
1693        // Check for DI decorators/annotations.
1694        let di_decorators = ["inject", "autowired", "autowire", "dependency", "injectable"];
1695        for decorator in &class.decorators {
1696            let dec_lower = decorator.to_lowercase();
1697            if di_decorators.iter().any(|d| dec_lower.contains(d)) {
1698                confidence += 0.25;
1699                evidence.push(format!("Has DI decorator: {}", decorator));
1700            }
1701        }
1702
1703        // Check for method injection via setters.
1704        let setter_injection = class.methods.iter().filter(|m| {
1705            let name_lower = m.name.to_lowercase();
1706            (name_lower.starts_with("set_") || name_lower.starts_with("inject_")) &&
1707            m.params.len() >= 2 // self + injected dependency
1708        }).count();
1709
1710        if setter_injection > 0 {
1711            confidence += 0.1 * setter_injection as f64;
1712            evidence.push(format!("{} setter injection methods", setter_injection));
1713        }
1714
1715        // Check for field-based injection.
1716        for field in &class.fields {
1717            for annotation in &field.annotations {
1718                let ann_lower = annotation.to_lowercase();
1719                if di_decorators.iter().any(|d| ann_lower.contains(d)) {
1720                    confidence += 0.2;
1721                    if let Some(ref ft) = field.field_type {
1722                        dependencies.push((field.name.clone(), ft.clone()));
1723                    }
1724                }
1725            }
1726        }
1727
1728        evidence.push(format!("{} dependencies detected", dependencies.len()));
1729
1730        if confidence >= 0.4 && !dependencies.is_empty() {
1731            let pattern = DesignPattern::DependencyInjection {
1732                class: class.name.clone(),
1733                dependencies,
1734            };
1735
1736            Some(PatternMatch::new(pattern, confidence.min(1.0))
1737                .with_location(Location::for_class(path.to_path_buf(), class))
1738                .with_evidence(evidence))
1739        } else {
1740            None
1741        }
1742    }
1743
1744    /// Detect Repository pattern.
1745    fn detect_repository(&self, class: &ClassInfo, path: &Path, _lang: &str) -> Option<PatternMatch> {
1746        let mut confidence: f64 = 0.0;
1747        let mut evidence = Vec::new();
1748        let mut entity_type = None;
1749        let mut crud_methods = Vec::new();
1750
1751        let class_name_lower = class.name.to_lowercase();
1752
1753        // Check for repository naming.
1754        if class_name_lower.ends_with("repository") || class_name_lower.ends_with("repo") {
1755            confidence += 0.35;
1756            evidence.push("Class name suggests repository pattern".to_string());
1757
1758            // Try to extract entity type from name.
1759            let entity = class_name_lower
1760                .strip_suffix("repository")
1761                .or_else(|| class_name_lower.strip_suffix("repo"));
1762            if let Some(e) = entity {
1763                if !e.is_empty() {
1764                    // Capitalize first letter.
1765                    let mut chars = e.chars();
1766                    if let Some(first) = chars.next() {
1767                        entity_type = Some(format!("{}{}", first.to_uppercase(), chars.collect::<String>()));
1768                    }
1769                }
1770            }
1771        }
1772
1773        // Check for DAO naming.
1774        if class_name_lower.ends_with("dao") {
1775            confidence += 0.25;
1776            evidence.push("Class name ends with 'DAO'".to_string());
1777        }
1778
1779        // Check for CRUD methods.
1780        let crud_patterns = [
1781            ("find", "read"), ("get", "read"), ("fetch", "read"), ("load", "read"),
1782            ("save", "write"), ("create", "write"), ("add", "write"), ("insert", "write"),
1783            ("update", "write"), ("modify", "write"),
1784            ("delete", "write"), ("remove", "write"),
1785            ("list", "read"), ("all", "read"), ("count", "read"),
1786        ];
1787
1788        for method in &class.methods {
1789            let method_lower = method.name.to_lowercase();
1790            for (pattern, _op_type) in &crud_patterns {
1791                if method_lower.contains(pattern) {
1792                    crud_methods.push(method.name.clone());
1793                    confidence += 0.05;
1794                    break;
1795                }
1796            }
1797        }
1798
1799        // Higher confidence for having multiple CRUD operations.
1800        let unique_operations: HashSet<_> = crud_methods.iter()
1801            .filter_map(|m| {
1802                let m_lower = m.to_lowercase();
1803                if m_lower.contains("find") || m_lower.contains("get") || m_lower.contains("load") {
1804                    Some("read")
1805                } else if m_lower.contains("save") || m_lower.contains("create") || m_lower.contains("insert") {
1806                    Some("create")
1807                } else if m_lower.contains("update") || m_lower.contains("modify") {
1808                    Some("update")
1809                } else if m_lower.contains("delete") || m_lower.contains("remove") {
1810                    Some("delete")
1811                } else {
1812                    None
1813                }
1814            })
1815            .collect();
1816
1817        if unique_operations.len() >= 3 {
1818            confidence += 0.2;
1819            evidence.push(format!("Has {} CRUD operations: {:?}", unique_operations.len(), unique_operations));
1820        }
1821
1822        // Check for entity type in methods or fields.
1823        if entity_type.is_none() {
1824            for method in &class.methods {
1825                if let Some(ref ret) = method.return_type {
1826                    let ret_lower = ret.to_lowercase();
1827                    if !ret_lower.contains("list") && !ret_lower.contains("vec") &&
1828                       !ret_lower.contains("option") && ret.starts_with(char::is_uppercase) {
1829                        entity_type = Some(ret.clone());
1830                        break;
1831                    }
1832                }
1833            }
1834        }
1835
1836        if confidence >= 0.4 && !crud_methods.is_empty() {
1837            let pattern = DesignPattern::Repository {
1838                class: class.name.clone(),
1839                entity_type,
1840                methods: crud_methods,
1841            };
1842
1843            Some(PatternMatch::new(pattern, confidence.min(1.0))
1844                .with_location(Location::for_class(path.to_path_buf(), class))
1845                .with_evidence(evidence))
1846        } else {
1847            None
1848        }
1849    }
1850
1851    /// Calculate and update statistics.
1852    fn calculate_stats(&self, analysis: &mut PatternAnalysis) {
1853        let mut by_category: HashMap<String, usize> = HashMap::new();
1854        let mut by_type: HashMap<String, usize> = HashMap::new();
1855        let mut total_confidence = 0.0;
1856        let mut files_with_patterns: HashSet<&Path> = HashSet::new();
1857
1858        for pattern_match in &analysis.patterns {
1859            let category = pattern_match.pattern.category().to_string();
1860            *by_category.entry(category).or_insert(0) += 1;
1861
1862            let pattern_name = pattern_match.pattern.name().to_string();
1863            *by_type.entry(pattern_name).or_insert(0) += 1;
1864
1865            total_confidence += pattern_match.confidence;
1866
1867            for loc in &pattern_match.locations {
1868                files_with_patterns.insert(&loc.file);
1869            }
1870        }
1871
1872        analysis.stats.patterns_detected = analysis.patterns.len();
1873        analysis.stats.files_with_patterns = files_with_patterns.len();
1874        analysis.stats.by_category = by_category;
1875        analysis.stats.by_type = by_type;
1876        analysis.stats.average_confidence = if analysis.patterns.is_empty() {
1877            0.0
1878        } else {
1879            total_confidence / analysis.patterns.len() as f64
1880        };
1881    }
1882}
1883
1884// =============================================================================
1885// HELPER FUNCTIONS
1886// =============================================================================
1887
1888/// Check if a file is a test file based on path patterns.
1889fn is_test_file(path: &Path) -> bool {
1890    let path_str = path.to_string_lossy().to_lowercase();
1891    path_str.contains("/test/") ||
1892    path_str.contains("/tests/") ||
1893    path_str.contains("_test.") ||
1894    path_str.contains("_spec.") ||
1895    path_str.contains(".test.") ||
1896    path_str.contains(".spec.") ||
1897    path_str.contains("/test_") ||
1898    path_str.contains("/__tests__/")
1899}
1900
1901/// Check if a file is generated based on path patterns.
1902fn is_generated_file(path: &Path) -> bool {
1903    let path_str = path.to_string_lossy().to_lowercase();
1904    path_str.contains("/generated/") ||
1905    path_str.contains("/gen/") ||
1906    path_str.contains(".generated.") ||
1907    path_str.contains(".g.") ||
1908    path_str.contains("_generated") ||
1909    path_str.contains("/build/") ||
1910    path_str.contains("/dist/") ||
1911    path_str.contains("/node_modules/") ||
1912    path_str.contains("__pycache__")
1913}
1914
1915// =============================================================================
1916// PUBLIC API
1917// =============================================================================
1918
1919/// Detect design patterns in a file or directory.
1920///
1921/// # Arguments
1922///
1923/// * `path` - Path to file or directory to analyze
1924/// * `pattern_filter` - Optional pattern name to filter (e.g., "singleton", "factory")
1925/// * `config` - Optional configuration (uses defaults if None)
1926///
1927/// # Returns
1928///
1929/// Analysis result with detected patterns and statistics.
1930///
1931/// # Example
1932///
1933/// ```ignore
1934/// use go_brrr::patterns::detect_patterns;
1935///
1936/// // Detect all patterns with default config
1937/// let result = detect_patterns("./src", None, None)?;
1938///
1939/// // Detect only singletons with high confidence
1940/// let config = PatternConfig::default().with_min_confidence(0.7);
1941/// let singletons = detect_patterns("./src", Some("singleton"), Some(config))?;
1942/// ```
1943pub fn detect_patterns(
1944    path: impl AsRef<Path>,
1945    pattern_filter: Option<&str>,
1946    config: Option<PatternConfig>,
1947) -> Result<PatternAnalysis> {
1948    let mut config = config.unwrap_or_default();
1949    if let Some(filter) = pattern_filter {
1950        config.patterns = vec![filter.to_string()];
1951    }
1952
1953    let detector = PatternDetector::new(config);
1954    detector.detect(path)
1955}
1956
1957/// Format a pattern analysis as a human-readable summary.
1958pub fn format_pattern_summary(analysis: &PatternAnalysis) -> String {
1959    let mut output = String::new();
1960
1961    output.push_str(&format!("Design Pattern Analysis: {}\n", analysis.path.display()));
1962    output.push_str(&format!("Files scanned: {}\n", analysis.stats.files_scanned));
1963    output.push_str(&format!("Files with patterns: {}\n", analysis.stats.files_with_patterns));
1964    output.push_str(&format!("Total patterns detected: {}\n", analysis.stats.patterns_detected));
1965    output.push_str(&format!("Average confidence: {:.1}%\n\n", analysis.stats.average_confidence * 100.0));
1966
1967    if !analysis.stats.by_category.is_empty() {
1968        output.push_str("By Category:\n");
1969        for (category, count) in &analysis.stats.by_category {
1970            output.push_str(&format!("  {}: {}\n", category, count));
1971        }
1972        output.push('\n');
1973    }
1974
1975    if !analysis.stats.by_type.is_empty() {
1976        output.push_str("By Pattern Type:\n");
1977        for (pattern_type, count) in &analysis.stats.by_type {
1978            output.push_str(&format!("  {}: {}\n", pattern_type, count));
1979        }
1980        output.push('\n');
1981    }
1982
1983    output.push_str("Detected Patterns:\n");
1984    for pattern_match in &analysis.patterns {
1985        output.push_str(&format!("\n  {} (confidence: {:.1}%)\n",
1986            pattern_match.pattern.name(),
1987            pattern_match.confidence * 100.0));
1988
1989        output.push_str(&format!("    Category: {}\n", pattern_match.pattern.category()));
1990        output.push_str(&format!("    Primary class: {}\n", pattern_match.pattern.primary_class()));
1991
1992        if let Some(loc) = pattern_match.primary_location() {
1993            output.push_str(&format!("    Location: {}:{}\n", loc.file.display(), loc.line));
1994        }
1995
1996        if let Some(ref note) = pattern_match.note {
1997            if !note.is_empty() {
1998                output.push_str(&format!("    Note: {}\n", note));
1999            }
2000        }
2001
2002        if !pattern_match.evidence.is_empty() {
2003            output.push_str("    Evidence:\n");
2004            for ev in &pattern_match.evidence {
2005                output.push_str(&format!("      - {}\n", ev));
2006            }
2007        }
2008    }
2009
2010    if !analysis.errors.is_empty() {
2011        output.push_str("\nErrors:\n");
2012        for error in &analysis.errors {
2013            output.push_str(&format!("  - {}\n", error));
2014        }
2015    }
2016
2017    output
2018}
2019
2020// =============================================================================
2021// TESTS
2022// =============================================================================
2023
2024#[cfg(test)]
2025mod tests {
2026    use super::*;
2027    use crate::ast::FieldInfo;
2028
2029    #[test]
2030    fn test_pattern_category_display() {
2031        assert_eq!(PatternCategory::Creational.to_string(), "Creational");
2032        assert_eq!(PatternCategory::Structural.to_string(), "Structural");
2033        assert_eq!(PatternCategory::Behavioral.to_string(), "Behavioral");
2034    }
2035
2036    #[test]
2037    fn test_design_pattern_name() {
2038        let singleton = DesignPattern::Singleton {
2039            class: "Config".to_string(),
2040            instance_method: "getInstance".to_string(),
2041            instance_field: None,
2042            private_constructor: true,
2043        };
2044        assert_eq!(singleton.name(), "Singleton");
2045        assert_eq!(singleton.category(), PatternCategory::Creational);
2046        assert_eq!(singleton.primary_class(), "Config");
2047    }
2048
2049    #[test]
2050    fn test_pattern_match_builder() {
2051        let pattern = DesignPattern::Builder {
2052            class: "RequestBuilder".to_string(),
2053            build_method: "build".to_string(),
2054            setters: vec!["with_url".to_string(), "with_headers".to_string()],
2055            target_type: Some("Request".to_string()),
2056        };
2057
2058        let match_ = PatternMatch::new(pattern, 0.85)
2059            .with_location(Location {
2060                file: PathBuf::from("/src/builder.rs"),
2061                line: 10,
2062                end_line: Some(50),
2063                name: "RequestBuilder".to_string(),
2064                kind: "class".to_string(),
2065            })
2066            .with_evidence(vec!["Has build method".to_string()]);
2067
2068        assert_eq!(match_.confidence, 0.85);
2069        assert!(match_.primary_location().is_some());
2070        assert_eq!(match_.evidence.len(), 1);
2071    }
2072
2073    #[test]
2074    fn test_pattern_config_defaults() {
2075        let config = PatternConfig::default();
2076        assert_eq!(config.min_confidence, 0.5);
2077        assert!(config.patterns.is_empty());
2078        assert!(config.language.is_none());
2079        assert!(config.detect_modern_patterns);
2080    }
2081
2082    #[test]
2083    fn test_pattern_config_builder() {
2084        let config = PatternConfig::for_pattern("singleton")
2085            .with_min_confidence(0.8)
2086            .with_language("rust")
2087            .with_modern_patterns(false);
2088
2089        assert_eq!(config.patterns, vec!["singleton"]);
2090        assert_eq!(config.min_confidence, 0.8);
2091        assert_eq!(config.language, Some("rust".to_string()));
2092        assert!(!config.detect_modern_patterns);
2093    }
2094
2095    #[test]
2096    fn test_is_test_file() {
2097        assert!(is_test_file(Path::new("/src/tests/test_foo.py")));
2098        assert!(is_test_file(Path::new("/src/foo_test.go")));
2099        assert!(is_test_file(Path::new("/src/foo.spec.ts")));
2100        assert!(is_test_file(Path::new("/src/__tests__/foo.js")));
2101        assert!(!is_test_file(Path::new("/src/foo.py")));
2102    }
2103
2104    #[test]
2105    fn test_is_generated_file() {
2106        assert!(is_generated_file(Path::new("/generated/foo.rs")));
2107        assert!(is_generated_file(Path::new("/src/foo.generated.ts")));
2108        assert!(is_generated_file(Path::new("/node_modules/foo/index.js")));
2109        assert!(is_generated_file(Path::new("/__pycache__/foo.pyc")));
2110        assert!(!is_generated_file(Path::new("/src/foo.rs")));
2111    }
2112
2113    #[test]
2114    fn test_pattern_analysis_filters() {
2115        let mut analysis = PatternAnalysis::new(PathBuf::from("."));
2116
2117        analysis.patterns.push(PatternMatch::new(
2118            DesignPattern::Singleton {
2119                class: "Config".to_string(),
2120                instance_method: "getInstance".to_string(),
2121                instance_field: None,
2122                private_constructor: true,
2123            },
2124            0.9,
2125        ));
2126
2127        analysis.patterns.push(PatternMatch::new(
2128            DesignPattern::Factory {
2129                class: "CarFactory".to_string(),
2130                create_methods: vec!["createCar".to_string()],
2131                products: vec!["Car".to_string()],
2132                is_abstract: false,
2133            },
2134            0.5,
2135        ));
2136
2137        assert_eq!(analysis.patterns_of_type("singleton").len(), 1);
2138        assert_eq!(analysis.patterns_in_category(PatternCategory::Creational).len(), 2);
2139        assert_eq!(analysis.high_confidence_patterns().len(), 1);
2140    }
2141
2142    #[test]
2143    fn test_singleton_detection_heuristics() {
2144        // Test class with singleton indicators.
2145        let class = ClassInfo {
2146            name: "DatabaseConnection".to_string(),
2147            methods: vec![
2148                FunctionInfo {
2149                    name: "__init__".to_string(),
2150                    params: vec!["self".to_string()],
2151                    decorators: vec!["@private".to_string()],
2152                    ..Default::default()
2153                },
2154                FunctionInfo {
2155                    name: "get_instance".to_string(),
2156                    params: vec![],
2157                    decorators: vec!["@staticmethod".to_string()],
2158                    return_type: Some("DatabaseConnection".to_string()),
2159                    ..Default::default()
2160                },
2161            ],
2162            fields: vec![
2163                FieldInfo {
2164                    name: "_instance".to_string(),
2165                    is_static: true,
2166                    field_type: Some("DatabaseConnection".to_string()),
2167                    ..Default::default()
2168                },
2169            ],
2170            language: "python".to_string(),
2171            ..Default::default()
2172        };
2173
2174        let detector = PatternDetector::new(PatternConfig::default());
2175        let result = detector.detect_singleton(&class, Path::new("/test.py"), "python");
2176
2177        assert!(result.is_some());
2178        let pattern_match = result.unwrap();
2179        assert!(pattern_match.confidence >= 0.5);
2180        assert!(matches!(pattern_match.pattern, DesignPattern::Singleton { .. }));
2181    }
2182
2183    #[test]
2184    fn test_builder_detection_heuristics() {
2185        // Test class with builder pattern indicators.
2186        let class = ClassInfo {
2187            name: "RequestBuilder".to_string(),
2188            methods: vec![
2189                FunctionInfo {
2190                    name: "with_url".to_string(),
2191                    params: vec!["self".to_string(), "url: str".to_string()],
2192                    return_type: Some("Self".to_string()),
2193                    ..Default::default()
2194                },
2195                FunctionInfo {
2196                    name: "with_headers".to_string(),
2197                    params: vec!["self".to_string(), "headers: dict".to_string()],
2198                    return_type: Some("Self".to_string()),
2199                    ..Default::default()
2200                },
2201                FunctionInfo {
2202                    name: "build".to_string(),
2203                    params: vec!["self".to_string()],
2204                    return_type: Some("Request".to_string()),
2205                    ..Default::default()
2206                },
2207            ],
2208            language: "python".to_string(),
2209            ..Default::default()
2210        };
2211
2212        let detector = PatternDetector::new(PatternConfig::default());
2213        let result = detector.detect_builder(&class, Path::new("/test.py"), "python");
2214
2215        assert!(result.is_some());
2216        let pattern_match = result.unwrap();
2217        assert!(pattern_match.confidence >= 0.5);
2218        if let DesignPattern::Builder { setters, build_method, .. } = &pattern_match.pattern {
2219            assert_eq!(*build_method, "build");
2220            assert!(setters.contains(&"with_url".to_string()));
2221            assert!(setters.contains(&"with_headers".to_string()));
2222        } else {
2223            panic!("Expected Builder pattern");
2224        }
2225    }
2226
2227    #[test]
2228    fn test_observer_detection_heuristics() {
2229        let class = ClassInfo {
2230            name: "EventEmitter".to_string(),
2231            methods: vec![
2232                FunctionInfo {
2233                    name: "add_listener".to_string(),
2234                    params: vec!["self".to_string(), "listener: Listener".to_string()],
2235                    ..Default::default()
2236                },
2237                FunctionInfo {
2238                    name: "remove_listener".to_string(),
2239                    params: vec!["self".to_string(), "listener: Listener".to_string()],
2240                    ..Default::default()
2241                },
2242                FunctionInfo {
2243                    name: "notify_all".to_string(),
2244                    params: vec!["self".to_string()],
2245                    ..Default::default()
2246                },
2247            ],
2248            fields: vec![
2249                FieldInfo {
2250                    name: "listeners".to_string(),
2251                    field_type: Some("List<Listener>".to_string()),
2252                    ..Default::default()
2253                },
2254            ],
2255            language: "python".to_string(),
2256            ..Default::default()
2257        };
2258
2259        let detector = PatternDetector::new(PatternConfig::default());
2260        let result = detector.detect_observer(&class, Path::new("/test.py"), "python");
2261
2262        assert!(result.is_some());
2263        let pattern_match = result.unwrap();
2264        assert!(pattern_match.confidence >= 0.5);
2265        assert!(matches!(pattern_match.pattern, DesignPattern::Observer { .. }));
2266    }
2267
2268    #[test]
2269    fn test_repository_detection_heuristics() {
2270        let class = ClassInfo {
2271            name: "UserRepository".to_string(),
2272            methods: vec![
2273                FunctionInfo {
2274                    name: "find_by_id".to_string(),
2275                    params: vec!["self".to_string(), "id: int".to_string()],
2276                    return_type: Some("User".to_string()),
2277                    ..Default::default()
2278                },
2279                FunctionInfo {
2280                    name: "save".to_string(),
2281                    params: vec!["self".to_string(), "user: User".to_string()],
2282                    ..Default::default()
2283                },
2284                FunctionInfo {
2285                    name: "delete".to_string(),
2286                    params: vec!["self".to_string(), "id: int".to_string()],
2287                    ..Default::default()
2288                },
2289                FunctionInfo {
2290                    name: "find_all".to_string(),
2291                    params: vec!["self".to_string()],
2292                    return_type: Some("List<User>".to_string()),
2293                    ..Default::default()
2294                },
2295            ],
2296            language: "python".to_string(),
2297            ..Default::default()
2298        };
2299
2300        let detector = PatternDetector::new(PatternConfig::default());
2301        let result = detector.detect_repository(&class, Path::new("/test.py"), "python");
2302
2303        assert!(result.is_some());
2304        let pattern_match = result.unwrap();
2305        assert!(pattern_match.confidence >= 0.5);
2306        if let DesignPattern::Repository { entity_type, methods, .. } = &pattern_match.pattern {
2307            assert_eq!(*entity_type, Some("User".to_string()));
2308            assert!(methods.len() >= 3);
2309        } else {
2310            panic!("Expected Repository pattern");
2311        }
2312    }
2313
2314    #[test]
2315    fn test_format_pattern_summary() {
2316        let mut analysis = PatternAnalysis::new(PathBuf::from("/test"));
2317        analysis.stats.files_scanned = 10;
2318        analysis.stats.patterns_detected = 2;
2319        analysis.stats.average_confidence = 0.75;
2320
2321        analysis.patterns.push(PatternMatch::new(
2322            DesignPattern::Singleton {
2323                class: "Config".to_string(),
2324                instance_method: "getInstance".to_string(),
2325                instance_field: None,
2326                private_constructor: true,
2327            },
2328            0.9,
2329        ).with_location(Location {
2330            file: PathBuf::from("/test/config.py"),
2331            line: 10,
2332            end_line: Some(30),
2333            name: "Config".to_string(),
2334            kind: "class".to_string(),
2335        }));
2336
2337        let summary = format_pattern_summary(&analysis);
2338        assert!(summary.contains("Singleton"));
2339        assert!(summary.contains("Config"));
2340        assert!(summary.contains("90.0%"));
2341    }
2342}