garbage_code_hunter/rules/
naming.rs1use regex::Regex;
2use std::path::Path;
3use syn::{visit::Visit, File, Ident};
4
5use crate::analyzer::{CodeIssue, Severity};
6use crate::context::FileContext;
7use crate::rules::Rule;
8use crate::utils::get_position;
9
10pub struct TerribleNamingRule;
11
12impl Rule for TerribleNamingRule {
13 fn name(&self) -> &'static str {
14 "terrible-naming"
15 }
16
17 fn check(
18 &self,
19 file_path: &Path,
20 syntax_tree: &File,
21 _content: &str,
22 lang: &str,
23 is_test_file: bool,
24 ) -> Vec<CodeIssue> {
25 if is_test_file {
26 return Vec::new();
27 }
28
29 let mut visitor = NamingVisitor::new(file_path.to_path_buf(), lang);
30 visitor.visit_file(syntax_tree);
31 visitor.issues
32 }
33
34 fn check_with_context(
35 &self,
36 file_path: &Path,
37 syntax_tree: &File,
38 content: &str,
39 lang: &str,
40 is_test_file: bool,
41 context: &FileContext,
42 _config: &crate::context::ProjectConfig,
43 ) -> Vec<CodeIssue> {
44 let weight = context.rule_weight_multiplier();
46 if weight < 0.5 {
47 return Vec::new();
48 }
49
50 self.check(file_path, syntax_tree, content, lang, is_test_file)
51 }
52}
53
54pub struct SingleLetterVariableRule;
55
56impl Rule for SingleLetterVariableRule {
57 fn name(&self) -> &'static str {
58 "single-letter-variable"
59 }
60
61 fn check(
62 &self,
63 file_path: &Path,
64 syntax_tree: &File,
65 _content: &str,
66 lang: &str,
67 is_test_file: bool,
68 ) -> Vec<CodeIssue> {
69 if is_test_file {
70 return Vec::new();
71 }
72
73 let mut visitor = SingleLetterVisitor::new(file_path.to_path_buf(), lang);
74 visitor.visit_file(syntax_tree);
75 visitor.issues
76 }
77}
78
79struct NamingVisitor {
80 file_path: std::path::PathBuf,
81 issues: Vec<CodeIssue>,
82 terrible_names: Regex,
83 lang: String,
84}
85
86impl NamingVisitor {
87 fn new(file_path: std::path::PathBuf, lang: &str) -> Self {
88 let terrible_names = Regex::new(r"^(data|info|temp|tmp|val|value|thing|stuff|obj|object|manager|handler|helper|util|utils)(\d+)?$").unwrap();
90
91 Self {
92 file_path,
93 issues: Vec::new(),
94 terrible_names,
95 lang: lang.to_string(),
96 }
97 }
98
99 fn check_name(&mut self, ident: &Ident, context: &str) {
100 let name = ident.to_string();
101
102 if self.terrible_names.is_match(&name.to_lowercase()) {
103 let messages = if self.lang == "zh-CN" {
105 let ctx = if context == "函数名" {
107 "函数名"
108 } else {
109 "变量名"
110 };
111 vec![
112 format!("{} '{}' - 比我的编程技能还要抽象", ctx, name),
113 format!(
114 "{} '{}' - 这个名字告诉我你已经放弃治疗了,建议直接转行卖煎饼果子",
115 ctx, name
116 ),
117 format!(
118 "{} '{}' - 用这个做名字?你是想让维护代码的人哭着辞职吗?",
119 ctx, name
120 ),
121 format!("{} '{}' - 恭喜你发明了最没有意义的标识符", ctx, name),
122 format!("{} '{}' - 创意程度约等于给孩子起名叫'小明'", ctx, name),
123 format!(
124 "{} '{}' - 看到这个名字,我的智商都下降了,现在只能数到3了",
125 ctx, name
126 ),
127 ]
128 } else {
129 let ctx = if context == "Function" {
131 "Function"
132 } else {
133 "Variable"
134 };
135 vec![
136 format!("{} '{}' - more abstract than my programming skills", ctx, name),
137 format!("{} '{}' - this name tells me you've given up on life and should sell hotdogs", ctx, name),
138 format!("{} '{}' - using this name? trying to make maintainers cry and quit?", ctx, name),
139 format!("{} '{}' - congrats on inventing the most meaningless identifier", ctx, name),
140 format!("{} '{}' - creativity level of naming a kid 'Child'", ctx, name),
141 format!("{} '{}' - seeing this name, my IQ dropped to single digits", ctx, name),
142 ]
143 };
144
145 let message_index =
146 (self.issues.len() + name.len() + name.chars().next().unwrap_or('a') as usize)
147 % messages.len();
148
149 let (line, column) = get_position(ident);
150
151 self.issues.push(CodeIssue {
152 file_path: self.file_path.clone(),
153 line,
154 column,
155 rule_name: "terrible-naming".to_string(),
156 message: messages[message_index].clone(),
157 severity: Severity::Spicy,
158 });
159 }
160 }
161}
162
163impl<'ast> Visit<'ast> for NamingVisitor {
164 fn visit_pat_ident(&mut self, pat_ident: &'ast syn::PatIdent) {
166 let context = if self.lang == "zh-CN" {
167 "变量名"
168 } else {
169 "Variable"
170 };
171 self.check_name(&pat_ident.ident, context);
172 syn::visit::visit_pat_ident(self, pat_ident);
173 }
174
175 fn visit_item_fn(&mut self, func: &'ast syn::ItemFn) {
177 let context = if self.lang == "zh-CN" {
178 "函数名"
179 } else {
180 "Function"
181 };
182 self.check_name(&func.sig.ident, context);
183 syn::visit::visit_item_fn(self, func);
184 }
185}
186
187struct SingleLetterVisitor {
188 file_path: std::path::PathBuf,
189 issues: Vec<CodeIssue>,
190 lang: String,
191}
192
193impl SingleLetterVisitor {
194 fn new(file_path: std::path::PathBuf, lang: &str) -> Self {
195 Self {
196 file_path,
197 issues: Vec::new(),
198 lang: lang.to_string(),
199 }
200 }
201}
202
203impl<'ast> Visit<'ast> for SingleLetterVisitor {
204 fn visit_pat_ident(&mut self, pat_ident: &'ast syn::PatIdent) {
205 let name = pat_ident.ident.to_string();
206
207 if name.len() == 1
209 && !matches!(
210 name.as_str(),
211 "i" | "j"
212 | "k"
213 | "x"
214 | "y"
215 | "z"
216 | "e"
217 | "a"
218 | "b"
219 | "c"
220 | "d"
221 | "f"
222 | "n"
223 | "r"
224 | "s"
225 | "t"
226 | "v"
227 | "w"
228 | "p"
229 | "l"
230 )
231 {
232 let messages = if self.lang == "zh-CN" {
233 [
234 format!("单字母变量 '{name}'?你是在写数学公式还是在折磨读代码的人?"),
235 format!("变量 '{name}'?这是变量名还是你键盘坏了?"),
236 format!("用 '{name}' 做变量名,你可能需要一本《如何给变量起名》的书"),
237 format!("单字母变量 '{name}':让代码比古埃及象形文字还难懂"),
238 format!("变量 '{name}' 的信息量约等于一个句号"),
239 ]
240 } else {
241 [
242 format!("Single-letter variable '{name}'? Writing math formulas or torturing readers?"),
243 format!("Variable '{name}'? Is this a name or did your keyboard break?"),
244 format!("Using '{name}' as a variable name? You need a book on naming"),
245 format!("Single-letter variable '{name}': harder to read than hieroglyphics"),
246 format!("Variable '{name}' has about as much info as a period"),
247 ]
248 };
249
250 let message_index =
251 (self.issues.len() + name.len() + name.chars().next().unwrap_or('a') as usize)
252 % messages.len();
253
254 let (line, column) = get_position(&pat_ident.ident);
255
256 self.issues.push(CodeIssue {
257 file_path: self.file_path.clone(),
258 line,
259 column,
260 rule_name: "single-letter-variable".to_string(),
261 message: messages[message_index].clone(),
262 severity: Severity::Mild,
263 });
264 }
265
266 syn::visit::visit_pat_ident(self, pat_ident);
267 }
268}