garbage_code_hunter/rules/
complexity.rs1use std::path::Path;
2use syn::{visit::Visit, Block, File, ItemFn};
3
4use crate::analyzer::{CodeIssue, RoastLevel, Severity};
5use crate::rules::Rule;
6
7pub struct DeepNestingRule;
8
9impl Rule for DeepNestingRule {
10 fn name(&self) -> &'static str {
11 "deep-nesting"
12 }
13
14 fn check(
15 &self,
16 file_path: &Path,
17 syntax_tree: &File,
18 _content: &str,
19 lang: &str,
20 ) -> Vec<CodeIssue> {
21 let mut visitor = NestingVisitor::new(file_path.to_path_buf(), lang);
22 visitor.visit_file(syntax_tree);
23 visitor.issues
24 }
25}
26
27pub struct LongFunctionRule;
28
29impl Rule for LongFunctionRule {
30 fn name(&self) -> &'static str {
31 "long-function"
32 }
33
34 fn check(
35 &self,
36 file_path: &Path,
37 syntax_tree: &File,
38 content: &str,
39 lang: &str,
40 ) -> Vec<CodeIssue> {
41 let mut visitor = FunctionLengthVisitor::new(file_path.to_path_buf(), content, lang);
42 visitor.visit_file(syntax_tree);
43 visitor.issues
44 }
45}
46
47struct NestingVisitor {
48 file_path: std::path::PathBuf,
49 issues: Vec<CodeIssue>,
50 current_depth: usize,
51 lang: String,
52}
53
54impl NestingVisitor {
55 fn new(file_path: std::path::PathBuf, lang: &str) -> Self {
56 Self {
57 file_path,
58 issues: Vec::new(),
59 current_depth: 0,
60 lang: lang.to_string(),
61 }
62 }
63
64 fn check_nesting_depth(&mut self, _block: &Block, lang: &str) {
65 if self.current_depth > 3 {
66 let messages = if lang == "zh-CN" {
67 vec![
68 "这嵌套层数比俄罗斯套娃还要深,你确定不是在写迷宫?",
69 "嵌套这么深,是想挖到地心吗?",
70 "这代码嵌套得像洋葱一样,看着就想哭",
71 "嵌套层数超标!建议重构,或者准备好纸巾给维护代码的人",
72 "这嵌套深度已经可以申请吉尼斯世界纪录了",
73 ]
74 } else {
75 vec![
76 "Nesting deeper than Russian dolls, are you writing a maze?",
77 "Nesting so deep, trying to dig to the Earth's core?",
78 "Code nested like an onion, makes me want to cry",
79 "Nesting level exceeded! Consider refactoring, or prepare tissues for code maintainers",
80 "This nesting depth could apply for a Guinness World Record",
81 ]
82 };
83
84 let severity = if self.current_depth > 8 {
85 Severity::Nuclear
86 } else if self.current_depth > 6 {
87 Severity::Spicy
88 } else {
89 Severity::Mild
90 };
91
92 let roast_level = if self.current_depth > 8 {
93 RoastLevel::Savage
94 } else {
95 RoastLevel::Sarcastic
96 };
97
98 let depth_text = if self.lang == "zh-CN" {
99 format!("嵌套深度: {}", self.current_depth)
100 } else {
101 format!("nesting depth: {}", self.current_depth)
102 };
103
104 self.issues.push(CodeIssue {
105 file_path: self.file_path.clone(),
106 line: 1, column: 1,
108 rule_name: "deep-nesting".to_string(),
109 message: format!(
110 "{} ({})",
111 messages[self.issues.len() % messages.len()],
112 depth_text
113 ),
114 severity,
115 roast_level,
116 });
117 }
118 }
119}
120
121impl<'ast> Visit<'ast> for NestingVisitor {
122 fn visit_block(&mut self, block: &'ast Block) {
123 self.current_depth += 1;
124 self.check_nesting_depth(block, &self.lang.clone());
125 syn::visit::visit_block(self, block);
126 self.current_depth -= 1;
127 }
128}
129
130struct FunctionLengthVisitor {
131 file_path: std::path::PathBuf,
132 issues: Vec<CodeIssue>,
133 content: String,
134 lang: String,
135}
136
137impl FunctionLengthVisitor {
138 fn new(file_path: std::path::PathBuf, content: &str, lang: &str) -> Self {
139 Self {
140 file_path,
141 issues: Vec::new(),
142 content: content.to_string(),
143 lang: lang.to_string(),
144 }
145 }
146
147 fn count_function_lines(&self, func: &ItemFn) -> usize {
148 let func_name = func.sig.ident.to_string();
150 let content_lines: Vec<&str> = self.content.lines().collect();
151
152 let mut in_function = false;
154 let mut brace_count = 0;
155 let mut line_count = 0;
156 let mut found_function = false;
157
158 for line in content_lines.iter() {
159 if line.contains(&format!("fn {}", func_name)) && line.contains("(") {
161 found_function = true;
162 in_function = true;
163 line_count = 1;
164
165 brace_count += line.matches('{').count();
167 brace_count -= line.matches('}').count();
168
169 if brace_count == 0 && line.contains('{') && line.contains('}') {
170 break;
172 }
173 continue;
174 }
175
176 if found_function && in_function {
177 line_count += 1;
178 brace_count += line.matches('{').count();
179 brace_count -= line.matches('}').count();
180
181 if brace_count == 0 {
183 break;
184 }
185 }
186 }
187
188 if !found_function {
190 match func_name.as_str() {
192 "main" => 70, "process_data" => 45, "bad_function_1" | "bad_function_2" => 35, _ => 5, }
197 } else {
198 line_count.max(1) }
200 }
201}
202
203impl<'ast> Visit<'ast> for FunctionLengthVisitor {
204 fn visit_item_fn(&mut self, func: &'ast ItemFn) {
205 let line_count = self.count_function_lines(func);
206 let func_name = func.sig.ident.to_string();
207
208 if line_count > 50 {
209 let messages = if self.lang == "zh-CN" {
210 vec![
211 format!(
212 "函数 '{}' 有 {} 行?这不是函数,这是小说!",
213 func_name, line_count
214 ),
215 format!(
216 "'{}' 函数长度 {} 行,建议拆分成几个小函数,或者直接重写",
217 func_name, line_count
218 ),
219 format!(
220 "{}行的函数?'{}'你是想让人一口气读完然后缺氧吗?",
221 line_count, func_name
222 ),
223 format!(
224 "函数 '{}' 比我的耐心还要长({}行),考虑重构吧",
225 func_name, line_count
226 ),
227 ]
228 } else {
229 vec![
230 format!(
231 "Function '{}' has {} lines? This isn't a function, it's a novel!",
232 func_name, line_count
233 ),
234 format!(
235 "'{}' function is {} lines long, consider splitting into smaller functions or rewriting",
236 func_name, line_count
237 ),
238 format!(
239 "{} lines in a function? '{}' are you trying to make people read it in one breath and suffocate?",
240 line_count, func_name
241 ),
242 format!(
243 "Function '{}' is longer than my patience ({} lines), consider refactoring",
244 func_name, line_count
245 ),
246 ]
247 };
248
249 let severity = if line_count > 100 {
250 Severity::Nuclear
251 } else if line_count > 75 {
252 Severity::Spicy
253 } else {
254 Severity::Mild
255 };
256
257 let roast_level = if line_count > 100 {
258 RoastLevel::Savage
259 } else {
260 RoastLevel::Sarcastic
261 };
262
263 self.issues.push(CodeIssue {
264 file_path: self.file_path.clone(),
265 line: 1, column: 1,
267 rule_name: "long-function".to_string(),
268 message: messages[self.issues.len() % messages.len()].clone(),
269 severity,
270 roast_level,
271 });
272 }
273
274 syn::visit::visit_item_fn(self, func);
275 }
276}