1#[allow(dead_code)]
2use colored::*;
3use std::collections::HashMap;
4
5use crate::analyzer::{CodeIssue, Severity};
6use crate::i18n::I18n;
7use crate::scoring::{CodeQualityScore, CodeScorer};
8
9pub struct Reporter {
10 harsh_mode: bool,
11 savage_mode: bool,
12 verbose: bool,
13 top_files: usize,
14 max_issues_per_file: usize,
15 summary_only: bool,
16 markdown: bool,
17 i18n: I18n,
18}
19
20#[allow(dead_code)]
21impl Reporter {
22 #[allow(clippy::too_many_arguments)]
23 pub fn new(
24 harsh_mode: bool,
25 savage_mode: bool,
26 verbose: bool,
27 top_files: usize,
28 max_issues_per_file: usize,
29 summary_only: bool,
30 markdown: bool,
31 lang: &str,
32 ) -> Self {
33 Self {
34 harsh_mode,
35 savage_mode,
36 verbose,
37 top_files,
38 max_issues_per_file,
39 summary_only,
40 markdown,
41 i18n: I18n::new(lang),
42 }
43 }
44
45 #[allow(dead_code)]
46 pub fn report(&self, issues: Vec<CodeIssue>) {
47 self.report_with_metrics(issues, 1, 100);
48 }
49
50 pub fn report_with_metrics(
51 &self,
52 mut issues: Vec<CodeIssue>,
53 file_count: usize,
54 total_lines: usize,
55 ) {
56 let scorer = CodeScorer::new();
58 let quality_score = scorer.calculate_score(&issues, file_count, total_lines);
59
60 if issues.is_empty() {
61 self.print_clean_code_message_with_score(&quality_score);
62 return;
63 }
64
65 issues.sort_by(|a, b| {
67 let severity_order = |s: &Severity| match s {
68 Severity::Nuclear => 3,
69 Severity::Spicy => 2,
70 Severity::Mild => 1,
71 };
72 severity_order(&b.severity).cmp(&severity_order(&a.severity))
73 });
74
75 if self.harsh_mode {
77 issues.retain(|issue| matches!(issue.severity, Severity::Nuclear | Severity::Spicy));
78 }
79
80 if self.markdown {
81 self.print_markdown_report(&issues);
82 } else {
83 if !self.summary_only {
84 self.print_header(&issues);
85 self.print_quality_score(&quality_score);
86 if self.verbose {
87 self.print_detailed_analysis(&issues);
88 }
89 self.print_top_files(&issues);
90 self.print_issues(&issues);
91 }
92 self.print_summary_with_score(&issues, &quality_score);
93 if !self.summary_only {
94 self.print_footer(&issues);
95 }
96 }
97 }
98
99 #[allow(dead_code)]
100 fn print_clean_code_message(&self) {
101 if self.markdown {
102 println!("# {}", self.i18n.get("title"));
103 println!();
104 println!("{}", self.i18n.get("clean_code"));
105 println!();
106 println!("{}", self.i18n.get("clean_code_warning"));
107 } else {
108 println!("{}", self.i18n.get("clean_code").bright_green().bold());
109 println!("{}", self.i18n.get("clean_code_warning").yellow());
110 }
111 }
112
113 fn print_clean_code_message_with_score(&self, quality_score: &CodeQualityScore) {
114 if self.markdown {
115 println!("# {}", self.i18n.get("title"));
116 println!();
117 println!("## 🏆 代码质量评分");
118 println!();
119 println!(
120 "**评分**: {:.1}/100 {}",
121 quality_score.total_score,
122 quality_score.quality_level.emoji()
123 );
124 println!(
125 "**等级**: {}",
126 quality_score.quality_level.description(&self.i18n.lang)
127 );
128 println!();
129 println!("{}", self.i18n.get("clean_code"));
130 println!();
131 println!("{}", self.i18n.get("clean_code_warning"));
132 } else {
133 println!("{}", self.i18n.get("clean_code").bright_green().bold());
134 println!();
135 println!(
136 "{} 代码质量评分: {:.1}/100 {}",
137 "🏆".bright_yellow(),
138 quality_score.total_score.to_string().bright_green().bold(),
139 quality_score.quality_level.emoji()
140 );
141 println!(
142 "{} 质量等级: {}",
143 "📊".bright_blue(),
144 quality_score
145 .quality_level
146 .description(&self.i18n.lang)
147 .bright_green()
148 .bold()
149 );
150 println!("{}", self.i18n.get("clean_code_warning").yellow());
151 }
152 }
153
154 fn print_quality_score(&self, quality_score: &CodeQualityScore) {
155 let title = match self.i18n.lang.as_str() {
156 "zh-CN" => "🏆 代码质量评分",
157 _ => "🏆 Code Quality Score",
158 };
159 println!("{}", title.bright_yellow().bold());
160 println!("{}", "─".repeat(50).bright_black());
161
162 let _score_color = match quality_score.quality_level {
163 crate::scoring::QualityLevel::Excellent => {
164 quality_score.total_score.to_string().bright_green().bold()
165 }
166 crate::scoring::QualityLevel::Good => quality_score.total_score.to_string().green(),
167 crate::scoring::QualityLevel::Average => quality_score.total_score.to_string().yellow(),
168 crate::scoring::QualityLevel::Poor => quality_score.total_score.to_string().red(),
169 crate::scoring::QualityLevel::Terrible => {
170 quality_score.total_score.to_string().bright_red().bold()
171 }
172 };
173
174 let (score_label, level_label) = match self.i18n.lang.as_str() {
175 "zh-CN" => ("📊 总分", "🎯 等级"),
176 _ => ("📊 Score", "🎯 Level"),
177 };
178
179 println!(
180 " {}: {:.1}/100 {}",
181 score_label,
182 quality_score.total_score,
183 quality_score.quality_level.emoji()
184 );
185 println!(
186 " {}: {}",
187 level_label,
188 quality_score
189 .quality_level
190 .description(&self.i18n.lang)
191 .bright_white()
192 .bold()
193 );
194
195 if quality_score.total_lines > 0 {
196 let (lines_label, files_label, density_label) = match self.i18n.lang.as_str() {
197 "zh-CN" => ("📏 代码行数", "📁 文件数量", "🔍 问题密度"),
198 _ => ("📏 Lines of Code", "📁 Files", "🔍 Issue Density"),
199 };
200 let density_unit = match self.i18n.lang.as_str() {
201 "zh-CN" => "问题/千行",
202 _ => "issues/1k lines",
203 };
204
205 println!(
206 " {}: {}",
207 lines_label,
208 quality_score.total_lines.to_string().cyan()
209 );
210 println!(
211 " {}: {}",
212 files_label,
213 quality_score.file_count.to_string().cyan()
214 );
215 println!(
216 " {}: {:.2} {}",
217 density_label,
218 quality_score.issue_density.to_string().cyan(),
219 density_unit
220 );
221 }
222
223 if quality_score.severity_distribution.nuclear > 0
225 || quality_score.severity_distribution.spicy > 0
226 || quality_score.severity_distribution.mild > 0
227 {
228 println!();
229 let distribution_title = match self.i18n.lang.as_str() {
230 "zh-CN" => "🎭 问题分布:",
231 _ => "🎭 Issue Distribution:",
232 };
233 let (nuclear_label, spicy_label, mild_label) = match self.i18n.lang.as_str() {
234 "zh-CN" => ("💥 核弹级", "🌶️ 严重", "😐 轻微"),
235 _ => ("💥 Nuclear", "🌶️ Spicy", "😐 Mild"),
236 };
237
238 println!(" {distribution_title}");
239 if quality_score.severity_distribution.nuclear > 0 {
240 println!(
241 " {}: {}",
242 nuclear_label,
243 quality_score
244 .severity_distribution
245 .nuclear
246 .to_string()
247 .red()
248 .bold()
249 );
250 }
251 if quality_score.severity_distribution.spicy > 0 {
252 println!(
253 " {}: {}",
254 spicy_label,
255 quality_score
256 .severity_distribution
257 .spicy
258 .to_string()
259 .yellow()
260 );
261 }
262 if quality_score.severity_distribution.mild > 0 {
263 println!(
264 " {}: {}",
265 mild_label,
266 quality_score.severity_distribution.mild.to_string().blue()
267 );
268 }
269 }
270
271 if !quality_score.category_scores.is_empty() && self.verbose {
273 println!();
274 let category_title = match self.i18n.lang.as_str() {
275 "zh-CN" => "📋 分类得分:",
276 _ => "📋 Category Scores:",
277 };
278 println!(" {category_title}");
279 let mut sorted_categories: Vec<_> = quality_score.category_scores.iter().collect();
280 sorted_categories
281 .sort_by(|a, b| b.1.partial_cmp(a.1).unwrap_or(std::cmp::Ordering::Equal));
282
283 for (category, score) in sorted_categories.iter().take(5) {
284 let category_name = match (self.i18n.lang.as_str(), category.as_str()) {
285 ("zh-CN", "naming") => "命名规范",
286 ("zh-CN", "complexity") => "复杂度",
287 ("zh-CN", "rust-basics") => "Rust基础",
288 ("zh-CN", "advanced-rust") => "高级特性",
289 ("zh-CN", "rust-features") => "Rust功能",
290 ("zh-CN", "structure") => "代码结构",
291 ("zh-CN", "duplication") => "重复代码",
292 (_, "naming") => "Naming",
293 (_, "complexity") => "Complexity",
294 (_, "rust-basics") => "Rust Basics",
295 (_, "advanced-rust") => "Advanced Rust",
296 (_, "rust-features") => "Rust Features",
297 (_, "structure") => "Code Structure",
298 (_, "duplication") => "Code Duplication",
299 _ => category,
300 };
301 println!(
302 " {} {:.1}",
303 category_name.cyan(),
304 score.to_string().yellow()
305 );
306 }
307 }
308
309 println!();
310 }
311
312 fn print_header(&self, issues: &[CodeIssue]) {
313 let total = issues.len();
314 let nuclear = issues
315 .iter()
316 .filter(|i| matches!(i.severity, Severity::Nuclear))
317 .count();
318 let spicy = issues
319 .iter()
320 .filter(|i| matches!(i.severity, Severity::Spicy))
321 .count();
322 let mild = issues
323 .iter()
324 .filter(|i| matches!(i.severity, Severity::Mild))
325 .count();
326
327 println!("{}", self.i18n.get("title").bright_red().bold());
328 println!("{}", self.i18n.get("preparing").yellow());
329 println!();
330
331 println!("{}", self.i18n.get("report_title").bright_red().bold());
332 println!("{}", "─".repeat(50).bright_black());
333
334 if self.savage_mode {
335 println!("{}", self.i18n.get("found_issues").red().bold());
336 } else {
337 println!("{}", self.i18n.get("found_issues").yellow());
338 }
339
340 println!();
341 println!("{}", self.i18n.get("statistics"));
342 println!(
343 " {} {}",
344 nuclear.to_string().red().bold(),
345 self.i18n.get("nuclear_issues")
346 );
347 println!(
348 " {} {}",
349 spicy.to_string().yellow().bold(),
350 self.i18n.get("spicy_issues")
351 );
352 println!(
353 " {} {}",
354 mild.to_string().blue().bold(),
355 self.i18n.get("mild_issues")
356 );
357 println!(
358 " {} {}",
359 total.to_string().bright_white().bold(),
360 self.i18n.get("total")
361 );
362 println!();
363 }
364
365 fn print_issues(&self, issues: &[CodeIssue]) {
366 let mut file_groups: HashMap<String, Vec<&CodeIssue>> = HashMap::new();
367
368 for issue in issues {
369 let file_name = issue
370 .file_path
371 .file_name()
372 .unwrap_or_default()
373 .to_string_lossy()
374 .to_string();
375 file_groups.entry(file_name).or_default().push(issue);
376 }
377
378 for (file_name, file_issues) in file_groups {
379 println!("{} {}", "📁".bright_blue(), file_name.bright_blue().bold());
380
381 let mut rule_groups: HashMap<String, Vec<&CodeIssue>> = HashMap::new();
383 for issue in &file_issues {
384 rule_groups
385 .entry(issue.rule_name.clone())
386 .or_default()
387 .push(issue);
388 }
389
390 let _max_per_rule = 5;
392 let mut total_shown = 0;
393 let max_total = if self.max_issues_per_file > 0 {
394 self.max_issues_per_file
395 } else {
396 usize::MAX
397 };
398
399 let mut sorted_rules: Vec<_> = rule_groups.into_iter().collect();
401 sorted_rules.sort_by(|a, b| {
402 let severity_order = |s: &Severity| match s {
403 Severity::Nuclear => 3,
404 Severity::Spicy => 2,
405 Severity::Mild => 1,
406 };
407 let max_severity_a =
408 a.1.iter()
409 .map(|i| severity_order(&i.severity))
410 .max()
411 .unwrap_or(1);
412 let max_severity_b =
413 b.1.iter()
414 .map(|i| severity_order(&i.severity))
415 .max()
416 .unwrap_or(1);
417 max_severity_b.cmp(&max_severity_a)
418 });
419
420 for (rule_name, rule_issues) in sorted_rules {
421 if total_shown >= max_total {
422 break;
423 }
424
425 let rule_issues_len = rule_issues.len();
426
427 if rule_name.contains("naming") || rule_name.contains("single-letter") {
429 let bad_names: Vec<String> = rule_issues
431 .iter()
432 .filter_map(|issue| {
433 if let Some(start) = issue.message.find("'") {
434 issue.message[start + 1..].find("'").map(|end| issue.message[start + 1..start + 1 + end].to_string())
435 } else {
436 None
437 }
438 })
439 .take(5)
440 .collect();
441
442 let names_display = if bad_names.len() < rule_issues_len {
443 format!("{}, ...", bad_names.join(", "))
444 } else {
445 bad_names.join(", ")
446 };
447
448 let label = if self.i18n.lang == "zh-CN" {
449 "变量命名问题"
450 } else {
451 "Variable naming issues"
452 };
453
454 println!(
455 " 🏷️ {}: {} ({})",
456 label.bright_yellow().bold(),
457 rule_issues_len.to_string().bright_red().bold(),
458 names_display.bright_black()
459 );
460 total_shown += 1;
461 } else if rule_name.contains("duplication") {
462 let label = if self.i18n.lang == "zh-CN" {
463 "代码重复问题"
464 } else {
465 "Code duplication issues"
466 };
467
468 let instance_info = if let Some(first_issue) = rule_issues.first() {
470 if first_issue.message.contains("instances") {
471 let parts: Vec<&str> = first_issue.message.split_whitespace().collect();
472 if let Some(pos) = parts.iter().position(|&x| x == "instances") {
473 if pos > 0 {
474 format!("{} instances", parts[pos - 1])
475 } else {
476 "multiple instances".to_string()
477 }
478 } else {
479 "multiple blocks".to_string()
480 }
481 } else {
482 "multiple blocks".to_string()
483 }
484 } else {
485 "multiple blocks".to_string()
486 };
487
488 println!(
489 " 🔄 {}: {} ({})",
490 label.bright_cyan().bold(),
491 rule_issues_len.to_string().bright_cyan().bold(),
492 instance_info.bright_black()
493 );
494 total_shown += 1;
495 } else if rule_name.contains("nesting") {
496 let label = if self.i18n.lang == "zh-CN" {
497 "嵌套深度问题"
498 } else {
499 "Nesting depth issues"
500 };
501
502 let depths: Vec<usize> = rule_issues
504 .iter()
505 .filter_map(|issue| {
506 if let Some(start) = issue.message.find("depth: ") {
507 let depth_str = &issue.message[start + 7..];
508 if let Some(end) = depth_str.find(')') {
509 depth_str[..end].parse().ok()
510 } else {
511 None
512 }
513 } else if let Some(start) = issue.message.find("深度: ") {
514 let depth_str = &issue.message[start + 6..];
515 if let Some(end) = depth_str.find(')') {
516 depth_str[..end].parse().ok()
517 } else {
518 None
519 }
520 } else {
521 None
522 }
523 })
524 .collect();
525
526 let depth_info = if !depths.is_empty() {
527 let min_depth = depths.iter().min().unwrap_or(&4);
528 let max_depth = depths.iter().max().unwrap_or(&8);
529 if min_depth == max_depth {
530 format!("depth {min_depth}")
531 } else {
532 format!("depth {min_depth}-{max_depth}")
533 }
534 } else {
535 "deep nesting".to_string()
536 };
537
538 println!(
539 " 📦 {}: {} ({})",
540 label.bright_magenta().bold(),
541 rule_issues_len.to_string().bright_magenta().bold(),
542 depth_info.bright_black()
543 );
544 total_shown += 1;
545 } else {
546 let clean_rule_name = rule_name.replace("-", " ");
548 println!(
549 " ⚠️ {}: {}",
550 clean_rule_name.bright_yellow().bold(),
551 rule_issues_len.to_string().bright_yellow().bold()
552 );
553 total_shown += 1;
554 }
555 }
556 println!();
557 }
558 }
559
560 fn print_issue(&self, issue: &CodeIssue) {
561 if issue.rule_name.contains("duplication") {
563 let message = if self.i18n.lang == "zh-CN" {
564 &issue.message
565 } else {
566 if issue.message.contains("相似代码块") {
568 "Found similar code blocks, consider refactoring into functions"
569 } else if issue.message.contains("DRY原则哭了") {
570 "Code duplication detected, DRY principle violated"
571 } else {
572 &issue.message
573 }
574 };
575 println!(
576 " {} {} {}",
577 "🔄".bright_cyan(),
578 "duplicate".bright_black(),
579 message.bright_cyan().bold()
580 );
581 } else if issue.rule_name.contains("nesting") {
582 let message = if self.i18n.lang == "zh-CN" {
583 &issue.message
584 } else {
585 if issue.message.contains("俄罗斯套娃") {
587 "Nesting deeper than Russian dolls, are you writing a maze?"
588 } else if issue.message.contains("挖到地心") {
589 "Nesting so deep, trying to dig to the Earth's core?"
590 } else if issue.message.contains("像洋葱一样") {
591 "Code nested like an onion, makes me want to cry"
592 } else {
593 &issue.message
594 }
595 };
596 println!(
597 " {} {} {}",
598 "📦".bright_magenta(),
599 "nesting".bright_black(),
600 message.bright_magenta()
601 );
602 } else {
603 let severity_icon = match issue.severity {
605 Severity::Nuclear => "💥",
606 Severity::Spicy => "🌶️",
607 Severity::Mild => "😐",
608 };
609
610 let line_info = format!("{}:{}", issue.line, issue.column);
611 let colored_message = match issue.severity {
612 Severity::Nuclear => issue.message.red().bold(),
613 Severity::Spicy => issue.message.yellow(),
614 Severity::Mild => issue.message.blue(),
615 };
616
617 let _final_message = if self.savage_mode {
618 self.make_message_savage(&issue.message)
619 } else {
620 issue.message.clone()
621 };
622
623 println!(
624 " {} {} {}",
625 severity_icon.bright_yellow(),
626 line_info.bright_black(),
627 colored_message
628 );
629 }
630 }
631
632 fn make_message_savage(&self, message: &str) -> String {
633 let savage_prefixes = vec![
634 "🔥 严重警告:",
635 "💀 代码死刑:",
636 "🗑️ 垃圾警报:",
637 "😱 恐怖发现:",
638 "🤮 令人作呕:",
639 ];
640
641 let prefix = savage_prefixes[message.len() % savage_prefixes.len()];
642 format!("{prefix} {message}")
643 }
644
645 fn print_summary_with_score(&self, issues: &[CodeIssue], quality_score: &CodeQualityScore) {
646 self.print_scoring_breakdown(issues, quality_score);
648 let _nuclear_count = issues
649 .iter()
650 .filter(|i| matches!(i.severity, Severity::Nuclear))
651 .count();
652 let _total_count = issues.len();
653
654 println!("{}", self.i18n.get("summary").bright_white().bold());
655 println!("{}", "─".repeat(50).bright_black());
656
657 let score_summary = match quality_score.quality_level {
659 crate::scoring::QualityLevel::Excellent => match self.i18n.lang.as_str() {
660 "zh-CN" => format!(
661 "🏆 代码质量优秀!评分: {:.1}/100",
662 quality_score.total_score
663 ),
664 _ => format!(
665 "🏆 Excellent code quality! Score: {:.1}/100",
666 quality_score.total_score
667 ),
668 },
669 crate::scoring::QualityLevel::Good => match self.i18n.lang.as_str() {
670 "zh-CN" => format!(
671 "👍 代码质量良好,评分: {:.1}/100",
672 quality_score.total_score
673 ),
674 _ => format!(
675 "👍 Good code quality, Score: {:.1}/100",
676 quality_score.total_score
677 ),
678 },
679 crate::scoring::QualityLevel::Average => match self.i18n.lang.as_str() {
680 "zh-CN" => format!(
681 "😐 代码质量一般,评分: {:.1}/100,还有改进空间",
682 quality_score.total_score
683 ),
684 _ => format!(
685 "😐 Average code quality, Score: {:.1}/100, room for improvement",
686 quality_score.total_score
687 ),
688 },
689 crate::scoring::QualityLevel::Poor => match self.i18n.lang.as_str() {
690 "zh-CN" => format!(
691 "😟 代码质量较差,评分: {:.1}/100,建议重构",
692 quality_score.total_score
693 ),
694 _ => format!(
695 "😟 Poor code quality, Score: {:.1}/100, refactoring recommended",
696 quality_score.total_score
697 ),
698 },
699 crate::scoring::QualityLevel::Terrible => match self.i18n.lang.as_str() {
700 "zh-CN" => format!(
701 "💀 代码质量糟糕,评分: {:.1}/100,急需重写",
702 quality_score.total_score
703 ),
704 _ => format!(
705 "💀 Terrible code quality, Score: {:.1}/100, rewrite urgently needed",
706 quality_score.total_score
707 ),
708 },
709 };
710
711 let score_color = match quality_score.quality_level {
712 crate::scoring::QualityLevel::Excellent => score_summary.bright_green().bold(),
713 crate::scoring::QualityLevel::Good => score_summary.green(),
714 crate::scoring::QualityLevel::Average => score_summary.yellow(),
715 crate::scoring::QualityLevel::Poor => score_summary.red(),
716 crate::scoring::QualityLevel::Terrible => score_summary.bright_red().bold(),
717 };
718
719 println!("{score_color}");
720 println!();
721
722 let nuclear_count = issues
723 .iter()
724 .filter(|i| matches!(i.severity, Severity::Nuclear))
725 .count();
726 let total_count = issues.len();
727
728 let summary_message = if nuclear_count > 0 {
729 if self.savage_mode {
730 match self.i18n.lang.as_str() {
731 "zh-CN" => "你的代码质量堪忧,建议重新学习编程基础 💀".to_string(),
732 _ => "Your code quality is concerning, suggest learning programming basics again 💀".to_string(),
733 }
734 } else {
735 match self.i18n.lang.as_str() {
736 "zh-CN" => "发现了一些严重问题,建议优先修复核弹级问题 🔥".to_string(),
737 _ => "Found some serious issues, suggest fixing nuclear problems first 🔥"
738 .to_string(),
739 }
740 }
741 } else if total_count > 10 {
742 match self.i18n.lang.as_str() {
743 "zh-CN" => "问题有点多,建议分批修复 📝".to_string(),
744 _ => "Quite a few issues, suggest fixing them in batches 📝".to_string(),
745 }
746 } else {
747 match self.i18n.lang.as_str() {
748 "zh-CN" => "问题不多,稍微改进一下就好了 👍".to_string(),
749 _ => "Not many issues, just need some minor improvements 👍".to_string(),
750 }
751 };
752
753 let color = if nuclear_count > 0 {
754 summary_message.red().bold()
755 } else if total_count > 10 {
756 summary_message.yellow()
757 } else {
758 summary_message.green()
759 };
760
761 println!("{color}");
762 }
763
764 fn print_scoring_breakdown(&self, _issues: &[CodeIssue], quality_score: &CodeQualityScore) {
765 let title = if self.i18n.lang == "zh-CN" {
766 "📊 评分详情"
767 } else {
768 "📊 Scoring Details"
769 };
770
771 println!("\n{}", title.bright_cyan().bold());
772 println!("{}", "─".repeat(50).bright_black());
773
774 self.print_category_scores(&quality_score.category_scores);
776
777 self.print_weighted_calculation(&quality_score.category_scores, quality_score.total_score);
779
780 let scale_title = if self.i18n.lang == "zh-CN" {
782 "\n📏 评分标准 (分数越高代码越烂):"
783 } else {
784 "\n📏 Scoring Scale (higher score = worse code):"
785 };
786
787 println!("{}", scale_title.bright_yellow());
788 if self.i18n.lang == "zh-CN" {
789 println!(" 💀 81-100: 糟糕 🔥 61-80: 较差 ⚠️ 41-60: 一般");
790 println!(" ✅ 21-40: 良好 🌟 0-20: 优秀");
791 } else {
792 println!(" 💀 81-100: Terrible 🔥 61-80: Poor ⚠️ 41-60: Average");
793 println!(" ✅ 21-40: Good 🌟 0-20: Excellent");
794 }
795 }
796
797 fn calculate_base_score_for_display(
798 &self,
799 issues: &[CodeIssue],
800 scorer: &crate::scoring::CodeScorer,
801 ) -> f64 {
802 let mut score = 0.0;
803 for issue in issues {
804 let rule_weight = scorer.rule_weights.get(&issue.rule_name).unwrap_or(&1.0);
805 let severity_weight = match issue.severity {
806 crate::analyzer::Severity::Nuclear => 10.0,
807 crate::analyzer::Severity::Spicy => 5.0,
808 crate::analyzer::Severity::Mild => 2.0,
809 };
810 score += rule_weight * severity_weight;
811 }
812 score
813 }
814
815 fn calculate_density_penalty_for_display(
816 &self,
817 issue_count: usize,
818 file_count: usize,
819 total_lines: usize,
820 ) -> f64 {
821 if total_lines == 0 || file_count == 0 {
822 return 0.0;
823 }
824
825 let issues_per_1000_lines = (issue_count as f64 / total_lines as f64) * 1000.0;
826 let issues_per_file = issue_count as f64 / file_count as f64;
827
828 let density_penalty = match issues_per_1000_lines {
829 x if x > 50.0 => 25.0,
830 x if x > 30.0 => 15.0,
831 x if x > 20.0 => 10.0,
832 x if x > 10.0 => 5.0,
833 _ => 0.0,
834 };
835
836 let file_penalty = match issues_per_file {
837 x if x > 20.0 => 15.0,
838 x if x > 10.0 => 10.0,
839 x if x > 5.0 => 5.0,
840 _ => 0.0,
841 };
842
843 density_penalty + file_penalty
844 }
845
846 fn calculate_severity_penalty_for_display(
847 &self,
848 distribution: &crate::scoring::SeverityDistribution,
849 ) -> f64 {
850 let mut penalty = 0.0;
851
852 if distribution.nuclear > 0 {
853 penalty += 20.0 + (distribution.nuclear as f64 - 1.0) * 5.0;
854 }
855
856 if distribution.spicy > 5 {
857 penalty += (distribution.spicy as f64 - 5.0) * 2.0;
858 }
859
860 if distribution.mild > 20 {
861 penalty += (distribution.mild as f64 - 20.0) * 0.5;
862 }
863
864 penalty
865 }
866
867 fn print_category_scores(&self, category_scores: &std::collections::HashMap<String, f64>) {
868 let title = if self.i18n.lang == "zh-CN" {
869 "📋 分类评分详情:"
870 } else {
871 "📋 Category Scores:"
872 };
873
874 println!("{}", title.bright_yellow());
875
876 let categories = [
878 ("naming", "命名规范", "Naming", "🏷️"),
879 ("complexity", "复杂度", "Complexity", "🧩"),
880 ("duplication", "代码重复", "Duplication", "🔄"),
881 ("rust-basics", "Rust基础", "Rust Basics", "🦀"),
882 ("advanced-rust", "高级特性", "Advanced Rust", "⚡"),
883 ("rust-features", "Rust功能", "Rust Features", "🚀"),
884 ("structure", "代码结构", "Code Structure", "🏗️"),
885 ];
886
887 for (category_key, zh_name, en_name, icon) in &categories {
888 if let Some(score) = category_scores.get(*category_key) {
889 let display_name = if self.i18n.lang == "zh-CN" {
890 zh_name
891 } else {
892 en_name
893 };
894 let (status_icon, status_text) = self.get_score_status(*score);
895
896 println!(
898 " {} {} {}分 {}",
899 status_icon,
900 format!("{icon} {display_name}").bright_white(),
901 format!("{score:.0}").bright_cyan(),
902 status_text.bright_black()
903 );
904
905 if let Some(roast) = self.get_category_roast(category_key, *score) {
907 println!(" 💬 {}", roast.bright_yellow().italic());
908 }
909 }
910 }
911 println!();
912 }
913
914 fn get_score_status(&self, score: f64) -> (&str, &str) {
915 match score as u32 {
917 81..=100 => (
918 "⚠",
919 if self.i18n.lang == "zh-CN" {
920 "糟糕,急需修复"
921 } else {
922 "Terrible, urgent fixes needed"
923 },
924 ),
925 61..=80 => (
926 "•",
927 if self.i18n.lang == "zh-CN" {
928 "较差,建议重构"
929 } else {
930 "Poor, refactoring recommended"
931 },
932 ),
933 41..=60 => (
934 "○",
935 if self.i18n.lang == "zh-CN" {
936 "一般,需要改进"
937 } else {
938 "Average, needs improvement"
939 },
940 ),
941 21..=40 => (
942 "✓",
943 if self.i18n.lang == "zh-CN" {
944 "良好,还有提升空间"
945 } else {
946 "Good, room for improvement"
947 },
948 ),
949 _ => (
950 "✓✓",
951 if self.i18n.lang == "zh-CN" {
952 "优秀,继续保持"
953 } else {
954 "Excellent, keep it up"
955 },
956 ),
957 }
958 }
959
960 fn get_category_roast(&self, category: &str, score: f64) -> Option<String> {
961 if score < 60.0 {
963 return None;
964 }
965
966 let roasts = match (self.i18n.lang.as_str(), category) {
967 ("zh-CN", "naming") => vec![
968 "变量命名比我的编程技能还要抽象 🤔",
969 "这些变量名让维护者想哭着辞职 😭",
970 "变量名的创意程度约等于给孩子起名叫'小明' 🙄",
971 "恭喜!你成功让变量名比注释还难懂 🏆",
972 ],
973 ("zh-CN", "complexity") => vec![
974 "这复杂度比俄罗斯套娃还要深 🪆",
975 "代码复杂得像洋葱一样,剥一层哭一次 🧅",
976 "这函数比我的人际关系还复杂 😵💫",
977 "复杂度爆表!连AI都看不懂了 🤖",
978 ],
979 ("zh-CN", "duplication") => vec![
980 "检测到重复代码!你是复制粘贴大师吗? 🥷",
981 "DRY原则哭了,你的代码湿得像雨季 🌧️",
982 "这些重复代码比双胞胎还像 👯♀️",
983 "建议改名为copy-paste.rs 📋",
984 ],
985 ("zh-CN", "rust-features") => vec![
986 "宏定义比我的借口还多 🎭",
987 "这么多宏,IDE都要罢工了 💻",
988 "宏滥用!编译时间都被你搞长了 ⏰",
989 ],
990 ("en-US", "naming") => vec![
991 "Variable names more abstract than modern art 🎨",
992 "These names make maintainers want to quit and sell hotdogs 🌭",
993 "Variable naming creativity level: calling a kid 'Child' 👶",
994 "Congrats! Variables harder to understand than comments 🏆",
995 ],
996 ("en-US", "complexity") => vec![
997 "Complexity deeper than Russian dolls 🪆",
998 "Code nested like an onion, peel one layer, cry once 🧅",
999 "This function is more complex than my relationships 😵💫",
1000 "Complexity off the charts! Even AI gave up 🤖",
1001 ],
1002 ("en-US", "duplication") => vec![
1003 "Copy-paste ninja detected! 🥷",
1004 "DRY principle crying while your code drowns in repetition 🌧️",
1005 "More duplicates than a hall of mirrors 🪞",
1006 "Suggest renaming to ctrl-c-ctrl-v.rs 📋",
1007 ],
1008 ("en-US", "rust-features") => vec![
1009 "More macros than my excuses 🎭",
1010 "So many macros, even the IDE wants to quit 💻",
1011 "Macro abuse! Compile time extended indefinitely ⏰",
1012 ],
1013 _ => vec![],
1014 };
1015
1016 if roasts.is_empty() {
1017 None
1018 } else {
1019 let index = ((score - 60.0) / 10.0) as usize;
1021 let roast_index = index.min(roasts.len() - 1);
1022 Some(roasts[roast_index].to_string())
1023 }
1024 }
1025
1026 fn print_weighted_calculation(
1027 &self,
1028 category_scores: &std::collections::HashMap<String, f64>,
1029 _total_score: f64,
1030 ) {
1031 let calc_title = if self.i18n.lang == "zh-CN" {
1032 "🧮 加权计算:"
1033 } else {
1034 "🧮 Weighted Calculation:"
1035 };
1036
1037 println!("{}", calc_title.bright_yellow());
1038
1039 let weights = [
1041 ("naming", 0.25, "命名规范", "Naming"),
1042 ("complexity", 0.20, "复杂度", "Complexity"),
1043 ("duplication", 0.15, "代码重复", "Duplication"),
1044 ("rust-basics", 0.15, "Rust基础", "Rust Basics"),
1045 ("advanced-rust", 0.10, "高级特性", "Advanced Rust"),
1046 ("rust-features", 0.10, "Rust功能", "Rust Features"),
1047 ("structure", 0.05, "代码结构", "Code Structure"),
1048 ];
1049
1050 let mut calculation_parts = Vec::new();
1051 let mut weighted_sum = 0.0;
1052
1053 for (category_key, weight, _zh_name, _en_name) in &weights {
1054 if let Some(score) = category_scores.get(*category_key) {
1055 let weighted_value = score * weight;
1056 weighted_sum += weighted_value;
1057 calculation_parts.push(format!("{score:.1}×{weight:.2}"));
1058 }
1059 }
1060
1061 if self.i18n.lang == "zh-CN" {
1062 println!(
1063 " 评分计算: ({}) ÷ 1.00 = {}",
1064 calculation_parts.join(" + ").bright_white(),
1065 format!("{weighted_sum:.1}").bright_green().bold()
1066 );
1067 } else {
1068 println!(
1069 " Score calculation: ({}) ÷ 1.00 = {}",
1070 calculation_parts.join(" + ").bright_white(),
1071 format!("{weighted_sum:.1}").bright_green().bold()
1072 );
1073 }
1074 }
1075
1076 fn print_detailed_base_score_breakdown(
1077 &self,
1078 issues: &[CodeIssue],
1079 scorer: &crate::scoring::CodeScorer,
1080 ) {
1081 let mut rule_scores: std::collections::HashMap<String, (usize, f64)> =
1083 std::collections::HashMap::new();
1084
1085 for issue in issues {
1086 let rule_weight = scorer.rule_weights.get(&issue.rule_name).unwrap_or(&1.0);
1087 let severity_weight = match issue.severity {
1088 crate::analyzer::Severity::Nuclear => 10.0,
1089 crate::analyzer::Severity::Spicy => 5.0,
1090 crate::analyzer::Severity::Mild => 2.0,
1091 };
1092 let issue_score = rule_weight * severity_weight;
1093
1094 let entry = rule_scores
1095 .entry(issue.rule_name.clone())
1096 .or_insert((0, 0.0));
1097 entry.0 += 1; entry.1 += issue_score; }
1100
1101 let mut sorted_rules: Vec<_> = rule_scores.into_iter().collect();
1103 sorted_rules.sort_by(|a, b| {
1104 b.1 .1
1105 .partial_cmp(&a.1 .1)
1106 .unwrap_or(std::cmp::Ordering::Equal)
1107 });
1108
1109 let breakdown_title = if self.i18n.lang == "zh-CN" {
1110 "🔍 基础分数详细计算:"
1111 } else {
1112 "🔍 Base score detailed calculation:"
1113 };
1114
1115 println!("{}", breakdown_title.bright_yellow());
1116
1117 for (rule_name, (count, total_score)) in sorted_rules.iter().take(10) {
1118 let rule_weight = scorer.rule_weights.get(rule_name).unwrap_or(&1.0);
1119
1120 let rule_display = match (self.i18n.lang.as_str(), rule_name.as_str()) {
1121 ("zh-CN", "terrible-naming") => "糟糕命名",
1122 ("zh-CN", "single-letter-variable") => "单字母变量",
1123 ("zh-CN", "deep-nesting") => "深度嵌套",
1124 ("zh-CN", "code-duplication") => "代码重复",
1125 ("zh-CN", "long-function") => "超长函数",
1126 ("zh-CN", "macro-abuse") => "宏滥用",
1127 (_, "terrible-naming") => "Terrible naming",
1128 (_, "single-letter-variable") => "Single letter vars",
1129 (_, "deep-nesting") => "Deep nesting",
1130 (_, "code-duplication") => "Code duplication",
1131 (_, "long-function") => "Long function",
1132 (_, "macro-abuse") => "Macro abuse",
1133 _ => rule_name,
1134 };
1135
1136 if self.i18n.lang == "zh-CN" {
1137 println!(
1138 " • {} × {} (权重{:.1}) = {}",
1139 format!("{count}").cyan(),
1140 rule_display.bright_white(),
1141 format!("{rule_weight:.1}").yellow(),
1142 format!("{total_score:.1}").bright_red()
1143 );
1144 } else {
1145 println!(
1146 " • {} × {} (weight {:.1}) = {}",
1147 format!("{count}").cyan(),
1148 rule_display.bright_white(),
1149 format!("{rule_weight:.1}").yellow(),
1150 format!("{total_score:.1}").bright_red()
1151 );
1152 }
1153 }
1154 println!();
1155 }
1156
1157 fn print_footer(&self, issues: &[CodeIssue]) {
1158 println!();
1159 println!("{}", self.i18n.get("suggestions").bright_cyan().bold());
1160 println!("{}", "─".repeat(50).bright_black());
1161
1162 let rule_names: Vec<String> = issues
1163 .iter()
1164 .map(|issue| issue.rule_name.clone())
1165 .collect::<std::collections::HashSet<_>>()
1166 .into_iter()
1167 .collect();
1168
1169 let suggestions = self.i18n.get_suggestions(&rule_names);
1170 for suggestion in suggestions {
1171 println!(" {}", suggestion.cyan());
1172 }
1173
1174 println!();
1175 let footer_message = if self.savage_mode {
1176 match self.i18n.lang.as_str() {
1177 "zh-CN" => "记住:写垃圾代码容易,写好代码需要用心 💪".to_string(),
1178 _ => "Remember: writing garbage code is easy, writing good code requires effort 💪"
1179 .to_string(),
1180 }
1181 } else {
1182 self.i18n.get("keep_improving")
1183 };
1184
1185 let color = if self.savage_mode {
1186 footer_message.bright_red().bold()
1187 } else {
1188 footer_message.bright_green().bold()
1189 };
1190
1191 println!("{color}");
1192 }
1193
1194 fn print_top_files(&self, issues: &[CodeIssue]) {
1195 if self.top_files == 0 {
1196 return;
1197 }
1198
1199 let mut file_issue_counts: HashMap<String, usize> = HashMap::new();
1200 for issue in issues {
1201 let file_name = issue
1202 .file_path
1203 .file_name()
1204 .unwrap_or_default()
1205 .to_string_lossy()
1206 .to_string();
1207 *file_issue_counts.entry(file_name).or_insert(0) += 1;
1208 }
1209
1210 let mut sorted_files: Vec<_> = file_issue_counts.into_iter().collect();
1211 sorted_files.sort_by(|a, b| b.1.cmp(&a.1));
1212
1213 if !sorted_files.is_empty() {
1214 println!("{}", self.i18n.get("top_files").bright_yellow().bold());
1215 println!("{}", "─".repeat(50).bright_black());
1216
1217 for (i, (file_name, count)) in sorted_files.iter().take(self.top_files).enumerate() {
1218 let rank = format!("{}.", i + 1);
1219 println!(
1220 " {} {} ({} issues)",
1221 rank.bright_white(),
1222 file_name.bright_blue(),
1223 count.to_string().red()
1224 );
1225 }
1226 println!();
1227 }
1228 }
1229
1230 fn print_detailed_analysis(&self, issues: &[CodeIssue]) {
1231 println!(
1232 "{}",
1233 self.i18n.get("detailed_analysis").bright_magenta().bold()
1234 );
1235 println!("{}", "─".repeat(50).bright_black());
1236
1237 let mut rule_stats: HashMap<String, usize> = HashMap::new();
1238 for issue in issues {
1239 *rule_stats.entry(issue.rule_name.clone()).or_insert(0) += 1;
1240 }
1241
1242 let rule_descriptions = match self.i18n.lang.as_str() {
1243 "zh-CN" => [
1244 ("terrible-naming", "糟糕的变量命名"),
1245 ("single-letter-variable", "单字母变量"),
1246 ("deep-nesting", "过度嵌套"),
1247 ("long-function", "超长函数"),
1248 ("unwrap-abuse", "unwrap() 滥用"),
1249 ("unnecessary-clone", "不必要的 clone()"),
1250 ]
1251 .iter()
1252 .cloned()
1253 .collect::<HashMap<_, _>>(),
1254 _ => [
1255 ("terrible-naming", "Terrible variable naming"),
1256 ("single-letter-variable", "Single letter variables"),
1257 ("deep-nesting", "Deep nesting"),
1258 ("long-function", "Long functions"),
1259 ("unwrap-abuse", "unwrap() abuse"),
1260 ("unnecessary-clone", "Unnecessary clone()"),
1261 ]
1262 .iter()
1263 .cloned()
1264 .collect::<HashMap<_, _>>(),
1265 };
1266
1267 for (rule_name, count) in rule_stats {
1268 let rule_name_str = rule_name.as_str();
1269 let description = rule_descriptions
1270 .get(rule_name_str)
1271 .unwrap_or(&rule_name_str);
1272 println!(
1273 " 📌 {}: {} issues",
1274 description.cyan(),
1275 count.to_string().yellow()
1276 );
1277 }
1278 println!();
1279 }
1280
1281 fn print_markdown_report(&self, issues: &[CodeIssue]) {
1282 let total = issues.len();
1283 let nuclear = issues
1284 .iter()
1285 .filter(|i| matches!(i.severity, Severity::Nuclear))
1286 .count();
1287 let spicy = issues
1288 .iter()
1289 .filter(|i| matches!(i.severity, Severity::Spicy))
1290 .count();
1291 let mild = issues
1292 .iter()
1293 .filter(|i| matches!(i.severity, Severity::Mild))
1294 .count();
1295
1296 println!("# {}", self.i18n.get("title"));
1297 println!();
1298 println!("## {}", self.i18n.get("statistics"));
1299 println!();
1300 println!("| Severity | Count | Description |");
1301 println!("| --- | --- | --- |");
1302 println!(
1303 "| 🔥 Nuclear | {} | {} |",
1304 nuclear,
1305 self.i18n.get("nuclear_issues")
1306 );
1307 println!(
1308 "| 🌶️ Spicy | {} | {} |",
1309 spicy,
1310 self.i18n.get("spicy_issues")
1311 );
1312 println!("| 😐 Mild | {} | {} |", mild, self.i18n.get("mild_issues"));
1313 println!(
1314 "| **Total** | **{}** | **{}** |",
1315 total,
1316 self.i18n.get("total")
1317 );
1318 println!();
1319
1320 if self.verbose {
1321 println!("## {}", self.i18n.get("detailed_analysis"));
1322 println!();
1323
1324 let mut rule_stats: HashMap<String, usize> = HashMap::new();
1325 for issue in issues {
1326 *rule_stats.entry(issue.rule_name.clone()).or_insert(0) += 1;
1327 }
1328
1329 for (rule_name, count) in rule_stats {
1330 println!("- **{}**: {} issues", rule_name, count);
1331 }
1332 println!();
1333 }
1334
1335 println!("## Issues by File");
1336 println!();
1337
1338 let mut file_groups: HashMap<String, Vec<&CodeIssue>> = HashMap::new();
1339 for issue in issues {
1340 let file_name = issue
1341 .file_path
1342 .file_name()
1343 .unwrap_or_default()
1344 .to_string_lossy()
1345 .to_string();
1346 file_groups.entry(file_name).or_default().push(issue);
1347 }
1348
1349 for (file_name, file_issues) in file_groups {
1350 println!("### 📁 {}", file_name);
1351 println!();
1352
1353 let issues_to_show = if self.max_issues_per_file > 0 {
1354 file_issues
1355 .into_iter()
1356 .take(self.max_issues_per_file)
1357 .collect::<Vec<_>>()
1358 } else {
1359 file_issues
1360 };
1361
1362 for issue in issues_to_show {
1363 let severity_icon = match issue.severity {
1364 Severity::Nuclear => "💥",
1365 Severity::Spicy => "🌶️",
1366 Severity::Mild => "😐",
1367 };
1368
1369 let messages = self.i18n.get_roast_messages(&issue.rule_name);
1370 let message = if !messages.is_empty() {
1371 messages[issue.line % messages.len()].clone()
1372 } else {
1373 issue.message.clone()
1374 };
1375
1376 println!(
1377 "- {} **Line {}:{}** - {}",
1378 severity_icon, issue.line, issue.column, message
1379 );
1380 }
1381 println!();
1382 }
1383
1384 println!("## {}", self.i18n.get("suggestions"));
1385 println!();
1386
1387 let rule_names: Vec<String> = issues
1388 .iter()
1389 .map(|issue| issue.rule_name.clone())
1390 .collect::<std::collections::HashSet<_>>()
1391 .into_iter()
1392 .collect();
1393
1394 let suggestions = self.i18n.get_suggestions(&rule_names);
1395 for suggestion in suggestions {
1396 println!("- {}", suggestion);
1397 }
1398 }
1399}