Skip to main content

agentic_codebase/semantic/
pattern_detector.rs

1//! Design pattern detection.
2//!
3//! Detects common design patterns in code: Singleton, Factory, Repository,
4//! Decorator, Observer, Strategy patterns.
5
6use crate::types::{AcbResult, CodeUnitType, Visibility};
7
8use super::resolver::ResolvedUnit;
9
10/// Detects common design patterns in code.
11pub struct PatternDetector {
12    /// Pattern matchers to run.
13    matchers: Vec<Box<dyn PatternMatcher>>,
14}
15
16/// A detected pattern instance.
17#[derive(Debug, Clone)]
18pub struct PatternInstance {
19    /// The pattern name.
20    pub pattern_name: String,
21    /// The primary unit involved.
22    pub primary_unit: u64,
23    /// All units participating in the pattern.
24    pub participating_units: Vec<u64>,
25    /// Confidence score (0.0 to 1.0).
26    pub confidence: f32,
27}
28
29/// Trait for pattern matchers.
30trait PatternMatcher: Send + Sync {
31    /// Detect instances of this pattern.
32    fn detect(&self, units: &[ResolvedUnit]) -> Vec<PatternInstance>;
33}
34
35impl PatternDetector {
36    /// Create a new pattern detector with all built-in matchers.
37    pub fn new() -> Self {
38        let matchers: Vec<Box<dyn PatternMatcher>> = vec![
39            Box::new(SingletonMatcher),
40            Box::new(FactoryMatcher),
41            Box::new(RepositoryMatcher),
42            Box::new(DecoratorMatcher),
43        ];
44        Self { matchers }
45    }
46
47    /// Detect all patterns in the resolved units.
48    pub fn detect(&self, units: &[ResolvedUnit]) -> AcbResult<Vec<PatternInstance>> {
49        let mut instances = Vec::new();
50
51        for matcher in &self.matchers {
52            instances.extend(matcher.detect(units));
53        }
54
55        Ok(instances)
56    }
57}
58
59impl Default for PatternDetector {
60    fn default() -> Self {
61        Self::new()
62    }
63}
64
65/// Detects Singleton pattern: classes with private constructors and
66/// static instance access methods.
67struct SingletonMatcher;
68
69impl PatternMatcher for SingletonMatcher {
70    fn detect(&self, units: &[ResolvedUnit]) -> Vec<PatternInstance> {
71        let mut instances = Vec::new();
72
73        for unit in units {
74            if unit.unit.unit_type != CodeUnitType::Type {
75                continue;
76            }
77
78            // Look for singleton indicators by checking sibling methods
79            let type_name = &unit.unit.name;
80            let type_name_lower = type_name.to_lowercase();
81
82            let mut has_instance_method = false;
83            let mut has_private_constructor = false;
84            let mut participants = vec![unit.unit.temp_id];
85
86            for other in units {
87                if other.unit.unit_type != CodeUnitType::Function {
88                    continue;
89                }
90
91                let other_qname_lower = other.unit.qualified_name.to_lowercase();
92                let other_name_lower = other.unit.name.to_lowercase();
93
94                // Check if this is a method of the type
95                if other_qname_lower.contains(&type_name_lower) {
96                    // Check for get_instance, instance, or shared patterns
97                    if other_name_lower.contains("instance")
98                        || other_name_lower.contains("shared")
99                        || other_name_lower == "default"
100                    {
101                        has_instance_method = true;
102                        participants.push(other.unit.temp_id);
103                    }
104
105                    // Check for private constructors
106                    if (other_name_lower == "__init__"
107                        || other_name_lower == "new"
108                        || other_name_lower == "constructor")
109                        && other.unit.visibility == Visibility::Private
110                    {
111                        has_private_constructor = true;
112                        participants.push(other.unit.temp_id);
113                    }
114                }
115            }
116
117            let score = (has_instance_method as u8 + has_private_constructor as u8) as f32 / 2.0;
118            if score > 0.0 {
119                instances.push(PatternInstance {
120                    pattern_name: "Singleton".to_string(),
121                    primary_unit: unit.unit.temp_id,
122                    participating_units: participants,
123                    confidence: score,
124                });
125            }
126        }
127
128        instances
129    }
130}
131
132/// Detects Factory pattern: functions or classes that create and return
133/// other object instances.
134struct FactoryMatcher;
135
136impl PatternMatcher for FactoryMatcher {
137    fn detect(&self, units: &[ResolvedUnit]) -> Vec<PatternInstance> {
138        let mut instances = Vec::new();
139
140        for unit in units {
141            if unit.unit.unit_type != CodeUnitType::Function
142                && unit.unit.unit_type != CodeUnitType::Type
143            {
144                continue;
145            }
146
147            let name_lower = unit.unit.name.to_lowercase();
148
149            // Name-based detection
150            if name_lower.contains("factory")
151                || name_lower.starts_with("create_")
152                || name_lower.starts_with("make_")
153                || name_lower.starts_with("build_")
154                || name_lower == "new"
155            {
156                instances.push(PatternInstance {
157                    pattern_name: "Factory".to_string(),
158                    primary_unit: unit.unit.temp_id,
159                    participating_units: vec![unit.unit.temp_id],
160                    confidence: if name_lower.contains("factory") {
161                        0.9
162                    } else {
163                        0.5
164                    },
165                });
166            }
167        }
168
169        instances
170    }
171}
172
173/// Detects Repository pattern: data access layer classes.
174struct RepositoryMatcher;
175
176impl PatternMatcher for RepositoryMatcher {
177    fn detect(&self, units: &[ResolvedUnit]) -> Vec<PatternInstance> {
178        let mut instances = Vec::new();
179
180        for unit in units {
181            if unit.unit.unit_type != CodeUnitType::Type {
182                continue;
183            }
184
185            let name_lower = unit.unit.name.to_lowercase();
186
187            if name_lower.contains("repository")
188                || name_lower.contains("repo")
189                || name_lower.contains("dao")
190                || name_lower.contains("store")
191            {
192                // Look for CRUD methods
193                let mut crud_count = 0;
194                for other in units {
195                    if other.unit.unit_type == CodeUnitType::Function {
196                        let method_lower = other.unit.name.to_lowercase();
197                        let in_type = other
198                            .unit
199                            .qualified_name
200                            .to_lowercase()
201                            .contains(&name_lower);
202                        if in_type
203                            && (method_lower.starts_with("get")
204                                || method_lower.starts_with("find")
205                                || method_lower.starts_with("create")
206                                || method_lower.starts_with("update")
207                                || method_lower.starts_with("delete")
208                                || method_lower.starts_with("save")
209                                || method_lower.starts_with("list"))
210                        {
211                            crud_count += 1;
212                        }
213                    }
214                }
215
216                let confidence = if crud_count >= 3 {
217                    0.9
218                } else if crud_count >= 1 {
219                    0.6
220                } else {
221                    0.4
222                };
223
224                instances.push(PatternInstance {
225                    pattern_name: "Repository".to_string(),
226                    primary_unit: unit.unit.temp_id,
227                    participating_units: vec![unit.unit.temp_id],
228                    confidence,
229                });
230            }
231        }
232
233        instances
234    }
235}
236
237/// Detects Decorator pattern by name convention.
238struct DecoratorMatcher;
239
240impl PatternMatcher for DecoratorMatcher {
241    fn detect(&self, units: &[ResolvedUnit]) -> Vec<PatternInstance> {
242        let mut instances = Vec::new();
243
244        for unit in units {
245            let name_lower = unit.unit.name.to_lowercase();
246
247            if name_lower.contains("decorator")
248                || name_lower.contains("wrapper")
249                || name_lower.contains("middleware")
250            {
251                instances.push(PatternInstance {
252                    pattern_name: "Decorator".to_string(),
253                    primary_unit: unit.unit.temp_id,
254                    participating_units: vec![unit.unit.temp_id],
255                    confidence: if name_lower.contains("decorator") {
256                        0.8
257                    } else {
258                        0.5
259                    },
260                });
261            }
262        }
263
264        instances
265    }
266}