1use std::path::Path;
2use syn::{
3 visit::Visit, ExprAsync, ExprAwait, ExprMatch, ExprUnsafe, File, ForeignItem, ItemFn,
4 ItemForeignMod, ItemMod, Macro, PatSlice, PatTuple, TypePath, TypeTraitObject,
5};
6
7use crate::analyzer::{CodeIssue, Severity};
8use crate::rules::Rule;
9use crate::utils::{count_non_import_matches, find_line_of_str, get_position};
10
11pub struct ChannelAbuseRule;
12pub struct AsyncAbuseRule;
13pub struct DynTraitAbuseRule;
14pub struct UnsafeAbuseRule;
15pub struct FFIAbuseRule;
16pub struct MacroAbuseRule;
17pub struct ModuleComplexityRule;
18pub struct PatternMatchingAbuseRule;
19
20impl Rule for ChannelAbuseRule {
21 fn name(&self) -> &'static str {
22 "channel-abuse"
23 }
24
25 fn check(
26 &self,
27 file_path: &Path,
28 syntax_tree: &File,
29 content: &str,
30 lang: &str,
31 is_test_file: bool,
32 ) -> Vec<CodeIssue> {
33 if is_test_file {
34 return Vec::new();
35 }
36 let mut visitor = ChannelVisitor::new(file_path.to_path_buf(), lang);
37 visitor.visit_file(syntax_tree);
38
39 if content.contains("std::sync::mpsc") || content.contains("tokio::sync") {
41 visitor.channel_count += count_non_import_matches(content, "channel");
42 visitor.channel_count += count_non_import_matches(content, "Sender");
43 visitor.channel_count += count_non_import_matches(content, "Receiver");
44 }
45
46 visitor.check_channel_overuse(content);
47 visitor.issues
48 }
49}
50
51impl Rule for AsyncAbuseRule {
52 fn name(&self) -> &'static str {
53 "async-abuse"
54 }
55
56 fn check(
57 &self,
58 file_path: &Path,
59 syntax_tree: &File,
60 _content: &str,
61 lang: &str,
62 is_test_file: bool,
63 ) -> Vec<CodeIssue> {
64 if is_test_file {
65 return Vec::new();
66 }
67 let mut visitor = AsyncVisitor::new(file_path.to_path_buf(), lang);
68 visitor.visit_file(syntax_tree);
69 visitor.issues
70 }
71}
72
73impl Rule for DynTraitAbuseRule {
74 fn name(&self) -> &'static str {
75 "dyn-trait-abuse"
76 }
77
78 fn check(
79 &self,
80 file_path: &Path,
81 syntax_tree: &File,
82 _content: &str,
83 lang: &str,
84 is_test_file: bool,
85 ) -> Vec<CodeIssue> {
86 if is_test_file {
87 return Vec::new();
88 }
89 let mut visitor = DynTraitVisitor::new(file_path.to_path_buf(), lang);
90 visitor.visit_file(syntax_tree);
91 visitor.issues
92 }
93}
94
95impl Rule for UnsafeAbuseRule {
96 fn name(&self) -> &'static str {
97 "unsafe-abuse"
98 }
99
100 fn check(
101 &self,
102 file_path: &Path,
103 syntax_tree: &File,
104 content: &str,
105 lang: &str,
106 is_test_file: bool,
107 ) -> Vec<CodeIssue> {
108 if is_test_file {
109 return Vec::new();
110 }
111 let mut visitor = UnsafeVisitor::new(file_path.to_path_buf(), lang);
112 visitor.visit_file(syntax_tree);
113
114 visitor.check_unsafe_in_content(content);
116 visitor.issues
117 }
118}
119
120impl Rule for FFIAbuseRule {
121 fn name(&self) -> &'static str {
122 "ffi-abuse"
123 }
124
125 fn check(
126 &self,
127 file_path: &Path,
128 syntax_tree: &File,
129 content: &str,
130 lang: &str,
131 is_test_file: bool,
132 ) -> Vec<CodeIssue> {
133 if is_test_file {
134 return Vec::new();
135 }
136 let mut visitor = FFIVisitor::new(file_path.to_path_buf(), lang);
137 visitor.visit_file(syntax_tree);
138
139 visitor.check_ffi_patterns_in_content(content);
141 visitor.issues
142 }
143}
144
145impl Rule for MacroAbuseRule {
146 fn name(&self) -> &'static str {
147 "macro-abuse"
148 }
149
150 fn check(
151 &self,
152 file_path: &Path,
153 syntax_tree: &File,
154 _content: &str,
155 lang: &str,
156 is_test_file: bool,
157 ) -> Vec<CodeIssue> {
158 if is_test_file {
159 return Vec::new();
160 }
161
162 let mut visitor = MacroVisitor::new(file_path.to_path_buf(), lang);
163 visitor.visit_file(syntax_tree);
164 visitor.issues
165 }
166}
167
168impl Rule for ModuleComplexityRule {
169 fn name(&self) -> &'static str {
170 "module-complexity"
171 }
172
173 fn check(
174 &self,
175 file_path: &Path,
176 syntax_tree: &File,
177 _content: &str,
178 lang: &str,
179 is_test_file: bool,
180 ) -> Vec<CodeIssue> {
181 if is_test_file {
182 return Vec::new();
183 }
184 let mut visitor = ModuleVisitor::new(file_path.to_path_buf(), lang);
185 visitor.visit_file(syntax_tree);
186 visitor.issues
187 }
188}
189
190impl Rule for PatternMatchingAbuseRule {
191 fn name(&self) -> &'static str {
192 "pattern-matching-abuse"
193 }
194
195 fn check(
196 &self,
197 file_path: &Path,
198 syntax_tree: &File,
199 _content: &str,
200 lang: &str,
201 is_test_file: bool,
202 ) -> Vec<CodeIssue> {
203 if is_test_file {
204 return Vec::new();
205 }
206
207 let file_name = file_path.file_name().and_then(|f| f.to_str()).unwrap_or("");
209 if file_name.contains("i18n") || file_name.contains("locale") || file_name.contains("lang")
210 {
211 return Vec::new();
212 }
213
214 let mut visitor = PatternVisitor::new(file_path.to_path_buf(), lang);
215 visitor.visit_file(syntax_tree);
216 visitor.issues
217 }
218}
219
220struct ChannelVisitor {
222 file_path: std::path::PathBuf,
223 issues: Vec<CodeIssue>,
224 channel_count: usize,
225 lang: String,
226 last_position: (usize, usize),
227}
228
229impl ChannelVisitor {
230 fn new(file_path: std::path::PathBuf, lang: &str) -> Self {
231 Self {
232 file_path,
233 issues: Vec::new(),
234 channel_count: 0,
235 lang: lang.to_string(),
236 last_position: (1, 1),
237 }
238 }
239
240 fn check_channel_overuse(&mut self, content: &str) {
241 if self.channel_count > 5 {
244 let messages = if self.lang == "zh-CN" {
245 [
246 "Channel 用得比我发微信还频繁,你确定不是在写聊天软件?",
247 "这么多 Channel,你是想开通讯公司吗?",
248 "Channel 滥用!你的程序比电话交换机还复杂",
249 "Channel 数量超标,建议重新设计架构",
250 "这么多 Channel,我怀疑你在写分布式系统",
251 ]
252 } else {
253 [
254 "You use channels more than I text - are you writing a chat app?",
255 "This many channels, planning to start a telecom company?",
256 "Channel abuse! Your program is more complex than a telephone switchboard",
257 "Channel count exceeds limits - consider redesigning the architecture",
258 "So many channels, I suspect you're building a distributed system",
259 ]
260 };
261
262 let candidates = [
263 find_line_of_str(content, "channel"),
264 find_line_of_str(content, "Sender"),
265 find_line_of_str(content, "Receiver"),
266 ];
267 let line = candidates
268 .iter()
269 .copied()
270 .filter(|&l| l > 1)
271 .min()
272 .unwrap_or(1);
273
274 self.issues.push(CodeIssue {
275 file_path: self.file_path.clone(),
276 line,
277 column: 1,
278 rule_name: "channel-abuse".to_string(),
279 message: messages[self.issues.len() % messages.len()].to_string(),
280 severity: Severity::Spicy,
281 });
282 }
283 }
284}
285
286impl<'ast> Visit<'ast> for ChannelVisitor {
287 fn visit_type_path(&mut self, type_path: &'ast TypePath) {
288 let path_str = quote::quote!(#type_path).to_string();
289 if path_str.contains("Sender")
290 || path_str.contains("Receiver")
291 || path_str.contains("channel")
292 {
293 self.channel_count += 1;
294 self.last_position = get_position(type_path);
295 }
296 syn::visit::visit_type_path(self, type_path);
297 }
298}
299
300struct AsyncVisitor {
302 file_path: std::path::PathBuf,
303 issues: Vec<CodeIssue>,
304 async_count: usize,
305 await_count: usize,
306 lang: String,
307 last_position: (usize, usize),
308}
309
310impl AsyncVisitor {
311 fn new(file_path: std::path::PathBuf, lang: &str) -> Self {
312 Self {
313 file_path,
314 issues: Vec::new(),
315 async_count: 0,
316 await_count: 0,
317 lang: lang.to_string(),
318 last_position: (1, 1),
319 }
320 }
321
322 fn check_async_abuse(&mut self) {
323 if self.async_count > 10 {
324 let messages = if self.lang == "zh-CN" {
325 [
326 "Async 函数比我的异步人生还要复杂",
327 "这么多 async,你确定不是在写 JavaScript?",
328 "Async 滥用!建议学习一下同步编程的美好",
329 "异步函数过多,小心把自己绕晕了",
330 ]
331 } else {
332 [
333 "Async functions are more complex than my async life",
334 "So much async - are you sure you're not writing JavaScript?",
335 "Async abuse! Consider the beauty of synchronous programming",
336 "Too many async functions - careful not to confuse yourself",
337 ]
338 };
339
340 let (line, column) = self.last_position;
341 self.issues.push(CodeIssue {
342 file_path: self.file_path.clone(),
343 line,
344 column,
345 rule_name: "async-abuse".to_string(),
346 message: messages[self.issues.len() % messages.len()].to_string(),
347 severity: Severity::Spicy,
348 });
349 }
350
351 if self.await_count > 20 {
352 let messages = if self.lang == "zh-CN" {
353 [
354 "Await 用得比我等外卖还频繁",
355 "这么多 await,你的程序是在等什么?世界末日吗?",
356 "Await 过度使用,建议批量处理",
357 "等待次数过多,你的程序比我还有耐心",
358 ]
359 } else {
360 [
361 "You await more than I wait for food delivery",
362 "So many awaits - what is your program waiting for, the apocalypse?",
363 "Excessive await usage - consider batching operations",
364 "Your program is more patient than I'll ever be",
365 ]
366 };
367
368 let (line, column) = self.last_position;
369 self.issues.push(CodeIssue {
370 file_path: self.file_path.clone(),
371 line,
372 column,
373 rule_name: "async-abuse".to_string(),
374 message: messages[self.issues.len() % messages.len()].to_string(),
375 severity: Severity::Mild,
376 });
377 }
378 }
379}
380
381impl<'ast> Visit<'ast> for AsyncVisitor {
382 fn visit_expr_async(&mut self, _async_expr: &'ast ExprAsync) {
383 self.async_count += 1;
384 self.last_position = get_position(_async_expr);
385 self.check_async_abuse();
386 syn::visit::visit_expr_async(self, _async_expr);
387 }
388
389 fn visit_expr_await(&mut self, _await_expr: &'ast ExprAwait) {
390 self.await_count += 1;
391 self.last_position = get_position(_await_expr);
392 self.check_async_abuse();
393 syn::visit::visit_expr_await(self, _await_expr);
394 }
395}
396
397struct DynTraitVisitor {
399 file_path: std::path::PathBuf,
400 issues: Vec<CodeIssue>,
401 dyn_count: usize,
402 lang: String,
403}
404
405impl DynTraitVisitor {
406 fn new(file_path: std::path::PathBuf, lang: &str) -> Self {
407 Self {
408 file_path,
409 issues: Vec::new(),
410 dyn_count: 0,
411 lang: lang.to_string(),
412 }
413 }
414}
415
416impl<'ast> Visit<'ast> for DynTraitVisitor {
417 fn visit_type_trait_object(&mut self, trait_object: &'ast TypeTraitObject) {
418 self.dyn_count += 1;
419
420 if self.dyn_count > 5 {
421 let messages = if self.lang == "zh-CN" {
422 [
423 "Dyn trait 用得比我换工作还频繁",
424 "这么多动态分发,性能都跑到哪里去了?",
425 "Dyn trait 滥用,你确定不是在写 Python?",
426 "动态 trait 过多,编译器优化都哭了",
427 "这么多 dyn,你的程序比变色龙还善变",
428 ]
429 } else {
430 [
431 "You use dyn traits more often than I change jobs",
432 "So much dynamic dispatch - where did all the performance go?",
433 "dyn trait abuse - are you sure you're not writing Python?",
434 "Too many dynamic traits - the compiler optimizer is crying",
435 "So many dyns, your program is more wishy-washy than a chameleon",
436 ]
437 };
438
439 let (line, column) = get_position(trait_object);
440 self.issues.push(CodeIssue {
441 file_path: self.file_path.clone(),
442 line,
443 column,
444 rule_name: "dyn-trait-abuse".to_string(),
445 message: messages[self.issues.len() % messages.len()].to_string(),
446 severity: Severity::Spicy,
447 });
448 }
449
450 syn::visit::visit_type_trait_object(self, trait_object);
451 }
452}
453
454struct UnsafeVisitor {
456 file_path: std::path::PathBuf,
457 issues: Vec<CodeIssue>,
458 unsafe_count: usize,
459 unsafe_fn_count: usize,
460 unsafe_impl_count: usize,
461 unsafe_trait_count: usize,
462 lang: String,
463}
464
465impl UnsafeVisitor {
466 fn new(file_path: std::path::PathBuf, lang: &str) -> Self {
467 Self {
468 file_path,
469 issues: Vec::new(),
470 unsafe_count: 0,
471 unsafe_fn_count: 0,
472 unsafe_impl_count: 0,
473 unsafe_trait_count: 0,
474 lang: lang.to_string(),
475 }
476 }
477
478 fn check_unsafe_in_content(&mut self, content: &str) {
479 let unsafe_fn_matches = content.matches("unsafe fn").count();
481 self.unsafe_fn_count += unsafe_fn_matches;
482
483 let unsafe_impl_matches = content.matches("unsafe impl").count();
485 self.unsafe_impl_count += unsafe_impl_matches;
486
487 let unsafe_trait_matches = content.matches("unsafe trait").count();
489 self.unsafe_trait_count += unsafe_trait_matches;
490
491 let raw_ptr_count = content.matches("*const").count() + content.matches("*mut").count();
493
494 let dangerous_ops = [
496 "std::ptr::write",
497 "std::ptr::read",
498 "std::ptr::copy",
499 "std::mem::transmute",
500 "std::mem::forget",
501 "std::mem::uninitialized",
502 "std::slice::from_raw_parts",
503 "std::str::from_utf8_unchecked",
504 "Box::from_raw",
505 "Vec::from_raw_parts",
506 "String::from_raw_parts",
507 ];
508
509 let mut dangerous_op_count = 0;
510 for op in &dangerous_ops {
511 dangerous_op_count += content.matches(op).count();
512 }
513
514 self.generate_unsafe_issues(content, raw_ptr_count, dangerous_op_count);
515 }
516
517 fn generate_unsafe_issues(
518 &mut self,
519 content: &str,
520 raw_ptr_count: usize,
521 dangerous_op_count: usize,
522 ) {
523 if self.unsafe_fn_count > 2 {
525 let messages = if self.lang == "zh-CN" {
526 [
527 "Unsafe 函数比我的黑历史还多!你确定这还是 Rust 吗?",
528 "这么多 unsafe 函数,Rust 的安全保证都被你玩坏了",
529 "Unsafe 函数过多,建议重新考虑设计架构",
530 "你的 unsafe 函数让 Rust 编译器都开始怀疑人生了",
531 ]
532 } else {
533 [
534 "More unsafe functions than my dark secrets! Are you sure this is still Rust?",
535 "So many unsafe functions - Rust's safety guarantees are in shambles",
536 "Too many unsafe functions - consider redesigning the architecture",
537 "Your unsafe functions are making the Rust compiler question its life choices",
538 ]
539 };
540
541 let line = find_line_of_str(content, "unsafe fn");
542 self.issues.push(CodeIssue {
543 file_path: self.file_path.clone(),
544 line,
545 column: 1,
546 rule_name: "unsafe-abuse".to_string(),
547 message: messages[self.issues.len() % messages.len()].to_string(),
548 severity: Severity::Nuclear,
549 });
550 }
551
552 if raw_ptr_count > 5 {
554 let messages = if self.lang == "zh-CN" {
555 [
556 "原始指针用得比我换手机还频繁,你这是在写 C 语言吗?",
557 "这么多原始指针,内存安全已经不在服务区了",
558 "原始指针过多,建议使用安全的 Rust 抽象",
559 "你的指针操作让 Valgrind 都要加班了",
560 ]
561 } else {
562 [
563 "You use raw pointers more than I change phones - are you writing C?",
564 "So many raw pointers, memory safety has left the building",
565 "Too many raw pointers - consider using safe Rust abstractions",
566 "Your pointer operations are making Valgrind work overtime",
567 ]
568 };
569
570 let line = find_line_of_str(content, "*const");
571 self.issues.push(CodeIssue {
572 file_path: self.file_path.clone(),
573 line,
574 column: 1,
575 rule_name: "unsafe-abuse".to_string(),
576 message: messages[self.issues.len() % messages.len()].to_string(),
577 severity: Severity::Nuclear,
578 });
579 }
580
581 if dangerous_op_count > 3 {
583 let messages = if self.lang == "zh-CN" {
584 [
585 "危险的内存操作比我的危险驾驶还要多!",
586 "这些危险操作让我想起了 C++ 的恐怖回忆",
587 "内存操作过于危险,建议使用安全替代方案",
588 "你的代码比走钢丝还危险,小心内存泄漏!",
589 ]
590 } else {
591 [
592 "More dangerous memory ops than my reckless driving!",
593 "These dangerous ops bring back terrifying C++ memories",
594 "Memory operations are too dangerous - use safe alternatives",
595 "Your code is more dangerous than tightrope walking - watch for memory leaks!",
596 ]
597 };
598
599 let line = find_line_of_str(content, "std::ptr::write")
600 .max(find_line_of_str(content, "std::mem::transmute"))
601 .max(find_line_of_str(content, "from_raw"));
602 self.issues.push(CodeIssue {
603 file_path: self.file_path.clone(),
604 line,
605 column: 1,
606 rule_name: "unsafe-abuse".to_string(),
607 message: messages[self.issues.len() % messages.len()].to_string(),
608 severity: Severity::Nuclear,
609 });
610 }
611 }
612}
613
614impl<'ast> Visit<'ast> for UnsafeVisitor {
615 fn visit_expr_unsafe(&mut self, _unsafe_expr: &'ast ExprUnsafe) {
616 self.unsafe_count += 1;
617
618 let messages = if self.lang == "zh-CN" {
619 [
620 "Unsafe 代码!你这是在玩火还是在挑战 Rust 的底线?",
621 "又见 unsafe!安全性是什么?能吃吗?",
622 "Unsafe 使用者,恭喜你获得了'内存安全破坏者'称号",
623 "这个 unsafe 让我想起了 C 语言的恐怖回忆",
624 "Unsafe 代码:让 Rust 程序员夜不能寐的存在",
625 ]
626 } else {
627 [
628 "Unsafe code! Playing with fire or challenging Rust's limits?",
629 "Unsafe again! What is safety, can you eat it?",
630 "Unsafe user - congratulations, you've earned the 'Memory Safety Destroyer' title",
631 "This unsafe brings back terrifying memories of C programming",
632 "Unsafe code: the thing that keeps Rust programmers up at night",
633 ]
634 };
635
636 let severity = if self.unsafe_count > 3 {
637 Severity::Nuclear
638 } else {
639 Severity::Spicy
640 };
641
642 let (line, column) = get_position(_unsafe_expr);
643 self.issues.push(CodeIssue {
644 file_path: self.file_path.clone(),
645 line,
646 column,
647 rule_name: "unsafe-abuse".to_string(),
648 message: messages[self.issues.len() % messages.len()].to_string(),
649 severity,
650 });
651
652 syn::visit::visit_expr_unsafe(self, _unsafe_expr);
653 }
654
655 fn visit_item_fn(&mut self, item_fn: &'ast ItemFn) {
656 if item_fn.sig.unsafety.is_some() {
657 self.unsafe_fn_count += 1;
658 }
659 syn::visit::visit_item_fn(self, item_fn);
660 }
661}
662
663struct FFIVisitor {
665 file_path: std::path::PathBuf,
666 issues: Vec<CodeIssue>,
667 extern_block_count: usize,
668 extern_fn_count: usize,
669 c_repr_count: usize,
670 lang: String,
671}
672
673impl FFIVisitor {
674 fn new(file_path: std::path::PathBuf, lang: &str) -> Self {
675 Self {
676 file_path,
677 issues: Vec::new(),
678 extern_block_count: 0,
679 extern_fn_count: 0,
680 c_repr_count: 0,
681 lang: lang.to_string(),
682 }
683 }
684
685 fn check_ffi_patterns_in_content(&mut self, content: &str) {
686 self.c_repr_count += content.matches("#[repr(C)]").count();
688
689 let c_string_ops = [
691 "CString",
692 "CStr",
693 "c_char",
694 "c_void",
695 "c_int",
696 "c_long",
697 "std::ffi::",
698 "libc::",
699 "std::os::raw::",
700 ];
701
702 let mut c_ops_count = 0;
703 for op in &c_string_ops {
704 c_ops_count += content.matches(op).count();
705 }
706
707 let dll_ops = ["libloading", "dlopen", "LoadLibrary", "GetProcAddress"];
709 let mut dll_count = 0;
710 for op in &dll_ops {
711 dll_count += content.matches(op).count();
712 }
713
714 self.generate_ffi_issues(content, c_ops_count, dll_count);
715 }
716
717 fn generate_ffi_issues(&mut self, content: &str, c_ops_count: usize, dll_count: usize) {
718 if self.extern_block_count > 2 {
720 let messages = if self.lang == "zh-CN" {
721 [
722 "Extern 块比我的前任还多,你这是要和多少种语言交互?",
723 "这么多 extern 块,你确定不是在写多语言翻译器?",
724 "FFI 接口过多,建议封装成统一的抽象层",
725 "外部接口比我的社交关系还复杂!",
726 ]
727 } else {
728 [
729 "More extern blocks than my exes - how many languages are you interfacing with?",
730 "So many extern blocks - are you building a multilingual translator?",
731 "Too many FFI interfaces - consider wrapping them in a unified abstraction layer",
732 "References are more complex than my social life!",
733 ]
734 };
735
736 let line = find_line_of_str(content, "extern \"C\"");
737 self.issues.push(CodeIssue {
738 file_path: self.file_path.clone(),
739 line,
740 column: 1,
741 rule_name: "ffi-abuse".to_string(),
742 message: messages[self.issues.len() % messages.len()].to_string(),
743 severity: Severity::Spicy,
744 });
745 }
746
747 if c_ops_count > 10 {
749 let messages = if self.lang == "zh-CN" {
750 [
751 "C 语言操作比我的 C 语言作业还多,你确定这是 Rust 项目?",
752 "这么多 C FFI,Rust 的安全性都要哭了",
753 "C 接口过多,建议使用更安全的 Rust 绑定",
754 "你的 FFI 代码让我想起了指针地狱的恐怖",
755 ]
756 } else {
757 [
758 "More C operations than my C homework - are you sure this is a Rust project?",
759 "So much C FFI, Rust's safety guarantees are crying",
760 "Too many C interfaces - consider using safer Rust bindings",
761 "Your FFI code brings back the horrors of pointer hell",
762 ]
763 };
764
765 let line =
766 find_line_of_str(content, "CString").max(find_line_of_str(content, "std::ffi"));
767 self.issues.push(CodeIssue {
768 file_path: self.file_path.clone(),
769 line,
770 column: 1,
771 rule_name: "ffi-abuse".to_string(),
772 message: messages[self.issues.len() % messages.len()].to_string(),
773 severity: Severity::Nuclear,
774 });
775 }
776
777 if dll_count > 0 {
779 let messages = if self.lang == "zh-CN" {
780 [
781 "动态库加载!你这是在运行时玩杂技吗?",
782 "动态加载库,小心加载到病毒!",
783 "运行时库加载,调试的时候准备哭吧",
784 "动态库操作,你的程序比变形金刚还会变身",
785 ]
786 } else {
787 [
788 "Dynamic library loading! Are you performing acrobatics at runtime?",
789 "Dynamic loading - careful not to load a virus!",
790 "Runtime library loading - get ready to cry during debugging",
791 "Dynamic library ops - your program transforms more than a Transformer",
792 ]
793 };
794
795 let line =
796 find_line_of_str(content, "libloading").max(find_line_of_str(content, "dlopen"));
797 self.issues.push(CodeIssue {
798 file_path: self.file_path.clone(),
799 line,
800 column: 1,
801 rule_name: "ffi-abuse".to_string(),
802 message: messages[self.issues.len() % messages.len()].to_string(),
803 severity: Severity::Spicy,
804 });
805 }
806
807 if self.c_repr_count > 5 {
809 let messages = if self.lang == "zh-CN" {
810 [
811 "repr(C) 用得比我说 C 语言还频繁!",
812 "这么多 C 表示法,你的结构体都要移民到 C 语言了",
813 "C 表示法过多,内存布局都要乱套了",
814 "repr(C) 滥用,Rust 的零成本抽象在哭泣",
815 ]
816 } else {
817 [
818 "You use repr(C) more than I speak C!",
819 "So many C representations, your structs are emigrating to C",
820 "Too many C representations - memory layout is a mess",
821 "repr(C) abuse - Rust's zero-cost abstractions are weeping",
822 ]
823 };
824
825 let line = find_line_of_str(content, "#[repr(C)]");
826 self.issues.push(CodeIssue {
827 file_path: self.file_path.clone(),
828 line,
829 column: 1,
830 rule_name: "ffi-abuse".to_string(),
831 message: messages[self.issues.len() % messages.len()].to_string(),
832 severity: Severity::Spicy,
833 });
834 }
835 }
836}
837
838impl<'ast> Visit<'ast> for FFIVisitor {
839 fn visit_item_foreign_mod(&mut self, foreign_mod: &'ast ItemForeignMod) {
840 self.extern_block_count += 1;
841
842 for item in &foreign_mod.items {
844 if matches!(item, ForeignItem::Fn(_)) {
845 self.extern_fn_count += 1;
846 }
847 }
848
849 if self.extern_fn_count > 10 {
851 let messages = if self.lang == "zh-CN" {
852 [
853 "外部函数比我的外卖订单还多!",
854 "这么多 extern 函数,你是在开联合国大会吗?",
855 "外部接口过多,建议分模块管理",
856 "FFI 函数数量超标,小心接口管理混乱",
857 ]
858 } else {
859 [
860 "More extern functions than my food delivery orders!",
861 "So many extern functions - are you hosting a UN summit?",
862 "Too many external interfaces - consider modular management",
863 "FFI function count exceeds limits - watch out for interface chaos",
864 ]
865 };
866
867 let (line, column) = get_position(foreign_mod);
868 self.issues.push(CodeIssue {
869 file_path: self.file_path.clone(),
870 line,
871 column,
872 rule_name: "ffi-abuse".to_string(),
873 message: messages[self.issues.len() % messages.len()].to_string(),
874 severity: Severity::Spicy,
875 });
876 }
877
878 syn::visit::visit_item_foreign_mod(self, foreign_mod);
879 }
880
881 fn visit_item_fn(&mut self, item_fn: &'ast ItemFn) {
882 if let Some(abi) = &item_fn.sig.abi {
884 if let Some(abi_name) = &abi.name {
885 if abi_name.value() == "C" {
886 self.extern_fn_count += 1;
887 }
888 }
889 }
890 syn::visit::visit_item_fn(self, item_fn);
891 }
892}
893
894struct MacroVisitor {
896 file_path: std::path::PathBuf,
897 issues: Vec<CodeIssue>,
898 macro_count: usize,
899 lang: String,
900}
901
902impl MacroVisitor {
903 fn new(file_path: std::path::PathBuf, lang: &str) -> Self {
904 Self {
905 file_path,
906 issues: Vec::new(),
907 macro_count: 0,
908 lang: lang.to_string(),
909 }
910 }
911}
912
913impl<'ast> Visit<'ast> for MacroVisitor {
914 fn visit_macro(&mut self, _macro: &'ast Macro) {
915 self.macro_count += 1;
916
917 if self.macro_count > 20 && self.issues.is_empty() {
919 let messages = if self.lang == "zh-CN" {
920 [
921 "宏定义比我的借口还多",
922 "这么多宏,你确定不是在写 C 语言?",
923 "宏滥用!编译时间都被你搞长了",
924 "宏过多,调试的时候准备哭吧",
925 "这么多宏,IDE 都要罢工了",
926 ]
927 } else {
928 [
929 "More macros than my excuses",
930 "So many macros - are you sure you're not writing C?",
931 "Macro abuse! You've inflated the compile times",
932 "Too many macros - get ready to cry during debugging",
933 "So many macros, your IDE is about to go on strike",
934 ]
935 };
936
937 let (line, column) = get_position(_macro);
938 self.issues.push(CodeIssue {
939 file_path: self.file_path.clone(),
940 line,
941 column,
942 rule_name: "macro-abuse".to_string(),
943 message: messages[self.issues.len() % messages.len()].to_string(),
944 severity: Severity::Mild,
945 });
946 }
947
948 syn::visit::visit_macro(self, _macro);
949 }
950}
951
952struct ModuleVisitor {
954 file_path: std::path::PathBuf,
955 issues: Vec<CodeIssue>,
956 module_depth: usize,
957 max_depth: usize,
958 lang: String,
959}
960
961impl ModuleVisitor {
962 fn new(file_path: std::path::PathBuf, lang: &str) -> Self {
963 Self {
964 file_path,
965 issues: Vec::new(),
966 module_depth: 0,
967 max_depth: 0,
968 lang: lang.to_string(),
969 }
970 }
971}
972
973impl<'ast> Visit<'ast> for ModuleVisitor {
974 fn visit_item_mod(&mut self, _module: &'ast ItemMod) {
975 self.module_depth += 1;
976 self.max_depth = self.max_depth.max(self.module_depth);
977
978 if self.module_depth > 5 {
979 let messages = if self.lang == "zh-CN" {
980 [
981 "模块嵌套比俄罗斯套娃还深",
982 "这模块结构比我的家族关系还复杂",
983 "模块嵌套过深,建议重新组织代码结构",
984 "这么深的模块,找个函数比找宝藏还难",
985 ]
986 } else {
987 [
988 "Module nesting is deeper than Russian dolls",
989 "This module structure is more complex than my family tree",
990 "Module nesting too deep - consider restructuring your code",
991 "Modules so deep, finding a function is harder than finding treasure",
992 ]
993 };
994
995 let (line, column) = get_position(_module);
996 self.issues.push(CodeIssue {
997 file_path: self.file_path.clone(),
998 line,
999 column,
1000 rule_name: "module-complexity".to_string(),
1001 message: messages[self.issues.len() % messages.len()].to_string(),
1002 severity: Severity::Spicy,
1003 });
1004 }
1005
1006 syn::visit::visit_item_mod(self, _module);
1007 self.module_depth -= 1;
1008 }
1009}
1010
1011struct PatternVisitor {
1013 file_path: std::path::PathBuf,
1014 issues: Vec<CodeIssue>,
1015 complex_pattern_count: usize,
1016 lang: String,
1017 last_position: (usize, usize),
1018}
1019
1020impl PatternVisitor {
1021 fn new(file_path: std::path::PathBuf, lang: &str) -> Self {
1022 Self {
1023 file_path,
1024 issues: Vec::new(),
1025 complex_pattern_count: 0,
1026 lang: lang.to_string(),
1027 last_position: (1, 1),
1028 }
1029 }
1030
1031 fn check_pattern_complexity(&mut self, pattern_type: &str) {
1032 self.complex_pattern_count += 1;
1033
1034 if self.complex_pattern_count > 15 {
1035 let messages = if self.lang == "zh-CN" {
1036 [
1037 format!("{pattern_type}模式匹配比我的感情生活还复杂"),
1038 format!("这么多{pattern_type}模式,你是在写解谜游戏吗?"),
1039 format!("{pattern_type}模式过多,建议简化逻辑"),
1040 format!("复杂的{pattern_type}模式让代码可读性直线下降"),
1041 ]
1042 } else {
1043 let en_type = match pattern_type {
1044 "元组" => "tuple",
1045 "切片" => "slice",
1046 _ => pattern_type,
1047 };
1048 [
1049 format!("{en_type} pattern matching is more complex than my love life"),
1050 format!("So many {en_type} patterns - are you writing a puzzle game?"),
1051 format!("Too many {en_type} patterns - consider simplifying the logic"),
1052 format!("Complex {en_type} patterns tanking your code readability"),
1053 ]
1054 };
1055
1056 let (line, column) = self.last_position;
1057 self.issues.push(CodeIssue {
1058 file_path: self.file_path.clone(),
1059 line,
1060 column,
1061 rule_name: "pattern-matching-abuse".to_string(),
1062 message: messages[self.issues.len() % messages.len()].to_string(),
1063 severity: Severity::Mild,
1064 });
1065 }
1066 }
1067}
1068
1069impl<'ast> Visit<'ast> for PatternVisitor {
1070 fn visit_pat_tuple(&mut self, _tuple_pat: &'ast PatTuple) {
1071 self.last_position = get_position(_tuple_pat);
1072 self.check_pattern_complexity("元组");
1073 syn::visit::visit_pat_tuple(self, _tuple_pat);
1074 }
1075
1076 fn visit_pat_slice(&mut self, _slice_pat: &'ast PatSlice) {
1077 self.last_position = get_position(_slice_pat);
1078 self.check_pattern_complexity("切片");
1079 syn::visit::visit_pat_slice(self, _slice_pat);
1080 }
1081
1082 fn visit_expr_match(&mut self, match_expr: &'ast ExprMatch) {
1083 if match_expr.arms.len() > 20 {
1085 let messages = if self.lang == "zh-CN" {
1086 [
1087 "Match 分支比我的人生选择还多",
1088 "这么多 match 分支,你确定不是在写状态机?",
1089 "Match 分支过多,建议重构",
1090 "这个 match 比电视遥控器的按钮还多",
1091 ]
1092 } else {
1093 [
1094 "More match arms than life choices I've made",
1095 "So many match branches - are you building a state machine?",
1096 "Too many match arms - consider refactoring",
1097 "This match has more arms than a TV remote has buttons",
1098 ]
1099 };
1100
1101 let (line, column) = get_position(match_expr);
1102 self.issues.push(CodeIssue {
1103 file_path: self.file_path.clone(),
1104 line,
1105 column,
1106 rule_name: "pattern-matching-abuse".to_string(),
1107 message: messages[self.issues.len() % messages.len()].to_string(),
1108 severity: Severity::Spicy,
1109 });
1110 }
1111 syn::visit::visit_expr_match(self, match_expr);
1112 }
1113}