Skip to main content

garbage_code_hunter/rules/
garbage_naming.rs

1use std::path::Path;
2use syn::{visit::Visit, File, Ident};
3
4use crate::analyzer::{CodeIssue, Severity};
5use crate::context::FileContext;
6use crate::rules::Rule;
7use crate::utils::get_position;
8
9/// Detect meaningless placeholder names: foo, bar, baz, qux, test, temp, etc.
10pub struct MeaninglessNamingRule;
11
12impl Rule for MeaninglessNamingRule {
13    fn name(&self) -> &'static str {
14        "meaningless-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 = MeaninglessNamingVisitor::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        use crate::context::FileContext::*;
45
46        match context {
47            // Test/Documentation/Benchmark: 完全跳过
48            Test | Documentation | Benchmark => return Vec::new(),
49
50            // Example/Demo: 仅报告 Nuclear 级别问题(极少)
51            Example => {
52                let issues = self.check(file_path, syntax_tree, content, lang, is_test_file);
53                return issues
54                    .into_iter()
55                    .filter(|issue| issue.severity == Severity::Nuclear)
56                    .collect();
57            }
58
59            // UI/TUI code: Skip UI-specific common names (x, y, w, h, r, g, b, etc.)
60            UI => {
61                let all_issues = self.check(file_path, syntax_tree, content, lang, is_test_file);
62                let ui_whitelist = [
63                    // Coordinates and dimensions
64                    "x", "y", "w", "h", // Colors
65                    "r", "g", "b", "a", // Deltas
66                    "dx", "dy", // Geometry
67                    "rect", "area", "size", // Positions/vectors
68                    "pos", "vec", // UI references
69                    "ui", "tui", // Common TUI data names (very common in UI callbacks)
70                    "data", "info", "value", "state", "config", "event", "input", "output",
71                    "result", // Layout-related
72                    "chunk", "layout", "frame", "block",
73                ];
74                return all_issues
75                    .into_iter()
76                    .filter(|issue| {
77                        !ui_whitelist.iter().any(|&name| {
78                            issue
79                                .message
80                                .to_lowercase()
81                                .contains(&format!("'{}'", name))
82                        })
83                    })
84                    .collect();
85            }
86
87            // GPU code: Skip GPU-common names (i, j, k, idx, src, dst, etc.)
88            GPU => {
89                let all_issues = self.check(file_path, syntax_tree, content, lang, is_test_file);
90                let gpu_whitelist = [
91                    "i", "j", "k",   // loop indices
92                    "idx", // index abbreviation
93                    "src", "dst",  // source/destination
94                    "buf",  // buffer
95                    "ptr",  // pointer
96                    "data", // data buffer (common in GPU)
97                ];
98                return all_issues
99                    .into_iter()
100                    .filter(|issue| {
101                        !gpu_whitelist.iter().any(|&name| {
102                            issue
103                                .message
104                                .to_lowercase()
105                                .contains(&format!("'{}'", name))
106                        })
107                    })
108                    .collect();
109            }
110
111            // Web code: Slightly relaxed - allow common web naming
112            Web => {
113                let all_issues = self.check(file_path, syntax_tree, content, lang, is_test_file);
114                let web_whitelist = [
115                    "data", // request/response data
116                    "info", // metadata
117                    "req", "res",    // request/response abbreviations
118                    "body",   // response body
119                    "config", // configuration objects
120                ];
121                return all_issues
122                    .into_iter()
123                    .filter(|issue| {
124                        !web_whitelist.iter().any(|&name| {
125                            issue
126                                .message
127                                .to_lowercase()
128                                .contains(&format!("'{}'", name))
129                        })
130                    })
131                    .collect();
132            }
133
134            // Business 上下文:正常检测(保持原有行为)
135            Business => {}
136
137            // Config files: skip completely (handled by should_skip_rule)
138            Config => return Vec::new(),
139        }
140
141        self.check(file_path, syntax_tree, content, lang, is_test_file)
142    }
143}
144
145/// Detect outdated Hungarian notation: strName, intCount, bIsValid, etc.
146pub struct HungarianNotationRule;
147
148impl Rule for HungarianNotationRule {
149    fn name(&self) -> &'static str {
150        "hungarian-notation"
151    }
152
153    fn check(
154        &self,
155        file_path: &Path,
156        syntax_tree: &File,
157        _content: &str,
158        lang: &str,
159        is_test_file: bool,
160    ) -> Vec<CodeIssue> {
161        if is_test_file {
162            return Vec::new();
163        }
164        let mut visitor = HungarianNotationVisitor::new(file_path.to_path_buf(), lang);
165        visitor.visit_file(syntax_tree);
166        visitor.issues
167    }
168}
169
170/// Detect excessive abbreviations: mgr, ctrl, btn, usr, pwd, etc.
171pub struct AbbreviationAbuseRule;
172
173impl Rule for AbbreviationAbuseRule {
174    fn name(&self) -> &'static str {
175        "abbreviation-abuse"
176    }
177
178    fn check(
179        &self,
180        file_path: &Path,
181        syntax_tree: &File,
182        _content: &str,
183        lang: &str,
184        is_test_file: bool,
185    ) -> Vec<CodeIssue> {
186        if is_test_file {
187            return Vec::new();
188        }
189        let mut visitor = AbbreviationAbuseVisitor::new(file_path.to_path_buf(), lang);
190        visitor.visit_file(syntax_tree);
191        visitor.issues
192    }
193}
194
195// ============================================================================
196// Visitor implementations
197// ============================================================================
198
199struct MeaninglessNamingVisitor {
200    file_path: std::path::PathBuf,
201    issues: Vec<CodeIssue>,
202    lang: String,
203}
204
205impl MeaninglessNamingVisitor {
206    fn new(file_path: std::path::PathBuf, lang: &str) -> Self {
207        Self {
208            file_path,
209            issues: Vec::new(),
210            lang: lang.to_string(),
211        }
212    }
213
214    fn is_meaningless_name(&self, name: &str) -> bool {
215        let meaningless_names = [
216            // Classic placeholders - truly meaningless
217            "foo",
218            "bar",
219            "baz",
220            "qux",
221            "quux",
222            "quuz",
223            // Generic words with no context (note: 'item' excluded as it's a common Rust iterator variable)
224            "data",
225            "info",
226            "obj",
227            "thing",
228            "stuff",
229            "value",
230            "temp",
231            "tmp",
232            "example",
233            "sample",
234            // Manager suffix abuse
235            "manager",
236            "handler",
237            "processor",
238            "controller",
239            // Chinese pinyin (common ones)
240            "yonghu",
241            "mima",
242            "denglu",
243            "zhuce",
244            "shuju",
245        ];
246
247        let name_lower = name.to_lowercase();
248        meaningless_names
249            .iter()
250            .any(|&bad_name| name_lower == bad_name)
251    }
252
253    fn create_issue(&self, name: &str, line: usize, column: usize) -> CodeIssue {
254        let messages = if self.lang == "zh-CN" {
255            vec![
256                format!("变量名 '{}' 比我的网名还随意", name),
257                format!("'{}' 这个名字,是从字典里随机选的吗?", name),
258                format!("用 '{}' 做变量名?你是想让下一个维护代码的人猜谜吗?", name),
259                format!("'{}' 这个名字毫无意义,就像我的人生一样", name),
260                format!("看到 '{}' 这个变量名,我的智商受到了侮辱", name),
261            ]
262        } else {
263            vec![
264                format!("Variable name '{}' is more meaningless than my existence", name),
265                format!("'{}' - did you pick this name with your eyes closed?", name),
266                format!("Using '{}' as a variable name? Are you playing charades with future developers?", name),
267                format!("'{}' tells me nothing about what this variable does", name),
268                format!("The name '{}' is as helpful as a chocolate teapot", name),
269            ]
270        };
271
272        let severity = if ["foo", "bar", "baz", "data", "temp"].contains(&name) {
273            Severity::Spicy
274        } else {
275            Severity::Mild
276        };
277
278        CodeIssue {
279            file_path: self.file_path.clone(),
280            line,
281            column,
282            rule_name: "meaningless-naming".to_string(),
283            message: messages[self.issues.len() % messages.len()].clone(),
284            severity,
285        }
286    }
287}
288
289impl<'ast> Visit<'ast> for MeaninglessNamingVisitor {
290    fn visit_ident(&mut self, ident: &'ast Ident) {
291        let name = ident.to_string();
292        if self.is_meaningless_name(&name) {
293            let (line, column) = get_position(ident);
294            self.issues.push(self.create_issue(&name, line, column));
295        }
296        syn::visit::visit_ident(self, ident);
297    }
298}
299
300// ============================================================================
301// Hungarian notation detection
302// ============================================================================
303
304struct HungarianNotationVisitor {
305    file_path: std::path::PathBuf,
306    issues: Vec<CodeIssue>,
307    lang: String,
308}
309
310impl HungarianNotationVisitor {
311    fn new(file_path: std::path::PathBuf, lang: &str) -> Self {
312        Self {
313            file_path,
314            issues: Vec::new(),
315            lang: lang.to_string(),
316        }
317    }
318
319    fn is_hungarian_notation(&self, name: &str) -> bool {
320        let hungarian_prefixes = [
321            // Type prefixes (only check with camelCase, not underscore)
322            "str", "int", "bool", "float", "double", "char", "arr", "vec", "list", "map", "set",
323            // Scope prefixes
324            "g_", "m_", "s_", "p_",
325        ];
326
327        // Check if starts with a Hungarian prefix
328        for prefix in hungarian_prefixes {
329            if name.starts_with(prefix) && name.len() > prefix.len() {
330                // Check if prefix is followed by uppercase letter (camelCase)
331                if let Some(next_char) = name.chars().nth(prefix.len()) {
332                    if next_char.is_uppercase() {
333                        // Avoid false positives on words like "stringify", "internal", "boolean"
334                        // by checking that the prefix is not part of a longer word
335                        let rest = &name[prefix.len()..];
336                        // If the rest starts with a common word continuation, skip
337                        if rest.starts_with("ify")
338                            || rest.starts_with("nal")
339                            || rest.starts_with("ean")
340                        {
341                            continue;
342                        }
343                        return true;
344                    }
345                }
346            }
347        }
348
349        // Check underscore-separated case separately
350        for prefix in &["g_", "m_", "s_", "p_"] {
351            if name.starts_with(prefix) {
352                return true;
353            }
354        }
355
356        false
357    }
358
359    fn create_issue(&self, name: &str, line: usize, column: usize) -> CodeIssue {
360        let messages = if self.lang == "zh-CN" {
361            vec![
362                format!("'{}' 使用了匈牙利命名法?这不是1990年代了", name),
363                format!("看到 '{}' 我仿佛回到了 C++ 的石器时代", name),
364                format!("'{}' 这种命名方式已经过时了,就像我的发型一样", name),
365                format!("匈牙利命名法 '{}'?Rust 编译器已经帮你检查类型了", name),
366                format!("'{}' 让我想起了那些痛苦的 C++ 岁月", name),
367            ]
368        } else {
369            vec![
370                format!(
371                    "'{}' uses Hungarian notation? This isn't the 1990s anymore",
372                    name
373                ),
374                format!(
375                    "Seeing '{}' makes me nostalgic for the dark ages of C++",
376                    name
377                ),
378                format!(
379                    "'{}' - Hungarian notation is as outdated as my haircut",
380                    name
381                ),
382                format!(
383                    "Hungarian notation '{}'? Rust's type system has got you covered",
384                    name
385                ),
386                format!("'{}' reminds me of painful C++ memories", name),
387            ]
388        };
389
390        CodeIssue {
391            file_path: self.file_path.clone(),
392            line,
393            column,
394            rule_name: "hungarian-notation".to_string(),
395            message: messages[self.issues.len() % messages.len()].clone(),
396            severity: Severity::Mild,
397        }
398    }
399}
400
401impl<'ast> Visit<'ast> for HungarianNotationVisitor {
402    fn visit_ident(&mut self, ident: &'ast Ident) {
403        let name = ident.to_string();
404        if self.is_hungarian_notation(&name) {
405            let (line, column) = get_position(ident);
406            self.issues.push(self.create_issue(&name, line, column));
407        }
408        syn::visit::visit_ident(self, ident);
409    }
410}
411
412// ============================================================================
413// Excessive abbreviation detection
414// ============================================================================
415
416struct AbbreviationAbuseVisitor {
417    file_path: std::path::PathBuf,
418    issues: Vec<CodeIssue>,
419    lang: String,
420}
421
422impl AbbreviationAbuseVisitor {
423    fn new(file_path: std::path::PathBuf, lang: &str) -> Self {
424        Self {
425            file_path,
426            issues: Vec::new(),
427            lang: lang.to_string(),
428        }
429    }
430
431    fn is_bad_abbreviation(&self, name: &str) -> Option<&'static str> {
432        let bad_abbreviations = [
433            // Management related - these are truly unclear
434            ("mgr", "manager"),
435            ("mngr", "manager"),
436            ("ctrl", "controller"),
437            ("hdlr", "handler"),
438            // User related
439            ("usr", "user"),
440            ("pwd", "password"),
441            ("prefs", "preferences"),
442            // UI related
443            ("btn", "button"),
444            ("lbl", "label"),
445            ("pic", "picture"),
446            // Data related
447            ("tbl", "table"),
448            ("col", "column"),
449            ("cnt", "count"),
450        ];
451
452        let name_lower = name.to_lowercase();
453        for (abbrev, full) in bad_abbreviations {
454            if name_lower == abbrev || name_lower.starts_with(&format!("{abbrev}_")) {
455                return Some(full);
456            }
457        }
458        None
459    }
460
461    fn create_issue(&self, name: &str, suggestion: &str, line: usize, column: usize) -> CodeIssue {
462        let messages = if self.lang == "zh-CN" {
463            vec![
464                format!("'{}' 缩写得太狠了,建议用 '{}'", name, suggestion),
465                format!("看到 '{}' 我需要解密,不如直接用 '{}'", name, suggestion),
466                format!(
467                    "'{}' 这个缩写让我想起了发电报的年代,用 '{}' 吧",
468                    name, suggestion
469                ),
470                format!(
471                    "'{}' 省了几个字母,却让代码可读性大打折扣,试试 '{}'",
472                    name, suggestion
473                ),
474                format!("缩写 '{}' 就像密码一样难懂,'{}'不香吗?", name, suggestion),
475            ]
476        } else {
477            vec![
478                format!("'{}' is too abbreviated, consider '{}'", name, suggestion),
479                format!(
480                    "Seeing '{}' makes me feel like I'm decoding, just use '{}'",
481                    name, suggestion
482                ),
483                format!(
484                    "'{}' reminds me of telegraph era, try '{}'",
485                    name, suggestion
486                ),
487                format!(
488                    "'{}' saves a few letters but kills readability, use '{}'",
489                    name, suggestion
490                ),
491                format!(
492                    "Abbreviation '{}' is cryptic, isn't '{}' better?",
493                    name, suggestion
494                ),
495            ]
496        };
497
498        CodeIssue {
499            file_path: self.file_path.clone(),
500            line,
501            column,
502            rule_name: "abbreviation-abuse".to_string(),
503            message: messages[self.issues.len() % messages.len()].clone(),
504            severity: Severity::Mild,
505        }
506    }
507}
508
509impl<'ast> Visit<'ast> for AbbreviationAbuseVisitor {
510    fn visit_ident(&mut self, ident: &'ast Ident) {
511        let name = ident.to_string();
512        if let Some(suggestion) = self.is_bad_abbreviation(&name) {
513            let (line, column) = get_position(ident);
514            self.issues
515                .push(self.create_issue(&name, suggestion, line, column));
516        }
517        syn::visit::visit_ident(self, ident);
518    }
519}