1use super::{
2 AttributedComplexity, CodeLocation, ComplexityComponent, ComplexitySourceType,
3 RecognizedPattern,
4};
5use crate::core::FunctionMetrics;
6use std::collections::HashMap;
7
8pub struct PatternTracker {
10 pattern_detectors: Vec<Box<dyn PatternDetector>>,
11}
12
13impl Default for PatternTracker {
14 fn default() -> Self {
15 Self {
16 pattern_detectors: vec![
17 Box::new(ErrorHandlingDetector::new()),
18 Box::new(ValidationDetector::new()),
19 Box::new(DataTransformationDetector::new()),
20 Box::new(StateManagementDetector::new()),
21 Box::new(IteratorDetector::new()),
22 ],
23 }
24 }
25}
26
27impl PatternTracker {
28 pub fn new() -> Self {
33 Self::default()
34 }
35
36 pub fn analyze_patterns(&self, functions: &[FunctionMetrics]) -> AttributedComplexity {
42 let mut total = 0u32;
43 let mut breakdown = Vec::new();
44 let mut pattern_counts = HashMap::new();
45
46 for func in functions {
47 for detector in &self.pattern_detectors {
48 if let Some(pattern_info) = detector.detect(func) {
49 *pattern_counts
50 .entry(pattern_info.pattern_type.clone())
51 .or_insert(0) += 1;
52
53 let adjustment =
55 (func.cyclomatic as f32 * pattern_info.adjustment_factor) as u32;
56 let reduction = func.cyclomatic.saturating_sub(adjustment);
57
58 if reduction > 0 {
59 breakdown.push(ComplexityComponent {
60 source_type: ComplexitySourceType::PatternRecognition {
61 pattern_type: pattern_info.pattern_type,
62 adjustment_factor: pattern_info.adjustment_factor,
63 },
64 contribution: reduction,
65 location: CodeLocation {
66 file: func.file.to_string_lossy().to_string(),
67 line: func.line as u32,
68 column: 0,
69 span: Some((func.line as u32, (func.line + func.length) as u32)),
70 },
71 description: format!("{} pattern in {}", pattern_info.name, func.name),
72 suggestions: pattern_info.optimization_suggestions,
73 });
74
75 total += reduction;
76 }
77 }
78 }
79 }
80
81 let confidence = if !pattern_counts.is_empty() {
83 let max_count = *pattern_counts.values().max().unwrap_or(&0);
84 let pattern_variety = pattern_counts.len();
85
86 let consistency_score = (max_count as f32 / functions.len().max(1) as f32).min(1.0);
88 let variety_score = (pattern_variety as f32 / 5.0).min(1.0);
89
90 (consistency_score * 0.6 + variety_score * 0.4).min(0.9)
91 } else {
92 0.3 };
94
95 AttributedComplexity {
96 total,
97 breakdown,
98 confidence,
99 }
100 }
101}
102
103trait PatternDetector: Send + Sync {
105 fn detect(&self, func: &FunctionMetrics) -> Option<PatternInfo>;
106}
107
108struct PatternInfo {
110 name: String,
111 pattern_type: RecognizedPattern,
112 adjustment_factor: f32,
113 optimization_suggestions: Vec<String>,
114}
115
116struct ErrorHandlingDetector;
118
119impl ErrorHandlingDetector {
120 fn new() -> Self {
121 Self
122 }
123}
124
125impl PatternDetector for ErrorHandlingDetector {
126 fn detect(&self, func: &FunctionMetrics) -> Option<PatternInfo> {
127 let name_lower = func.name.to_lowercase();
129 if name_lower.contains("error")
130 || name_lower.contains("handle")
131 || name_lower.contains("catch")
132 {
133 Some(PatternInfo {
134 name: "Error Handling".to_string(),
135 pattern_type: RecognizedPattern::ErrorHandling,
136 adjustment_factor: 0.8, optimization_suggestions: vec![
138 "Consider using Result type consistently".to_string(),
139 "Extract error transformation logic".to_string(),
140 ],
141 })
142 } else {
143 None
144 }
145 }
146}
147
148struct ValidationDetector;
150
151impl ValidationDetector {
152 fn new() -> Self {
153 Self
154 }
155}
156
157impl PatternDetector for ValidationDetector {
158 fn detect(&self, func: &FunctionMetrics) -> Option<PatternInfo> {
159 let name_lower = func.name.to_lowercase();
160 if name_lower.contains("valid")
161 || name_lower.contains("check")
162 || name_lower.contains("verify")
163 {
164 Some(PatternInfo {
165 name: "Validation".to_string(),
166 pattern_type: RecognizedPattern::Validation,
167 adjustment_factor: 0.85,
168 optimization_suggestions: vec![
169 "Consider using validation libraries".to_string(),
170 "Extract validation rules into constants".to_string(),
171 ],
172 })
173 } else {
174 None
175 }
176 }
177}
178
179struct DataTransformationDetector;
181
182impl DataTransformationDetector {
183 fn new() -> Self {
184 Self
185 }
186}
187
188impl PatternDetector for DataTransformationDetector {
189 fn detect(&self, func: &FunctionMetrics) -> Option<PatternInfo> {
190 let name_lower = func.name.to_lowercase();
191 if name_lower.contains("transform")
192 || name_lower.contains("convert")
193 || name_lower.contains("map")
194 || name_lower.contains("parse")
195 {
196 Some(PatternInfo {
197 name: "Data Transformation".to_string(),
198 pattern_type: RecognizedPattern::DataTransformation,
199 adjustment_factor: 0.9,
200 optimization_suggestions: vec![
201 "Consider using functional transformations".to_string(),
202 "Extract transformation logic into pure functions".to_string(),
203 ],
204 })
205 } else {
206 None
207 }
208 }
209}
210
211struct StateManagementDetector;
213
214impl StateManagementDetector {
215 fn new() -> Self {
216 Self
217 }
218}
219
220impl PatternDetector for StateManagementDetector {
221 fn detect(&self, func: &FunctionMetrics) -> Option<PatternInfo> {
222 let name_lower = func.name.to_lowercase();
223 if name_lower.contains("state")
224 || name_lower.contains("update")
225 || name_lower.contains("sync")
226 {
227 Some(PatternInfo {
228 name: "State Management".to_string(),
229 pattern_type: RecognizedPattern::StateManagement,
230 adjustment_factor: 0.75, optimization_suggestions: vec![
232 "Consider immutable state updates".to_string(),
233 "Use state machines for complex state logic".to_string(),
234 ],
235 })
236 } else {
237 None
238 }
239 }
240}
241
242struct IteratorDetector;
244
245impl IteratorDetector {
246 fn new() -> Self {
247 Self
248 }
249}
250
251impl PatternDetector for IteratorDetector {
252 fn detect(&self, func: &FunctionMetrics) -> Option<PatternInfo> {
253 let name_lower = func.name.to_lowercase();
254 if name_lower.contains("iter")
255 || name_lower.contains("next")
256 || name_lower.contains("collect")
257 || name_lower.contains("fold")
258 {
259 Some(PatternInfo {
260 name: "Iterator".to_string(),
261 pattern_type: RecognizedPattern::Iterator,
262 adjustment_factor: 0.95,
263 optimization_suggestions: vec![
264 "Consider using iterator combinators".to_string(),
265 "Avoid unnecessary intermediate collections".to_string(),
266 ],
267 })
268 } else {
269 None
270 }
271 }
272}
273
274#[cfg(test)]
275mod tests {
276 use super::*;
277 use std::path::PathBuf;
278
279 #[test]
280 fn test_pattern_tracker_new() {
281 let tracker = PatternTracker::new();
282 assert!(!tracker.pattern_detectors.is_empty());
283 }
284
285 #[test]
286 fn test_error_handling_detection() {
287 let detector = ErrorHandlingDetector::new();
288
289 let func = FunctionMetrics {
290 name: "handle_error".to_string(),
291 file: PathBuf::from("test.rs"),
292 line: 10,
293 cyclomatic: 5,
294 cognitive: 3,
295 nesting: 1,
296 length: 20,
297 is_test: false,
298 visibility: None,
299 is_trait_method: false,
300 in_test_module: false,
301 entropy_score: None,
302 is_pure: None,
303 purity_confidence: None,
304 purity_reason: None,
305 call_dependencies: None,
306 detected_patterns: None,
307 upstream_callers: None,
308 downstream_callees: None,
309 mapping_pattern_result: None,
310 adjusted_complexity: None,
311 composition_metrics: None,
312 language_specific: None,
313 purity_level: None,
314 error_swallowing_count: None,
315 error_swallowing_patterns: None,
316 entropy_analysis: None,
317 };
318
319 let pattern = detector.detect(&func);
320 assert!(pattern.is_some());
321
322 let info = pattern.unwrap();
323 assert_eq!(info.pattern_type, RecognizedPattern::ErrorHandling);
324 assert_eq!(info.adjustment_factor, 0.8);
325 }
326
327 #[test]
328 fn test_validation_detection() {
329 let detector = ValidationDetector::new();
330
331 let func = FunctionMetrics {
332 name: "validate_input".to_string(),
333 file: PathBuf::from("test.rs"),
334 line: 20,
335 cyclomatic: 8,
336 cognitive: 5,
337 nesting: 2,
338 length: 30,
339 is_test: false,
340 visibility: None,
341 is_trait_method: false,
342 in_test_module: false,
343 entropy_score: None,
344 is_pure: None,
345 purity_confidence: None,
346 purity_reason: None,
347 call_dependencies: None,
348 detected_patterns: None,
349 upstream_callers: None,
350 downstream_callees: None,
351 mapping_pattern_result: None,
352 adjusted_complexity: None,
353 composition_metrics: None,
354 language_specific: None,
355 purity_level: None,
356 error_swallowing_count: None,
357 error_swallowing_patterns: None,
358 entropy_analysis: None,
359 };
360
361 let pattern = detector.detect(&func);
362 assert!(pattern.is_some());
363
364 let info = pattern.unwrap();
365 assert_eq!(info.pattern_type, RecognizedPattern::Validation);
366 }
367
368 #[test]
369 fn test_no_pattern_detection() {
370 let detector = ErrorHandlingDetector::new();
371
372 let func = FunctionMetrics {
373 name: "calculate_sum".to_string(),
374 file: PathBuf::from("test.rs"),
375 line: 30,
376 cyclomatic: 2,
377 cognitive: 1,
378 nesting: 0,
379 length: 10,
380 is_test: false,
381 visibility: None,
382 is_trait_method: false,
383 in_test_module: false,
384 entropy_score: None,
385 is_pure: None,
386 purity_confidence: None,
387 purity_reason: None,
388 call_dependencies: None,
389 detected_patterns: None,
390 upstream_callers: None,
391 downstream_callees: None,
392 mapping_pattern_result: None,
393 adjusted_complexity: None,
394 composition_metrics: None,
395 language_specific: None,
396 purity_level: None,
397 error_swallowing_count: None,
398 error_swallowing_patterns: None,
399 entropy_analysis: None,
400 };
401
402 let pattern = detector.detect(&func);
403 assert!(pattern.is_none());
404 }
405
406 #[test]
407 fn test_analyze_patterns_empty() {
408 let tracker = PatternTracker::new();
409 let functions = vec![];
410
411 let result = tracker.analyze_patterns(&functions);
412 assert_eq!(result.total, 0);
413 assert_eq!(result.confidence, 0.3);
414 }
415
416 #[test]
417 fn test_analyze_patterns_with_functions() {
418 let tracker = PatternTracker::new();
419 let functions = vec![FunctionMetrics {
420 name: "handle_request_error".to_string(),
421 file: PathBuf::from("test.rs"),
422 line: 10,
423 cyclomatic: 10,
424 cognitive: 8,
425 nesting: 2,
426 length: 40,
427 is_test: false,
428 visibility: None,
429 is_trait_method: false,
430 in_test_module: false,
431 entropy_score: None,
432 is_pure: None,
433 purity_confidence: None,
434 purity_reason: None,
435 call_dependencies: None,
436 detected_patterns: None,
437 upstream_callers: None,
438 downstream_callees: None,
439 mapping_pattern_result: None,
440 adjusted_complexity: None,
441 composition_metrics: None,
442 language_specific: None,
443 purity_level: None,
444 error_swallowing_count: None,
445 error_swallowing_patterns: None,
446 entropy_analysis: None,
447 }];
448
449 let result = tracker.analyze_patterns(&functions);
450 assert!(result.total > 0);
451 assert!(!result.breakdown.is_empty());
452 }
453}