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