garbage_code_hunter/rules/
rust_patterns.rs

1use std::path::Path;
2use syn::{
3    visit::Visit, Expr, ExprForLoop, ExprMatch, ExprMethodCall, File, Pat, PatIdent, Type, TypePath,
4};
5
6use crate::analyzer::{CodeIssue, RoastLevel, Severity};
7use crate::rules::Rule;
8use crate::utils::get_position;
9
10/// 检测到处用 String 而不用 &str
11pub struct StringAbuseRule;
12
13impl Rule for StringAbuseRule {
14    fn name(&self) -> &'static str {
15        "string-abuse"
16    }
17
18    fn check(
19        &self,
20        file_path: &Path,
21        syntax_tree: &File,
22        content: &str,
23        lang: &str,
24    ) -> Vec<CodeIssue> {
25        let mut visitor = StringAbuseVisitor::new(file_path.to_path_buf(), lang);
26        visitor.visit_file(syntax_tree);
27
28        // 检查内容中的 String 使用模式
29        let string_new_count = content.matches("String::new()").count();
30        let string_from_count = content.matches("String::from(").count();
31        let to_string_count = content.matches(".to_string()").count();
32
33        if string_new_count + string_from_count + to_string_count > 5 {
34            visitor.add_excessive_string_conversion_issue(
35                string_new_count + string_from_count + to_string_count,
36            );
37        }
38
39        visitor.issues
40    }
41}
42
43/// 检测不必要的 Vec 分配
44pub struct VecAbuseRule;
45
46impl Rule for VecAbuseRule {
47    fn name(&self) -> &'static str {
48        "vec-abuse"
49    }
50
51    fn check(
52        &self,
53        file_path: &Path,
54        syntax_tree: &File,
55        content: &str,
56        lang: &str,
57    ) -> Vec<CodeIssue> {
58        let mut visitor = VecAbuseVisitor::new(file_path.to_path_buf(), lang);
59        visitor.visit_file(syntax_tree);
60
61        // 检查内容中的 Vec 使用模式
62        let vec_new_count = content.matches("Vec::new()").count();
63        let _vec_with_capacity_count = content.matches("Vec::with_capacity(").count();
64        let _vec_macro_count = content.matches("vec![").count();
65
66        if vec_new_count > 3 {
67            visitor.add_excessive_vec_allocation_issue(vec_new_count);
68        }
69
70        visitor.issues
71    }
72}
73
74/// 检测用循环代替迭代器的情况
75pub struct IteratorAbuseRule;
76
77impl Rule for IteratorAbuseRule {
78    fn name(&self) -> &'static str {
79        "iterator-abuse"
80    }
81
82    fn check(
83        &self,
84        file_path: &Path,
85        syntax_tree: &File,
86        _content: &str,
87        lang: &str,
88    ) -> Vec<CodeIssue> {
89        let mut visitor = IteratorAbuseVisitor::new(file_path.to_path_buf(), lang);
90        visitor.visit_file(syntax_tree);
91        visitor.issues
92    }
93}
94
95/// 检测可以用 if let 的复杂 match
96pub struct MatchAbuseRule;
97
98impl Rule for MatchAbuseRule {
99    fn name(&self) -> &'static str {
100        "match-abuse"
101    }
102
103    fn check(
104        &self,
105        file_path: &Path,
106        syntax_tree: &File,
107        _content: &str,
108        lang: &str,
109    ) -> Vec<CodeIssue> {
110        let mut visitor = MatchAbuseVisitor::new(file_path.to_path_buf(), lang);
111        visitor.visit_file(syntax_tree);
112        visitor.issues
113    }
114}
115
116// ============================================================================
117// Visitor 实现
118// ============================================================================
119
120struct StringAbuseVisitor {
121    file_path: std::path::PathBuf,
122    issues: Vec<CodeIssue>,
123    lang: String,
124}
125
126impl StringAbuseVisitor {
127    fn new(file_path: std::path::PathBuf, lang: &str) -> Self {
128        Self {
129            file_path,
130            issues: Vec::new(),
131            lang: lang.to_string(),
132        }
133    }
134
135    fn add_excessive_string_conversion_issue(&mut self, count: usize) {
136        let messages = if self.lang == "zh-CN" {
137            vec![
138                format!("{} 次 String 转换?你是在开字符串工厂吗?", count),
139                format!("这么多 String 分配,内存都要哭了",),
140                format!("{} 个 String 转换,考虑用 &str 吧", count),
141                format!("String 用得比我换衣服还频繁",),
142                format!("到处都是 String::from(),性能在哭泣",),
143            ]
144        } else {
145            vec![
146                format!(
147                    "{} String conversions? Are you running a string factory?",
148                    count
149                ),
150                format!("So many String allocations, memory is crying"),
151                format!("{} String conversions - consider using &str", count),
152                format!("You use String more than I change clothes"),
153                format!("String::from() everywhere - performance is weeping"),
154            ]
155        };
156
157        self.issues.push(CodeIssue {
158            file_path: self.file_path.clone(),
159            line: 1,
160            column: 1,
161            rule_name: "string-abuse".to_string(),
162            message: messages[count % messages.len()].clone(),
163            severity: Severity::Spicy,
164            roast_level: RoastLevel::Sarcastic,
165        });
166    }
167
168    fn check_string_parameter(&mut self, ty: &Type) {
169        if let Type::Path(TypePath { path, .. }) = ty {
170            if let Some(segment) = path.segments.last() {
171                if segment.ident == "String" {
172                    let messages = if self.lang == "zh-CN" {
173                        vec![
174                            "参数用 String?考虑用 &str 吧",
175                            "String 参数会强制调用者分配内存",
176                            "这个 String 参数设计得不太优雅",
177                            "用 &str 参数会更灵活",
178                        ]
179                    } else {
180                        vec![
181                            "String parameter? Consider using &str",
182                            "String parameter forces caller to allocate",
183                            "This String parameter design isn't elegant",
184                            "&str parameter would be more flexible",
185                        ]
186                    };
187
188                    let (line, column) = get_position(ty);
189                    self.issues.push(CodeIssue {
190                        file_path: self.file_path.clone(),
191                        line,
192                        column,
193                        rule_name: "string-abuse".to_string(),
194                        message: messages[self.issues.len() % messages.len()].to_string(),
195                        severity: Severity::Mild,
196                        roast_level: RoastLevel::Gentle,
197                    });
198                }
199            }
200        }
201    }
202}
203
204impl<'ast> Visit<'ast> for StringAbuseVisitor {
205    fn visit_expr_method_call(&mut self, method_call: &'ast ExprMethodCall) {
206        if method_call.method == "to_string" {
207            let messages = if self.lang == "zh-CN" {
208                vec![
209                    "又见 to_string(),真的需要分配内存吗?",
210                    "to_string() 调用,考虑是否必要",
211                    "这个 to_string() 可能可以避免",
212                    "to_string() 会分配内存,确定需要吗?",
213                ]
214            } else {
215                vec![
216                    "Another to_string() - do you really need to allocate?",
217                    "to_string() call - consider if necessary",
218                    "This to_string() might be avoidable",
219                    "to_string() allocates memory - sure you need it?",
220                ]
221            };
222
223            let (line, column) = get_position(method_call);
224            self.issues.push(CodeIssue {
225                file_path: self.file_path.clone(),
226                line,
227                column,
228                rule_name: "string-abuse".to_string(),
229                message: messages[self.issues.len() % messages.len()].to_string(),
230                severity: Severity::Mild,
231                roast_level: RoastLevel::Gentle,
232            });
233        }
234        syn::visit::visit_expr_method_call(self, method_call);
235    }
236
237    fn visit_type(&mut self, ty: &'ast Type) {
238        self.check_string_parameter(ty);
239        syn::visit::visit_type(self, ty);
240    }
241}
242
243// ============================================================================
244// Vec 滥用检测
245// ============================================================================
246
247struct VecAbuseVisitor {
248    file_path: std::path::PathBuf,
249    issues: Vec<CodeIssue>,
250    lang: String,
251}
252
253impl VecAbuseVisitor {
254    fn new(file_path: std::path::PathBuf, lang: &str) -> Self {
255        Self {
256            file_path,
257            issues: Vec::new(),
258            lang: lang.to_string(),
259        }
260    }
261
262    fn add_excessive_vec_allocation_issue(&mut self, count: usize) {
263        let messages = if self.lang == "zh-CN" {
264            vec![
265                format!("{} 个 Vec::new()?你在收集什么?", count),
266                format!("这么多 Vec 分配,考虑用数组或切片",),
267                format!("{} 次 Vec 分配,内存分配器很忙", count),
268                format!("Vec 用得这么多,确定都需要动态数组吗?",),
269            ]
270        } else {
271            vec![
272                format!("{} Vec::new()s? What are you collecting?", count),
273                format!("So many Vec allocations - consider arrays or slices"),
274                format!("{} Vec allocations - memory allocator is busy", count),
275                format!("So many Vecs - sure you need all these dynamic arrays?"),
276            ]
277        };
278
279        self.issues.push(CodeIssue {
280            file_path: self.file_path.clone(),
281            line: 1,
282            column: 1,
283            rule_name: "vec-abuse".to_string(),
284            message: messages[count % messages.len()].clone(),
285            severity: Severity::Mild,
286            roast_level: RoastLevel::Gentle,
287        });
288    }
289}
290
291impl<'ast> Visit<'ast> for VecAbuseVisitor {
292    fn visit_expr_method_call(&mut self, method_call: &'ast ExprMethodCall) {
293        // 检测 Vec::new() 调用
294        if let Expr::Path(path_expr) = &*method_call.receiver {
295            if let Some(segment) = path_expr.path.segments.last() {
296                if segment.ident == "Vec" && method_call.method == "new" {
297                    let messages = if self.lang == "zh-CN" {
298                        vec![
299                            "Vec::new() 出现,确定需要动态数组吗?",
300                            "又一个 Vec::new(),考虑用数组",
301                            "Vec::new() 会分配堆内存",
302                            "这个 Vec::new() 可能可以优化",
303                        ]
304                    } else {
305                        vec![
306                            "Vec::new() spotted - sure you need a dynamic array?",
307                            "Another Vec::new() - consider using an array",
308                            "Vec::new() allocates heap memory",
309                            "This Vec::new() might be optimizable",
310                        ]
311                    };
312
313                    let (line, column) = get_position(method_call);
314                    self.issues.push(CodeIssue {
315                        file_path: self.file_path.clone(),
316                        line,
317                        column,
318                        rule_name: "vec-abuse".to_string(),
319                        message: messages[self.issues.len() % messages.len()].to_string(),
320                        severity: Severity::Mild,
321                        roast_level: RoastLevel::Gentle,
322                    });
323                }
324            }
325        }
326        syn::visit::visit_expr_method_call(self, method_call);
327    }
328}
329
330// ============================================================================
331// 迭代器滥用检测
332// ============================================================================
333
334struct IteratorAbuseVisitor {
335    file_path: std::path::PathBuf,
336    issues: Vec<CodeIssue>,
337    lang: String,
338}
339
340impl IteratorAbuseVisitor {
341    fn new(file_path: std::path::PathBuf, lang: &str) -> Self {
342        Self {
343            file_path,
344            issues: Vec::new(),
345            lang: lang.to_string(),
346        }
347    }
348
349    fn check_simple_for_loop(&mut self, for_loop: &ExprForLoop) {
350        // 检测简单的 for 循环,可能可以用迭代器替代
351        if let Pat::Ident(PatIdent { ident, .. }) = for_loop.pat.as_ref() {
352            let _var_name = ident.to_string();
353
354            // 检测常见的可以用迭代器替代的模式
355            let loop_body = format!("{:?}", for_loop.body);
356
357            // 如果循环体很简单,建议用迭代器
358            if loop_body.lines().count() < 5 {
359                let messages = if self.lang == "zh-CN" {
360                    vec![
361                        format!("简单的 for 循环,考虑用迭代器链"),
362                        format!("这个循环可以用 .iter().for_each() 替代"),
363                        format!("迭代器比传统循环更 Rust 风格"),
364                        format!("考虑用函数式编程风格重写这个循环"),
365                    ]
366                } else {
367                    vec![
368                        format!("Simple for loop - consider using iterator chains"),
369                        format!("This loop could use .iter().for_each() instead"),
370                        format!("Iterators are more idiomatic than traditional loops"),
371                        format!("Consider rewriting this loop in functional style"),
372                    ]
373                };
374
375                let (line, column) = get_position(for_loop);
376                self.issues.push(CodeIssue {
377                    file_path: self.file_path.clone(),
378                    line,
379                    column,
380                    rule_name: "iterator-abuse".to_string(),
381                    message: messages[self.issues.len() % messages.len()].to_string(),
382                    severity: Severity::Mild,
383                    roast_level: RoastLevel::Gentle,
384                });
385            }
386        }
387    }
388}
389
390impl<'ast> Visit<'ast> for IteratorAbuseVisitor {
391    fn visit_expr_for_loop(&mut self, for_loop: &'ast ExprForLoop) {
392        self.check_simple_for_loop(for_loop);
393        syn::visit::visit_expr_for_loop(self, for_loop);
394    }
395}
396
397// ============================================================================
398// Match 滥用检测
399// ============================================================================
400
401struct MatchAbuseVisitor {
402    file_path: std::path::PathBuf,
403    issues: Vec<CodeIssue>,
404    lang: String,
405}
406
407impl MatchAbuseVisitor {
408    fn new(file_path: std::path::PathBuf, lang: &str) -> Self {
409        Self {
410            file_path,
411            issues: Vec::new(),
412            lang: lang.to_string(),
413        }
414    }
415
416    fn check_simple_match(&mut self, match_expr: &ExprMatch) {
417        let arms_count = match_expr.arms.len();
418
419        // 检测只有两个分支的 match,可能可以用 if let 替代
420        if arms_count == 2 {
421            let match_str = format!("{match_expr:?}");
422
423            // 检测 Option 或 Result 的简单匹配
424            if match_str.contains("Some") && match_str.contains("None") {
425                let messages = if self.lang == "zh-CN" {
426                    vec![
427                        "简单的 Option match,考虑用 if let",
428                        "这个 match 可以用 if let Some() 替代",
429                        "Option 的二分支 match 不如 if let 简洁",
430                        "if let 比 match 更适合这种情况",
431                    ]
432                } else {
433                    vec![
434                        "Simple Option match - consider using if let",
435                        "This match could use if let Some() instead",
436                        "Two-arm Option match is less concise than if let",
437                        "if let is more suitable for this case than match",
438                    ]
439                };
440
441                let (line, column) = get_position(match_expr);
442                self.issues.push(CodeIssue {
443                    file_path: self.file_path.clone(),
444                    line,
445                    column,
446                    rule_name: "match-abuse".to_string(),
447                    message: messages[self.issues.len() % messages.len()].to_string(),
448                    severity: Severity::Mild,
449                    roast_level: RoastLevel::Gentle,
450                });
451            }
452
453            if match_str.contains("Ok") && match_str.contains("Err") {
454                let messages = if self.lang == "zh-CN" {
455                    vec![
456                        "简单的 Result match,考虑用 if let",
457                        "这个 match 可以用 if let Ok() 替代",
458                        "Result 的二分支 match 可以简化",
459                        "或者考虑用 ? 操作符",
460                    ]
461                } else {
462                    vec![
463                        "Simple Result match - consider using if let",
464                        "This match could use if let Ok() instead",
465                        "Two-arm Result match can be simplified",
466                        "Or consider using the ? operator",
467                    ]
468                };
469
470                let (line, column) = get_position(match_expr);
471                self.issues.push(CodeIssue {
472                    file_path: self.file_path.clone(),
473                    line,
474                    column,
475                    rule_name: "match-abuse".to_string(),
476                    message: messages[self.issues.len() % messages.len()].to_string(),
477                    severity: Severity::Mild,
478                    roast_level: RoastLevel::Gentle,
479                });
480            }
481        }
482    }
483}
484
485impl<'ast> Visit<'ast> for MatchAbuseVisitor {
486    fn visit_expr_match(&mut self, match_expr: &'ast ExprMatch) {
487        self.check_simple_match(match_expr);
488        syn::visit::visit_expr_match(self, match_expr);
489    }
490}