garbage_code_hunter/
scoring.rs

1use std::collections::HashMap;
2use crate::analyzer::{CodeIssue, Severity};
3
4/// 代码质量评分系统
5/// 分数范围:0-100分,分数越低代码质量越好
6/// 0-20: 优秀 (Excellent)
7/// 21-40: 良好 (Good) 
8/// 41-60: 一般 (Average)
9/// 61-80: 较差 (Poor)
10/// 81-100: 糟糕 (Terrible)
11#[derive(Debug, Clone)]
12pub struct CodeQualityScore {
13    pub total_score: f64,
14    pub category_scores: HashMap<String, f64>,
15    pub file_count: usize,
16    pub total_lines: usize,
17    pub issue_density: f64,
18    pub severity_distribution: SeverityDistribution,
19    pub quality_level: QualityLevel,
20}
21
22#[derive(Debug, Clone)]
23pub struct SeverityDistribution {
24    pub nuclear: usize,
25    pub spicy: usize,
26    pub mild: usize,
27}
28
29#[derive(Debug, Clone, PartialEq)]
30pub enum QualityLevel {
31    Excellent,  // 0-20
32    Good,       // 21-40
33    Average,    // 41-60
34    Poor,       // 61-80
35    Terrible,   // 81-100
36}
37
38impl QualityLevel {
39    pub fn from_score(score: f64) -> Self {
40        match score as u32 {
41            0..=20 => QualityLevel::Excellent,
42            21..=40 => QualityLevel::Good,
43            41..=60 => QualityLevel::Average,
44            61..=80 => QualityLevel::Poor,
45            _ => QualityLevel::Terrible,
46        }
47    }
48
49    pub fn description(&self, lang: &str) -> &'static str {
50        match (self, lang) {
51            (QualityLevel::Excellent, "zh-CN") => "优秀",
52            (QualityLevel::Good, "zh-CN") => "良好",
53            (QualityLevel::Average, "zh-CN") => "一般",
54            (QualityLevel::Poor, "zh-CN") => "较差",
55            (QualityLevel::Terrible, "zh-CN") => "糟糕",
56            (QualityLevel::Excellent, _) => "Excellent",
57            (QualityLevel::Good, _) => "Good",
58            (QualityLevel::Average, _) => "Average",
59            (QualityLevel::Poor, _) => "Poor",
60            (QualityLevel::Terrible, _) => "Terrible",
61        }
62    }
63
64    pub fn emoji(&self) -> &'static str {
65        match self {
66            QualityLevel::Excellent => "🏆",
67            QualityLevel::Good => "👍",
68            QualityLevel::Average => "😐",
69            QualityLevel::Poor => "😟",
70            QualityLevel::Terrible => "💀",
71        }
72    }
73}
74
75pub struct CodeScorer {
76    /// 权重配置:不同规则类型的权重
77    rule_weights: HashMap<String, f64>,
78    /// 严重程度权重
79    severity_weights: HashMap<Severity, f64>,
80}
81
82impl CodeScorer {
83    pub fn new() -> Self {
84        let mut rule_weights = HashMap::new();
85        
86        // 基础代码质量问题权重
87        rule_weights.insert("terrible-naming".to_string(), 2.0);
88        rule_weights.insert("single-letter-variable".to_string(), 1.5);
89        
90        // 复杂度问题权重(影响较大)
91        rule_weights.insert("deep-nesting".to_string(), 3.0);
92        rule_weights.insert("long-function".to_string(), 2.5);
93        
94        // Rust特定问题权重
95        rule_weights.insert("unwrap-abuse".to_string(), 4.0);  // 高权重,因为可能导致panic
96        rule_weights.insert("unnecessary-clone".to_string(), 2.0);
97        
98        // 高级Rust特性滥用权重
99        rule_weights.insert("complex-closure".to_string(), 2.5);
100        rule_weights.insert("lifetime-abuse".to_string(), 3.5);
101        rule_weights.insert("trait-complexity".to_string(), 3.0);
102        rule_weights.insert("generic-abuse".to_string(), 2.5);
103        
104        // 综合Rust特性问题权重
105        rule_weights.insert("channel-abuse".to_string(), 3.0);
106        rule_weights.insert("async-abuse".to_string(), 3.5);
107        rule_weights.insert("dyn-trait-abuse".to_string(), 2.5);
108        rule_weights.insert("unsafe-abuse".to_string(), 5.0);  // 最高权重,安全问题
109        rule_weights.insert("ffi-abuse".to_string(), 4.5);     // 高权重,FFI安全问题
110        rule_weights.insert("macro-abuse".to_string(), 3.0);
111        rule_weights.insert("module-complexity".to_string(), 2.0);
112        rule_weights.insert("pattern-matching-abuse".to_string(), 2.0);
113        rule_weights.insert("reference-abuse".to_string(), 2.5);
114        rule_weights.insert("box-abuse".to_string(), 2.0);
115        rule_weights.insert("slice-abuse".to_string(), 1.5);
116
117        let mut severity_weights = HashMap::new();
118        severity_weights.insert(Severity::Nuclear, 10.0);  // 核弹级问题权重最高
119        severity_weights.insert(Severity::Spicy, 5.0);     // 中等问题
120        severity_weights.insert(Severity::Mild, 2.0);      // 轻微问题
121
122        Self {
123            rule_weights,
124            severity_weights,
125        }
126    }
127
128    /// 计算代码质量评分
129    pub fn calculate_score(&self, issues: &[CodeIssue], file_count: usize, total_lines: usize) -> CodeQualityScore {
130        if issues.is_empty() {
131            return CodeQualityScore {
132                total_score: 0.0,
133                category_scores: HashMap::new(),
134                file_count,
135                total_lines,
136                issue_density: 0.0,
137                severity_distribution: SeverityDistribution { nuclear: 0, spicy: 0, mild: 0 },
138                quality_level: QualityLevel::Excellent,
139            };
140        }
141
142        // 统计严重程度分布
143        let severity_distribution = self.calculate_severity_distribution(issues);
144        
145        // 计算基础分数
146        let base_score = self.calculate_base_score(issues);
147        
148        // 计算密度惩罚
149        let density_penalty = self.calculate_density_penalty(issues.len(), file_count, total_lines);
150        
151        // 计算严重程度惩罚
152        let severity_penalty = self.calculate_severity_penalty(&severity_distribution);
153        
154        // 计算分类分数
155        let category_scores = self.calculate_category_scores(issues);
156        
157        // 计算最终分数
158        let total_score = (base_score + density_penalty + severity_penalty).min(100.0);
159        
160        let issue_density = if total_lines > 0 {
161            issues.len() as f64 / total_lines as f64 * 1000.0  // 每千行代码的问题数
162        } else {
163            0.0
164        };
165
166        CodeQualityScore {
167            total_score,
168            category_scores,
169            file_count,
170            total_lines,
171            issue_density,
172            severity_distribution,
173            quality_level: QualityLevel::from_score(total_score),
174        }
175    }
176
177    fn calculate_severity_distribution(&self, issues: &[CodeIssue]) -> SeverityDistribution {
178        let mut nuclear = 0;
179        let mut spicy = 0;
180        let mut mild = 0;
181
182        for issue in issues {
183            match issue.severity {
184                Severity::Nuclear => nuclear += 1,
185                Severity::Spicy => spicy += 1,
186                Severity::Mild => mild += 1,
187            }
188        }
189
190        SeverityDistribution { nuclear, spicy, mild }
191    }
192
193    fn calculate_base_score(&self, issues: &[CodeIssue]) -> f64 {
194        let mut score = 0.0;
195
196        for issue in issues {
197            let rule_weight = self.rule_weights.get(&issue.rule_name).unwrap_or(&1.0);
198            let severity_weight = self.severity_weights.get(&issue.severity).unwrap_or(&1.0);
199            
200            // 基础分数 = 规则权重 × 严重程度权重
201            score += rule_weight * severity_weight;
202        }
203
204        score
205    }
206
207    fn calculate_density_penalty(&self, issue_count: usize, file_count: usize, total_lines: usize) -> f64 {
208        if total_lines == 0 || file_count == 0 {
209            return 0.0;
210        }
211
212        // 计算问题密度(每千行代码的问题数)
213        let issues_per_1000_lines = (issue_count as f64 / total_lines as f64) * 1000.0;
214        
215        // 计算文件平均问题数
216        let issues_per_file = issue_count as f64 / file_count as f64;
217
218        // 密度惩罚:问题密度越高,惩罚越重
219        let density_penalty = match issues_per_1000_lines {
220            x if x > 50.0 => 25.0,   // 极高密度
221            x if x > 30.0 => 15.0,   // 高密度
222            x if x > 20.0 => 10.0,   // 中等密度
223            x if x > 10.0 => 5.0,    // 低密度
224            _ => 0.0,                // 很低密度
225        };
226
227        // 文件问题数惩罚
228        let file_penalty = match issues_per_file {
229            x if x > 20.0 => 15.0,
230            x if x > 10.0 => 10.0,
231            x if x > 5.0 => 5.0,
232            _ => 0.0,
233        };
234
235        density_penalty + file_penalty
236    }
237
238    fn calculate_severity_penalty(&self, distribution: &SeverityDistribution) -> f64 {
239        let mut penalty = 0.0;
240
241        // 核弹级问题的额外惩罚
242        if distribution.nuclear > 0 {
243            penalty += 20.0 + (distribution.nuclear as f64 - 1.0) * 5.0;  // 第一个核弹级+20,后续每个+5
244        }
245
246        // 严重问题的额外惩罚
247        if distribution.spicy > 5 {
248            penalty += (distribution.spicy as f64 - 5.0) * 2.0;  // 超过5个严重问题后,每个+2
249        }
250
251        // 轻微问题的累积惩罚
252        if distribution.mild > 20 {
253            penalty += (distribution.mild as f64 - 20.0) * 0.5;  // 超过20个轻微问题后,每个+0.5
254        }
255
256        penalty
257    }
258
259    fn calculate_category_scores(&self, issues: &[CodeIssue]) -> HashMap<String, f64> {
260        let mut category_scores = HashMap::new();
261        let mut category_counts: HashMap<String, usize> = HashMap::new();
262
263        // 定义问题分类
264        let categories = [
265            ("naming", vec!["terrible-naming", "single-letter-variable"]),
266            ("complexity", vec!["deep-nesting", "long-function"]),
267            ("rust-basics", vec!["unwrap-abuse", "unnecessary-clone"]),
268            ("advanced-rust", vec!["complex-closure", "lifetime-abuse", "trait-complexity", "generic-abuse"]),
269            ("rust-features", vec!["channel-abuse", "async-abuse", "dyn-trait-abuse", "unsafe-abuse", "ffi-abuse", "macro-abuse"]),
270            ("structure", vec!["module-complexity", "pattern-matching-abuse", "reference-abuse", "box-abuse", "slice-abuse"]),
271        ];
272
273        // 统计每个分类的问题
274        for issue in issues {
275            for (category_name, rules) in &categories {
276                if rules.contains(&issue.rule_name.as_str()) {
277                    *category_counts.entry(category_name.to_string()).or_insert(0) += 1;
278                    
279                    let rule_weight = self.rule_weights.get(&issue.rule_name).unwrap_or(&1.0);
280                    let severity_weight = self.severity_weights.get(&issue.severity).unwrap_or(&1.0);
281                    
282                    *category_scores.entry(category_name.to_string()).or_insert(0.0) += 
283                        rule_weight * severity_weight;
284                }
285            }
286        }
287
288        category_scores
289    }
290}
291
292impl Default for CodeScorer {
293    fn default() -> Self {
294        Self::new()
295    }
296}