Skip to main content

garbage_code_hunter/treesitter/rules/
rust_rules.rs

1use super::base_rules::{CountRule, MacroRule, MethodCallRule};
2use super::complex_rules::{
3    AbbreviationAbuseTsRule, ComplexClosureRule, DeepNestingRule, GodFunctionRule,
4    HungarianNotationTsRule, LongFunctionRule, MagicNumberRule, PrintlnDebuggingRule,
5    SingleLetterTsRule, TerribleNamingRule,
6};
7use super::remaining_rules::{
8    CommentedCodeRule, DeadCodeRule, DuplicateImportsRule, FileTooLongRule, MeaninglessRule,
9    TodoCommentRule,
10};
11use crate::analyzer::Severity;
12use crate::language::Language;
13
14/// Register all tree-sitter based Rust rules.
15pub fn register_rust_rules(engine: &mut crate::treesitter::rule::TreeSitterRuleEngine) {
16    // Unwrap abuse (escalating severity)
17    engine.add(Box::new(MethodCallRule {
18        name: "unwrap-abuse",
19        method_name: "unwrap",
20        threshold: 0,
21        severity_fn: |count| {
22            if count > 15 {
23                Severity::Nuclear
24            } else if count > 8 {
25                Severity::Spicy
26            } else {
27                Severity::Mild
28            }
29        },
30        message_fn: |count| {
31            format!(
32                "Found {} .unwrap() calls — use proper error handling",
33                count
34            )
35        },
36    }));
37
38    // Unnecessary clone
39    engine.add(Box::new(MethodCallRule {
40        name: "unnecessary-clone",
41        method_name: "clone",
42        threshold: 24,
43        severity_fn: |_| Severity::Spicy,
44        message_fn: |count| {
45            format!(
46                "Found {} .clone() calls — consider using references instead",
47                count
48            )
49        },
50    }));
51
52    // Async abuse (>10 async blocks)
53    engine.add(Box::new(CountRule {
54        name: "async-abuse",
55        pattern: "(async_block) @block",
56        threshold: 10,
57        severity: Severity::Spicy,
58        languages: &[Language::Rust],
59        message_fn: |count| {
60            format!(
61                "Found {} async blocks — consider consolidating async operations",
62                count
63            )
64        },
65    }));
66
67    // Macro abuse
68    engine.add(Box::new(CountRule {
69        name: "macro-abuse",
70        pattern: "(macro_invocation) @m",
71        threshold: 20,
72        severity: Severity::Mild,
73        languages: &[Language::Rust],
74        message_fn: |count| {
75            format!(
76                "Found {} macro invocations — consider reducing macro usage",
77                count
78            )
79        },
80    }));
81
82    // Lifetime abuse (non-static lifetimes)
83    engine.add(Box::new(CountRule {
84        name: "lifetime-abuse",
85        pattern: "(lifetime) @life",
86        threshold: 20,
87        severity: Severity::Spicy,
88        languages: &[Language::Rust],
89        message_fn: |count| {
90            format!(
91                "Found {} lifetime annotations — consider simplifying lifetime management",
92                count
93            )
94        },
95    }));
96
97    // Trait complexity (methods in trait body)
98    engine.add(Box::new(CountRule {
99        name: "trait-complexity",
100        pattern: "(trait_item body: (declaration_list (function_item) @method))",
101        threshold: 10,
102        severity: Severity::Spicy,
103        languages: &[Language::Rust],
104        message_fn: |count| {
105            format!(
106                "Trait has {} methods — consider splitting into smaller traits",
107                count
108            )
109        },
110    }));
111
112    // Generic abuse (>5 type parameters)
113    engine.add(Box::new(CountRule {
114        name: "generic-abuse",
115        pattern: "(type_parameters (type_parameter) @param)",
116        threshold: 5,
117        severity: Severity::Spicy,
118        languages: &[Language::Rust],
119        message_fn: |count| {
120            format!(
121                "Found {} generic parameters — consider simplifying the type signature",
122                count
123            )
124        },
125    }));
126
127    // Pattern matching abuse (tuple patterns)
128    engine.add(Box::new(CountRule {
129        name: "pattern-matching-abuse",
130        pattern: "(tuple_pattern) @tp",
131        threshold: 15,
132        severity: Severity::Mild,
133        languages: &[Language::Rust],
134        message_fn: |count| {
135            format!(
136                "Found {} complex tuple patterns — consider using named structs",
137                count
138            )
139        },
140    }));
141
142    // Box abuse
143    engine.add(Box::new(CountRule {
144        name: "box-abuse",
145        pattern: "(call_expression function: (scoped_identifier) @si)",
146        threshold: 8,
147        severity: Severity::Spicy,
148        languages: &[Language::Rust],
149        message_fn: |count| {
150            format!(
151                "Found {} Box::new() calls — consider using stack allocation",
152                count
153            )
154        },
155    }));
156
157    // Reference abuse (type references)
158    engine.add(Box::new(CountRule {
159        name: "reference-abuse",
160        pattern: "(reference_type) @rt",
161        threshold: 50,
162        severity: Severity::Mild,
163        languages: &[Language::Rust],
164        message_fn: |count| {
165            format!(
166                "Found {} reference types — consider simplifying ownership",
167                count
168            )
169        },
170    }));
171
172    // Slice abuse
173    engine.add(Box::new(CountRule {
174        name: "slice-abuse",
175        pattern: "(slice_type) @st",
176        threshold: 29,
177        severity: Severity::Mild,
178        languages: &[Language::Rust],
179        message_fn: |count| {
180            format!(
181                "Found {} slice types — consider using concrete collection types",
182                count
183            )
184        },
185    }));
186
187    // Module complexity (nested mod_items)
188    engine.add(Box::new(CountRule {
189        name: "module-complexity",
190        pattern: "(mod_item body: (declaration_list (mod_item) @nested))",
191        threshold: 0,
192        severity: Severity::Spicy,
193        languages: &[Language::Rust],
194        message_fn: |count| {
195            format!(
196                "Found {} nested modules — consider flattening the module structure",
197                count
198            )
199        },
200    }));
201
202    // Panic abuse (count panic! invocations)
203    engine.add(Box::new(MacroRule {
204        name: "panic-abuse",
205        macro_name: "panic",
206        threshold: 2,
207        severity: Severity::Nuclear,
208        message_fn: |count| {
209            format!(
210                "Found {} panic! calls — use proper error handling with Result",
211                count
212            )
213        },
214    }));
215
216    // Deep nesting
217    engine.add(Box::new(DeepNestingRule));
218    // Long function
219    engine.add(Box::new(LongFunctionRule));
220
221    // God function
222    engine.add(Box::new(GodFunctionRule));
223
224    // Complex closure
225    engine.add(Box::new(ComplexClosureRule));
226    // Terrible naming
227    engine.add(Box::new(TerribleNamingRule));
228
229    // Single letter variable
230    engine.add(Box::new(SingleLetterTsRule));
231
232    // Hungarian notation
233    engine.add(Box::new(HungarianNotationTsRule));
234
235    // Abbreviation abuse
236    engine.add(Box::new(AbbreviationAbuseTsRule));
237
238    // Println debugging
239    engine.add(Box::new(PrintlnDebuggingRule));
240
241    // Magic number
242    engine.add(Box::new(MagicNumberRule));
243
244    // Meaningless naming
245    engine.add(Box::new(MeaninglessRule));
246
247    // Commented code
248    engine.add(Box::new(CommentedCodeRule));
249
250    // Dead code
251    engine.add(Box::new(DeadCodeRule));
252
253    // TODO/FIXME comments
254    engine.add(Box::new(TodoCommentRule));
255
256    // Duplicate imports
257    engine.add(Box::new(DuplicateImportsRule));
258
259    // File too long
260    engine.add(Box::new(FileTooLongRule));
261
262    // String abuse (String::from, .to_string)
263    engine.add(Box::new(MethodCallRule {
264        name: "string-abuse",
265        method_name: "to_string",
266        threshold: 20,
267        severity_fn: |_| Severity::Mild,
268        message_fn: |count| format!("Found {} .to_string() calls — consider using &str", count),
269    }));
270
271    // Vec abuse (vec! macro)
272    engine.add(Box::new(CountRule {
273        name: "vec-abuse",
274        pattern: "(macro_invocation macro: (identifier) @m (#eq? @m \"vec\"))",
275        threshold: 15,
276        severity: Severity::Mild,
277        languages: &[Language::Rust],
278        message_fn: |count| format!("Found {} vec![] calls — consider using arrays", count),
279    }));
280
281    // Goto abuse (C/C++)
282    engine.add(Box::new(CountRule {
283        name: "goto-abuse",
284        pattern: "(goto_statement) @goto",
285        threshold: 0,
286        severity: Severity::Spicy,
287        languages: &[Language::C, Language::Cpp],
288        message_fn: |count| {
289            format!(
290                "Found {} goto statements — Dijkstra is turning in his grave",
291                count
292            )
293        },
294    }));
295
296    // C++ new expression detection
297    engine.add(Box::new(CountRule {
298        name: "new-expression",
299        pattern: "(new_expression) @new",
300        threshold: 0,
301        severity: Severity::Spicy,
302        languages: &[Language::Cpp],
303        message_fn: |count| {
304            format!(
305                "Found {} new expressions — did you delete() everything?",
306                count
307            )
308        },
309    }));
310
311    // Malloc leak detection (C/C++): count heap allocation calls
312    // Matches both raw malloc and framework-specific allocators (curlx_malloc, zmalloc, ngx_alloc, etc.)
313    engine.add(Box::new(CountRule {
314        name: "malloc-leak",
315        pattern: "(call_expression function: (identifier) @func (#match? @func \"^(malloc|curlx_malloc|Curl_cmalloc|zmalloc|zcalloc|zrealloc|ngx_alloc|ngx_palloc|ngx_pcalloc)$\"))",
316        threshold: 0,
317        severity: Severity::Spicy,
318        languages: &[Language::C, Language::Cpp],
319        message_fn: |count| {
320            format!(
321                "Found {} heap allocation calls — did you free() everything?",
322                count
323            )
324        },
325    }));
326}
327
328#[cfg(test)]
329mod tests {
330    use super::*;
331    use crate::treesitter::engine::ParsedFile;
332    use crate::treesitter::query::collect_captures;
333    use crate::treesitter::rule::TreeSitterRule;
334    use crate::treesitter::TreeSitterEngine;
335    use std::path::Path;
336
337    fn parse_rust(code: &str) -> ParsedFile {
338        parse_rust_as("main.rs", code)
339    }
340
341    fn parse_rust_as(filename: &str, code: &str) -> ParsedFile {
342        let engine = TreeSitterEngine::new();
343        engine
344            .parse_file(Path::new(filename), code)
345            .expect("Should parse")
346    }
347
348    /// Objective: Verify unwrap-abuse detects .unwrap() calls
349    /// Invariants: Multiple unwrap calls should trigger with correct severity
350    #[test]
351    fn test_unwrap_abuse_detection() {
352        let file = parse_rust(
353            r#"
354fn main() {
355    let a = x.unwrap();
356    let b = y.unwrap();
357    let c = z.unwrap();
358    let d = w.unwrap();
359}
360"#,
361        );
362        let rule = MethodCallRule {
363            name: "unwrap-abuse",
364            method_name: "unwrap",
365            threshold: 0,
366            severity_fn: |count| {
367                if count > 15 {
368                    Severity::Nuclear
369                } else if count > 8 {
370                    Severity::Spicy
371                } else {
372                    Severity::Mild
373                }
374            },
375            message_fn: |count| format!("{} unwraps", count),
376        };
377        let issues = rule.check(&file);
378        assert_eq!(issues.len(), 1, "Should report one aggregated issue");
379        assert_eq!(issues[0].severity, Severity::Mild);
380        assert!(issues[0].message.contains("4"), "Should count 4 unwraps");
381    }
382
383    /// Objective: Verify unwrap-abuse escalates to Nuclear for >15 calls
384    /// Invariants: 16+ unwraps should be Nuclear severity
385    #[test]
386    fn test_unwrap_abuse_nuclear() {
387        let mut code = String::from("fn main() {\n");
388        for i in 0..16 {
389            code.push_str(&format!("    let x{} = v{}.unwrap();\n", i, i));
390        }
391        code.push('}');
392        let file = parse_rust(&code);
393        let rule = MethodCallRule {
394            name: "unwrap-abuse",
395            method_name: "unwrap",
396            threshold: 0,
397            severity_fn: |count| {
398                if count > 15 {
399                    Severity::Nuclear
400                } else if count > 8 {
401                    Severity::Spicy
402                } else {
403                    Severity::Mild
404                }
405            },
406            message_fn: |count| format!("{} unwraps", count),
407        };
408        let issues = rule.check(&file);
409        assert_eq!(issues.len(), 1);
410        assert_eq!(issues[0].severity, Severity::Nuclear);
411    }
412
413    /// Objective: Verify async-abuse counts async blocks
414    /// Invariants: Should find async blocks via tree-sitter query
415    #[test]
416    fn test_async_abuse_detection() {
417        let file = parse_rust(
418            r#"
419async fn foo() {
420    let _ = async { 1 }.await;
421    let _ = async { 2 }.await;
422}
423"#,
424        );
425        let pattern = "(async_block) @block";
426        let captures = collect_captures(&file, pattern).expect("query should work");
427        let count: usize = captures.iter().map(|c| c.len()).sum();
428        assert!(
429            count >= 2,
430            "Should find at least 2 async blocks, found {}",
431            count
432        );
433    }
434
435    /// Objective: Verify macro-abuse counts macro invocations
436    /// Invariants: Should detect println!, vec!, etc.
437    #[test]
438    fn test_macro_abuse_detection() {
439        let file = parse_rust(
440            r#"
441fn main() {
442    println!("a");
443    println!("b");
444    vec![1, 2, 3];
445}
446"#,
447        );
448        let pattern = "(macro_invocation) @m";
449        let captures = collect_captures(&file, pattern).expect("query should work");
450        let count: usize = captures.iter().map(|c| c.len()).sum();
451        assert!(
452            count >= 3,
453            "Should find at least 3 macro invocations, found {}",
454            count
455        );
456    }
457
458    /// Objective: Verify lifetime detection finds lifetime annotations
459    /// Invariants: Should find 'a and 'b lifetimes
460    #[test]
461    fn test_lifetime_detection() {
462        let file = parse_rust(
463            r#"
464fn foo<'a, 'b>(x: &'a str, y: &'b str) -> &'a str { x }
465"#,
466        );
467        let pattern = "(lifetime) @life";
468        let captures = collect_captures(&file, pattern).expect("query should work");
469        let count: usize = captures.iter().map(|c| c.len()).sum();
470        assert!(
471            count >= 2,
472            "Should find at least 2 lifetimes, found {}",
473            count
474        );
475    }
476
477    /// Objective: Verify deep-nesting detects deeply nested code
478    /// Invariants: 6+ levels of nesting should trigger
479    #[test]
480    fn test_deep_nesting_detection() {
481        let file = parse_rust(
482            r#"
483fn main() {
484    if true {
485        if true {
486            if true {
487                if true {
488                    if true {
489                        if true {
490                            println!("deep");
491                        }
492                    }
493                }
494            }
495        }
496    }
497}
498"#,
499        );
500        let rule = DeepNestingRule;
501        let issues = rule.check(&file);
502        assert!(!issues.is_empty(), "Should detect deep nesting");
503        assert!(issues.iter().any(|i| i.rule_name == "deep-nesting"));
504    }
505
506    /// Objective: Verify deep-nesting does not trigger on shallow code
507    /// Invariants: 2 levels of nesting should be fine
508    #[test]
509    fn test_deep_nesting_clean_code() {
510        let file = parse_rust(
511            r#"
512fn main() {
513    if true {
514        println!("shallow");
515    }
516}
517"#,
518        );
519        let rule = DeepNestingRule;
520        let issues = rule.check(&file);
521        assert!(
522            issues.is_empty(),
523            "Shallow nesting should not trigger, found {} issues",
524            issues.len()
525        );
526    }
527
528    /// Objective: Verify long-function detects functions > 80 lines
529    /// Invariants: A function with 90+ lines should trigger
530    #[test]
531    fn test_long_function_detection() {
532        let mut code = String::from("fn long_function() {\n");
533        for i in 0..90 {
534            code.push_str(&format!("    let x{} = {};\n", i, i));
535        }
536        code.push_str("}\n");
537        let file = parse_rust_as("main.rs", &code);
538        let rule = LongFunctionRule;
539        let issues = rule.check(&file);
540        assert!(!issues.is_empty(), "Should detect long function");
541        assert!(issues.iter().any(|i| i.rule_name == "long-function"));
542    }
543
544    /// Objective: Verify long-function does not trigger on short functions
545    /// Invariants: A function with 10 lines should not trigger
546    #[test]
547    fn test_long_function_clean_code() {
548        let file = parse_rust(
549            r#"
550fn short_function() {
551    let x = 1;
552    let y = 2;
553    println!("{}", x + y);
554}
555"#,
556        );
557        let rule = LongFunctionRule;
558        let issues = rule.check(&file);
559        assert!(issues.is_empty(), "Short function should not trigger");
560    }
561
562    /// Objective: Verify god-function detects high complexity
563    /// Invariants: A function with many control flow + params should trigger
564    #[test]
565    fn test_god_function_detection() {
566        let file = parse_rust(
567            r#"
568fn god(a: i32, b: i32, c: i32, d: i32, e: i32, f: i32, g: i32) {
569    if a > 0 {
570        if b > 0 {
571            if c > 0 {
572                for x in 0..10 {
573                    match d {
574                        1 => {},
575                        2 => {},
576                        _ => {},
577                    }
578                }
579            }
580        }
581    }
582    if a > 1 {
583        if b > 1 {
584            if c > 1 {
585                for x in 0..10 {
586                    match d {
587                        1 => {},
588                        2 => {},
589                        _ => {},
590                    }
591                }
592            }
593        }
594    }
595    if a > 2 {
596        if b > 2 {
597            if c > 2 {
598                for x in 0..10 {
599                    match d {
600                        1 => {},
601                        2 => {},
602                        _ => {},
603                    }
604                }
605            }
606        }
607    }
608}
609"#,
610        );
611        let rule = GodFunctionRule;
612        let issues = rule.check(&file);
613        assert!(!issues.is_empty(), "Should detect god function");
614        assert!(issues.iter().any(|i| i.rule_name == "god-function"));
615    }
616
617    /// Objective: Verify complex-closure detects deeply nested closures
618    /// Invariants: 3+ levels of closure nesting should trigger
619    #[test]
620    fn test_complex_closure_detection() {
621        let file = parse_rust(
622            r#"
623fn main() {
624    let f = |x| {
625        let g = |y| {
626            let h = |z| {
627                z + 1
628            };
629            h(y)
630        };
631        g(x)
632    };
633}
634"#,
635        );
636        let rule = ComplexClosureRule;
637        let issues = rule.check(&file);
638        assert!(!issues.is_empty(), "Should detect complex closure nesting");
639        assert!(issues.iter().any(|i| i.rule_name == "complex-closure"));
640    }
641
642    /// Objective: Verify module-complexity detects nested modules
643    /// Invariants: Nested mod items should be detected
644    #[test]
645    fn test_module_complexity_detection() {
646        let file = parse_rust(
647            r#"
648mod outer {
649    mod inner {
650        fn foo() {}
651    }
652}
653"#,
654        );
655        let pattern = "(mod_item body: (declaration_list (mod_item) @nested))";
656        let captures = collect_captures(&file, pattern).expect("query should work");
657        let count: usize = captures.iter().map(|c| c.len()).sum();
658        assert!(count >= 1, "Should find nested module, found {}", count);
659    }
660
661    /// Objective: Verify terrible-naming detects bad variable names
662    /// Invariants: Names like 'data', 'temp' should trigger
663    #[test]
664    fn test_terrible_naming_detection() {
665        let file = parse_rust(
666            r#"
667fn main() {
668    let data = 1;
669    let temp = 2;
670    let value = 3;
671}
672"#,
673        );
674        let rule = TerribleNamingRule;
675        let issues = rule.check(&file);
676        assert!(!issues.is_empty(), "Should detect terrible naming");
677        assert!(issues.iter().any(|i| i.rule_name == "terrible-naming"));
678    }
679
680    /// Objective: Verify terrible-naming does not trigger on good names
681    /// Invariants: Meaningful names should not trigger
682    #[test]
683    fn test_terrible_naming_clean_code() {
684        let file = parse_rust(
685            r#"
686fn main() {
687    let user_count = 1;
688    let max_retries = 3;
689}
690"#,
691        );
692        let rule = TerribleNamingRule;
693        let issues = rule.check(&file);
694        assert!(issues.is_empty(), "Good names should not trigger");
695    }
696
697    /// Objective: Verify single-letter-variable detects bad single-letter names
698    /// Invariants: 'q' should trigger, 'i' should not
699    #[test]
700    fn test_single_letter_variable_detection() {
701        let file = parse_rust(
702            r#"
703fn main() {
704    let q = 1;
705}
706"#,
707        );
708        let rule = SingleLetterTsRule;
709        let issues = rule.check(&file);
710        assert!(
711            !issues.is_empty(),
712            "Should detect single-letter variable 'q'"
713        );
714        assert!(issues
715            .iter()
716            .any(|i| i.rule_name == "single-letter-variable"));
717    }
718
719    /// Objective: Verify single-letter-variable allows loop counters by context
720    /// Invariants: 'i', 'j' in for loops should be allowed; 'q' standalone should not
721    #[test]
722    fn test_single_letter_allows_loop_counters() {
723        let file = parse_rust(
724            r#"
725fn main() {
726    for i in 0..10 {
727        for j in 0..5 {
728            let q = 42;
729        }
730    }
731}
732"#,
733        );
734        let rule = SingleLetterTsRule;
735        let issues = rule.check(&file);
736        let names: Vec<&str> = issues
737            .iter()
738            .map(|i| i.message.split('\'').nth(1).unwrap_or(""))
739            .collect();
740        assert!(
741            !names.contains(&"i"),
742            "Loop variable 'i' should be allowed (not in issues)"
743        );
744        assert!(
745            !names.contains(&"j"),
746            "Loop variable 'j' should be allowed (not in issues)"
747        );
748        assert!(
749            names.contains(&"q"),
750            "Standalone single-letter 'q' should still be flagged"
751        );
752    }
753
754    /// Objective: Verify panic-abuse detects panic! calls
755    /// Invariants: Multiple panic! calls should trigger
756    #[test]
757    fn test_panic_abuse_detection() {
758        let file = parse_rust(
759            r#"
760fn main() {
761    panic!("oh no");
762    panic!("again");
763    panic!("and again");
764}
765"#,
766        );
767        let rule = MacroRule {
768            name: "panic-abuse",
769            macro_name: "panic",
770            threshold: 2,
771            severity: Severity::Nuclear,
772            message_fn: |count| format!("{} panics", count),
773        };
774        let issues = rule.check(&file);
775        assert!(!issues.is_empty(), "Should detect panic abuse");
776        assert!(issues[0].message.contains("3"), "Should count 3 panics");
777    }
778
779    /// Objective: Verify hungarian-notation detects type prefixes
780    /// Invariants: 'strName', 'intCount' should trigger
781    #[test]
782    fn test_hungarian_notation_detection() {
783        let file = parse_rust(
784            r#"
785fn main() {
786    let strName = "test";
787    let intCount = 42;
788}
789"#,
790        );
791        let rule = HungarianNotationTsRule;
792        let issues = rule.check(&file);
793        assert!(!issues.is_empty(), "Should detect Hungarian notation");
794        assert!(issues.iter().any(|i| i.rule_name == "hungarian-notation"));
795    }
796
797    /// Objective: Verify abbreviation-abuse detects bad abbreviations
798    /// Invariants: 'mgr', 'btn' should trigger
799    #[test]
800    fn test_abbreviation_abuse_detection() {
801        let file = parse_rust(
802            r#"
803fn main() {
804    let mgr = get_manager();
805    let btn = get_button();
806}
807"#,
808        );
809        let rule = AbbreviationAbuseTsRule;
810        let issues = rule.check(&file);
811        assert!(!issues.is_empty(), "Should detect abbreviation abuse");
812        assert!(issues.iter().any(|i| i.rule_name == "abbreviation-abuse"));
813    }
814
815    /// Objective: Verify abbreviation-abuse does not trigger on full words
816    /// Invariants: 'manager', 'button' should not trigger
817    #[test]
818    fn test_abbreviation_abuse_clean_code() {
819        let file = parse_rust(
820            r#"
821fn main() {
822    let manager = get_manager();
823    let button = get_button();
824}
825"#,
826        );
827        let rule = AbbreviationAbuseTsRule;
828        let issues = rule.check(&file);
829        assert!(
830            issues.is_empty(),
831            "Full words should not trigger abbreviation abuse"
832        );
833    }
834
835    /// Objective: Verify ALL tree-sitter rules fire on comprehensive test code
836    /// Invariants: Every registered rule should detect at least one issue
837    #[test]
838    fn test_all_rules_fire_on_comprehensive_code() {
839        use crate::treesitter::rule::TreeSitterRuleEngine;
840        use crate::treesitter::TreeSitterEngine;
841
842        let code = r#"
843fn test_unwrap() {
844    let a = Some(1).unwrap();
845    let b = Some(2).unwrap();
846    let c = Some(3).unwrap();
847    let d = Some(4).unwrap();
848    let e = Some(5).unwrap();
849}
850
851fn test_clone() {
852    let x = vec![1, 2, 3];
853    let y = x.clone(); let z = y.clone(); let w = z.clone();
854    let v = w.clone(); let u = v.clone(); let t = u.clone();
855    let s = t.clone(); let r = s.clone(); let q = r.clone();
856    let p = q.clone(); let o = p.clone(); let n = o.clone();
857    let m = n.clone(); let l = m.clone(); let k = l.clone();
858    let j = k.clone(); let i = j.clone(); let h = i.clone();
859    let g = h.clone(); let f = g.clone(); let e = f.clone();
860    let d = e.clone(); let c = d.clone(); let b = c.clone();
861    let a = b.clone(); let aa = a.clone(); let bb = aa.clone();
862}
863
864fn test_async() {
865    let _ = async { 1 }; let _ = async { 2 }; let _ = async { 3 };
866    let _ = async { 4 }; let _ = async { 5 }; let _ = async { 6 };
867    let _ = async { 7 }; let _ = async { 8 }; let _ = async { 9 };
868    let _ = async { 10 }; let _ = async { 11 };
869}
870
871fn test_panic() {
872    panic!("a"); panic!("b"); panic!("c");
873}
874
875fn test_macros() {
876    println!("a"); println!("b"); println!("c"); println!("d"); println!("e");
877    println!("f"); println!("g"); println!("h"); println!("i"); println!("j");
878    println!("k"); println!("l"); println!("m"); println!("n"); println!("o");
879    println!("p"); println!("q"); println!("r"); println!("s"); println!("t");
880    println!("u");
881}
882
883fn test_nesting() {
884    if true { if true { if true { if true { if true { if true {
885        println!("deep");
886    } } } } } }
887}
888
889fn test_long_fn() {
890    let x0 = 0;
891    let x1 = 1;
892    let x2 = 2;
893    let x3 = 3;
894    let x4 = 4;
895    let x5 = 5;
896    let x6 = 6;
897    let x7 = 7;
898    let x8 = 8;
899    let x9 = 9;
900    let x10 = 10;
901    let x11 = 11;
902    let x12 = 12;
903    let x13 = 13;
904    let x14 = 14;
905    let x15 = 15;
906    let x16 = 16;
907    let x17 = 17;
908    let x18 = 18;
909    let x19 = 19;
910    let x20 = 20;
911    let x21 = 21;
912    let x22 = 22;
913    let x23 = 23;
914    let x24 = 24;
915    let x25 = 25;
916    let x26 = 26;
917    let x27 = 27;
918    let x28 = 28;
919    let x29 = 29;
920    let x30 = 30;
921    let x31 = 31;
922    let x32 = 32;
923    let x33 = 33;
924    let x34 = 34;
925    let x35 = 35;
926    let x36 = 36;
927    let x37 = 37;
928    let x38 = 38;
929    let x39 = 39;
930    let x40 = 40;
931    let x41 = 41;
932    let x42 = 42;
933    let x43 = 43;
934    let x44 = 44;
935    let x45 = 45;
936    let x46 = 46;
937    let x47 = 47;
938    let x48 = 48;
939    let x49 = 49;
940    let x50 = 50;
941    let x51 = 51;
942    let x52 = 52;
943    let x53 = 53;
944    let x54 = 54;
945    let x55 = 55;
946    let x56 = 56;
947    let x57 = 57;
948    let x58 = 58;
949    let x59 = 59;
950    let x60 = 60;
951    let x61 = 61;
952    let x62 = 62;
953    let x63 = 63;
954    let x64 = 64;
955    let x65 = 65;
956    let x66 = 66;
957    let x67 = 67;
958    let x68 = 68;
959    let x69 = 69;
960    let x70 = 70;
961    let x71 = 71;
962    let x72 = 72;
963    let x73 = 73;
964    let x74 = 74;
965    let x75 = 75;
966    let x76 = 76;
967    let x77 = 77;
968    let x78 = 78;
969    let x79 = 79;
970    let x80 = 80;
971    let x81 = 81;
972    let x82 = 82;
973    let x83 = 83;
974    let x84 = 84;
975    let x85 = 85;
976}
977
978fn test_god(a:i32,b:i32,c:i32,d:i32,e:i32,f:i32,g:i32) {
979    if a>0{if b>0{if c>0{for x in 0..10{match d{1=>{},2=>{},_=>{}}}}}}
980    if a>1{if b>1{if c>1{for x in 0..10{match d{1=>{},2=>{},_=>{}}}}}}
981    if a>2{if b>2{if c>2{for x in 0..10{match d{1=>{},2=>{},_=>{}}}}}}
982    if a>3{if b>3{if c>3{for x in 0..10{match d{1=>{},2=>{},_=>{}}}}}}
983    if a>4{if b>4{if c>4{for x in 0..10{match d{1=>{},2=>{},_=>{}}}}}}
984}
985
986fn test_closure() {
987    let f = |x| { let g = |y| { let h = |z| { z + 1 }; h(y) }; g(x) };
988}
989
990fn test_naming() { let data = 1; let temp = 2; let value = 3; let info = 4; }
991
992fn test_single_letter() { let q = 1; let m = 2; }
993
994mod outer { mod inner { fn foo() {} } }
995
996fn test_lifetime<'a,'b,'c,'d,'e,'f,'g,'h,'i,'j,'k,'l,'m,'n,'o,'p,'q,'r,'s,'t,'u,'v>(x:&'a str)->&'b str{x}
997
998fn test_generics<T,U,V,W,X,Y,Z>(a:T,b:U,c:V,d:W,e:X,f:Y,g:Z)->T{a}
999"#;
1000
1001        let engine = TreeSitterEngine::new();
1002        let file = engine
1003            .parse_file(Path::new("comprehensive.rs"), code)
1004            .expect("Should parse");
1005
1006        let mut ts_engine = TreeSitterRuleEngine::new();
1007        register_rust_rules(&mut ts_engine);
1008
1009        let issues = ts_engine.check_file(&file, false);
1010        let rule_names: std::collections::HashSet<&str> =
1011            issues.iter().map(|i| i.rule_name.as_str()).collect();
1012
1013        let expected_rules = [
1014            "unwrap-abuse",
1015            "unnecessary-clone",
1016            "async-abuse",
1017            "panic-abuse",
1018            "macro-abuse",
1019            "deep-nesting",
1020            "long-function",
1021            "god-function",
1022            "complex-closure",
1023            "terrible-naming",
1024            "single-letter-variable",
1025            "module-complexity",
1026            "lifetime-abuse",
1027            "generic-abuse",
1028        ];
1029
1030        let mut missing = Vec::new();
1031        for rule in &expected_rules {
1032            if !rule_names.contains(rule) {
1033                missing.push(*rule);
1034            }
1035        }
1036
1037        assert!(
1038            missing.is_empty(),
1039            "These tree-sitter rules did NOT fire: {:?}\nRules that did fire: {:?}\nTotal issues: {}",
1040            missing,
1041            rule_names,
1042            issues.len()
1043        );
1044
1045        // Verify issue counts are reasonable
1046        let unwrap_count = issues
1047            .iter()
1048            .filter(|i| i.rule_name == "unwrap-abuse")
1049            .count();
1050        assert!(
1051            unwrap_count >= 1,
1052            "unwrap-abuse should produce at least 1 issue, got {}",
1053            unwrap_count
1054        );
1055
1056        let clone_count = issues
1057            .iter()
1058            .filter(|i| i.rule_name == "unnecessary-clone")
1059            .count();
1060        assert!(
1061            clone_count >= 1,
1062            "unnecessary-clone should produce at least 1 issue, got {}",
1063            clone_count
1064        );
1065
1066        let deep_count = issues
1067            .iter()
1068            .filter(|i| i.rule_name == "deep-nesting")
1069            .count();
1070        assert!(
1071            deep_count >= 1,
1072            "deep-nesting should produce at least 1 issue, got {}",
1073            deep_count
1074        );
1075
1076        let long_count = issues
1077            .iter()
1078            .filter(|i| i.rule_name == "long-function")
1079            .count();
1080        assert!(
1081            long_count >= 1,
1082            "long-function should produce at least 1 issue, got {}",
1083            long_count
1084        );
1085
1086        let god_count = issues
1087            .iter()
1088            .filter(|i| i.rule_name == "god-function")
1089            .count();
1090        assert!(
1091            god_count >= 1,
1092            "god-function should produce at least 1 issue, got {}",
1093            god_count
1094        );
1095
1096        let closure_count = issues
1097            .iter()
1098            .filter(|i| i.rule_name == "complex-closure")
1099            .count();
1100        assert!(
1101            closure_count >= 1,
1102            "complex-closure should produce at least 1 issue, got {}",
1103            closure_count
1104        );
1105
1106        println!(
1107            "All {} tree-sitter rules fired! Total issues: {}",
1108            expected_rules.len(),
1109            issues.len()
1110        );
1111    }
1112}