1use std::path::Path;
2use syn::{visit::Visit, ExprLit, File, ItemFn, Lit};
3
4use crate::analyzer::{CodeIssue, RoastLevel, Severity};
5use crate::rules::Rule;
6use crate::utils::get_position;
7
8pub struct MagicNumberRule;
10
11impl Rule for MagicNumberRule {
12 fn name(&self) -> &'static str {
13 "magic-number"
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 = MagicNumberVisitor::new(file_path.to_path_buf(), lang);
24 visitor.visit_file(syntax_tree);
25 visitor.issues
26 }
27}
28
29pub struct GodFunctionRule;
31
32impl Rule for GodFunctionRule {
33 fn name(&self) -> &'static str {
34 "god-function"
35 }
36
37 fn check(
38 &self,
39 file_path: &Path,
40 syntax_tree: &File,
41 content: &str,
42 lang: &str,
43 ) -> Vec<CodeIssue> {
44 let mut visitor = GodFunctionVisitor::new(file_path.to_path_buf(), content, lang);
45 visitor.visit_file(syntax_tree);
46 visitor.issues
47 }
48}
49
50pub struct CommentedCodeRule;
52
53impl Rule for CommentedCodeRule {
54 fn name(&self) -> &'static str {
55 "commented-code"
56 }
57
58 fn check(
59 &self,
60 file_path: &Path,
61 _syntax_tree: &File,
62 content: &str,
63 lang: &str,
64 ) -> Vec<CodeIssue> {
65 let mut issues = Vec::new();
66 let lines: Vec<&str> = content.lines().collect();
67
68 let mut _commented_code_blocks = 0;
69 let mut current_block_size = 0;
70
71 for (line_num, line) in lines.iter().enumerate() {
72 let trimmed = line.trim();
73
74 if trimmed.starts_with("//") {
76 let comment_content = trimmed.trim_start_matches("//").trim();
77
78 if is_likely_code(comment_content) {
80 current_block_size += 1;
81 } else if current_block_size > 0 {
82 if current_block_size >= 3 {
84 _commented_code_blocks += 1;
85 issues.push(create_commented_code_issue(
86 file_path,
87 line_num + 1 - current_block_size,
88 current_block_size,
89 lang,
90 ));
91 }
92 current_block_size = 0;
93 }
94 } else if current_block_size > 0 {
95 if current_block_size >= 3 {
97 _commented_code_blocks += 1;
98 issues.push(create_commented_code_issue(
99 file_path,
100 line_num - current_block_size,
101 current_block_size,
102 lang,
103 ));
104 }
105 current_block_size = 0;
106 }
107 }
108
109 if current_block_size >= 3 {
111 issues.push(create_commented_code_issue(
112 file_path,
113 lines.len() - current_block_size,
114 current_block_size,
115 lang,
116 ));
117 }
118
119 issues
120 }
121}
122
123pub struct DeadCodeRule;
125
126impl Rule for DeadCodeRule {
127 fn name(&self) -> &'static str {
128 "dead-code"
129 }
130
131 fn check(
132 &self,
133 file_path: &Path,
134 _syntax_tree: &File,
135 content: &str,
136 lang: &str,
137 ) -> Vec<CodeIssue> {
138 let mut issues = Vec::new();
139 let lines: Vec<&str> = content.lines().collect();
140
141 for (line_num, line) in lines.iter().enumerate() {
142 let trimmed = line.trim();
143
144 if is_dead_code_pattern(trimmed) {
146 let messages = if lang == "zh-CN" {
147 vec![
148 "发现死代码,这行永远不会执行",
149 "这行代码比我的社交生活还死",
150 "死代码警告:这里是代码的坟墓",
151 "这行代码已经去世了,建议删除",
152 "发现僵尸代码,需要清理",
153 ]
154 } else {
155 vec![
156 "Dead code detected - this line will never execute",
157 "This code is deader than my social life",
158 "Dead code alert: code graveyard found here",
159 "This line of code has passed away, consider removal",
160 "Zombie code detected, cleanup needed",
161 ]
162 };
163
164 issues.push(CodeIssue {
165 file_path: file_path.to_path_buf(),
166 line: line_num + 1,
167 column: 1,
168 rule_name: "dead-code".to_string(),
169 message: messages[line_num % messages.len()].to_string(),
170 severity: Severity::Mild,
171 roast_level: RoastLevel::Sarcastic,
172 });
173 }
174 }
175
176 issues
177 }
178}
179
180fn is_likely_code(content: &str) -> bool {
185 let code_patterns = [
187 "let ", "fn ", "if ", "else", "for ", "while ", "match ", "struct ", "enum ", "impl ",
188 "use ", "mod ", "return ", "break", "continue", "{", "}", "(", ")", "[", "]", ";", "=",
189 "==", "!=", "&&", "||", "->", "::",
190 ];
191
192 let rust_keywords = [
193 "pub", "const", "static", "mut", "ref", "move", "async", "await", "unsafe", "extern",
194 "crate",
195 ];
196
197 let pattern_count = code_patterns
199 .iter()
200 .filter(|&&pattern| content.contains(pattern))
201 .count();
202
203 let keyword_count = rust_keywords
204 .iter()
205 .filter(|&&keyword| content.contains(keyword))
206 .count();
207
208 pattern_count >= 2 || keyword_count >= 1
209}
210
211fn create_commented_code_issue(
212 file_path: &Path,
213 line: usize,
214 block_size: usize,
215 lang: &str,
216) -> CodeIssue {
217 let messages = if lang == "zh-CN" {
218 vec![
219 format!("发现 {} 行被注释的代码,是舍不得删除吗?", block_size),
220 format!("{} 行注释代码,版本控制系统不香吗?", block_size),
221 format!("这 {} 行注释代码就像前任,该放手就放手", block_size),
222 format!("{} 行死代码注释,建议断舍离", block_size),
223 format!("注释了 {} 行代码,Git 会记住它们的", block_size),
224 ]
225 } else {
226 vec![
227 format!(
228 "Found {} lines of commented code - can't let go?",
229 block_size
230 ),
231 format!(
232 "{} lines of commented code - isn't version control enough?",
233 block_size
234 ),
235 format!(
236 "These {} commented lines are like an ex - time to let go",
237 block_size
238 ),
239 format!(
240 "{} lines of dead commented code - Marie Kondo would disapprove",
241 block_size
242 ),
243 format!(
244 "Commented {} lines of code - Git remembers them anyway",
245 block_size
246 ),
247 ]
248 };
249
250 let severity = if block_size > 10 {
251 Severity::Spicy
252 } else {
253 Severity::Mild
254 };
255
256 CodeIssue {
257 file_path: file_path.to_path_buf(),
258 line,
259 column: 1,
260 rule_name: "commented-code".to_string(),
261 message: messages[block_size % messages.len()].clone(),
262 severity,
263 roast_level: RoastLevel::Sarcastic,
264 }
265}
266
267fn is_dead_code_pattern(line: &str) -> bool {
268 let dead_patterns = [
270 "return;",
271 "return ", "break;",
273 "continue;", "panic!(",
275 "unreachable!(", "std::process::exit(",
277 ];
278
279 dead_patterns.iter().any(|&pattern| line.contains(pattern))
280}
281
282struct MagicNumberVisitor {
287 file_path: std::path::PathBuf,
288 issues: Vec<CodeIssue>,
289 lang: String,
290}
291
292impl MagicNumberVisitor {
293 fn new(file_path: std::path::PathBuf, lang: &str) -> Self {
294 Self {
295 file_path,
296 issues: Vec::new(),
297 lang: lang.to_string(),
298 }
299 }
300
301 fn is_magic_number(&self, value: i64) -> bool {
302 !matches!(value, -1 | 0 | 1 | 2 | 10 | 100 | 1000)
304 }
305
306 fn create_magic_number_issue(&self, value: i64, line: usize, column: usize) -> CodeIssue {
307 let messages = if self.lang == "zh-CN" {
308 vec![
309 format!("魔法数字 {}?这是什么咒语?", value),
310 format!("硬编码数字 {},维护性-1", value),
311 format!("数字 {} 从天而降,没人知道它的含义", value),
312 format!("魔法数字 {},建议定义为常量", value),
313 format!("看到数字 {},我陷入了沉思", value),
314 ]
315 } else {
316 vec![
317 format!("Magic number {}? What spell is this?", value),
318 format!("Hardcoded number {} - maintainability -1", value),
319 format!(
320 "Number {} fell from the sky, nobody knows its meaning",
321 value
322 ),
323 format!("Magic number {} - consider defining as a constant", value),
324 format!("Seeing number {}, I'm lost in thought", value),
325 ]
326 };
327
328 let severity = if !(-100..=1000).contains(&value) {
329 Severity::Spicy
330 } else {
331 Severity::Mild
332 };
333
334 CodeIssue {
335 file_path: self.file_path.clone(),
336 line,
337 column,
338 rule_name: "magic-number".to_string(),
339 message: messages[self.issues.len() % messages.len()].clone(),
340 severity,
341 roast_level: RoastLevel::Gentle,
342 }
343 }
344}
345
346impl<'ast> Visit<'ast> for MagicNumberVisitor {
347 fn visit_expr_lit(&mut self, expr_lit: &'ast ExprLit) {
348 if let Lit::Int(lit_int) = &expr_lit.lit {
349 if let Ok(value) = lit_int.base10_parse::<i64>() {
350 if self.is_magic_number(value) {
351 let (line, column) = get_position(expr_lit);
352 self.issues
353 .push(self.create_magic_number_issue(value, line, column));
354 }
355 }
356 }
357 syn::visit::visit_expr_lit(self, expr_lit);
358 }
359}
360
361struct GodFunctionVisitor {
366 file_path: std::path::PathBuf,
367 issues: Vec<CodeIssue>,
368 _content: String,
369 lang: String,
370}
371
372impl GodFunctionVisitor {
373 fn new(file_path: std::path::PathBuf, content: &str, lang: &str) -> Self {
374 Self {
375 file_path,
376 issues: Vec::new(),
377 _content: content.to_string(),
378 lang: lang.to_string(),
379 }
380 }
381
382 fn analyze_function_complexity(&mut self, func: &ItemFn) {
383 let func_name = func.sig.ident.to_string();
384
385 let mut complexity_score = 0;
387
388 let param_count = func.sig.inputs.len();
390 if param_count > 5 {
391 complexity_score += (param_count - 5) * 2;
392 }
393
394 let func_str = format!("{func:?}");
396 let line_count = func_str.lines().count();
397 if line_count > 50 {
398 complexity_score += (line_count - 50) / 10;
399 }
400
401 let control_keywords = ["if", "else", "for", "while", "match", "loop"];
403 for keyword in &control_keywords {
404 complexity_score += func_str.matches(keyword).count();
405 }
406
407 if complexity_score > 15 {
409 let messages = if self.lang == "zh-CN" {
410 vec![
411 format!("函数 '{}' 做的事情比我一天做的还多", func_name),
412 format!("'{}' 是上帝函数吗?什么都想管", func_name),
413 format!("函数 '{}' 复杂得像我的感情生活", func_name),
414 format!("'{}' 这个函数需要拆分,太臃肿了", func_name),
415 format!("函数 '{}' 违反了单一职责原则", func_name),
416 ]
417 } else {
418 vec![
419 format!(
420 "Function '{}' does more things than I do in a day",
421 func_name
422 ),
423 format!(
424 "Is '{}' a god function? Wants to control everything",
425 func_name
426 ),
427 format!("Function '{}' is as complex as my love life", func_name),
428 format!("Function '{}' needs to be split - too bloated", func_name),
429 format!(
430 "Function '{}' violates single responsibility principle",
431 func_name
432 ),
433 ]
434 };
435
436 let severity = if complexity_score > 25 {
437 Severity::Spicy
438 } else {
439 Severity::Mild
440 };
441
442 let (line, column) = get_position(func);
443 self.issues.push(CodeIssue {
444 file_path: self.file_path.clone(),
445 line,
446 column,
447 rule_name: "god-function".to_string(),
448 message: messages[self.issues.len() % messages.len()].clone(),
449 severity,
450 roast_level: RoastLevel::Sarcastic,
451 });
452 }
453 }
454}
455
456impl<'ast> Visit<'ast> for GodFunctionVisitor {
457 fn visit_item_fn(&mut self, func: &'ast ItemFn) {
458 self.analyze_function_complexity(func);
459 syn::visit::visit_item_fn(self, func);
460 }
461}