garbage_code_hunter/treesitter/rules/
base_rules.rs1use crate::analyzer::{CodeIssue, Severity};
2use crate::language::Language;
3use crate::treesitter::engine::ParsedFile;
4use crate::treesitter::query::collect_captures;
5use crate::treesitter::rule::TreeSitterRule;
6
7pub(crate) const BLOCK_PARENT_TYPES: &[&str] = &[
9 "if_expression",
11 "for_expression",
12 "while_expression",
13 "loop_expression",
14 "match_expression",
15 "match_arm",
16 "if_statement",
18 "for_statement",
19 "while_statement",
20 "try_statement",
21 "with_statement",
22 "function_definition",
23 "class_definition",
24 "if_statement",
26 "for_statement",
27 "while_statement",
28 "do_statement",
29 "try_statement",
30 "switch_case",
31 "function_declaration",
32 "if_statement",
34 "for_statement",
35 "while_statement",
36 "do_statement",
37 "switch_statement",
38 "case_statement",
39];
40
41pub(crate) const BLOCK_KINDS: &[&str] = &[
43 "block", "statement_block", "compound_statement", ];
47
48pub(crate) struct CountRule {
51 pub(crate) name: &'static str,
52 pub(crate) pattern: &'static str,
53 pub(crate) threshold: usize,
54 pub(crate) severity: Severity,
55 pub(crate) message_fn: fn(usize) -> String,
56 pub(crate) languages: &'static [Language],
57}
58
59impl TreeSitterRule for CountRule {
60 fn name(&self) -> &'static str {
61 self.name
62 }
63
64 fn supported_languages(&self) -> &'static [Language] {
65 self.languages
66 }
67
68 fn check(&self, file: &ParsedFile) -> Vec<CodeIssue> {
69 let captures = match collect_captures(file, self.pattern) {
70 Ok(c) => c,
71 Err(_) => return vec![],
72 };
73 let count: usize = captures.iter().map(|c| c.len()).sum();
74 if count > self.threshold {
75 vec![CodeIssue {
76 file_path: file.path.clone(),
77 line: 1,
78 column: 1,
79 rule_name: self.name.to_string(),
80 message: (self.message_fn)(count),
81 severity: self.severity.clone(),
82 }]
83 } else {
84 vec![]
85 }
86 }
87}
88
89pub(crate) struct MacroRule {
92 pub(crate) name: &'static str,
93 pub(crate) macro_name: &'static str,
94 pub(crate) threshold: usize,
95 pub(crate) severity: Severity,
96 pub(crate) message_fn: fn(usize) -> String,
97}
98
99impl TreeSitterRule for MacroRule {
100 fn name(&self) -> &'static str {
101 self.name
102 }
103
104 fn supported_languages(&self) -> &'static [Language] {
105 &[Language::Rust]
106 }
107
108 fn check(&self, file: &ParsedFile) -> Vec<CodeIssue> {
109 let pattern = "(macro_invocation macro: (identifier) @m)";
110 let captures = match collect_captures(file, pattern) {
111 Ok(c) => c,
112 Err(_) => return vec![],
113 };
114 let count = captures
115 .iter()
116 .filter_map(|group| group.first())
117 .filter(|cap| cap.text == self.macro_name)
118 .count();
119 if count > self.threshold {
120 vec![CodeIssue {
121 file_path: file.path.clone(),
122 line: 1,
123 column: 1,
124 rule_name: self.name.to_string(),
125 message: (self.message_fn)(count),
126 severity: self.severity.clone(),
127 }]
128 } else {
129 vec![]
130 }
131 }
132}
133
134pub(crate) struct MethodCallRule {
137 pub(crate) name: &'static str,
138 pub(crate) method_name: &'static str,
139 pub(crate) threshold: usize,
140 pub(crate) severity_fn: fn(usize) -> Severity,
141 pub(crate) message_fn: fn(usize) -> String,
142}
143
144impl TreeSitterRule for MethodCallRule {
145 fn name(&self) -> &'static str {
146 self.name
147 }
148
149 fn supported_languages(&self) -> &'static [Language] {
150 &[Language::Rust]
151 }
152
153 fn check(&self, file: &ParsedFile) -> Vec<CodeIssue> {
154 let pattern = "(field_expression field: (field_identifier) @method)";
156 let captures = match collect_captures(file, pattern) {
157 Ok(c) => c,
158 Err(_) => return vec![],
159 };
160 let count = captures
161 .iter()
162 .filter_map(|group| group.first())
163 .filter(|cap| cap.text == self.method_name)
164 .count();
165 if count > self.threshold {
166 vec![CodeIssue {
167 file_path: file.path.clone(),
168 line: 1,
169 column: 1,
170 rule_name: self.name.to_string(),
171 message: (self.message_fn)(count),
172 severity: (self.severity_fn)(count),
173 }]
174 } else {
175 vec![]
176 }
177 }
178}
179
180pub(crate) fn find_function_name(node: tree_sitter::Node, content: &[u8]) -> String {
181 let mut cursor = node.walk();
182 for child in node.named_children(&mut cursor) {
183 if child.kind() == "identifier" {
184 if let Ok(text) = child.utf8_text(content) {
185 return text.to_string();
186 }
187 }
188 }
189 "<anonymous>".to_string()
190}
191
192pub(crate) fn count_parameters(func_node: tree_sitter::Node) -> u32 {
193 let mut cursor = func_node.walk();
194 for child in func_node.named_children(&mut cursor) {
195 if child.kind() == "parameters" {
196 return child.named_child_count() as u32;
197 }
198 }
199 0
200}
201
202pub(crate) fn count_descendants_of_types(node: tree_sitter::Node, types: &[&str]) -> u32 {
203 let mut count = 0;
204 let mut cursor = node.walk();
205 let mut visited_children = false;
206
207 loop {
208 let current = cursor.node();
209 if !visited_children && types.contains(¤t.kind()) {
210 count += 1;
211 }
212
213 if (!visited_children && cursor.goto_first_child()) || cursor.goto_next_sibling() {
214 visited_children = false;
215 } else if !cursor.goto_parent() {
216 break;
217 } else {
218 visited_children = true;
219 }
220 }
221
222 count
223}
224
225pub(crate) fn closure_depth(node: tree_sitter::Node) -> u32 {
226 let mut depth = 1;
227 let mut current = node;
228 while let Some(parent) = current.parent() {
229 if parent.kind() == "closure_expression" {
230 depth += 1;
231 }
232 current = parent;
233 }
234 depth
235}