1use crate::analysis::purity_analysis::PurityViolation;
29use serde::{Deserialize, Serialize};
30use std::fmt;
31
32pub const REPETITIVE_TRAIT_THRESHOLD: usize = 5;
34pub const MAX_DISPLAYED_EXAMPLES: usize = 3;
35pub const MAX_ALMOST_PURE_VIOLATIONS: usize = 2;
36
37#[derive(Debug, Clone, Serialize, Deserialize, Default)]
39pub struct PurityMetrics {
40 pub strictly_pure: usize,
41 pub locally_pure: usize,
42 pub read_only: usize,
43 pub impure: usize,
44 pub almost_pure: Vec<AlmostPureFunction>,
45}
46
47impl PurityMetrics {
48 pub fn total_functions(&self) -> usize {
50 self.strictly_pure + self.locally_pure + self.read_only + self.impure
51 }
52
53 pub fn purity_percentage(&self) -> f64 {
55 let total = self.total_functions();
56 if total == 0 {
57 0.0
58 } else {
59 (self.strictly_pure + self.locally_pure) as f64 / total as f64
60 }
61 }
62
63 pub fn top_refactoring_opportunities(&self, limit: usize) -> &[AlmostPureFunction] {
65 let end = self.almost_pure.len().min(limit);
66 &self.almost_pure[..end]
67 }
68
69 pub fn has_functions(&self) -> bool {
71 self.total_functions() > 0
72 }
73
74 pub fn has_opportunities(&self) -> bool {
76 !self.almost_pure.is_empty()
77 }
78}
79
80#[derive(Debug, Clone, Serialize, Deserialize)]
82pub struct AlmostPureFunction {
83 pub name: String,
84 pub violations: Vec<PurityViolation>,
85 pub refactoring_suggestion: String,
86}
87
88#[derive(Debug, Clone, Serialize, Deserialize, Default)]
90pub struct FrameworkPatternMetrics {
91 pub patterns: Vec<DetectedPattern>,
92}
93
94impl FrameworkPatternMetrics {
95 pub fn sorted_by_frequency(&self) -> Vec<&DetectedPattern> {
97 let mut sorted: Vec<_> = self.patterns.iter().collect();
98 sorted.sort_by(|a, b| b.count.cmp(&a.count));
99 sorted
100 }
101
102 pub fn has_patterns(&self) -> bool {
104 !self.patterns.is_empty()
105 }
106}
107
108#[derive(Debug, Clone, Serialize, Deserialize)]
110pub struct DetectedPattern {
111 pub framework: String,
112 pub pattern_type: String,
113 pub count: usize,
114 pub examples: Vec<String>,
115 pub recommendation: String,
116}
117
118#[derive(Debug, Clone, Serialize, Deserialize, Default)]
120pub struct RustPatternMetrics {
121 pub trait_impls: Vec<TraitImplementation>,
122 pub async_patterns: AsyncPatternSummary,
123 pub error_handling: ErrorHandlingSummary,
124 pub builder_candidates: Vec<String>,
125}
126
127impl RustPatternMetrics {
128 pub fn repetitive_traits(&self) -> Vec<&TraitImplementation> {
130 self.trait_impls
131 .iter()
132 .filter(|t| t.count >= REPETITIVE_TRAIT_THRESHOLD)
133 .collect()
134 }
135
136 pub fn has_async_patterns(&self) -> bool {
138 self.async_patterns.async_functions > 0
139 }
140
141 pub fn has_error_handling(&self) -> bool {
143 self.error_handling.question_mark_density > 0.0 || self.error_handling.unwrap_count > 0
144 }
145
146 pub fn has_builder_candidates(&self) -> bool {
148 !self.builder_candidates.is_empty()
149 }
150
151 pub fn has_patterns(&self) -> bool {
153 !self.trait_impls.is_empty()
154 || self.has_async_patterns()
155 || self.has_error_handling()
156 || self.has_builder_candidates()
157 }
158}
159
160#[derive(Debug, Clone, Serialize, Deserialize)]
162pub struct TraitImplementation {
163 pub trait_name: String,
164 pub count: usize,
165 pub types: Vec<String>,
166}
167
168#[derive(Debug, Clone, Serialize, Deserialize, Default)]
170pub struct AsyncPatternSummary {
171 pub async_functions: usize,
172 pub spawn_calls: usize,
173 pub channel_usage: bool,
174 pub mutex_usage: bool,
175}
176
177#[derive(Debug, Clone, Serialize, Deserialize)]
179pub struct ErrorHandlingSummary {
180 pub question_mark_density: f64,
182 pub custom_error_types: Vec<String>,
183 pub unwrap_count: usize,
185}
186
187impl Default for ErrorHandlingSummary {
188 fn default() -> Self {
189 Self {
190 question_mark_density: 0.0,
191 custom_error_types: vec![],
192 unwrap_count: 0,
193 }
194 }
195}
196
197#[derive(Debug, Clone, Serialize, Deserialize, Default)]
200pub struct PatternAnalysis {
201 pub purity: PurityMetrics,
202 pub frameworks: FrameworkPatternMetrics,
203 pub rust_patterns: RustPatternMetrics,
204}
205
206impl PatternAnalysis {
207 pub fn new() -> Self {
209 Self::default()
210 }
211
212 pub fn has_patterns(&self) -> bool {
214 self.purity.has_functions()
215 || self.frameworks.has_patterns()
216 || self.rust_patterns.has_patterns()
217 }
218
219 pub fn from_functions(functions: &[crate::priority::FunctionAnalysis]) -> Self {
222 Self {
223 purity: aggregate_purity_metrics(functions),
224 frameworks: aggregate_framework_patterns(functions),
225 rust_patterns: aggregate_rust_patterns(functions),
226 }
227 }
228}
229
230fn aggregate_purity_metrics(functions: &[crate::priority::FunctionAnalysis]) -> PurityMetrics {
232 functions
233 .iter()
234 .map(classify_purity_bucket)
235 .fold(PurityCounts::default(), PurityCounts::with_bucket)
236 .into_metrics()
237}
238
239#[derive(Debug, Clone, Copy, PartialEq, Eq)]
240enum PurityBucket {
241 StrictlyPure,
242 LocallyPure,
243 ReadOnly,
244 Impure,
245}
246
247#[derive(Debug, Clone, Copy, Default)]
248struct PurityCounts {
249 strictly_pure: usize,
250 locally_pure: usize,
251 read_only: usize,
252 impure: usize,
253}
254
255impl PurityCounts {
256 fn with_bucket(self, bucket: PurityBucket) -> Self {
257 match bucket {
258 PurityBucket::StrictlyPure => Self {
259 strictly_pure: self.strictly_pure + 1,
260 ..self
261 },
262 PurityBucket::LocallyPure => Self {
263 locally_pure: self.locally_pure + 1,
264 ..self
265 },
266 PurityBucket::ReadOnly => Self {
267 read_only: self.read_only + 1,
268 ..self
269 },
270 PurityBucket::Impure => Self {
271 impure: self.impure + 1,
272 ..self
273 },
274 }
275 }
276
277 fn into_metrics(self) -> PurityMetrics {
278 PurityMetrics {
279 strictly_pure: self.strictly_pure,
280 locally_pure: self.locally_pure,
281 read_only: self.read_only,
282 impure: self.impure,
283 almost_pure: Vec::new(),
284 }
285 }
286}
287
288fn classify_purity_bucket(func: &crate::priority::FunctionAnalysis) -> PurityBucket {
289 match func.is_pure {
290 Some(true) if has_strict_purity_confidence(func) => PurityBucket::StrictlyPure,
291 Some(true) => PurityBucket::LocallyPure,
292 Some(false) if is_read_only_candidate(func) => PurityBucket::ReadOnly,
293 _ => PurityBucket::Impure,
294 }
295}
296
297fn has_strict_purity_confidence(func: &crate::priority::FunctionAnalysis) -> bool {
298 func.purity_confidence.unwrap_or(0.0) >= 0.9
299}
300
301fn is_read_only_candidate(func: &crate::priority::FunctionAnalysis) -> bool {
302 func.cyclomatic_complexity <= 5 && func.cognitive_complexity <= 7
303}
304
305fn aggregate_framework_patterns(
307 _functions: &[crate::priority::FunctionAnalysis],
308) -> FrameworkPatternMetrics {
309 FrameworkPatternMetrics {
312 patterns: Vec::new(),
313 }
314}
315
316fn aggregate_rust_patterns(_functions: &[crate::priority::FunctionAnalysis]) -> RustPatternMetrics {
318 RustPatternMetrics::default()
321}
322
323pub fn suggest_purity_refactoring(violations: &[PurityViolation]) -> Vec<String> {
326 let mut suggestions = Vec::new();
327
328 for violation in violations {
329 let suggestion = match violation {
330 PurityViolation::StateMutation { .. } => {
331 "Pass state as function parameter instead of mutating external state"
332 }
333 PurityViolation::IoOperation { .. } => {
334 "Move I/O to function boundaries; separate pure logic from side effects"
335 }
336 PurityViolation::NonDeterministic { .. } => {
337 "Make function deterministic by accepting random seed or current time as parameter"
338 }
339 PurityViolation::ImpureCall { .. } => {
340 "Extract pure logic from impure function calls; isolate side effects"
341 }
342 };
343
344 if !suggestions.contains(&suggestion.to_string()) {
345 suggestions.push(suggestion.to_string());
346 }
347 }
348
349 suggestions
350}
351
352pub fn generate_framework_recommendation(
355 framework: &str,
356 pattern_type: &str,
357 count: usize,
358) -> String {
359 match (framework, pattern_type) {
360 ("React", "hooks") => {
361 format!(
362 "Consider extracting {} hook usages into custom hooks for reusability",
363 count
364 )
365 }
366 ("React", "component") => {
367 format!(
368 "Review {} components for proper memoization and render optimization",
369 count
370 )
371 }
372 ("Tokio", "async") => {
373 format!(
374 "Verify {} async operations use proper error handling and cancellation",
375 count
376 )
377 }
378 ("Actix", "handler") => {
379 format!(
380 "Ensure {} handlers follow async best practices and proper error propagation",
381 count
382 )
383 }
384 ("Django", "view") => {
385 format!(
386 "Review {} views for proper transaction handling and query optimization",
387 count
388 )
389 }
390 ("Flask", "route") => {
391 format!(
392 "Consider adding validation and error handling to {} route handlers",
393 count
394 )
395 }
396 _ => {
397 format!(
398 "Review {} instances of {} {} pattern for consistency and best practices",
399 count, framework, pattern_type
400 )
401 }
402 }
403}
404
405impl fmt::Display for PurityViolation {
407 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
408 write!(f, "{}", self.description())
409 }
410}
411
412#[cfg(test)]
413mod tests {
414 use super::*;
415 use crate::analysis::purity_analysis::PurityViolation;
416 use crate::priority::{FunctionAnalysis, FunctionVisibility};
417 use std::path::PathBuf;
418
419 fn function_analysis(
420 is_pure: Option<bool>,
421 purity_confidence: Option<f32>,
422 cyclomatic_complexity: u32,
423 cognitive_complexity: u32,
424 ) -> FunctionAnalysis {
425 FunctionAnalysis {
426 file: PathBuf::from("src/lib.rs"),
427 function: "analyze".to_string(),
428 line: 1,
429 function_length: 10,
430 cyclomatic_complexity,
431 cognitive_complexity,
432 is_pure,
433 purity_confidence,
434 nesting_depth: 0,
435 is_test: false,
436 visibility: FunctionVisibility::Private,
437 }
438 }
439
440 #[test]
441 fn test_purity_metrics_total_functions() {
442 let metrics = PurityMetrics {
443 strictly_pure: 10,
444 locally_pure: 5,
445 read_only: 3,
446 impure: 7,
447 almost_pure: vec![],
448 };
449
450 assert_eq!(metrics.total_functions(), 25);
451 }
452
453 #[test]
454 fn test_purity_metrics_purity_percentage() {
455 let metrics = PurityMetrics {
456 strictly_pure: 15,
457 locally_pure: 10,
458 read_only: 5,
459 impure: 20,
460 almost_pure: vec![],
461 };
462
463 assert_eq!(metrics.purity_percentage(), 0.5);
465 }
466
467 #[test]
468 fn test_purity_metrics_purity_percentage_zero_functions() {
469 let metrics = PurityMetrics::default();
470 assert_eq!(metrics.purity_percentage(), 0.0);
471 }
472
473 #[test]
474 fn test_purity_metrics_top_refactoring_opportunities() {
475 let func1 = AlmostPureFunction {
476 name: "func1".to_string(),
477 violations: vec![],
478 refactoring_suggestion: "test".to_string(),
479 };
480 let func2 = AlmostPureFunction {
481 name: "func2".to_string(),
482 violations: vec![],
483 refactoring_suggestion: "test".to_string(),
484 };
485 let func3 = AlmostPureFunction {
486 name: "func3".to_string(),
487 violations: vec![],
488 refactoring_suggestion: "test".to_string(),
489 };
490
491 let metrics = PurityMetrics {
492 strictly_pure: 0,
493 locally_pure: 0,
494 read_only: 0,
495 impure: 0,
496 almost_pure: vec![func1, func2, func3],
497 };
498
499 let top = metrics.top_refactoring_opportunities(2);
500 assert_eq!(top.len(), 2);
501 assert_eq!(top[0].name, "func1");
502 assert_eq!(top[1].name, "func2");
503 }
504
505 #[test]
506 fn test_purity_metrics_has_functions() {
507 let empty = PurityMetrics::default();
508 assert!(!empty.has_functions());
509
510 let with_functions = PurityMetrics {
511 strictly_pure: 1,
512 locally_pure: 0,
513 read_only: 0,
514 impure: 0,
515 almost_pure: vec![],
516 };
517 assert!(with_functions.has_functions());
518 }
519
520 #[test]
521 fn test_purity_metrics_has_opportunities() {
522 let empty = PurityMetrics::default();
523 assert!(!empty.has_opportunities());
524
525 let func = AlmostPureFunction {
526 name: "test".to_string(),
527 violations: vec![],
528 refactoring_suggestion: "fix".to_string(),
529 };
530 let with_opportunities = PurityMetrics {
531 strictly_pure: 0,
532 locally_pure: 0,
533 read_only: 0,
534 impure: 0,
535 almost_pure: vec![func],
536 };
537 assert!(with_opportunities.has_opportunities());
538 }
539
540 #[test]
541 fn test_framework_pattern_metrics_sorted_by_frequency() {
542 let pattern1 = DetectedPattern {
543 framework: "React".to_string(),
544 pattern_type: "hooks".to_string(),
545 count: 5,
546 examples: vec![],
547 recommendation: "test".to_string(),
548 };
549 let pattern2 = DetectedPattern {
550 framework: "Vue".to_string(),
551 pattern_type: "components".to_string(),
552 count: 15,
553 examples: vec![],
554 recommendation: "test".to_string(),
555 };
556 let pattern3 = DetectedPattern {
557 framework: "Angular".to_string(),
558 pattern_type: "services".to_string(),
559 count: 10,
560 examples: vec![],
561 recommendation: "test".to_string(),
562 };
563
564 let metrics = FrameworkPatternMetrics {
565 patterns: vec![pattern1, pattern2, pattern3],
566 };
567
568 let sorted = metrics.sorted_by_frequency();
569 assert_eq!(sorted.len(), 3);
570 assert_eq!(sorted[0].count, 15); assert_eq!(sorted[1].count, 10); assert_eq!(sorted[2].count, 5); }
574
575 #[test]
576 fn test_rust_pattern_metrics_repetitive_traits() {
577 let trait1 = TraitImplementation {
578 trait_name: "Display".to_string(),
579 count: 3,
580 types: vec![],
581 };
582 let trait2 = TraitImplementation {
583 trait_name: "Clone".to_string(),
584 count: 10,
585 types: vec![],
586 };
587
588 let metrics = RustPatternMetrics {
589 trait_impls: vec![trait1, trait2],
590 async_patterns: AsyncPatternSummary::default(),
591 error_handling: ErrorHandlingSummary::default(),
592 builder_candidates: vec![],
593 };
594
595 let repetitive = metrics.repetitive_traits();
596 assert_eq!(repetitive.len(), 1);
597 assert_eq!(repetitive[0].trait_name, "Clone");
598 assert_eq!(repetitive[0].count, 10);
599 }
600
601 #[test]
602 fn test_rust_pattern_metrics_has_patterns() {
603 let empty = RustPatternMetrics::default();
604 assert!(!empty.has_patterns());
605
606 let with_traits = RustPatternMetrics {
607 trait_impls: vec![TraitImplementation {
608 trait_name: "Debug".to_string(),
609 count: 1,
610 types: vec![],
611 }],
612 async_patterns: AsyncPatternSummary::default(),
613 error_handling: ErrorHandlingSummary::default(),
614 builder_candidates: vec![],
615 };
616 assert!(with_traits.has_patterns());
617
618 let with_async = RustPatternMetrics {
619 trait_impls: vec![],
620 async_patterns: AsyncPatternSummary {
621 async_functions: 5,
622 spawn_calls: 0,
623 channel_usage: false,
624 mutex_usage: false,
625 },
626 error_handling: ErrorHandlingSummary::default(),
627 builder_candidates: vec![],
628 };
629 assert!(with_async.has_patterns());
630 }
631
632 #[test]
633 fn test_pattern_analysis_has_patterns() {
634 let empty = PatternAnalysis::default();
635 assert!(!empty.has_patterns());
636
637 let with_purity = PatternAnalysis {
638 purity: PurityMetrics {
639 strictly_pure: 5,
640 locally_pure: 0,
641 read_only: 0,
642 impure: 0,
643 almost_pure: vec![],
644 },
645 frameworks: FrameworkPatternMetrics::default(),
646 rust_patterns: RustPatternMetrics::default(),
647 };
648 assert!(with_purity.has_patterns());
649 }
650
651 #[test]
652 fn test_classify_purity_bucket_strictly_pure() {
653 let function = function_analysis(Some(true), Some(0.9), 1, 1);
654
655 assert_eq!(
656 classify_purity_bucket(&function),
657 PurityBucket::StrictlyPure
658 );
659 }
660
661 #[test]
662 fn test_classify_purity_bucket_locally_pure() {
663 let function = function_analysis(Some(true), Some(0.89), 1, 1);
664
665 assert_eq!(classify_purity_bucket(&function), PurityBucket::LocallyPure);
666 }
667
668 #[test]
669 fn test_classify_purity_bucket_read_only() {
670 let function = function_analysis(Some(false), None, 5, 7);
671
672 assert_eq!(classify_purity_bucket(&function), PurityBucket::ReadOnly);
673 }
674
675 #[test]
676 fn test_classify_purity_bucket_impure_when_complexity_exceeds_read_only_threshold() {
677 let high_cyclomatic = function_analysis(Some(false), None, 6, 7);
678 let high_cognitive = function_analysis(Some(false), None, 5, 8);
679
680 assert_eq!(
681 classify_purity_bucket(&high_cyclomatic),
682 PurityBucket::Impure
683 );
684 assert_eq!(
685 classify_purity_bucket(&high_cognitive),
686 PurityBucket::Impure
687 );
688 }
689
690 #[test]
691 fn test_classify_purity_bucket_unknown_purity_as_impure() {
692 let function = function_analysis(None, None, 1, 1);
693
694 assert_eq!(classify_purity_bucket(&function), PurityBucket::Impure);
695 }
696
697 #[test]
698 fn test_aggregate_purity_metrics_counts_all_buckets() {
699 let functions = vec![
700 function_analysis(Some(true), Some(0.95), 1, 1),
701 function_analysis(Some(true), Some(0.5), 1, 1),
702 function_analysis(Some(false), None, 5, 7),
703 function_analysis(Some(false), None, 9, 9),
704 function_analysis(None, None, 1, 1),
705 ];
706
707 let metrics = aggregate_purity_metrics(&functions);
708
709 assert_eq!(metrics.strictly_pure, 1);
710 assert_eq!(metrics.locally_pure, 1);
711 assert_eq!(metrics.read_only, 1);
712 assert_eq!(metrics.impure, 2);
713 assert!(metrics.almost_pure.is_empty());
714 }
715
716 #[test]
717 fn test_suggest_purity_refactoring() {
718 let violations = vec![
719 PurityViolation::StateMutation {
720 target: "x".to_string(),
721 line: Some(10),
722 },
723 PurityViolation::IoOperation {
724 description: "println".to_string(),
725 line: Some(15),
726 },
727 ];
728
729 let suggestions = suggest_purity_refactoring(&violations);
730 assert_eq!(suggestions.len(), 2);
731 assert!(suggestions[0].contains("state as function parameter"));
732 assert!(suggestions[1].contains("Move I/O to function boundaries"));
733 }
734
735 #[test]
736 fn test_suggest_purity_refactoring_no_duplicates() {
737 let violations = vec![
738 PurityViolation::StateMutation {
739 target: "x".to_string(),
740 line: Some(10),
741 },
742 PurityViolation::StateMutation {
743 target: "y".to_string(),
744 line: Some(20),
745 },
746 ];
747
748 let suggestions = suggest_purity_refactoring(&violations);
749 assert_eq!(suggestions.len(), 1); }
751
752 #[test]
753 fn test_generate_framework_recommendation() {
754 let rec = generate_framework_recommendation("React", "hooks", 10);
755 assert!(rec.contains("React") || rec.contains("10") || rec.contains("hook"));
756
757 let rec2 = generate_framework_recommendation("Tokio", "async", 5);
758 assert!(rec2.contains("async") || rec2.contains("5"));
759 }
760}