Skip to main content

garbage_code_hunter/
hall_of_shame.rs

1use crate::analyzer::{CodeIssue, Severity};
2/// Hall of Shame - tracks and ranks the worst code patterns and files
3use std::collections::HashMap;
4use std::path::PathBuf;
5
6#[derive(Debug, Clone)]
7pub struct ShameEntry {
8    pub file_path: PathBuf,
9    pub total_issues: usize,
10    pub shame_score: f64,
11}
12
13#[derive(Debug, Clone)]
14pub struct PatternStats {
15    pub rule_name: String,
16    pub count: usize,
17    pub severity_distribution: HashMap<Severity, usize>,
18    pub example_files: Vec<PathBuf>,
19}
20
21#[derive(Debug, Clone)]
22pub struct ProjectShameStats {
23    pub total_files_analyzed: usize,
24    pub total_issues: usize,
25    pub garbage_density: f64,           // issues per 1000 lines of code
26    pub hall_of_shame: Vec<ShameEntry>, // worst files
27}
28
29pub struct HallOfShame {
30    entries: Vec<ShameEntry>,
31    pattern_stats: HashMap<String, PatternStats>,
32    total_lines: usize,
33}
34
35impl HallOfShame {
36    pub fn new() -> Self {
37        Self {
38            entries: Vec::new(),
39            pattern_stats: HashMap::new(),
40            total_lines: 0,
41        }
42    }
43
44    pub fn add_file_analysis(
45        &mut self,
46        file_path: PathBuf,
47        issues: &[CodeIssue],
48        file_lines: usize,
49    ) {
50        self.total_lines += file_lines;
51
52        if issues.is_empty() {
53            return;
54        }
55
56        let mut nuclear_count = 0;
57        let mut spicy_count = 0;
58        let mut mild_count = 0;
59
60        // Analyze issues for this file
61        for issue in issues {
62            match issue.severity {
63                Severity::Nuclear => nuclear_count += 1,
64                Severity::Spicy => spicy_count += 1,
65                Severity::Mild => mild_count += 1,
66            }
67
68            // Track pattern statistics
69            self.update_pattern_stats(&issue.rule_name, &issue.severity, &file_path);
70        }
71
72        // Calculate shame score (weighted by severity)
73        let shame_score =
74            (nuclear_count as f64 * 10.0) + (spicy_count as f64 * 3.0) + (mild_count as f64 * 1.0);
75
76        let entry = ShameEntry {
77            file_path,
78            total_issues: issues.len(),
79            shame_score,
80        };
81
82        self.entries.push(entry);
83    }
84
85    fn update_pattern_stats(&mut self, rule_name: &str, severity: &Severity, file_path: &PathBuf) {
86        let stats = self
87            .pattern_stats
88            .entry(rule_name.to_string())
89            .or_insert_with(|| PatternStats {
90                rule_name: rule_name.to_string(),
91                count: 0,
92                severity_distribution: HashMap::new(),
93                example_files: Vec::new(),
94            });
95
96        stats.count += 1;
97        *stats
98            .severity_distribution
99            .entry(severity.clone())
100            .or_insert(0) += 1;
101
102        // Add file to examples if not already present and we have less than 5 examples
103        if stats.example_files.len() < 5 && !stats.example_files.contains(file_path) {
104            stats.example_files.push(file_path.clone());
105        }
106    }
107
108    pub fn generate_shame_report(&self) -> ProjectShameStats {
109        let mut sorted_entries = self.entries.clone();
110        sorted_entries.sort_by(|a, b| b.shame_score.partial_cmp(&a.shame_score).unwrap());
111
112        // Take top 10 worst files
113        let hall_of_shame = sorted_entries.into_iter().take(10).collect();
114
115        // Calculate garbage density (issues per 1000 lines)
116        let total_issues: usize = self.entries.iter().map(|e| e.total_issues).sum();
117        let garbage_density = if self.total_lines > 0 {
118            (total_issues as f64 / self.total_lines as f64) * 1000.0
119        } else {
120            0.0
121        };
122
123        ProjectShameStats {
124            total_files_analyzed: self.entries.len(),
125            total_issues,
126            garbage_density,
127            hall_of_shame,
128        }
129    }
130}
131
132impl Default for HallOfShame {
133    fn default() -> Self {
134        Self::new()
135    }
136}