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
8pub 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 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
36pub 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 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
69pub 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 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
153struct 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
245struct 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}