garbage_code_hunter/rules/
student_code.rs

1use std::path::Path;
2use syn::{visit::Visit, ExprMacro, File};
3
4use crate::analyzer::{CodeIssue, RoastLevel, Severity};
5use crate::rules::Rule;
6use crate::utils::get_position;
7
8/// 检测到处都是 println! 调试语句
9pub struct PrintlnDebuggingRule;
10
11impl Rule for PrintlnDebuggingRule {
12    fn name(&self) -> &'static str {
13        "println-debugging"
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 = PrintlnDebuggingVisitor::new(file_path.to_path_buf(), lang);
24        visitor.visit_file(syntax_tree);
25
26        // 同时检查内容中的 println! 数量
27        let println_count = content.matches("println!").count();
28        if println_count > 5 {
29            visitor.add_excessive_println_issue(println_count);
30        }
31
32        visitor.issues
33    }
34}
35
36/// 检测随意使用 panic! 和 unwrap()
37pub struct PanicAbuseRule;
38
39impl Rule for PanicAbuseRule {
40    fn name(&self) -> &'static str {
41        "panic-abuse"
42    }
43
44    fn check(
45        &self,
46        file_path: &Path,
47        syntax_tree: &File,
48        content: &str,
49        lang: &str,
50    ) -> Vec<CodeIssue> {
51        let mut visitor = PanicAbuseVisitor::new(file_path.to_path_buf(), lang);
52        visitor.visit_file(syntax_tree);
53
54        // 检查内容中的 panic! 和 unwrap 使用
55        let panic_count = content.matches("panic!").count();
56        let unwrap_count = content.matches(".unwrap()").count();
57
58        if panic_count > 2 {
59            visitor.add_excessive_panic_issue(panic_count);
60        }
61        if unwrap_count > 3 {
62            visitor.add_excessive_unwrap_issue(unwrap_count);
63        }
64
65        visitor.issues
66    }
67}
68
69/// 检测过多的 TODO 注释
70pub struct TodoCommentRule;
71
72impl Rule for TodoCommentRule {
73    fn name(&self) -> &'static str {
74        "todo-comment"
75    }
76
77    fn check(
78        &self,
79        file_path: &Path,
80        _syntax_tree: &File,
81        content: &str,
82        lang: &str,
83    ) -> Vec<CodeIssue> {
84        let mut issues = Vec::new();
85
86        // 检查各种 TODO 模式
87        let todo_patterns = [
88            "TODO",
89            "FIXME",
90            "XXX",
91            "HACK",
92            "BUG",
93            "NOTE",
94            "todo!",
95            "unimplemented!",
96            "unreachable!",
97        ];
98
99        let mut total_todos = 0;
100        for pattern in &todo_patterns {
101            total_todos += content.matches(pattern).count();
102        }
103
104        if total_todos > 5 {
105            let messages = if lang == "zh-CN" {
106                vec![
107                    format!("发现 {} 个 TODO/FIXME,这是代码还是购物清单?", total_todos),
108                    format!("{} 个未完成项目?你是在写代码还是在记日记?", total_todos),
109                    format!("TODO 比实际代码还多,建议改名叫 'TODO Hunter'",),
110                    format!("{} 个 TODO,看来这个项目还在'施工中'", total_todos),
111                    format!("这么多 TODO,是不是该考虑换个职业了?",),
112                ]
113            } else {
114                vec![
115                    format!(
116                        "Found {} TODOs/FIXMEs - is this code or a shopping list?",
117                        total_todos
118                    ),
119                    format!(
120                        "{} unfinished items? Are you coding or journaling?",
121                        total_todos
122                    ),
123                    format!("More TODOs than actual code, consider renaming to 'TODO Hunter'"),
124                    format!(
125                        "{} TODOs - looks like this project is still 'under construction'",
126                        total_todos
127                    ),
128                    format!("So many TODOs, maybe consider a career change?"),
129                ]
130            };
131
132            let severity = if total_todos > 10 {
133                Severity::Spicy
134            } else {
135                Severity::Mild
136            };
137
138            issues.push(CodeIssue {
139                file_path: file_path.to_path_buf(),
140                line: 1,
141                column: 1,
142                rule_name: "todo-comment".to_string(),
143                message: messages[total_todos % messages.len()].clone(),
144                severity,
145                roast_level: RoastLevel::Sarcastic,
146            });
147        }
148
149        issues
150    }
151}
152
153// ============================================================================
154// Visitor 实现
155// ============================================================================
156
157struct PrintlnDebuggingVisitor {
158    file_path: std::path::PathBuf,
159    issues: Vec<CodeIssue>,
160    lang: String,
161    println_count: usize,
162}
163
164impl PrintlnDebuggingVisitor {
165    fn new(file_path: std::path::PathBuf, lang: &str) -> Self {
166        Self {
167            file_path,
168            issues: Vec::new(),
169            lang: lang.to_string(),
170            println_count: 0,
171        }
172    }
173
174    fn add_excessive_println_issue(&mut self, count: usize) {
175        let messages = if self.lang == "zh-CN" {
176            vec![
177                format!("{} 个 println! 调试?你是在开演唱会吗?", count),
178                format!("这么多 println!,控制台都要被刷屏了",),
179                format!("{} 个打印语句,建议学学 debugger 的使用", count),
180                format!("println! 用得比我说话还频繁",),
181                format!("代码里的 println! 比注释还多,这是什么操作?",),
182            ]
183        } else {
184            vec![
185                format!("{} println! statements? Are you hosting a concert?", count),
186                format!("So many println!s, the console is crying"),
187                format!("{} print statements - time to learn about debuggers", count),
188                format!("You use println! more than I use excuses"),
189                format!("More println!s than comments - what's the strategy here?"),
190            ]
191        };
192
193        self.issues.push(CodeIssue {
194            file_path: self.file_path.clone(),
195            line: 1,
196            column: 1,
197            rule_name: "println-debugging".to_string(),
198            message: messages[count % messages.len()].clone(),
199            severity: Severity::Spicy,
200            roast_level: RoastLevel::Savage,
201        });
202    }
203}
204
205impl<'ast> Visit<'ast> for PrintlnDebuggingVisitor {
206    fn visit_expr_macro(&mut self, expr_macro: &'ast ExprMacro) {
207        if let Some(ident) = expr_macro.mac.path.get_ident() {
208            if ident == "println" {
209                self.println_count += 1;
210
211                let messages = if self.lang == "zh-CN" {
212                    vec![
213                        "又一个 println! 调试,专业!",
214                        "println! 调试大法好,但是...",
215                        "看到这个 println!,我想起了我的学生时代",
216                        "println! 调试:简单粗暴,但不优雅",
217                        "这个 println! 是临时的,对吧?对吧?",
218                    ]
219                } else {
220                    vec![
221                        "Another println! debug - so professional!",
222                        "println! debugging strikes again",
223                        "This println! brings back student memories",
224                        "println! debugging: simple, crude, but effective",
225                        "This println! is temporary, right? Right?",
226                    ]
227                };
228
229                let (line, column) = get_position(expr_macro);
230                self.issues.push(CodeIssue {
231                    file_path: self.file_path.clone(),
232                    line,
233                    column,
234                    rule_name: "println-debugging".to_string(),
235                    message: messages[self.println_count % messages.len()].to_string(),
236                    severity: Severity::Mild,
237                    roast_level: RoastLevel::Gentle,
238                });
239            }
240        }
241        syn::visit::visit_expr_macro(self, expr_macro);
242    }
243}
244
245// ============================================================================
246// Panic 滥用检测
247// ============================================================================
248
249struct PanicAbuseVisitor {
250    file_path: std::path::PathBuf,
251    issues: Vec<CodeIssue>,
252    lang: String,
253}
254
255impl PanicAbuseVisitor {
256    fn new(file_path: std::path::PathBuf, lang: &str) -> Self {
257        Self {
258            file_path,
259            issues: Vec::new(),
260            lang: lang.to_string(),
261        }
262    }
263
264    fn add_excessive_panic_issue(&mut self, count: usize) {
265        let messages = if self.lang == "zh-CN" {
266            vec![
267                format!("{} 个 panic!?你的程序是定时炸弹吗?", count),
268                format!("这么多 panic!,用户体验堪忧",),
269                format!("{} 个 panic!,建议学学错误处理", count),
270                format!("panic! 用得这么随意,Rust 编译器都要哭了",),
271            ]
272        } else {
273            vec![
274                format!("{} panic!s? Is your program a time bomb?", count),
275                format!("So many panic!s, user experience is questionable"),
276                format!("{} panic!s - time to learn error handling", count),
277                format!("Using panic! so casually, even Rust compiler is crying"),
278            ]
279        };
280
281        self.issues.push(CodeIssue {
282            file_path: self.file_path.clone(),
283            line: 1,
284            column: 1,
285            rule_name: "panic-abuse".to_string(),
286            message: messages[count % messages.len()].clone(),
287            severity: Severity::Nuclear,
288            roast_level: RoastLevel::Savage,
289        });
290    }
291
292    fn add_excessive_unwrap_issue(&mut self, count: usize) {
293        let messages = if self.lang == "zh-CN" {
294            vec![
295                format!("{} 个 unwrap(),你对代码很有信心啊", count),
296                format!("unwrap() 用得这么多,建议买个保险",),
297                format!("{} 个 unwrap(),错误处理呢?", count),
298                format!("这么多 unwrap(),程序随时可能崩溃",),
299            ]
300        } else {
301            vec![
302                format!("{} unwrap()s - you're very confident in your code", count),
303                format!("So many unwrap()s, consider buying insurance"),
304                format!("{} unwrap()s - where's the error handling?", count),
305                format!("So many unwrap()s, program might crash anytime"),
306            ]
307        };
308
309        self.issues.push(CodeIssue {
310            file_path: self.file_path.clone(),
311            line: 1,
312            column: 1,
313            rule_name: "panic-abuse".to_string(),
314            message: messages[count % messages.len()].clone(),
315            severity: Severity::Spicy,
316            roast_level: RoastLevel::Sarcastic,
317        });
318    }
319}
320
321impl<'ast> Visit<'ast> for PanicAbuseVisitor {
322    fn visit_expr_macro(&mut self, expr_macro: &'ast ExprMacro) {
323        if let Some(ident) = expr_macro.mac.path.get_ident() {
324            if ident == "panic" {
325                let messages = if self.lang == "zh-CN" {
326                    vec![
327                        "发现一个 panic!,程序要爆炸了",
328                        "panic! 出现,用户体验-1",
329                        "又见 panic!,优雅的错误处理在哪里?",
330                        "panic! 大法好,但是用户不这么想",
331                    ]
332                } else {
333                    vec![
334                        "Found a panic! - program is about to explode",
335                        "panic! spotted, user experience -1",
336                        "Another panic! - where's the graceful error handling?",
337                        "panic! is great, but users disagree",
338                    ]
339                };
340
341                let (line, column) = get_position(expr_macro);
342                self.issues.push(CodeIssue {
343                    file_path: self.file_path.clone(),
344                    line,
345                    column,
346                    rule_name: "panic-abuse".to_string(),
347                    message: messages[self.issues.len() % messages.len()].to_string(),
348                    severity: Severity::Spicy,
349                    roast_level: RoastLevel::Sarcastic,
350                });
351            }
352        }
353        syn::visit::visit_expr_macro(self, expr_macro);
354    }
355}