garbage_code_hunter/
reporter.rs

1use colored::*;
2use std::collections::HashMap;
3
4use crate::analyzer::{CodeIssue, Severity};
5use crate::i18n::I18n;
6use crate::scoring::{CodeScorer, CodeQualityScore};
7
8pub struct Reporter {
9    harsh_mode: bool,
10    savage_mode: bool,
11    verbose: bool,
12    top_files: usize,
13    max_issues_per_file: usize,
14    summary_only: bool,
15    markdown: bool,
16    i18n: I18n,
17}
18
19impl Reporter {
20    pub fn new(
21        harsh_mode: bool,
22        savage_mode: bool,
23        verbose: bool,
24        top_files: usize,
25        max_issues_per_file: usize,
26        summary_only: bool,
27        markdown: bool,
28        lang: &str,
29    ) -> Self {
30        Self {
31            harsh_mode,
32            savage_mode,
33            verbose,
34            top_files,
35            max_issues_per_file,
36            summary_only,
37            markdown,
38            i18n: I18n::new(lang),
39        }
40    }
41
42    #[allow(dead_code)]
43    pub fn report(&self, issues: Vec<CodeIssue>) {
44        self.report_with_metrics(issues, 1, 100);
45    }
46
47    pub fn report_with_metrics(&self, mut issues: Vec<CodeIssue>, file_count: usize, total_lines: usize) {
48        // 计算代码质量评分
49        let scorer = CodeScorer::new();
50        let quality_score = scorer.calculate_score(&issues, file_count, total_lines);
51
52        if issues.is_empty() {
53            self.print_clean_code_message_with_score(&quality_score);
54            return;
55        }
56
57        // 按严重程度排序
58        issues.sort_by(|a, b| {
59            let severity_order = |s: &Severity| match s {
60                Severity::Nuclear => 3,
61                Severity::Spicy => 2,
62                Severity::Mild => 1,
63            };
64            severity_order(&b.severity).cmp(&severity_order(&a.severity))
65        });
66
67        // 如果是 harsh 模式,只显示最严重的问题
68        if self.harsh_mode {
69            issues.retain(|issue| matches!(issue.severity, Severity::Nuclear | Severity::Spicy));
70        }
71
72        if self.markdown {
73            self.print_markdown_report(&issues);
74        } else {
75            if !self.summary_only {
76                self.print_header(&issues);
77                self.print_quality_score(&quality_score);
78                if self.verbose {
79                    self.print_detailed_analysis(&issues);
80                }
81                self.print_top_files(&issues);
82                self.print_issues(&issues);
83            }
84            self.print_summary_with_score(&issues, &quality_score);
85            if !self.summary_only {
86                self.print_footer(&issues);
87            }
88        }
89    }
90
91    #[allow(dead_code)]
92    fn print_clean_code_message(&self) {
93        if self.markdown {
94            println!("# {}", self.i18n.get("title"));
95            println!();
96            println!("{}", self.i18n.get("clean_code"));
97            println!();
98            println!("{}", self.i18n.get("clean_code_warning"));
99        } else {
100            println!("{}", self.i18n.get("clean_code").bright_green().bold());
101            println!("{}", self.i18n.get("clean_code_warning").yellow());
102        }
103    }
104
105    fn print_clean_code_message_with_score(&self, quality_score: &CodeQualityScore) {
106        if self.markdown {
107            println!("# {}", self.i18n.get("title"));
108            println!();
109            println!("## 🏆 代码质量评分");
110            println!();
111            println!("**评分**: {:.1}/100 {}", quality_score.total_score, quality_score.quality_level.emoji());
112            println!("**等级**: {}", quality_score.quality_level.description(&self.i18n.lang));
113            println!();
114            println!("{}", self.i18n.get("clean_code"));
115            println!();
116            println!("{}", self.i18n.get("clean_code_warning"));
117        } else {
118            println!("{}", self.i18n.get("clean_code").bright_green().bold());
119            println!();
120            println!("{} 代码质量评分: {:.1}/100 {}", 
121                "🏆".bright_yellow(),
122                quality_score.total_score.to_string().bright_green().bold(),
123                quality_score.quality_level.emoji()
124            );
125            println!("{} 质量等级: {}", 
126                "📊".bright_blue(),
127                quality_score.quality_level.description(&self.i18n.lang).bright_green().bold()
128            );
129            println!("{}", self.i18n.get("clean_code_warning").yellow());
130        }
131    }
132
133    fn print_quality_score(&self, quality_score: &CodeQualityScore) {
134        println!("{}", "🏆 代码质量评分".bright_yellow().bold());
135        println!("{}", "─".repeat(50).bright_black());
136        
137        let score_color = match quality_score.quality_level {
138            crate::scoring::QualityLevel::Excellent => quality_score.total_score.to_string().bright_green().bold(),
139            crate::scoring::QualityLevel::Good => quality_score.total_score.to_string().green(),
140            crate::scoring::QualityLevel::Average => quality_score.total_score.to_string().yellow(),
141            crate::scoring::QualityLevel::Poor => quality_score.total_score.to_string().red(),
142            crate::scoring::QualityLevel::Terrible => quality_score.total_score.to_string().bright_red().bold(),
143        };
144
145        println!("   📊 总分: {:.1}/100 {}", 
146            score_color,
147            quality_score.quality_level.emoji()
148        );
149        println!("   🎯 等级: {}", 
150            quality_score.quality_level.description(&self.i18n.lang).bright_white().bold()
151        );
152        
153        if quality_score.total_lines > 0 {
154            println!("   📏 代码行数: {}", quality_score.total_lines.to_string().cyan());
155            println!("   📁 文件数量: {}", quality_score.file_count.to_string().cyan());
156            println!("   🔍 问题密度: {:.2} 问题/千行", quality_score.issue_density.to_string().cyan());
157        }
158
159        // 显示严重程度分布
160        if quality_score.severity_distribution.nuclear > 0 || 
161           quality_score.severity_distribution.spicy > 0 || 
162           quality_score.severity_distribution.mild > 0 {
163            println!();
164            println!("   🎭 问题分布:");
165            if quality_score.severity_distribution.nuclear > 0 {
166                println!("      💥 核弹级: {}", quality_score.severity_distribution.nuclear.to_string().red().bold());
167            }
168            if quality_score.severity_distribution.spicy > 0 {
169                println!("      🌶️  严重: {}", quality_score.severity_distribution.spicy.to_string().yellow());
170            }
171            if quality_score.severity_distribution.mild > 0 {
172                println!("      😐 轻微: {}", quality_score.severity_distribution.mild.to_string().blue());
173            }
174        }
175
176        // 显示分类得分(如果有的话)
177        if !quality_score.category_scores.is_empty() && self.verbose {
178            println!();
179            println!("   📋 分类得分:");
180            let mut sorted_categories: Vec<_> = quality_score.category_scores.iter().collect();
181            sorted_categories.sort_by(|a, b| b.1.partial_cmp(a.1).unwrap_or(std::cmp::Ordering::Equal));
182            
183            for (category, score) in sorted_categories.iter().take(5) {
184                let category_name = match category.as_str() {
185                    "naming" => "命名规范",
186                    "complexity" => "复杂度",
187                    "rust-basics" => "Rust基础",
188                    "advanced-rust" => "高级特性",
189                    "rust-features" => "Rust功能",
190                    "structure" => "代码结构",
191                    _ => category,
192                };
193                println!("      {} {:.1}", category_name.cyan(), score.to_string().yellow());
194            }
195        }
196        
197        println!();
198    }
199
200    fn print_header(&self, issues: &[CodeIssue]) {
201        let total = issues.len();
202        let nuclear = issues
203            .iter()
204            .filter(|i| matches!(i.severity, Severity::Nuclear))
205            .count();
206        let spicy = issues
207            .iter()
208            .filter(|i| matches!(i.severity, Severity::Spicy))
209            .count();
210        let mild = issues
211            .iter()
212            .filter(|i| matches!(i.severity, Severity::Mild))
213            .count();
214
215        println!("{}", self.i18n.get("title").bright_red().bold());
216        println!("{}", self.i18n.get("preparing").yellow());
217        println!();
218
219        println!("{}", self.i18n.get("report_title").bright_red().bold());
220        println!("{}", "─".repeat(50).bright_black());
221
222        if self.savage_mode {
223            println!("{}", self.i18n.get("found_issues").red().bold());
224        } else {
225            println!("{}", self.i18n.get("found_issues").yellow());
226        }
227
228        println!();
229        println!("{}", self.i18n.get("statistics"));
230        println!(
231            "   {} {}",
232            nuclear.to_string().red().bold(),
233            self.i18n.get("nuclear_issues")
234        );
235        println!(
236            "   {} {}",
237            spicy.to_string().yellow().bold(),
238            self.i18n.get("spicy_issues")
239        );
240        println!(
241            "   {} {}",
242            mild.to_string().blue().bold(),
243            self.i18n.get("mild_issues")
244        );
245        println!(
246            "   {} {}",
247            total.to_string().bright_white().bold(),
248            self.i18n.get("total")
249        );
250        println!();
251    }
252
253    fn print_issues(&self, issues: &[CodeIssue]) {
254        let mut file_groups: HashMap<String, Vec<&CodeIssue>> = HashMap::new();
255
256        for issue in issues {
257            let file_name = issue
258                .file_path
259                .file_name()
260                .unwrap_or_default()
261                .to_string_lossy()
262                .to_string();
263            file_groups.entry(file_name).or_default().push(issue);
264        }
265
266        for (file_name, file_issues) in file_groups {
267            println!("{} {}", "📁".bright_blue(), file_name.bright_blue().bold());
268
269            let issues_to_show = if self.max_issues_per_file > 0 {
270                file_issues
271                    .into_iter()
272                    .take(self.max_issues_per_file)
273                    .collect::<Vec<_>>()
274            } else {
275                file_issues
276            };
277
278            for issue in issues_to_show {
279                self.print_issue(issue);
280            }
281            println!();
282        }
283    }
284
285    fn print_issue(&self, issue: &CodeIssue) {
286        let severity_icon = match issue.severity {
287            Severity::Nuclear => "💥",
288            Severity::Spicy => "🌶️",
289            Severity::Mild => "😐",
290        };
291
292        let line_info = format!("{}:{}", issue.line, issue.column).bright_black();
293
294        
295        let messages = self.i18n.get_roast_messages(&issue.rule_name);
296        let message = if !messages.is_empty() {
297            messages[issue.line % messages.len()].clone()
298        } else {
299            issue.message.clone()
300        };
301
302     
303        let final_message = if self.savage_mode {
304            self.make_message_savage(&message)
305        } else {
306            message
307        };
308
309        let colored_message = match issue.severity {
310            Severity::Nuclear => final_message.red().bold(),
311            Severity::Spicy => final_message.yellow(),
312            Severity::Mild => final_message.blue(),
313        };
314
315        println!("  {} {} {}", severity_icon, line_info, colored_message);
316    }
317
318    fn make_message_savage(&self, message: &str) -> String {
319        let savage_prefixes = vec![
320            "🔥 严重警告:",
321            "💀 代码死刑:",
322            "🗑️ 垃圾警报:",
323            "😱 恐怖发现:",
324            "🤮 令人作呕:",
325        ];
326
327        let prefix = savage_prefixes[message.len() % savage_prefixes.len()];
328        format!("{} {}", prefix, message)
329    }
330
331    fn print_summary_with_score(&self, issues: &[CodeIssue], quality_score: &CodeQualityScore) {
332        let _nuclear_count = issues
333            .iter()
334            .filter(|i| matches!(i.severity, Severity::Nuclear))
335            .count();
336        let total_count = issues.len();
337
338        println!("{}", self.i18n.get("summary").bright_white().bold());
339        println!("{}", "─".repeat(50).bright_black());
340
341        // 显示评分总结
342        let score_summary = match quality_score.quality_level {
343            crate::scoring::QualityLevel::Excellent => {
344                match self.i18n.lang.as_str() {
345                    "zh-CN" => format!("🏆 代码质量优秀!评分: {:.1}/100", quality_score.total_score),
346                    _ => format!("🏆 Excellent code quality! Score: {:.1}/100", quality_score.total_score),
347                }
348            },
349            crate::scoring::QualityLevel::Good => {
350                match self.i18n.lang.as_str() {
351                    "zh-CN" => format!("👍 代码质量良好,评分: {:.1}/100", quality_score.total_score),
352                    _ => format!("👍 Good code quality, Score: {:.1}/100", quality_score.total_score),
353                }
354            },
355            crate::scoring::QualityLevel::Average => {
356                match self.i18n.lang.as_str() {
357                    "zh-CN" => format!("😐 代码质量一般,评分: {:.1}/100,还有改进空间", quality_score.total_score),
358                    _ => format!("😐 Average code quality, Score: {:.1}/100, room for improvement", quality_score.total_score),
359                }
360            },
361            crate::scoring::QualityLevel::Poor => {
362                match self.i18n.lang.as_str() {
363                    "zh-CN" => format!("😟 代码质量较差,评分: {:.1}/100,建议重构", quality_score.total_score),
364                    _ => format!("😟 Poor code quality, Score: {:.1}/100, refactoring recommended", quality_score.total_score),
365                }
366            },
367            crate::scoring::QualityLevel::Terrible => {
368                match self.i18n.lang.as_str() {
369                    "zh-CN" => format!("💀 代码质量糟糕,评分: {:.1}/100,急需重写", quality_score.total_score),
370                    _ => format!("💀 Terrible code quality, Score: {:.1}/100, rewrite urgently needed", quality_score.total_score),
371                }
372            },
373        };
374
375        let score_color = match quality_score.quality_level {
376            crate::scoring::QualityLevel::Excellent => score_summary.bright_green().bold(),
377            crate::scoring::QualityLevel::Good => score_summary.green(),
378            crate::scoring::QualityLevel::Average => score_summary.yellow(),
379            crate::scoring::QualityLevel::Poor => score_summary.red(),
380            crate::scoring::QualityLevel::Terrible => score_summary.bright_red().bold(),
381        };
382
383        println!("{}", score_color);
384        println!();
385
386        // 原有的总结逻辑
387        let _nuclear_count = issues
388            .iter()
389            .filter(|i| matches!(i.severity, Severity::Nuclear))
390            .count();
391        let _total_count = issues.len();
392
393        println!("{}", self.i18n.get("summary").bright_white().bold());
394        println!("{}", "─".repeat(50).bright_black());
395
396        let summary_message = if _nuclear_count > 0 {
397            if self.savage_mode {
398                match self.i18n.lang.as_str() {
399                    "zh-CN" => "你的代码质量堪忧,建议重新学习编程基础 💀".to_string(),
400                    _ => "Your code quality is concerning, suggest learning programming basics again 💀".to_string(),
401                }
402            } else {
403                match self.i18n.lang.as_str() {
404                    "zh-CN" => "发现了一些严重问题,建议优先修复核弹级问题 🔥".to_string(),
405                    _ => "Found some serious issues, suggest fixing nuclear problems first 🔥"
406                        .to_string(),
407                }
408            }
409        } else if total_count > 10 {
410            match self.i18n.lang.as_str() {
411                "zh-CN" => "问题有点多,建议分批修复 📝".to_string(),
412                _ => "Quite a few issues, suggest fixing them in batches 📝".to_string(),
413            }
414        } else {
415            match self.i18n.lang.as_str() {
416                "zh-CN" => "问题不多,稍微改进一下就好了 👍".to_string(),
417                _ => "Not many issues, just need some minor improvements 👍".to_string(),
418            }
419        };
420
421        let color = if _nuclear_count > 0 {
422            summary_message.red().bold()
423        } else if _total_count > 10 {
424            summary_message.yellow()
425        } else {
426            summary_message.green()
427        };
428
429        println!("{}", color);
430    }
431
432    fn print_footer(&self, issues: &[CodeIssue]) {
433        println!();
434        println!("{}", self.i18n.get("suggestions").bright_cyan().bold());
435        println!("{}", "─".repeat(50).bright_black());
436
437        let rule_names: Vec<String> = issues
438            .iter()
439            .map(|issue| issue.rule_name.clone())
440            .collect::<std::collections::HashSet<_>>()
441            .into_iter()
442            .collect();
443
444        let suggestions = self.i18n.get_suggestions(&rule_names);
445        for suggestion in suggestions {
446            println!("   {}", suggestion.cyan());
447        }
448
449        println!();
450        let footer_message = if self.savage_mode {
451            match self.i18n.lang.as_str() {
452                "zh-CN" => "记住:写垃圾代码容易,写好代码需要用心 💪".to_string(),
453                _ => "Remember: writing garbage code is easy, writing good code requires effort 💪"
454                    .to_string(),
455            }
456        } else {
457            self.i18n.get("keep_improving")
458        };
459
460        let color = if self.savage_mode {
461            footer_message.bright_red().bold()
462        } else {
463            footer_message.bright_green().bold()
464        };
465
466        println!("{}", color);
467    }
468
469    fn print_top_files(&self, issues: &[CodeIssue]) {
470        if self.top_files == 0 {
471            return;
472        }
473
474        let mut file_issue_counts: HashMap<String, usize> = HashMap::new();
475        for issue in issues {
476            let file_name = issue
477                .file_path
478                .file_name()
479                .unwrap_or_default()
480                .to_string_lossy()
481                .to_string();
482            *file_issue_counts.entry(file_name).or_insert(0) += 1;
483        }
484
485        let mut sorted_files: Vec<_> = file_issue_counts.into_iter().collect();
486        sorted_files.sort_by(|a, b| b.1.cmp(&a.1));
487
488        if !sorted_files.is_empty() {
489            println!("{}", self.i18n.get("top_files").bright_yellow().bold());
490            println!("{}", "─".repeat(50).bright_black());
491
492            for (i, (file_name, count)) in sorted_files.iter().take(self.top_files).enumerate() {
493                let rank = format!("{}.", i + 1);
494                println!(
495                    "   {} {} ({} issues)",
496                    rank.bright_white(),
497                    file_name.bright_blue(),
498                    count.to_string().red()
499                );
500            }
501            println!();
502        }
503    }
504
505    fn print_detailed_analysis(&self, issues: &[CodeIssue]) {
506        println!(
507            "{}",
508            self.i18n.get("detailed_analysis").bright_magenta().bold()
509        );
510        println!("{}", "─".repeat(50).bright_black());
511
512        let mut rule_stats: HashMap<String, usize> = HashMap::new();
513        for issue in issues {
514            *rule_stats.entry(issue.rule_name.clone()).or_insert(0) += 1;
515        }
516
517        let rule_descriptions = match self.i18n.lang.as_str() {
518            "zh-CN" => [
519                ("terrible-naming", "糟糕的变量命名"),
520                ("single-letter-variable", "单字母变量"),
521                ("deep-nesting", "过度嵌套"),
522                ("long-function", "超长函数"),
523                ("unwrap-abuse", "unwrap() 滥用"),
524                ("unnecessary-clone", "不必要的 clone()"),
525            ]
526            .iter()
527            .cloned()
528            .collect::<HashMap<_, _>>(),
529            _ => [
530                ("terrible-naming", "Terrible variable naming"),
531                ("single-letter-variable", "Single letter variables"),
532                ("deep-nesting", "Deep nesting"),
533                ("long-function", "Long functions"),
534                ("unwrap-abuse", "unwrap() abuse"),
535                ("unnecessary-clone", "Unnecessary clone()"),
536            ]
537            .iter()
538            .cloned()
539            .collect::<HashMap<_, _>>(),
540        };
541
542        for (rule_name, count) in rule_stats {
543            let rule_name_str = rule_name.as_str();
544            let description = rule_descriptions
545                .get(rule_name_str)
546                .unwrap_or(&rule_name_str);
547            println!(
548                "   📌 {}: {} issues",
549                description.cyan(),
550                count.to_string().yellow()
551            );
552        }
553        println!();
554    }
555
556    fn print_markdown_report(&self, issues: &[CodeIssue]) {
557        let total = issues.len();
558        let nuclear = issues
559            .iter()
560            .filter(|i| matches!(i.severity, Severity::Nuclear))
561            .count();
562        let spicy = issues
563            .iter()
564            .filter(|i| matches!(i.severity, Severity::Spicy))
565            .count();
566        let mild = issues
567            .iter()
568            .filter(|i| matches!(i.severity, Severity::Mild))
569            .count();
570
571        println!("# {}", self.i18n.get("title"));
572        println!();
573        println!("## {}", self.i18n.get("statistics"));
574        println!();
575        println!("| Severity | Count | Description |");
576        println!("| --- | --- | --- |");
577        println!(
578            "| 🔥 Nuclear | {} | {} |",
579            nuclear,
580            self.i18n.get("nuclear_issues")
581        );
582        println!(
583            "| 🌶️ Spicy | {} | {} |",
584            spicy,
585            self.i18n.get("spicy_issues")
586        );
587        println!("| 😐 Mild | {} | {} |", mild, self.i18n.get("mild_issues"));
588        println!(
589            "| **Total** | **{}** | **{}** |",
590            total,
591            self.i18n.get("total")
592        );
593        println!();
594
595        if self.verbose {
596            println!("## {}", self.i18n.get("detailed_analysis"));
597            println!();
598
599            let mut rule_stats: HashMap<String, usize> = HashMap::new();
600            for issue in issues {
601                *rule_stats.entry(issue.rule_name.clone()).or_insert(0) += 1;
602            }
603
604            for (rule_name, count) in rule_stats {
605                println!("- **{}**: {} issues", rule_name, count);
606            }
607            println!();
608        }
609
610        println!("## Issues by File");
611        println!();
612
613        let mut file_groups: HashMap<String, Vec<&CodeIssue>> = HashMap::new();
614        for issue in issues {
615            let file_name = issue
616                .file_path
617                .file_name()
618                .unwrap_or_default()
619                .to_string_lossy()
620                .to_string();
621            file_groups.entry(file_name).or_default().push(issue);
622        }
623
624        for (file_name, file_issues) in file_groups {
625            println!("### 📁 {}", file_name);
626            println!();
627
628            let issues_to_show = if self.max_issues_per_file > 0 {
629                file_issues
630                    .into_iter()
631                    .take(self.max_issues_per_file)
632                    .collect::<Vec<_>>()
633            } else {
634                file_issues
635            };
636
637            for issue in issues_to_show {
638                let severity_icon = match issue.severity {
639                    Severity::Nuclear => "💥",
640                    Severity::Spicy => "🌶️",
641                    Severity::Mild => "😐",
642                };
643
644                let messages = self.i18n.get_roast_messages(&issue.rule_name);
645                let message = if !messages.is_empty() {
646                    messages[issue.line % messages.len()].clone()
647                } else {
648                    issue.message.clone()
649                };
650
651                println!(
652                    "- {} **Line {}:{}** - {}",
653                    severity_icon, issue.line, issue.column, message
654                );
655            }
656            println!();
657        }
658
659        println!("## {}", self.i18n.get("suggestions"));
660        println!();
661
662        let rule_names: Vec<String> = issues
663            .iter()
664            .map(|issue| issue.rule_name.clone())
665            .collect::<std::collections::HashSet<_>>()
666            .into_iter()
667            .collect();
668
669        let suggestions = self.i18n.get_suggestions(&rule_names);
670        for suggestion in suggestions {
671            println!("- {}", suggestion);
672        }
673    }
674}