garbage-code-hunter 0.2.0

A humorous Rust code quality detector that roasts your garbage code
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
use std::path::Path;
use syn::{visit::Visit, ExprLit, File, ItemFn, Lit};

use crate::analyzer::{CodeIssue, Severity};
use crate::context::FileContext;
use crate::rules::Rule;
use crate::utils::get_position;

// ============================================================================
// Magic number detection
// ============================================================================

/// Categories of magic numbers for better error messages
enum MagicNumberCategory {
    /// Timeout values in milliseconds (100, 500, 1000, etc.)
    Timeout,
    /// Buffer sizes (1024, 2048, 4096, etc.)
    BufferSize,
    /// Network port numbers (80, 443, 3000, 8080, etc.)
    PortNumber,
    /// Threshold/limit values (10, 50, 100, etc.)
    Threshold,
    /// Unclassified magic numbers
    General,
}

/// Detect magic numbers (hardcoded numeric constants)
pub struct MagicNumberRule;

impl Rule for MagicNumberRule {
    fn name(&self) -> &'static str {
        "magic-number"
    }

    fn check(
        &self,
        file_path: &Path,
        syntax_tree: &File,
        _content: &str,
        lang: &str,
        is_test_file: bool,
    ) -> Vec<CodeIssue> {
        if is_test_file {
            return Vec::new();
        }

        let mut visitor = MagicNumberVisitor::new(file_path.to_path_buf(), lang);
        visitor.visit_file(syntax_tree);
        visitor.issues
    }

    fn check_with_context(
        &self,
        file_path: &Path,
        syntax_tree: &File,
        content: &str,
        lang: &str,
        is_test_file: bool,
        context: &FileContext,
        _config: &crate::context::ProjectConfig,
    ) -> Vec<CodeIssue> {
        // Test/Documentation/Benchmark: 完全跳过
        let weight = context.rule_weight_multiplier();
        if weight < 0.3 {
            return Vec::new();
        }

        // 获取所有问题
        let issues = self.check(file_path, syntax_tree, content, lang, is_test_file);

        // UI/TUI 文件过滤:检测到 UI 相关路径时,仅保留严重问题
        let path_str = file_path.to_string_lossy().to_lowercase();
        let is_ui_file = path_str.contains("ui")
            || path_str.contains("tui")
            || path_str.contains("gui")
            || path_str.contains("view")
            || path_str.contains("screen")
            || path_str.contains("layout")
            || path_str.contains("display")
            || path_str.contains("render");

        // 如果是 UI 上下文或 UI 文件,过滤掉 Mild 级别的 magic-number 问题
        if matches!(context, FileContext::Business) && is_ui_file {
            return issues
                .into_iter()
                .filter(|issue| matches!(issue.severity, Severity::Spicy | Severity::Nuclear))
                .collect();
        }

        issues
    }
}

/// Detect functions that do too much (god functions)
pub struct GodFunctionRule;

impl Rule for GodFunctionRule {
    fn name(&self) -> &'static str {
        "god-function"
    }

    fn check(
        &self,
        file_path: &Path,
        syntax_tree: &File,
        content: &str,
        lang: &str,
        is_test_file: bool,
    ) -> Vec<CodeIssue> {
        if is_test_file {
            return Vec::new();
        }
        let mut visitor = GodFunctionVisitor::new(file_path.to_path_buf(), content, lang);
        visitor.visit_file(syntax_tree);
        visitor.issues
    }
}

/// Detect commented-out code blocks
pub struct CommentedCodeRule;

impl Rule for CommentedCodeRule {
    fn name(&self) -> &'static str {
        "commented-code"
    }

    fn check(
        &self,
        file_path: &Path,
        _syntax_tree: &File,
        content: &str,
        lang: &str,
        is_test_file: bool,
    ) -> Vec<CodeIssue> {
        if is_test_file {
            return Vec::new();
        }
        let mut issues = Vec::new();
        let lines: Vec<&str> = content.lines().collect();

        let mut _commented_code_blocks = 0;
        let mut current_block_size = 0;

        for (line_num, line) in lines.iter().enumerate() {
            let trimmed = line.trim();

            // Detect commented code lines
            if trimmed.starts_with("//") {
                let comment_content = trimmed.trim_start_matches("//").trim();

                // Check if it looks like code (contains common code patterns)
                if is_likely_code(comment_content) {
                    current_block_size += 1;
                } else if current_block_size > 0 {
                    // End a code block
                    if current_block_size >= 3 {
                        _commented_code_blocks += 1;
                        issues.push(create_commented_code_issue(
                            file_path,
                            line_num + 1 - current_block_size,
                            current_block_size,
                            lang,
                        ));
                    }
                    current_block_size = 0;
                }
            } else if current_block_size > 0 {
                // Non-comment line, end current block
                if current_block_size >= 3 {
                    _commented_code_blocks += 1;
                    issues.push(create_commented_code_issue(
                        file_path,
                        line_num - current_block_size,
                        current_block_size,
                        lang,
                    ));
                }
                current_block_size = 0;
            }
        }

        // Handle code block at end of file
        if current_block_size >= 3 {
            issues.push(create_commented_code_issue(
                file_path,
                lines.len() - current_block_size,
                current_block_size,
                lang,
            ));
        }

        issues
    }
}

/// Detect obvious dead code
pub struct DeadCodeRule;

impl Rule for DeadCodeRule {
    fn name(&self) -> &'static str {
        "dead-code"
    }

    fn check(
        &self,
        file_path: &Path,
        _syntax_tree: &File,
        content: &str,
        lang: &str,
        is_test_file: bool,
    ) -> Vec<CodeIssue> {
        if is_test_file {
            return Vec::new();
        }

        let mut issues = Vec::new();
        let lines: Vec<&str> = content.lines().collect();
        let mut dead_code_start: Option<usize> = None;

        for (line_num, line) in lines.iter().enumerate() {
            let trimmed = line.trim();

            // Check if this line is a control flow terminator
            if is_control_flow_terminator(trimmed) {
                // Mark that subsequent lines are dead code
                dead_code_start = Some(line_num + 1);
                continue;
            }

            // If we're in a dead code region and this line has actual code
            if dead_code_start.is_some() {
                // Skip empty lines and comments
                if trimmed.is_empty() || trimmed.starts_with("//") || trimmed.starts_with("/*") {
                    continue;
                }

                // Check if this line closes a block (could be end of if/match/etc)
                if trimmed == "}" || trimmed == "} else {" || trimmed == "} else if" {
                    // Reset dead code region - we've exited the block
                    dead_code_start = None;
                    continue;
                }

                // This is actual dead code after a terminator
                let messages = if lang == "zh-CN" {
                    vec![
                        "发现死代码,这行永远不会执行",
                        "这行代码比我的社交生活还死",
                        "死代码警告:这里是代码的坟墓",
                        "这行代码已经去世了,建议删除",
                        "发现僵尸代码,需要清理",
                    ]
                } else {
                    vec![
                        "Dead code detected - this line will never execute",
                        "This code is deader than my social life",
                        "Dead code alert: code graveyard found here",
                        "This line of code has passed away, consider removal",
                        "Zombie code detected, cleanup needed",
                    ]
                };

                issues.push(CodeIssue {
                    file_path: file_path.to_path_buf(),
                    line: line_num + 1,
                    column: 1,
                    rule_name: "dead-code".to_string(),
                    message: messages[line_num % messages.len()].to_string(),
                    severity: Severity::Mild,
                });

                // Only report one dead code block per terminator
                dead_code_start = None;
            }
        }

        issues
    }
}

// ============================================================================
// Helper functions
// ============================================================================

fn is_likely_code(content: &str) -> bool {
    // Patterns that look like code
    let code_patterns = [
        "let ", "fn ", "if ", "else", "for ", "while ", "match ", "struct ", "enum ", "impl ",
        "use ", "mod ", "return ", "break", "continue", "{", "}", "(", ")", "[", "]", ";", "=",
        "==", "!=", "&&", "||", "->", "::",
    ];

    let rust_keywords = [
        "pub", "const", "static", "mut", "ref", "move", "async", "await", "unsafe", "extern",
        "crate",
    ];

    // If it contains multiple code patterns, it's likely code
    let pattern_count = code_patterns
        .iter()
        .filter(|&&pattern| content.contains(pattern))
        .count();

    let keyword_count = rust_keywords
        .iter()
        .filter(|&&keyword| content.contains(keyword))
        .count();

    pattern_count >= 2 || keyword_count >= 1
}

fn create_commented_code_issue(
    file_path: &Path,
    line: usize,
    block_size: usize,
    lang: &str,
) -> CodeIssue {
    let messages = if lang == "zh-CN" {
        vec![
            format!("发现 {} 行被注释的代码,是舍不得删除吗?", block_size),
            format!("{} 行注释代码,版本控制系统不香吗?", block_size),
            format!("{} 行注释代码就像前任,该放手就放手", block_size),
            format!("{} 行死代码注释,建议断舍离", block_size),
            format!("注释了 {} 行代码,Git 会记住它们的", block_size),
        ]
    } else {
        vec![
            format!(
                "Found {} lines of commented code - can't let go?",
                block_size
            ),
            format!(
                "{} lines of commented code - isn't version control enough?",
                block_size
            ),
            format!(
                "These {} commented lines are like an ex - time to let go",
                block_size
            ),
            format!(
                "{} lines of dead commented code - Marie Kondo would disapprove",
                block_size
            ),
            format!(
                "Commented {} lines of code - Git remembers them anyway",
                block_size
            ),
        ]
    };

    let severity = if block_size > 10 {
        Severity::Spicy
    } else {
        Severity::Mild
    };

    CodeIssue {
        file_path: file_path.to_path_buf(),
        line,
        column: 1,
        rule_name: "commented-code".to_string(),
        message: messages[block_size % messages.len()].clone(),
        severity,
    }
}

fn is_control_flow_terminator(line: &str) -> bool {
    // Check if this line is a pure control flow terminator
    // (the line itself terminates execution, not just contains these keywords)
    // Explicit parentheses used to clarify operator precedence (&& binds tighter than ||)
    let exact_matches = matches!(
        line,
        "return;"
            | "break;"
            | "continue;"
            | "unreachable!()"
            | "unreachable!();"
            | "std::process::exit(0);"
            | "std::process::exit(1);"
    );

    let has_return_statement =
        line.starts_with("return ") && line.ends_with(';') && !line.contains("//");

    let has_panic = line.starts_with("panic!(") && line.ends_with(';');

    let has_unreachable = line.starts_with("unreachable!(") && line.ends_with(')');

    exact_matches || has_return_statement || has_panic || has_unreachable
}

// ============================================================================
// Visitor implementations
// ============================================================================

struct MagicNumberVisitor {
    file_path: std::path::PathBuf,
    issues: Vec<CodeIssue>,
    lang: String,
}

impl MagicNumberVisitor {
    fn new(file_path: std::path::PathBuf, lang: &str) -> Self {
        Self {
            file_path,
            issues: Vec::new(),
            lang: lang.to_string(),
        }
    }

    fn is_magic_number(&self, value: i64) -> bool {
        // Common NON-magic numbers (powers of 2, common sizes, small counts, etc.)
        // These are so common they're almost always intentional
        let safe_numbers = [
            -1,      // Sentinel/error value
            0,       // Zero/initial/default
            1,       // Single/first/true
            2,       // Pair/dual/binary
            3,       // Triple/RGB/xyz
            4,       // Quad/nibble
            5,       // Quint/count
            6,       // Hex/week
            7,       // Week/oct
            8,       // Byte/octet
            9,       // Max digit
            10,      // Decimal base
            12,      // Dozen/months
            16,      // Nibble/4 bits
            20,      // Score
            24,      // Hours/day
            32,      // 5 bits
            64,      // 6 bits
            100,     // Percent/century
            128,     // 7 bits
            256,     // Byte/8 bits
            365,     // Days/year (approx)
            512,     // 9 bits
            1024,    // KB/10 bits
            2048,    // 11 bits
            4096,    // 12 bits / page size
            8192,    // 13 bits
            65536,   // u16 max / 16 bits
            100000,  // 100K
            1000000, // 1M
        ];

        !safe_numbers.contains(&value)
    }

    fn create_magic_number_issue(&self, value: i64, line: usize, column: usize) -> CodeIssue {
        // Categorize the magic number for better messages
        let category = self.categorize_magic_number(value);

        let messages = if self.lang == "zh-CN" {
            match category {
                MagicNumberCategory::Timeout => vec![
                    format!("超时值 {}?建议定义为常量如 TIMEOUT_MS", value),
                    format!("硬编码超时 {}ms,维护性-1,建议用常量", value),
                    format!("魔法数字 {} 看起来像超时,提取为命名常量", value),
                ],
                MagicNumberCategory::BufferSize => vec![
                    format!("缓冲区大小 {}?这是什么咒语?", value),
                    format!("硬编码缓冲区 {},维护性-1", value),
                    format!("缓冲区大小 {} 从天而降,没人知道它的含义", value),
                ],
                MagicNumberCategory::PortNumber => vec![
                    format!("端口号 {}?硬编码端口不安全且难以维护", value),
                    format!("发现硬编码端口 {},建议使用配置文件或环境变量", value),
                    format!("端口号 {} 应该定义为常量或从配置读取", value),
                ],
                MagicNumberCategory::Threshold => vec![
                    format!("阈值 {}?这个数字有什么特殊含义?", value),
                    format!("硬编码阈值 {},建议定义为有意义的常量", value),
                    format!("阈值 {} 缺乏语义,维护者会困惑", value),
                ],
                MagicNumberCategory::General => vec![
                    format!("魔法数字 {}?这是什么咒语?", value),
                    format!("硬编码数字 {},维护性-1", value),
                    format!("数字 {} 从天而降,没人知道它的含义", value),
                    format!("魔法数字 {},建议定义为常量", value),
                    format!("看到数字 {},我陷入了沉思", value),
                ],
            }
        } else {
            match category {
                MagicNumberCategory::Timeout => vec![
                    format!("Timeout value {}? Define as TIMEOUT_MS constant", value),
                    format!("Hardcoded timeout {}ms - extract to constant", value),
                    format!("Magic number {} looks like a timeout, name it", value),
                ],
                MagicNumberCategory::BufferSize => vec![
                    format!("Buffer size {}? What spell is this?", value),
                    format!("Hardcoded buffer {} - maintainability -1", value),
                    format!("Buffer size {} fell from sky, no meaning", value),
                ],
                MagicNumberCategory::PortNumber => vec![
                    format!("Port {}? Hardcoded ports are unmaintainable", value),
                    format!("Hardcoded port {} - use config or env var", value),
                    format!("Port {} should be constant or config-driven", value),
                ],
                MagicNumberCategory::Threshold => vec![
                    format!("Threshold {}? What's special about this?", value),
                    format!("Hardcoded threshold {} - define meaningful const", value),
                    format!("Threshold {} lacks semantics, confusing", value),
                ],
                MagicNumberCategory::General => vec![
                    format!("Magic number {}? What spell is this?", value),
                    format!("Hardcoded number {} - maintainability -1", value),
                    format!(
                        "Number {} fell from the sky, nobody knows its meaning",
                        value
                    ),
                    format!("Magic number {} - consider defining as a constant", value),
                    format!("Seeing number {}, I'm lost in thought", value),
                ],
            }
        };

        // Severity based on value magnitude and category
        let severity = match category {
            MagicNumberCategory::Timeout | MagicNumberCategory::Threshold => {
                if value > 1000 {
                    Severity::Spicy
                } else {
                    Severity::Mild
                }
            }
            MagicNumberCategory::PortNumber => Severity::Spicy, // Ports are always important to name
            _ => {
                if !(-100..=100).contains(&value)
                    && value != 800
                    && value != 1000
                    && value != 2000
                    && value != 3000
                    && value != 5000
                {
                    Severity::Spicy
                } else {
                    Severity::Mild
                }
            }
        };

        CodeIssue {
            file_path: self.file_path.clone(),
            line,
            column,
            rule_name: "magic-number".to_string(),
            message: messages[self.issues.len() % messages.len()].clone(),
            severity,
        }
    }

    fn categorize_magic_number(&self, value: i64) -> MagicNumberCategory {
        // Common timeout values (milliseconds)
        if [
            100, 200, 300, 500, 800, 1000, 1500, 2000, 3000, 5000, 10000, 30000, 60000,
        ]
        .contains(&value)
        {
            return MagicNumberCategory::Timeout;
        }

        // Common buffer sizes
        if [1024, 2048, 4096, 8192, 16384, 32768, 65536].contains(&value) {
            return MagicNumberCategory::BufferSize;
        }

        // Common port numbers
        if (3000..=9999).contains(&value) || value == 80 || value == 443 || value == 8080 {
            return MagicNumberCategory::PortNumber;
        }

        // Common threshold values
        if [
            10, 25, 50, 75, 90, 95, 99, 100, 150, 200, 250, 500, 750, 1000,
        ]
        .contains(&value)
        {
            return MagicNumberCategory::Threshold;
        }

        MagicNumberCategory::General
    }
}

impl<'ast> Visit<'ast> for MagicNumberVisitor {
    fn visit_expr_lit(&mut self, expr_lit: &'ast ExprLit) {
        if let Lit::Int(lit_int) = &expr_lit.lit {
            if let Ok(value) = lit_int.base10_parse::<i64>() {
                if self.is_magic_number(value) {
                    let (line, column) = get_position(expr_lit);
                    self.issues
                        .push(self.create_magic_number_issue(value, line, column));
                }
            }
        }
        syn::visit::visit_expr_lit(self, expr_lit);
    }
}

// ============================================================================
// God function detection
// ============================================================================

struct GodFunctionVisitor {
    file_path: std::path::PathBuf,
    issues: Vec<CodeIssue>,
    _content: String,
    lang: String,
}

impl GodFunctionVisitor {
    fn new(file_path: std::path::PathBuf, content: &str, lang: &str) -> Self {
        Self {
            file_path,
            issues: Vec::new(),
            _content: content.to_string(),
            lang: lang.to_string(),
        }
    }

    fn analyze_function_complexity(&mut self, func: &ItemFn) {
        let func_name = func.sig.ident.to_string();

        // Calculate various complexity metrics for the function
        let mut complexity_score = 0;

        // 1. Parameter count
        let param_count = func.sig.inputs.len();
        if param_count > 5 {
            complexity_score += (param_count - 5) * 2;
        }

        // 2. Function body size (estimated via string analysis)
        let func_str = format!("{func:?}");
        let line_count = func_str.lines().count();
        if line_count > 50 {
            complexity_score += (line_count - 50) / 10;
        }

        // 3. Nesting depth and control flow complexity
        let control_keywords = ["if", "else", "for", "while", "match", "loop"];
        for keyword in &control_keywords {
            complexity_score += func_str.matches(keyword).count();
        }

        // If complexity is too high, report the issue
        if complexity_score > 15 {
            let messages = if self.lang == "zh-CN" {
                vec![
                    format!("函数 '{}' 做的事情比我一天做的还多", func_name),
                    format!("'{}' 是上帝函数吗?什么都想管", func_name),
                    format!("函数 '{}' 复杂得像我的感情生活", func_name),
                    format!("'{}' 这个函数需要拆分,太臃肿了", func_name),
                    format!("函数 '{}' 违反了单一职责原则", func_name),
                ]
            } else {
                vec![
                    format!(
                        "Function '{}' does more things than I do in a day",
                        func_name
                    ),
                    format!(
                        "Is '{}' a god function? Wants to control everything",
                        func_name
                    ),
                    format!("Function '{}' is as complex as my love life", func_name),
                    format!("Function '{}' needs to be split - too bloated", func_name),
                    format!(
                        "Function '{}' violates single responsibility principle",
                        func_name
                    ),
                ]
            };

            let severity = if complexity_score > 25 {
                Severity::Spicy
            } else {
                Severity::Mild
            };

            let (line, column) = get_position(func);
            self.issues.push(CodeIssue {
                file_path: self.file_path.clone(),
                line,
                column,
                rule_name: "god-function".to_string(),
                message: messages[self.issues.len() % messages.len()].clone(),
                severity,
            });
        }
    }
}

impl<'ast> Visit<'ast> for GodFunctionVisitor {
    fn visit_item_fn(&mut self, func: &'ast ItemFn) {
        self.analyze_function_complexity(func);
        syn::visit::visit_item_fn(self, func);
    }
}