garbage_code_hunter/rules/
code_smells.rs

1use std::path::Path;
2use syn::{visit::Visit, ExprLit, File, ItemFn, Lit};
3
4use crate::analyzer::{CodeIssue, RoastLevel, Severity};
5use crate::rules::Rule;
6use crate::utils::get_position;
7
8/// 检测魔法数字(硬编码的数字常量)
9pub struct MagicNumberRule;
10
11impl Rule for MagicNumberRule {
12    fn name(&self) -> &'static str {
13        "magic-number"
14    }
15
16    fn check(
17        &self,
18        file_path: &Path,
19        syntax_tree: &File,
20        _content: &str,
21        lang: &str,
22    ) -> Vec<CodeIssue> {
23        let mut visitor = MagicNumberVisitor::new(file_path.to_path_buf(), lang);
24        visitor.visit_file(syntax_tree);
25        visitor.issues
26    }
27}
28
29/// 检测做太多事的函数(上帝函数)
30pub struct GodFunctionRule;
31
32impl Rule for GodFunctionRule {
33    fn name(&self) -> &'static str {
34        "god-function"
35    }
36
37    fn check(
38        &self,
39        file_path: &Path,
40        syntax_tree: &File,
41        content: &str,
42        lang: &str,
43    ) -> Vec<CodeIssue> {
44        let mut visitor = GodFunctionVisitor::new(file_path.to_path_buf(), content, lang);
45        visitor.visit_file(syntax_tree);
46        visitor.issues
47    }
48}
49
50/// 检测被注释掉的代码块
51pub struct CommentedCodeRule;
52
53impl Rule for CommentedCodeRule {
54    fn name(&self) -> &'static str {
55        "commented-code"
56    }
57
58    fn check(
59        &self,
60        file_path: &Path,
61        _syntax_tree: &File,
62        content: &str,
63        lang: &str,
64    ) -> Vec<CodeIssue> {
65        let mut issues = Vec::new();
66        let lines: Vec<&str> = content.lines().collect();
67
68        let mut _commented_code_blocks = 0;
69        let mut current_block_size = 0;
70
71        for (line_num, line) in lines.iter().enumerate() {
72            let trimmed = line.trim();
73
74            // 检测被注释的代码行
75            if trimmed.starts_with("//") {
76                let comment_content = trimmed.trim_start_matches("//").trim();
77
78                // 检测是否像代码(包含常见的代码模式)
79                if is_likely_code(comment_content) {
80                    current_block_size += 1;
81                } else if current_block_size > 0 {
82                    // 结束一个代码块
83                    if current_block_size >= 3 {
84                        _commented_code_blocks += 1;
85                        issues.push(create_commented_code_issue(
86                            file_path,
87                            line_num + 1 - current_block_size,
88                            current_block_size,
89                            lang,
90                        ));
91                    }
92                    current_block_size = 0;
93                }
94            } else if current_block_size > 0 {
95                // 非注释行,结束当前块
96                if current_block_size >= 3 {
97                    _commented_code_blocks += 1;
98                    issues.push(create_commented_code_issue(
99                        file_path,
100                        line_num - current_block_size,
101                        current_block_size,
102                        lang,
103                    ));
104                }
105                current_block_size = 0;
106            }
107        }
108
109        // 处理文件末尾的代码块
110        if current_block_size >= 3 {
111            issues.push(create_commented_code_issue(
112                file_path,
113                lines.len() - current_block_size,
114                current_block_size,
115                lang,
116            ));
117        }
118
119        issues
120    }
121}
122
123/// 检测明显的死代码
124pub struct DeadCodeRule;
125
126impl Rule for DeadCodeRule {
127    fn name(&self) -> &'static str {
128        "dead-code"
129    }
130
131    fn check(
132        &self,
133        file_path: &Path,
134        _syntax_tree: &File,
135        content: &str,
136        lang: &str,
137    ) -> Vec<CodeIssue> {
138        let mut issues = Vec::new();
139        let lines: Vec<&str> = content.lines().collect();
140
141        for (line_num, line) in lines.iter().enumerate() {
142            let trimmed = line.trim();
143
144            // 检测明显的死代码模式
145            if is_dead_code_pattern(trimmed) {
146                let messages = if lang == "zh-CN" {
147                    vec![
148                        "发现死代码,这行永远不会执行",
149                        "这行代码比我的社交生活还死",
150                        "死代码警告:这里是代码的坟墓",
151                        "这行代码已经去世了,建议删除",
152                        "发现僵尸代码,需要清理",
153                    ]
154                } else {
155                    vec![
156                        "Dead code detected - this line will never execute",
157                        "This code is deader than my social life",
158                        "Dead code alert: code graveyard found here",
159                        "This line of code has passed away, consider removal",
160                        "Zombie code detected, cleanup needed",
161                    ]
162                };
163
164                issues.push(CodeIssue {
165                    file_path: file_path.to_path_buf(),
166                    line: line_num + 1,
167                    column: 1,
168                    rule_name: "dead-code".to_string(),
169                    message: messages[line_num % messages.len()].to_string(),
170                    severity: Severity::Mild,
171                    roast_level: RoastLevel::Sarcastic,
172                });
173            }
174        }
175
176        issues
177    }
178}
179
180// ============================================================================
181// 辅助函数
182// ============================================================================
183
184fn is_likely_code(content: &str) -> bool {
185    // 检测是否像代码的模式
186    let code_patterns = [
187        "let ", "fn ", "if ", "else", "for ", "while ", "match ", "struct ", "enum ", "impl ",
188        "use ", "mod ", "return ", "break", "continue", "{", "}", "(", ")", "[", "]", ";", "=",
189        "==", "!=", "&&", "||", "->", "::",
190    ];
191
192    let rust_keywords = [
193        "pub", "const", "static", "mut", "ref", "move", "async", "await", "unsafe", "extern",
194        "crate",
195    ];
196
197    // 如果包含多个代码模式,很可能是代码
198    let pattern_count = code_patterns
199        .iter()
200        .filter(|&&pattern| content.contains(pattern))
201        .count();
202
203    let keyword_count = rust_keywords
204        .iter()
205        .filter(|&&keyword| content.contains(keyword))
206        .count();
207
208    pattern_count >= 2 || keyword_count >= 1
209}
210
211fn create_commented_code_issue(
212    file_path: &Path,
213    line: usize,
214    block_size: usize,
215    lang: &str,
216) -> CodeIssue {
217    let messages = if lang == "zh-CN" {
218        vec![
219            format!("发现 {} 行被注释的代码,是舍不得删除吗?", block_size),
220            format!("{} 行注释代码,版本控制系统不香吗?", block_size),
221            format!("这 {} 行注释代码就像前任,该放手就放手", block_size),
222            format!("{} 行死代码注释,建议断舍离", block_size),
223            format!("注释了 {} 行代码,Git 会记住它们的", block_size),
224        ]
225    } else {
226        vec![
227            format!(
228                "Found {} lines of commented code - can't let go?",
229                block_size
230            ),
231            format!(
232                "{} lines of commented code - isn't version control enough?",
233                block_size
234            ),
235            format!(
236                "These {} commented lines are like an ex - time to let go",
237                block_size
238            ),
239            format!(
240                "{} lines of dead commented code - Marie Kondo would disapprove",
241                block_size
242            ),
243            format!(
244                "Commented {} lines of code - Git remembers them anyway",
245                block_size
246            ),
247        ]
248    };
249
250    let severity = if block_size > 10 {
251        Severity::Spicy
252    } else {
253        Severity::Mild
254    };
255
256    CodeIssue {
257        file_path: file_path.to_path_buf(),
258        line,
259        column: 1,
260        rule_name: "commented-code".to_string(),
261        message: messages[block_size % messages.len()].clone(),
262        severity,
263        roast_level: RoastLevel::Sarcastic,
264    }
265}
266
267fn is_dead_code_pattern(line: &str) -> bool {
268    // 检测明显的死代码模式
269    let dead_patterns = [
270        "return;",
271        "return ", // return 后的代码
272        "break;",
273        "continue;", // break/continue 后的代码
274        "panic!(",
275        "unreachable!(", // panic 后的代码
276        "std::process::exit(",
277    ];
278
279    dead_patterns.iter().any(|&pattern| line.contains(pattern))
280}
281
282// ============================================================================
283// Visitor 实现
284// ============================================================================
285
286struct MagicNumberVisitor {
287    file_path: std::path::PathBuf,
288    issues: Vec<CodeIssue>,
289    lang: String,
290}
291
292impl MagicNumberVisitor {
293    fn new(file_path: std::path::PathBuf, lang: &str) -> Self {
294        Self {
295            file_path,
296            issues: Vec::new(),
297            lang: lang.to_string(),
298        }
299    }
300
301    fn is_magic_number(&self, value: i64) -> bool {
302        // 常见的非魔法数字
303        !matches!(value, -1 | 0 | 1 | 2 | 10 | 100 | 1000)
304    }
305
306    fn create_magic_number_issue(&self, value: i64, line: usize, column: usize) -> CodeIssue {
307        let messages = if self.lang == "zh-CN" {
308            vec![
309                format!("魔法数字 {}?这是什么咒语?", value),
310                format!("硬编码数字 {},维护性-1", value),
311                format!("数字 {} 从天而降,没人知道它的含义", value),
312                format!("魔法数字 {},建议定义为常量", value),
313                format!("看到数字 {},我陷入了沉思", value),
314            ]
315        } else {
316            vec![
317                format!("Magic number {}? What spell is this?", value),
318                format!("Hardcoded number {} - maintainability -1", value),
319                format!(
320                    "Number {} fell from the sky, nobody knows its meaning",
321                    value
322                ),
323                format!("Magic number {} - consider defining as a constant", value),
324                format!("Seeing number {}, I'm lost in thought", value),
325            ]
326        };
327
328        let severity = if !(-100..=1000).contains(&value) {
329            Severity::Spicy
330        } else {
331            Severity::Mild
332        };
333
334        CodeIssue {
335            file_path: self.file_path.clone(),
336            line,
337            column,
338            rule_name: "magic-number".to_string(),
339            message: messages[self.issues.len() % messages.len()].clone(),
340            severity,
341            roast_level: RoastLevel::Gentle,
342        }
343    }
344}
345
346impl<'ast> Visit<'ast> for MagicNumberVisitor {
347    fn visit_expr_lit(&mut self, expr_lit: &'ast ExprLit) {
348        if let Lit::Int(lit_int) = &expr_lit.lit {
349            if let Ok(value) = lit_int.base10_parse::<i64>() {
350                if self.is_magic_number(value) {
351                    let (line, column) = get_position(expr_lit);
352                    self.issues
353                        .push(self.create_magic_number_issue(value, line, column));
354                }
355            }
356        }
357        syn::visit::visit_expr_lit(self, expr_lit);
358    }
359}
360
361// ============================================================================
362// 上帝函数检测
363// ============================================================================
364
365struct GodFunctionVisitor {
366    file_path: std::path::PathBuf,
367    issues: Vec<CodeIssue>,
368    _content: String,
369    lang: String,
370}
371
372impl GodFunctionVisitor {
373    fn new(file_path: std::path::PathBuf, content: &str, lang: &str) -> Self {
374        Self {
375            file_path,
376            issues: Vec::new(),
377            _content: content.to_string(),
378            lang: lang.to_string(),
379        }
380    }
381
382    fn analyze_function_complexity(&mut self, func: &ItemFn) {
383        let func_name = func.sig.ident.to_string();
384
385        // 计算函数的各种复杂度指标
386        let mut complexity_score = 0;
387
388        // 1. 参数数量
389        let param_count = func.sig.inputs.len();
390        if param_count > 5 {
391            complexity_score += (param_count - 5) * 2;
392        }
393
394        // 2. 函数体大小(通过字符串分析估算)
395        let func_str = format!("{func:?}");
396        let line_count = func_str.lines().count();
397        if line_count > 50 {
398            complexity_score += (line_count - 50) / 10;
399        }
400
401        // 3. 嵌套深度和控制流复杂度
402        let control_keywords = ["if", "else", "for", "while", "match", "loop"];
403        for keyword in &control_keywords {
404            complexity_score += func_str.matches(keyword).count();
405        }
406
407        // 如果复杂度过高,报告问题
408        if complexity_score > 15 {
409            let messages = if self.lang == "zh-CN" {
410                vec![
411                    format!("函数 '{}' 做的事情比我一天做的还多", func_name),
412                    format!("'{}' 是上帝函数吗?什么都想管", func_name),
413                    format!("函数 '{}' 复杂得像我的感情生活", func_name),
414                    format!("'{}' 这个函数需要拆分,太臃肿了", func_name),
415                    format!("函数 '{}' 违反了单一职责原则", func_name),
416                ]
417            } else {
418                vec![
419                    format!(
420                        "Function '{}' does more things than I do in a day",
421                        func_name
422                    ),
423                    format!(
424                        "Is '{}' a god function? Wants to control everything",
425                        func_name
426                    ),
427                    format!("Function '{}' is as complex as my love life", func_name),
428                    format!("Function '{}' needs to be split - too bloated", func_name),
429                    format!(
430                        "Function '{}' violates single responsibility principle",
431                        func_name
432                    ),
433                ]
434            };
435
436            let severity = if complexity_score > 25 {
437                Severity::Spicy
438            } else {
439                Severity::Mild
440            };
441
442            let (line, column) = get_position(func);
443            self.issues.push(CodeIssue {
444                file_path: self.file_path.clone(),
445                line,
446                column,
447                rule_name: "god-function".to_string(),
448                message: messages[self.issues.len() % messages.len()].clone(),
449                severity,
450                roast_level: RoastLevel::Sarcastic,
451            });
452        }
453    }
454}
455
456impl<'ast> Visit<'ast> for GodFunctionVisitor {
457    fn visit_item_fn(&mut self, func: &'ast ItemFn) {
458        self.analyze_function_complexity(func);
459        syn::visit::visit_item_fn(self, func);
460    }
461}