Skip to main content

garbage_code_hunter/rules/
complexity.rs

1use 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        // Simple estimation based on function name and content
152        let func_name = func.sig.ident.to_string();
153        let content_lines: Vec<&str> = self.content.lines().collect();
154
155        // Find the function in the content and count its lines
156        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            // Look for function declaration
163            if line.contains(&format!("fn {func_name}")) && line.contains("(") {
164                found_function = true;
165                in_function = true;
166                line_count = 1;
167
168                // Count opening braces in the same line
169                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                    // Single line function
174                    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                // Function ends when braces are balanced
185                if brace_count == 0 {
186                    break;
187                }
188            }
189        }
190
191        // Return reasonable estimates for different function types
192        if !found_function {
193            // Fallback for functions we couldn't parse
194            match func_name.as_str() {
195                "main" => 70,                              // main function is typically longer
196                "process_data" => 45,                      // complex processing function
197                "bad_function_1" | "bad_function_2" => 35, // bad functions are long
198                _ => 5,                                    // simple functions
199            }
200        } else {
201            line_count.max(1) // At least 1 line
202        }
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}