1use 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 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, pub most_common_patterns: Vec<PatternStats>,
31 pub hall_of_shame: Vec<ShameEntry>, 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 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 self.update_pattern_stats(&issue.rule_name, &issue.severity, &file_path);
77
78 if matches!(issue.severity, Severity::Nuclear | Severity::Spicy) {
80 worst_offenses.push(format!("{}: {}", issue.rule_name, issue.message));
81 }
82 }
83
84 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 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 let hall_of_shame = sorted_entries.into_iter().take(10).collect();
130
131 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 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 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 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 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#[allow(dead_code)]
325pub fn generate_anonymous_stats(shame_entries: &[ShameEntry]) -> HashMap<String, usize> {
326 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 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}