garbage-code-hunter 0.2.0

A humorous Rust code quality detector that roasts your garbage code
Documentation
use std::path::Path;
use syn::{visit::Visit, File, ItemMod};

use crate::analyzer::{CodeIssue, Severity};
use crate::rules::Rule;
use crate::utils::find_line_of_str;

/// Detects files that are too long (>1000 lines)
pub struct FileStructureRule;

impl Rule for FileStructureRule {
    fn name(&self) -> &'static str {
        "file-structure"
    }

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

        let threshold = if is_test_file { 2000 } else { 1000 };
        if line_count > threshold {
            let messages_zh = [
                "这个文件比我的毕业论文还长!建议拆分成多个模块 📚",
                "文件长度突破天际!是想创造吉尼斯纪录吗? 🚀",
                "这么长的文件,建议配个目录和索引 📖",
                "文件行数比我一年的代码还多! 📈",
                "这个文件需要电梯才能到底部 🏢",
                "建议把这个巨型文件拆分成几个小文件,拯救一下可读性 🆘",
            ];

            let messages_en = [
                "This file is longer than my thesis! Consider splitting into modules 📚",
                "File length has reached the stratosphere! Going for a world record? 🚀",
                "Such a long file needs a table of contents and index 📖",
                "More lines than I write in a year! 📈",
                "This file needs an elevator to reach the bottom 🏢",
                "Please split this monster file into smaller ones, save the readability 🆘",
            ];

            let messages = if lang == "zh-CN" {
                &messages_zh
            } else {
                &messages_en
            };
            let message = messages[line_count % messages.len()];

            let severity = if line_count > 2000 {
                Severity::Nuclear
            } else if line_count > 1500 {
                Severity::Spicy
            } else {
                Severity::Mild
            };

            issues.push(CodeIssue {
                file_path: file_path.to_path_buf(),
                line: threshold,
                column: 1,
                rule_name: "file-too-long".to_string(),
                message: format!("{} ({}行)", message, line_count),
                severity,
            });
        }

        issues
    }
}

/// Detects chaotic import order
pub struct ImportChaosRule;

impl Rule for ImportChaosRule {
    fn name(&self) -> &'static str {
        "import-chaos"
    }

    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();

        // Check for duplicate use statements
        let use_lines: Vec<&str> = content
            .lines()
            .filter(|line| line.trim().starts_with("use "))
            .collect();

        if use_lines.len() > 1 {
            // Check for duplicate imports
            let mut seen_imports = std::collections::HashSet::new();
            for use_line in &use_lines {
                if !seen_imports.insert(use_line) {
                    let messages = if lang == "zh-CN" {
                        [
                            "重复的 import!是想强调重要性吗? 🔄",
                            "同样的 use 语句出现了多次,建议去重 🗑️",
                            "重复 import 比我重复的话还多 💬",
                        ]
                    } else {
                        [
                            "Duplicate imports! Trying to emphasize importance? 🔄",
                            "Same use statement appears multiple times, consider deduplication 🗑️",
                            "More duplicate imports than my repeated words 💬",
                        ]
                    };

                    issues.push(CodeIssue {
                        file_path: file_path.to_path_buf(),
                        line: find_line_of_str(content, "use "),
                        column: 1,
                        rule_name: "duplicate-imports".to_string(),
                        message: messages[issues.len() % messages.len()].to_string(),
                        severity: Severity::Mild,
                    });
                }
            }
        }

        issues
    }
}

/// Detects overly deep module nesting
pub struct ModuleNestingRule;

impl Rule for ModuleNestingRule {
    fn name(&self) -> &'static str {
        "module-nesting"
    }

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

struct ModuleNestingVisitor {
    file_path: std::path::PathBuf,
    lang: String,
    issues: Vec<CodeIssue>,
    nesting_depth: usize,
    max_depth: usize,
    is_test_file: bool,
    content: String,
}

impl ModuleNestingVisitor {
    fn new(file_path: std::path::PathBuf, lang: &str, is_test_file: bool, content: &str) -> Self {
        Self {
            file_path,
            lang: lang.to_string(),
            issues: Vec::new(),
            nesting_depth: 0,
            max_depth: 0,
            is_test_file,
            content: content.to_string(),
        }
    }

    fn check_nesting_depth(&mut self) {
        let threshold = if self.is_test_file { 6 } else { 4 };
        if self.nesting_depth > threshold {
            let messages_zh = [
                "模块嵌套比俄罗斯套娃还深!建议扁平化结构 🪆",
                "这个嵌套深度需要GPS导航才能找到出口 🗺️",
                "模块嵌套层数比我的心理防线还多 🏰",
                "建议重新设计模块结构,当前嵌套过深 📐",
            ];

            let messages_en = [
                "Module nesting deeper than Russian dolls! Consider flattening 🪆",
                "This nesting depth needs GPS navigation to find the exit 🗺️",
                "More module nesting layers than my psychological defenses 🏰",
                "Consider redesigning module structure, current nesting too deep 📐",
            ];

            let messages = if self.lang == "zh-CN" {
                &messages_zh
            } else {
                &messages_en
            };
            let message = messages[self.issues.len() % messages.len()];

            let severity = if self.nesting_depth > threshold + 2 {
                Severity::Spicy
            } else {
                Severity::Mild
            };

            self.issues.push(CodeIssue {
                file_path: self.file_path.clone(),
                line: find_line_of_str(&self.content, "mod "),
                column: 1,
                rule_name: "deep-module-nesting".to_string(),
                message: format!("{} (深度: {})", message, self.nesting_depth),
                severity,
            });
        }
    }
}

impl<'ast> Visit<'ast> for ModuleNestingVisitor {
    fn visit_item_mod(&mut self, module: &'ast ItemMod) {
        self.nesting_depth += 1;
        self.max_depth = self.max_depth.max(self.nesting_depth);

        self.check_nesting_depth();

        syn::visit::visit_item_mod(self, module);
        self.nesting_depth -= 1;
    }
}