garbage_code_hunter/rules/
struct_patterns.rs1use 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 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
121struct 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 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 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
186struct 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 }
200
201struct 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 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}