Skip to main content

garbage_code_hunter/rules/
naming.rs

1use regex::Regex;
2use std::path::Path;
3use syn::{visit::Visit, File, Ident};
4
5use crate::analyzer::{CodeIssue, Severity};
6use crate::context::FileContext;
7use crate::rules::Rule;
8use crate::utils::get_position;
9
10pub struct TerribleNamingRule;
11
12impl Rule for TerribleNamingRule {
13    fn name(&self) -> &'static str {
14        "terrible-naming"
15    }
16
17    fn check(
18        &self,
19        file_path: &Path,
20        syntax_tree: &File,
21        _content: &str,
22        lang: &str,
23        is_test_file: bool,
24    ) -> Vec<CodeIssue> {
25        if is_test_file {
26            return Vec::new();
27        }
28
29        let mut visitor = NamingVisitor::new(file_path.to_path_buf(), lang);
30        visitor.visit_file(syntax_tree);
31        visitor.issues
32    }
33
34    fn check_with_context(
35        &self,
36        file_path: &Path,
37        syntax_tree: &File,
38        content: &str,
39        lang: &str,
40        is_test_file: bool,
41        context: &FileContext,
42        _config: &crate::context::ProjectConfig,
43    ) -> Vec<CodeIssue> {
44        // Example/Demo/Documentation: 完全跳过
45        let weight = context.rule_weight_multiplier();
46        if weight < 0.5 {
47            return Vec::new();
48        }
49
50        self.check(file_path, syntax_tree, content, lang, is_test_file)
51    }
52}
53
54pub struct SingleLetterVariableRule;
55
56impl Rule for SingleLetterVariableRule {
57    fn name(&self) -> &'static str {
58        "single-letter-variable"
59    }
60
61    fn check(
62        &self,
63        file_path: &Path,
64        syntax_tree: &File,
65        _content: &str,
66        lang: &str,
67        is_test_file: bool,
68    ) -> Vec<CodeIssue> {
69        if is_test_file {
70            return Vec::new();
71        }
72
73        let mut visitor = SingleLetterVisitor::new(file_path.to_path_buf(), lang);
74        visitor.visit_file(syntax_tree);
75        visitor.issues
76    }
77}
78
79struct NamingVisitor {
80    file_path: std::path::PathBuf,
81    issues: Vec<CodeIssue>,
82    terrible_names: Regex,
83    lang: String,
84}
85
86impl NamingVisitor {
87    fn new(file_path: std::path::PathBuf, lang: &str) -> Self {
88        // Note: 'item' is excluded as it's a common Rust iterator variable
89        let terrible_names = Regex::new(r"^(data|info|temp|tmp|val|value|thing|stuff|obj|object|manager|handler|helper|util|utils)(\d+)?$").unwrap();
90
91        Self {
92            file_path,
93            issues: Vec::new(),
94            terrible_names,
95            lang: lang.to_string(),
96        }
97    }
98
99    fn check_name(&mut self, ident: &Ident, context: &str) {
100        let name = ident.to_string();
101
102        if self.terrible_names.is_match(&name.to_lowercase()) {
103            // Select messages based on language setting
104            let messages = if self.lang == "zh-CN" {
105                // Chinese messages
106                let ctx = if context == "函数名" {
107                    "函数名"
108                } else {
109                    "变量名"
110                };
111                vec![
112                    format!("{} '{}' - 比我的编程技能还要抽象", ctx, name),
113                    format!(
114                        "{} '{}' - 这个名字告诉我你已经放弃治疗了,建议直接转行卖煎饼果子",
115                        ctx, name
116                    ),
117                    format!(
118                        "{} '{}' - 用这个做名字?你是想让维护代码的人哭着辞职吗?",
119                        ctx, name
120                    ),
121                    format!("{} '{}' - 恭喜你发明了最没有意义的标识符", ctx, name),
122                    format!("{} '{}' - 创意程度约等于给孩子起名叫'小明'", ctx, name),
123                    format!(
124                        "{} '{}' - 看到这个名字,我的智商都下降了,现在只能数到3了",
125                        ctx, name
126                    ),
127                ]
128            } else {
129                // English messages
130                let ctx = if context == "Function" {
131                    "Function"
132                } else {
133                    "Variable"
134                };
135                vec![
136                    format!("{} '{}' - more abstract than my programming skills", ctx, name),
137                    format!("{} '{}' - this name tells me you've given up on life and should sell hotdogs", ctx, name),
138                    format!("{} '{}' - using this name? trying to make maintainers cry and quit?", ctx, name),
139                    format!("{} '{}' - congrats on inventing the most meaningless identifier", ctx, name),
140                    format!("{} '{}' - creativity level of naming a kid 'Child'", ctx, name),
141                    format!("{} '{}' - seeing this name, my IQ dropped to single digits", ctx, name),
142                ]
143            };
144
145            let message_index =
146                (self.issues.len() + name.len() + name.chars().next().unwrap_or('a') as usize)
147                    % messages.len();
148
149            let (line, column) = get_position(ident);
150
151            self.issues.push(CodeIssue {
152                file_path: self.file_path.clone(),
153                line,
154                column,
155                rule_name: "terrible-naming".to_string(),
156                message: messages[message_index].clone(),
157                severity: Severity::Spicy,
158            });
159        }
160    }
161}
162
163impl<'ast> Visit<'ast> for NamingVisitor {
164    // Check variable names (pattern identifiers)
165    fn visit_pat_ident(&mut self, pat_ident: &'ast syn::PatIdent) {
166        let context = if self.lang == "zh-CN" {
167            "变量名"
168        } else {
169            "Variable"
170        };
171        self.check_name(&pat_ident.ident, context);
172        syn::visit::visit_pat_ident(self, pat_ident);
173    }
174
175    // Check function names
176    fn visit_item_fn(&mut self, func: &'ast syn::ItemFn) {
177        let context = if self.lang == "zh-CN" {
178            "函数名"
179        } else {
180            "Function"
181        };
182        self.check_name(&func.sig.ident, context);
183        syn::visit::visit_item_fn(self, func);
184    }
185}
186
187struct SingleLetterVisitor {
188    file_path: std::path::PathBuf,
189    issues: Vec<CodeIssue>,
190    lang: String,
191}
192
193impl SingleLetterVisitor {
194    fn new(file_path: std::path::PathBuf, lang: &str) -> Self {
195        Self {
196            file_path,
197            issues: Vec::new(),
198            lang: lang.to_string(),
199        }
200    }
201}
202
203impl<'ast> Visit<'ast> for SingleLetterVisitor {
204    fn visit_pat_ident(&mut self, pat_ident: &'ast syn::PatIdent) {
205        let name = pat_ident.ident.to_string();
206
207        // Exclude common single-letter variables (loop counters, closure params, etc.)
208        if name.len() == 1
209            && !matches!(
210                name.as_str(),
211                "i" | "j"
212                    | "k"
213                    | "x"
214                    | "y"
215                    | "z"
216                    | "e"
217                    | "a"
218                    | "b"
219                    | "c"
220                    | "d"
221                    | "f"
222                    | "n"
223                    | "r"
224                    | "s"
225                    | "t"
226                    | "v"
227                    | "w"
228                    | "p"
229                    | "l"
230            )
231        {
232            let messages = if self.lang == "zh-CN" {
233                [
234                    format!("单字母变量 '{name}'?你是在写数学公式还是在折磨读代码的人?"),
235                    format!("变量 '{name}'?这是变量名还是你键盘坏了?"),
236                    format!("用 '{name}' 做变量名,你可能需要一本《如何给变量起名》的书"),
237                    format!("单字母变量 '{name}':让代码比古埃及象形文字还难懂"),
238                    format!("变量 '{name}' 的信息量约等于一个句号"),
239                ]
240            } else {
241                [
242                    format!("Single-letter variable '{name}'? Writing math formulas or torturing readers?"),
243                    format!("Variable '{name}'? Is this a name or did your keyboard break?"),
244                    format!("Using '{name}' as a variable name? You need a book on naming"),
245                    format!("Single-letter variable '{name}': harder to read than hieroglyphics"),
246                    format!("Variable '{name}' has about as much info as a period"),
247                ]
248            };
249
250            let message_index =
251                (self.issues.len() + name.len() + name.chars().next().unwrap_or('a') as usize)
252                    % messages.len();
253
254            let (line, column) = get_position(&pat_ident.ident);
255
256            self.issues.push(CodeIssue {
257                file_path: self.file_path.clone(),
258                line,
259                column,
260                rule_name: "single-letter-variable".to_string(),
261                message: messages[message_index].clone(),
262                severity: Severity::Mild,
263            });
264        }
265
266        syn::visit::visit_pat_ident(self, pat_ident);
267    }
268}