garbage_code_hunter/
hall_of_shame.rs1use crate::analyzer::{CodeIssue, Severity};
2use 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, pub hall_of_shame: Vec<ShameEntry>, }
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 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 self.update_pattern_stats(&issue.rule_name, &issue.severity, &file_path);
70 }
71
72 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 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 let hall_of_shame = sorted_entries.into_iter().take(10).collect();
114
115 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}