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 nuclear_issues: usize,
11    pub spicy_issues: usize,
12    pub mild_issues: usize,
13    pub shame_score: f64,
14    pub _worst_offenses: Vec<String>,
15}
16
17#[derive(Debug, Clone)]
18pub struct PatternStats {
19    pub rule_name: String,
20    pub count: usize,
21    pub severity_distribution: HashMap<Severity, usize>,
22    pub example_files: Vec<PathBuf>,
23}
24
25#[derive(Debug, Clone)]
26pub struct ProjectShameStats {
27    pub total_files_analyzed: usize,
28    pub total_issues: usize,
29    pub garbage_density: f64, // issues per 1000 lines of code
30    pub most_common_patterns: Vec<PatternStats>,
31    pub hall_of_shame: Vec<ShameEntry>, // worst files
32    pub _shame_categories: HashMap<String, usize>,
33}
34
35pub struct HallOfShame {
36    entries: Vec<ShameEntry>,
37    pattern_stats: HashMap<String, PatternStats>,
38    total_lines: usize,
39}
40
41impl HallOfShame {
42    pub fn new() -> Self {
43        Self {
44            entries: Vec::new(),
45            pattern_stats: HashMap::new(),
46            total_lines: 0,
47        }
48    }
49
50    pub fn add_file_analysis(
51        &mut self,
52        file_path: PathBuf,
53        issues: &[CodeIssue],
54        file_lines: usize,
55    ) {
56        self.total_lines += file_lines;
57
58        if issues.is_empty() {
59            return;
60        }
61
62        let mut nuclear_count = 0;
63        let mut spicy_count = 0;
64        let mut mild_count = 0;
65        let mut worst_offenses = Vec::new();
66
67        // Analyze issues for this file
68        for issue in issues {
69            match issue.severity {
70                Severity::Nuclear => nuclear_count += 1,
71                Severity::Spicy => spicy_count += 1,
72                Severity::Mild => mild_count += 1,
73            }
74
75            // Track pattern statistics
76            self.update_pattern_stats(&issue.rule_name, &issue.severity, &file_path);
77
78            // Collect worst offenses (Nuclear and Spicy issues)
79            if matches!(issue.severity, Severity::Nuclear | Severity::Spicy) {
80                worst_offenses.push(format!("{}: {}", issue.rule_name, issue.message));
81            }
82        }
83
84        // Calculate shame score (weighted by severity)
85        let shame_score =
86            (nuclear_count as f64 * 10.0) + (spicy_count as f64 * 3.0) + (mild_count as f64 * 1.0);
87
88        let entry = ShameEntry {
89            file_path,
90            total_issues: issues.len(),
91            nuclear_issues: nuclear_count,
92            spicy_issues: spicy_count,
93            mild_issues: mild_count,
94            shame_score,
95            _worst_offenses: worst_offenses,
96        };
97
98        self.entries.push(entry);
99    }
100
101    fn update_pattern_stats(&mut self, rule_name: &str, severity: &Severity, file_path: &PathBuf) {
102        let stats = self
103            .pattern_stats
104            .entry(rule_name.to_string())
105            .or_insert_with(|| PatternStats {
106                rule_name: rule_name.to_string(),
107                count: 0,
108                severity_distribution: HashMap::new(),
109                example_files: Vec::new(),
110            });
111
112        stats.count += 1;
113        *stats
114            .severity_distribution
115            .entry(severity.clone())
116            .or_insert(0) += 1;
117
118        // Add file to examples if not already present and we have less than 5 examples
119        if stats.example_files.len() < 5 && !stats.example_files.contains(file_path) {
120            stats.example_files.push(file_path.clone());
121        }
122    }
123
124    pub fn generate_shame_report(&self) -> ProjectShameStats {
125        let mut sorted_entries = self.entries.clone();
126        sorted_entries.sort_by(|a, b| b.shame_score.partial_cmp(&a.shame_score).unwrap());
127
128        // Take top 10 worst files
129        let hall_of_shame = sorted_entries.into_iter().take(10).collect();
130
131        // Sort patterns by frequency
132        let mut most_common_patterns: Vec<PatternStats> =
133            self.pattern_stats.values().cloned().collect();
134        most_common_patterns.sort_by(|a, b| b.count.cmp(&a.count));
135
136        // Calculate garbage density (issues per 1000 lines)
137        let total_issues: usize = self.entries.iter().map(|e| e.total_issues).sum();
138        let garbage_density = if self.total_lines > 0 {
139            (total_issues as f64 / self.total_lines as f64) * 1000.0
140        } else {
141            0.0
142        };
143
144        // Categorize shame by rule types
145        let mut shame_categories = HashMap::new();
146        for pattern in &most_common_patterns {
147            let category = self.categorize_rule(&pattern.rule_name);
148            *shame_categories.entry(category).or_insert(0) += pattern.count;
149        }
150
151        ProjectShameStats {
152            total_files_analyzed: self.entries.len(),
153            total_issues,
154            garbage_density,
155            most_common_patterns,
156            hall_of_shame,
157            _shame_categories: shame_categories,
158        }
159    }
160
161    fn categorize_rule(&self, rule_name: &str) -> String {
162        match rule_name {
163            name if name.contains("naming") => "Naming Issues".to_string(),
164            name if name.contains("complexity")
165                || name.contains("nesting")
166                || name.contains("function") =>
167            {
168                "Complexity Issues".to_string()
169            }
170            name if name.contains("unwrap")
171                || name.contains("panic")
172                || name.contains("string")
173                || name.contains("clone") =>
174            {
175                "Rust-specific Issues".to_string()
176            }
177            name if name.contains("println") || name.contains("todo") => {
178                "Student Code Issues".to_string()
179            }
180            name if name.contains("import") || name.contains("file") || name.contains("module") => {
181                "Structure Issues".to_string()
182            }
183            name if name.contains("magic") || name.contains("dead") || name.contains("comment") => {
184                "Code Smells".to_string()
185            }
186            _ => "Other Issues".to_string(),
187        }
188    }
189
190    #[allow(dead_code)]
191    pub fn get_worst_files(&self, limit: usize) -> Vec<&ShameEntry> {
192        let mut sorted_entries: Vec<&ShameEntry> = self.entries.iter().collect();
193        sorted_entries.sort_by(|a, b| b.shame_score.partial_cmp(&a.shame_score).unwrap());
194        sorted_entries.into_iter().take(limit).collect()
195    }
196
197    #[allow(dead_code)]
198    pub fn get_most_common_patterns(&self, limit: usize) -> Vec<&PatternStats> {
199        let mut patterns: Vec<&PatternStats> = self.pattern_stats.values().collect();
200        patterns.sort_by(|a, b| b.count.cmp(&a.count));
201        patterns.into_iter().take(limit).collect()
202    }
203
204    #[allow(dead_code)]
205    pub fn generate_shame_heatmap(&self) -> HashMap<String, f64> {
206        // Generate a "heatmap" of shame density by file extension or directory
207        let mut heatmap = HashMap::new();
208
209        for entry in &self.entries {
210            let key = if let Some(parent) = entry.file_path.parent() {
211                parent.to_string_lossy().to_string()
212            } else {
213                "root".to_string()
214            };
215
216            let current_shame = heatmap.get(&key).unwrap_or(&0.0);
217            heatmap.insert(key, current_shame + entry.shame_score);
218        }
219
220        heatmap
221    }
222
223    pub fn get_improvement_suggestions(&self, lang: &str) -> Vec<String> {
224        let stats = self.generate_shame_report();
225        let mut suggestions = Vec::new();
226
227        // Suggest improvements based on most common issues
228        for pattern in stats.most_common_patterns.iter().take(3) {
229            match pattern.rule_name.as_str() {
230                name if name.contains("naming") => {
231                    if lang == "zh-CN" {
232                        suggestions.push(
233                            "🏷️ 重点改进变量和函数命名 - 清晰的名称让代码自文档化".to_string(),
234                        );
235                    } else {
236                        suggestions.push("🏷️ Focus on improving variable and function naming - clear names make code self-documenting".to_string());
237                    }
238                }
239                name if name.contains("unwrap") => {
240                    if lang == "zh-CN" {
241                        suggestions.push(
242                            "🛡️ 用适当的错误处理替换 unwrap() 调用,使用 Result 和 Option"
243                                .to_string(),
244                        );
245                    } else {
246                        suggestions.push("🛡️ Replace unwrap() calls with proper error handling using Result and Option".to_string());
247                    }
248                }
249                name if name.contains("complexity") || name.contains("nesting") => {
250                    if lang == "zh-CN" {
251                        suggestions.push("🧩 将复杂函数分解为更小、更专注的函数".to_string());
252                    } else {
253                        suggestions.push(
254                            "🧩 Break down complex functions into smaller, focused functions"
255                                .to_string(),
256                        );
257                    }
258                }
259                name if name.contains("println") => {
260                    if lang == "zh-CN" {
261                        suggestions
262                            .push("🔍 移除调试用的 println! 语句,使用适当的日志记录".to_string());
263                    } else {
264                        suggestions.push(
265                            "🔍 Remove debug println! statements and use proper logging instead"
266                                .to_string(),
267                        );
268                    }
269                }
270                name if name.contains("clone") => {
271                    if lang == "zh-CN" {
272                        suggestions
273                            .push("⚡ 通过使用引用和理解所有权来减少不必要的克隆".to_string());
274                    } else {
275                        suggestions.push("⚡ Reduce unnecessary clones by using references and understanding ownership".to_string());
276                    }
277                }
278                _ => {
279                    if lang == "zh-CN" {
280                        suggestions.push(format!(
281                            "🔧 处理 {} 问题,发现 {} 次",
282                            pattern.rule_name, pattern.count
283                        ));
284                    } else {
285                        suggestions.push(format!(
286                            "🔧 Address {} issues found {} times",
287                            pattern.rule_name, pattern.count
288                        ));
289                    }
290                }
291            }
292        }
293
294        if stats.garbage_density > 50.0 {
295            if lang == "zh-CN" {
296                suggestions.push("📊 检测到高问题密度 - 考虑系统性重构方法".to_string());
297            } else {
298                suggestions.push(
299                    "📊 High issue density detected - consider a systematic refactoring approach"
300                        .to_string(),
301                );
302            }
303        }
304
305        if suggestions.is_empty() {
306            if lang == "zh-CN" {
307                suggestions.push("🎉 干得好!你的代码质量看起来不错!".to_string());
308            } else {
309                suggestions.push("🎉 Great job! Your code quality is looking good!".to_string());
310            }
311        }
312
313        suggestions
314    }
315}
316
317impl Default for HallOfShame {
318    fn default() -> Self {
319        Self::new()
320    }
321}
322
323/// Generate anonymous team member statistics (for team environments)
324#[allow(dead_code)]
325pub fn generate_anonymous_stats(shame_entries: &[ShameEntry]) -> HashMap<String, usize> {
326    // This would typically integrate with git blame or similar
327    // For now, we'll create a simple hash-based anonymization
328    use std::collections::hash_map::DefaultHasher;
329    use std::hash::{Hash, Hasher};
330
331    let mut team_stats = HashMap::new();
332
333    for entry in shame_entries {
334        // Create anonymous identifier based on file path
335        let mut hasher = DefaultHasher::new();
336        entry.file_path.hash(&mut hasher);
337        let anonymous_id = format!("Developer_{}", hasher.finish() % 100);
338
339        *team_stats.entry(anonymous_id).or_insert(0) += entry.total_issues;
340    }
341
342    team_stats
343}