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