1use std::path::Path;
2use syn::{visit::Visit, File, Ident};
3
4use crate::analyzer::{CodeIssue, Severity};
5use crate::context::FileContext;
6use crate::rules::Rule;
7use crate::utils::get_position;
8
9pub struct MeaninglessNamingRule;
11
12impl Rule for MeaninglessNamingRule {
13 fn name(&self) -> &'static str {
14 "meaningless-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 = MeaninglessNamingVisitor::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 use crate::context::FileContext::*;
45
46 match context {
47 Test | Documentation | Benchmark => return Vec::new(),
49
50 Example => {
52 let issues = self.check(file_path, syntax_tree, content, lang, is_test_file);
53 return issues
54 .into_iter()
55 .filter(|issue| issue.severity == Severity::Nuclear)
56 .collect();
57 }
58
59 UI => {
61 let all_issues = self.check(file_path, syntax_tree, content, lang, is_test_file);
62 let ui_whitelist = [
63 "x", "y", "w", "h", "r", "g", "b", "a", "dx", "dy", "rect", "area", "size", "pos", "vec", "ui", "tui", "data", "info", "value", "state", "config", "event", "input", "output",
71 "result", "chunk", "layout", "frame", "block",
73 ];
74 return all_issues
75 .into_iter()
76 .filter(|issue| {
77 !ui_whitelist.iter().any(|&name| {
78 issue
79 .message
80 .to_lowercase()
81 .contains(&format!("'{}'", name))
82 })
83 })
84 .collect();
85 }
86
87 GPU => {
89 let all_issues = self.check(file_path, syntax_tree, content, lang, is_test_file);
90 let gpu_whitelist = [
91 "i", "j", "k", "idx", "src", "dst", "buf", "ptr", "data", ];
98 return all_issues
99 .into_iter()
100 .filter(|issue| {
101 !gpu_whitelist.iter().any(|&name| {
102 issue
103 .message
104 .to_lowercase()
105 .contains(&format!("'{}'", name))
106 })
107 })
108 .collect();
109 }
110
111 Web => {
113 let all_issues = self.check(file_path, syntax_tree, content, lang, is_test_file);
114 let web_whitelist = [
115 "data", "info", "req", "res", "body", "config", ];
121 return all_issues
122 .into_iter()
123 .filter(|issue| {
124 !web_whitelist.iter().any(|&name| {
125 issue
126 .message
127 .to_lowercase()
128 .contains(&format!("'{}'", name))
129 })
130 })
131 .collect();
132 }
133
134 Business => {}
136
137 Config => return Vec::new(),
139 }
140
141 self.check(file_path, syntax_tree, content, lang, is_test_file)
142 }
143}
144
145pub struct HungarianNotationRule;
147
148impl Rule for HungarianNotationRule {
149 fn name(&self) -> &'static str {
150 "hungarian-notation"
151 }
152
153 fn check(
154 &self,
155 file_path: &Path,
156 syntax_tree: &File,
157 _content: &str,
158 lang: &str,
159 is_test_file: bool,
160 ) -> Vec<CodeIssue> {
161 if is_test_file {
162 return Vec::new();
163 }
164 let mut visitor = HungarianNotationVisitor::new(file_path.to_path_buf(), lang);
165 visitor.visit_file(syntax_tree);
166 visitor.issues
167 }
168}
169
170pub struct AbbreviationAbuseRule;
172
173impl Rule for AbbreviationAbuseRule {
174 fn name(&self) -> &'static str {
175 "abbreviation-abuse"
176 }
177
178 fn check(
179 &self,
180 file_path: &Path,
181 syntax_tree: &File,
182 _content: &str,
183 lang: &str,
184 is_test_file: bool,
185 ) -> Vec<CodeIssue> {
186 if is_test_file {
187 return Vec::new();
188 }
189 let mut visitor = AbbreviationAbuseVisitor::new(file_path.to_path_buf(), lang);
190 visitor.visit_file(syntax_tree);
191 visitor.issues
192 }
193}
194
195struct MeaninglessNamingVisitor {
200 file_path: std::path::PathBuf,
201 issues: Vec<CodeIssue>,
202 lang: String,
203}
204
205impl MeaninglessNamingVisitor {
206 fn new(file_path: std::path::PathBuf, lang: &str) -> Self {
207 Self {
208 file_path,
209 issues: Vec::new(),
210 lang: lang.to_string(),
211 }
212 }
213
214 fn is_meaningless_name(&self, name: &str) -> bool {
215 let meaningless_names = [
216 "foo",
218 "bar",
219 "baz",
220 "qux",
221 "quux",
222 "quuz",
223 "data",
225 "info",
226 "obj",
227 "thing",
228 "stuff",
229 "value",
230 "temp",
231 "tmp",
232 "example",
233 "sample",
234 "manager",
236 "handler",
237 "processor",
238 "controller",
239 "yonghu",
241 "mima",
242 "denglu",
243 "zhuce",
244 "shuju",
245 ];
246
247 let name_lower = name.to_lowercase();
248 meaningless_names
249 .iter()
250 .any(|&bad_name| name_lower == bad_name)
251 }
252
253 fn create_issue(&self, name: &str, line: usize, column: usize) -> CodeIssue {
254 let messages = if self.lang == "zh-CN" {
255 vec![
256 format!("变量名 '{}' 比我的网名还随意", name),
257 format!("'{}' 这个名字,是从字典里随机选的吗?", name),
258 format!("用 '{}' 做变量名?你是想让下一个维护代码的人猜谜吗?", name),
259 format!("'{}' 这个名字毫无意义,就像我的人生一样", name),
260 format!("看到 '{}' 这个变量名,我的智商受到了侮辱", name),
261 ]
262 } else {
263 vec![
264 format!("Variable name '{}' is more meaningless than my existence", name),
265 format!("'{}' - did you pick this name with your eyes closed?", name),
266 format!("Using '{}' as a variable name? Are you playing charades with future developers?", name),
267 format!("'{}' tells me nothing about what this variable does", name),
268 format!("The name '{}' is as helpful as a chocolate teapot", name),
269 ]
270 };
271
272 let severity = if ["foo", "bar", "baz", "data", "temp"].contains(&name) {
273 Severity::Spicy
274 } else {
275 Severity::Mild
276 };
277
278 CodeIssue {
279 file_path: self.file_path.clone(),
280 line,
281 column,
282 rule_name: "meaningless-naming".to_string(),
283 message: messages[self.issues.len() % messages.len()].clone(),
284 severity,
285 }
286 }
287}
288
289impl<'ast> Visit<'ast> for MeaninglessNamingVisitor {
290 fn visit_ident(&mut self, ident: &'ast Ident) {
291 let name = ident.to_string();
292 if self.is_meaningless_name(&name) {
293 let (line, column) = get_position(ident);
294 self.issues.push(self.create_issue(&name, line, column));
295 }
296 syn::visit::visit_ident(self, ident);
297 }
298}
299
300struct HungarianNotationVisitor {
305 file_path: std::path::PathBuf,
306 issues: Vec<CodeIssue>,
307 lang: String,
308}
309
310impl HungarianNotationVisitor {
311 fn new(file_path: std::path::PathBuf, lang: &str) -> Self {
312 Self {
313 file_path,
314 issues: Vec::new(),
315 lang: lang.to_string(),
316 }
317 }
318
319 fn is_hungarian_notation(&self, name: &str) -> bool {
320 let hungarian_prefixes = [
321 "str", "int", "bool", "float", "double", "char", "arr", "vec", "list", "map", "set",
323 "g_", "m_", "s_", "p_",
325 ];
326
327 for prefix in hungarian_prefixes {
329 if name.starts_with(prefix) && name.len() > prefix.len() {
330 if let Some(next_char) = name.chars().nth(prefix.len()) {
332 if next_char.is_uppercase() {
333 let rest = &name[prefix.len()..];
336 if rest.starts_with("ify")
338 || rest.starts_with("nal")
339 || rest.starts_with("ean")
340 {
341 continue;
342 }
343 return true;
344 }
345 }
346 }
347 }
348
349 for prefix in &["g_", "m_", "s_", "p_"] {
351 if name.starts_with(prefix) {
352 return true;
353 }
354 }
355
356 false
357 }
358
359 fn create_issue(&self, name: &str, line: usize, column: usize) -> CodeIssue {
360 let messages = if self.lang == "zh-CN" {
361 vec![
362 format!("'{}' 使用了匈牙利命名法?这不是1990年代了", name),
363 format!("看到 '{}' 我仿佛回到了 C++ 的石器时代", name),
364 format!("'{}' 这种命名方式已经过时了,就像我的发型一样", name),
365 format!("匈牙利命名法 '{}'?Rust 编译器已经帮你检查类型了", name),
366 format!("'{}' 让我想起了那些痛苦的 C++ 岁月", name),
367 ]
368 } else {
369 vec![
370 format!(
371 "'{}' uses Hungarian notation? This isn't the 1990s anymore",
372 name
373 ),
374 format!(
375 "Seeing '{}' makes me nostalgic for the dark ages of C++",
376 name
377 ),
378 format!(
379 "'{}' - Hungarian notation is as outdated as my haircut",
380 name
381 ),
382 format!(
383 "Hungarian notation '{}'? Rust's type system has got you covered",
384 name
385 ),
386 format!("'{}' reminds me of painful C++ memories", name),
387 ]
388 };
389
390 CodeIssue {
391 file_path: self.file_path.clone(),
392 line,
393 column,
394 rule_name: "hungarian-notation".to_string(),
395 message: messages[self.issues.len() % messages.len()].clone(),
396 severity: Severity::Mild,
397 }
398 }
399}
400
401impl<'ast> Visit<'ast> for HungarianNotationVisitor {
402 fn visit_ident(&mut self, ident: &'ast Ident) {
403 let name = ident.to_string();
404 if self.is_hungarian_notation(&name) {
405 let (line, column) = get_position(ident);
406 self.issues.push(self.create_issue(&name, line, column));
407 }
408 syn::visit::visit_ident(self, ident);
409 }
410}
411
412struct AbbreviationAbuseVisitor {
417 file_path: std::path::PathBuf,
418 issues: Vec<CodeIssue>,
419 lang: String,
420}
421
422impl AbbreviationAbuseVisitor {
423 fn new(file_path: std::path::PathBuf, lang: &str) -> Self {
424 Self {
425 file_path,
426 issues: Vec::new(),
427 lang: lang.to_string(),
428 }
429 }
430
431 fn is_bad_abbreviation(&self, name: &str) -> Option<&'static str> {
432 let bad_abbreviations = [
433 ("mgr", "manager"),
435 ("mngr", "manager"),
436 ("ctrl", "controller"),
437 ("hdlr", "handler"),
438 ("usr", "user"),
440 ("pwd", "password"),
441 ("prefs", "preferences"),
442 ("btn", "button"),
444 ("lbl", "label"),
445 ("pic", "picture"),
446 ("tbl", "table"),
448 ("col", "column"),
449 ("cnt", "count"),
450 ];
451
452 let name_lower = name.to_lowercase();
453 for (abbrev, full) in bad_abbreviations {
454 if name_lower == abbrev || name_lower.starts_with(&format!("{abbrev}_")) {
455 return Some(full);
456 }
457 }
458 None
459 }
460
461 fn create_issue(&self, name: &str, suggestion: &str, line: usize, column: usize) -> CodeIssue {
462 let messages = if self.lang == "zh-CN" {
463 vec![
464 format!("'{}' 缩写得太狠了,建议用 '{}'", name, suggestion),
465 format!("看到 '{}' 我需要解密,不如直接用 '{}'", name, suggestion),
466 format!(
467 "'{}' 这个缩写让我想起了发电报的年代,用 '{}' 吧",
468 name, suggestion
469 ),
470 format!(
471 "'{}' 省了几个字母,却让代码可读性大打折扣,试试 '{}'",
472 name, suggestion
473 ),
474 format!("缩写 '{}' 就像密码一样难懂,'{}'不香吗?", name, suggestion),
475 ]
476 } else {
477 vec![
478 format!("'{}' is too abbreviated, consider '{}'", name, suggestion),
479 format!(
480 "Seeing '{}' makes me feel like I'm decoding, just use '{}'",
481 name, suggestion
482 ),
483 format!(
484 "'{}' reminds me of telegraph era, try '{}'",
485 name, suggestion
486 ),
487 format!(
488 "'{}' saves a few letters but kills readability, use '{}'",
489 name, suggestion
490 ),
491 format!(
492 "Abbreviation '{}' is cryptic, isn't '{}' better?",
493 name, suggestion
494 ),
495 ]
496 };
497
498 CodeIssue {
499 file_path: self.file_path.clone(),
500 line,
501 column,
502 rule_name: "abbreviation-abuse".to_string(),
503 message: messages[self.issues.len() % messages.len()].clone(),
504 severity: Severity::Mild,
505 }
506 }
507}
508
509impl<'ast> Visit<'ast> for AbbreviationAbuseVisitor {
510 fn visit_ident(&mut self, ident: &'ast Ident) {
511 let name = ident.to_string();
512 if let Some(suggestion) = self.is_bad_abbreviation(&name) {
513 let (line, column) = get_position(ident);
514 self.issues
515 .push(self.create_issue(&name, suggestion, line, column));
516 }
517 syn::visit::visit_ident(self, ident);
518 }
519}