Skip to main content

garbage_code_hunter/rules/
struct_patterns.rs

1use std::path::Path;
2use syn::{visit::Visit, File, TypeReference, TypeSlice};
3
4use crate::analyzer::{CodeIssue, Severity};
5use crate::rules::Rule;
6use crate::utils::{find_line_of_str_non_import, get_position};
7
8pub struct ReferenceAbuseRule;
9pub struct BoxAbuseRule;
10pub struct SliceAbuseRule;
11
12impl Rule for ReferenceAbuseRule {
13    fn name(&self) -> &'static str {
14        "reference-abuse"
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 = ReferenceVisitor::new(file_path.to_path_buf(), lang);
30        visitor.visit_file(syntax_tree);
31        visitor.issues
32    }
33}
34
35impl Rule for BoxAbuseRule {
36    fn name(&self) -> &'static str {
37        "box-abuse"
38    }
39
40    fn check(
41        &self,
42        file_path: &Path,
43        syntax_tree: &File,
44        content: &str,
45        lang: &str,
46        is_test_file: bool,
47    ) -> Vec<CodeIssue> {
48        if is_test_file {
49            return Vec::new();
50        }
51        let mut visitor = BoxVisitor::new();
52        visitor.visit_file(syntax_tree);
53
54        // Check for Box usage in content since ExprBox doesn't exist in syn 2.0
55        let box_count = content.matches("Box::new").count() + content.matches("Box<").count();
56        if box_count > 8 {
57            let messages = if lang == "zh-CN" {
58                [
59                    "Box 用得比快递还频繁",
60                    "这么多 Box,你是在开仓库吗?",
61                    "Box 过多,堆内存都要爆炸了",
62                    "Box 滥用,建议考虑栈分配",
63                    "这么多 Box,内存分配器都累了",
64                ]
65            } else {
66                [
67                    "You use Box more than Amazon uses cardboard",
68                    "So many Boxes, are you running a warehouse?",
69                    "Box overuse - the heap memory is about to file a restraining order",
70                    "Box abuse detected - ever heard of stack allocation?",
71                    "So many Boxes, even the memory allocator needs a vacation",
72                ]
73            };
74
75            let candidates = [
76                find_line_of_str_non_import(content, "Box::new"),
77                find_line_of_str_non_import(content, "Box<"),
78            ];
79            let line = candidates
80                .iter()
81                .copied()
82                .filter(|&l| l > 1)
83                .min()
84                .unwrap_or(1);
85            visitor.issues.push(CodeIssue {
86                file_path: file_path.to_path_buf(),
87                line,
88                column: 1,
89                rule_name: "box-abuse".to_string(),
90                message: messages[0].to_string(),
91                severity: Severity::Spicy,
92            });
93        }
94
95        visitor.issues
96    }
97}
98
99impl Rule for SliceAbuseRule {
100    fn name(&self) -> &'static str {
101        "slice-abuse"
102    }
103
104    fn check(
105        &self,
106        file_path: &Path,
107        syntax_tree: &File,
108        _content: &str,
109        lang: &str,
110        is_test_file: bool,
111    ) -> Vec<CodeIssue> {
112        if is_test_file {
113            return Vec::new();
114        }
115        let mut visitor = SliceVisitor::new(file_path.to_path_buf(), lang);
116        visitor.visit_file(syntax_tree);
117        visitor.issues
118    }
119}
120
121// Reference Visitor
122struct ReferenceVisitor {
123    file_path: std::path::PathBuf,
124    issues: Vec<CodeIssue>,
125    reference_count: usize,
126    lang: String,
127}
128
129impl ReferenceVisitor {
130    fn new(file_path: std::path::PathBuf, lang: &str) -> Self {
131        Self {
132            file_path,
133            issues: Vec::new(),
134            reference_count: 0,
135            lang: lang.to_string(),
136        }
137    }
138}
139
140impl<'ast> Visit<'ast> for ReferenceVisitor {
141    fn visit_type_reference(&mut self, ref_type: &'ast TypeReference) {
142        // Skip &self and &mut self
143        if let syn::Type::Path(type_path) = &*ref_type.elem {
144            if let Some(segment) = type_path.path.segments.last() {
145                if segment.ident == "Self" {
146                    return;
147                }
148            }
149        }
150
151        self.reference_count += 1;
152
153        // Cap to 1 issue per file
154        if self.reference_count > 50 && self.issues.is_empty() {
155            let messages = if self.lang == "zh-CN" {
156                [
157                    "引用比我的社交关系还复杂",
158                    "这么多引用,你确定不是在写指针迷宫?",
159                    "引用过多,小心借用检查器罢工",
160                    "引用数量超标,建议重新设计数据结构",
161                ]
162            } else {
163                [
164                    "More references than my social circle has connections",
165                    "So many references, are you writing a pointer maze?",
166                    "Reference overuse - the borrow checker is about to go on strike",
167                    "Too many references - time to rethink your data structure",
168                ]
169            };
170
171            let (line, column) = get_position(ref_type);
172            self.issues.push(CodeIssue {
173                file_path: self.file_path.clone(),
174                line,
175                column,
176                rule_name: "reference-abuse".to_string(),
177                message: messages[self.issues.len() % messages.len()].to_string(),
178                severity: Severity::Mild,
179            });
180        }
181
182        syn::visit::visit_type_reference(self, ref_type);
183    }
184}
185
186// Box Visitor
187struct BoxVisitor {
188    issues: Vec<CodeIssue>,
189}
190
191impl BoxVisitor {
192    fn new() -> Self {
193        Self { issues: Vec::new() }
194    }
195}
196
197impl<'ast> Visit<'ast> for BoxVisitor {
198    // Box detection is handled in the rule implementation via content analysis
199}
200
201// Slice Visitor
202struct SliceVisitor {
203    file_path: std::path::PathBuf,
204    issues: Vec<CodeIssue>,
205    slice_count: usize,
206    lang: String,
207}
208
209impl SliceVisitor {
210    fn new(file_path: std::path::PathBuf, lang: &str) -> Self {
211        Self {
212            file_path,
213            issues: Vec::new(),
214            slice_count: 0,
215            lang: lang.to_string(),
216        }
217    }
218}
219
220impl<'ast> Visit<'ast> for SliceVisitor {
221    fn visit_type_slice(&mut self, slice_type: &'ast TypeSlice) {
222        self.slice_count += 1;
223
224        // Only report once per file when slice count is very high
225        if self.slice_count == 30 {
226            let messages = if self.lang == "zh-CN" {
227                [
228                    "切片比我切菜还频繁",
229                    "这么多切片,你是在开水果店吗?",
230                    "切片过多,数组都被你切碎了",
231                    "Slice 滥用,建议使用 Vec",
232                ]
233            } else {
234                [
235                    "You slice more than a professional chef",
236                    "So many slices, are you running a fruit stand?",
237                    "Slice overuse - the array has been shredded to pieces",
238                    "Slice abuse detected - have you considered using Vec?",
239                ]
240            };
241
242            let (line, column) = get_position(slice_type);
243            self.issues.push(CodeIssue {
244                file_path: self.file_path.clone(),
245                line,
246                column,
247                rule_name: "slice-abuse".to_string(),
248                message: messages[self.issues.len() % messages.len()].to_string(),
249                severity: Severity::Mild,
250            });
251        }
252
253        syn::visit::visit_type_slice(self, slice_type);
254    }
255}