garbage_code_hunter/rules/
rust_specific.rs1use std::path::Path;
2use syn::{visit::Visit, ExprMethodCall, File};
3
4use crate::analyzer::{CodeIssue, Severity};
5use crate::context::FileContext;
6use crate::rules::Rule;
7use crate::utils::get_position;
8
9pub struct UnwrapAbuseRule;
10
11impl Rule for UnwrapAbuseRule {
12 fn name(&self) -> &'static str {
13 "unwrap-abuse"
14 }
15
16 fn check(
17 &self,
18 file_path: &Path,
19 syntax_tree: &File,
20 _content: &str,
21 lang: &str,
22 is_test_file: bool,
23 ) -> Vec<CodeIssue> {
24 if is_test_file {
25 return Vec::new();
26 }
27 let mut visitor = UnwrapVisitor::new(file_path.to_path_buf(), lang);
28 visitor.visit_file(syntax_tree);
29 visitor.issues
30 }
31
32 fn check_with_context(
33 &self,
34 file_path: &Path,
35 syntax_tree: &File,
36 content: &str,
37 lang: &str,
38 is_test_file: bool,
39 context: &FileContext,
40 _config: &crate::context::ProjectConfig,
41 ) -> Vec<CodeIssue> {
42 let weight = context.rule_weight_multiplier();
44 if weight < 0.5 {
45 return Vec::new();
46 }
47
48 if weight < 0.7 {
50 let issues = self.check(file_path, syntax_tree, content, lang, is_test_file);
51 if issues.len() > 10 {
53 return issues;
54 }
55 return Vec::new();
56 }
57
58 self.check(file_path, syntax_tree, content, lang, is_test_file)
59 }
60}
61
62pub struct UnnecessaryCloneRule;
63
64impl Rule for UnnecessaryCloneRule {
65 fn name(&self) -> &'static str {
66 "unnecessary-clone"
67 }
68
69 fn check(
70 &self,
71 file_path: &Path,
72 syntax_tree: &File,
73 _content: &str,
74 lang: &str,
75 is_test_file: bool,
76 ) -> Vec<CodeIssue> {
77 if is_test_file {
78 return Vec::new();
79 }
80 let mut visitor = CloneVisitor::new(file_path.to_path_buf(), lang);
81 visitor.visit_file(syntax_tree);
82 visitor.issues
83 }
84}
85
86struct UnwrapVisitor {
87 file_path: std::path::PathBuf,
88 issues: Vec<CodeIssue>,
89 unwrap_count: usize,
90 first_position: (usize, usize),
91 lang: String,
92}
93
94impl UnwrapVisitor {
95 fn new(file_path: std::path::PathBuf, lang: &str) -> Self {
96 Self {
97 file_path,
98 issues: Vec::new(),
99 unwrap_count: 0,
100 first_position: (1, 1),
101 lang: lang.to_string(),
102 }
103 }
104}
105
106impl<'ast> Visit<'ast> for UnwrapVisitor {
107 fn visit_expr_method_call(&mut self, method_call: &'ast ExprMethodCall) {
108 if method_call.method == "unwrap" {
109 if self.unwrap_count == 0 {
110 self.first_position = get_position(method_call);
111 }
112 self.unwrap_count += 1;
113 }
114
115 syn::visit::visit_expr_method_call(self, method_call);
116 }
117
118 fn visit_file(&mut self, file: &'ast syn::File) {
119 syn::visit::visit_file(self, file);
120 if self.unwrap_count > 0 {
122 let (line, column) = self.first_position;
123
124 let (severity, message) = if self.unwrap_count > 15 {
126 let messages = if self.lang == "zh-CN" {
127 vec![
128 format!(
129 "🚨 {} 个 unwrap()!这是在生产环境玩俄罗斯轮盘",
130 self.unwrap_count
131 ),
132 format!(
133 "{} 个 unwrap(),你的程序比我的感情生活还不稳定",
134 self.unwrap_count
135 ),
136 format!("unwrap() 滥用警报:{} 处潜在崩溃点", self.unwrap_count),
137 format!(
138 "{} 个 unwrap() = {} 次潜在的 panic!立即修复!",
139 self.unwrap_count, self.unwrap_count
140 ),
141 ]
142 } else {
143 vec![
144 format!(
145 "🚨 {} unwrap()s! Playing Russian roulette in production?",
146 self.unwrap_count
147 ),
148 format!(
149 "{} unwrap()s - less stable than my love life",
150 self.unwrap_count
151 ),
152 format!(
153 "unwrap() abuse alert: {} potential crash points",
154 self.unwrap_count
155 ),
156 format!(
157 "{} unwrap()s = {} potential panics! Fix immediately!",
158 self.unwrap_count, self.unwrap_count
159 ),
160 ]
161 };
162 (
163 Severity::Nuclear,
164 messages[self.issues.len() % messages.len()].clone(),
165 )
166 } else if self.unwrap_count > 8 {
167 let messages = if self.lang == "zh-CN" {
168 vec![
169 format!(
170 "⚠️ {} 个 unwrap(),建议用 ? 运算符或 match 替代",
171 self.unwrap_count
172 ),
173 format!("发现 {} 个 unwrap(),错误处理在哪里?", self.unwrap_count),
174 format!("{} 个 unwrap(),程序健壮性堪忧", self.unwrap_count),
175 format!("这么多 unwrap(),Rust 的类型系统在哭泣",),
176 ]
177 } else {
178 vec![
179 format!(
180 "⚠️ {} unwrap()s - consider using ? or match",
181 self.unwrap_count
182 ),
183 format!(
184 "Found {} unwrap()s - where's error handling?",
185 self.unwrap_count
186 ),
187 format!(
188 "{} unwrap()s - program robustness questionable",
189 self.unwrap_count
190 ),
191 format!("So many unwrap()s, Rust's type system is crying",),
192 ]
193 };
194 (
195 Severity::Spicy,
196 messages[self.issues.len() % messages.len()].clone(),
197 )
198 } else if self.unwrap_count > 3 {
199 let messages = if self.lang == "zh-CN" {
200 vec![
201 format!(
202 "{} 个 unwrap(),建议使用 ? 运算符或 match 处理错误",
203 self.unwrap_count
204 ),
205 format!(
206 "发现 {} 个 unwrap(),考虑用 expect() 至少给个错误信息",
207 self.unwrap_count
208 ),
209 format!(
210 "{} 个 unwrap() 调用,可以用 .ok()? 或 .map_err()? 替代",
211 self.unwrap_count
212 ),
213 ]
214 } else {
215 vec![
216 format!(
217 "{} unwrap()s - consider using ? operator or match",
218 self.unwrap_count
219 ),
220 format!(
221 "Found {} unwrap()s - at least use expect() for error messages",
222 self.unwrap_count
223 ),
224 format!(
225 "{} unwrap() calls - can use .ok()? or .map_err()?",
226 self.unwrap_count
227 ),
228 ]
229 };
230 (
231 Severity::Mild,
232 messages[self.issues.len() % messages.len()].clone(),
233 )
234 } else {
235 let messages = if self.lang == "zh-CN" {
237 vec![
238 format!(
239 "{} 个 unwrap(),小提醒:生产代码建议用 ? 运算符",
240 self.unwrap_count
241 ),
242 format!(
243 "检测到 {} 个 unwrap(),记得处理可能的 None/Err",
244 self.unwrap_count
245 ),
246 ]
247 } else {
248 vec![
249 format!(
250 "{} unwrap()s - reminder: use ? in production code",
251 self.unwrap_count
252 ),
253 format!(
254 "Detected {} unwrap()s - remember to handle None/Err",
255 self.unwrap_count
256 ),
257 ]
258 };
259 (
260 Severity::Mild,
261 messages[self.issues.len() % messages.len()].clone(),
262 )
263 };
264
265 self.issues.push(CodeIssue {
266 file_path: self.file_path.clone(),
267 line,
268 column,
269 rule_name: "unwrap-abuse".to_string(),
270 message,
271 severity,
272 });
273 }
274 }
275}
276
277struct CloneVisitor {
278 file_path: std::path::PathBuf,
279 issues: Vec<CodeIssue>,
280 clone_count: usize,
281 lang: String,
282}
283
284impl CloneVisitor {
285 fn new(file_path: std::path::PathBuf, lang: &str) -> Self {
286 Self {
287 file_path,
288 issues: Vec::new(),
289 clone_count: 0,
290 lang: lang.to_string(),
291 }
292 }
293}
294
295impl<'ast> Visit<'ast> for CloneVisitor {
296 fn visit_expr_method_call(&mut self, method_call: &'ast ExprMethodCall) {
297 if method_call.method == "clone" {
298 self.clone_count += 1;
299
300 if self.clone_count >= 25 && self.issues.is_empty() {
304 let messages = if self.lang == "zh-CN" {
305 [
306 "clone() 狂魔!你是想把内存用完吗?",
307 "这么多 clone(),你确定不是在写 Java?",
308 "clone() 使用过度!Rust 的借用检查器在哭泣",
309 "又见 clone()!也许你需要重新学习一下 Rust 的所有权系统",
310 "这些 clone() 让我想起了复印机店的老板",
311 ]
312 } else {
313 [
314 "clone() fanatic! Trying to use up all the memory?",
315 "This many clone()s - are you sure you're not writing Java?",
316 "clone() abuse! Rust's borrow checker is crying",
317 "clone() again! Maybe you need to relearn Rust's ownership system",
318 "These clone()s remind me of a copy shop owner",
319 ]
320 };
321
322 let (line, column) = get_position(method_call);
323
324 self.issues.push(CodeIssue {
325 file_path: self.file_path.clone(),
326 line,
327 column,
328 rule_name: "unnecessary-clone".to_string(),
329 message: messages[self.issues.len() % messages.len()].to_string(),
330 severity: Severity::Spicy,
331 });
332 }
333 }
334
335 syn::visit::visit_expr_method_call(self, method_call);
336 }
337}