1use regex::Regex;
8
9pub struct Rule {
10 pub id: &'static str,
11 #[allow(dead_code)]
13 pub description: &'static str,
14 pub extensions: &'static [&'static str],
15 pub pattern: &'static str,
17}
18
19pub const RULES: &[Rule] = &[
20 Rule {
21 id: "typescript-no-any",
22 description: "Detect explicit `any` usage in TypeScript.",
23 extensions: &["ts", "tsx"],
24 pattern: r"(?P<a>:\s*any\b)|(?P<b><any>)|(?P<c>\bany\[\])|(?P<d>Array<any>)",
26 },
27 Rule {
28 id: "python-missing-typing",
29 description: "Detect Python `def` declarations without type annotations.",
30 extensions: &["py"],
31 pattern: r"^\s*def\s+\w+\(([^):]*)\)\s*:\s*$",
34 },
35 Rule {
36 id: "django-fbv",
37 description: "Detect Django function-based views taking `request` as first arg.",
38 extensions: &["py"],
39 pattern: r"^\s*def\s+\w+\(\s*request\s*[,)]",
40 },
41];
42
43pub fn find_rule(id: &str) -> Option<&'static Rule> {
44 RULES.iter().find(|r| r.id == id)
45}
46
47pub fn compile(rule: &Rule) -> Regex {
48 Regex::new(&format!("(?m){}", rule.pattern)).expect("built-in rule regex must compile")
50}
51
52#[cfg(test)]
53mod tests {
54 use super::*;
55
56 #[test]
57 fn typescript_no_any_matches_colon_any() {
58 let re = compile(find_rule("typescript-no-any").unwrap());
59 assert!(re.is_match("const x: any = 1;"));
60 assert!(re.is_match("function f<T>(x: any) {}"));
61 assert!(re.is_match("let y: any[] = [];"));
62 assert!(re.is_match("let z: Array<any> = [];"));
63 }
64
65 #[test]
66 fn typescript_no_any_skips_clean_code() {
67 let re = compile(find_rule("typescript-no-any").unwrap());
68 assert!(!re.is_match("const x: number = 1;"));
69 assert!(!re.is_match("const company = 'jimolab';")); }
71
72 #[test]
73 fn python_missing_typing_matches_untyped_def() {
74 let re = compile(find_rule("python-missing-typing").unwrap());
75 assert!(re.is_match("def foo(x, y):"));
76 assert!(re.is_match(" def bar():"));
77 }
78
79 #[test]
80 fn python_missing_typing_skips_annotated_def() {
81 let re = compile(find_rule("python-missing-typing").unwrap());
82 assert!(!re.is_match("def foo(x: int) -> int:"));
83 assert!(!re.is_match("def bar() -> None:"));
84 }
85
86 #[test]
87 fn django_fbv_matches_request_arg() {
88 let re = compile(find_rule("django-fbv").unwrap());
89 assert!(re.is_match("def home(request):"));
90 assert!(re.is_match("def detail(request, pk):"));
91 }
92
93 #[test]
94 fn django_fbv_skips_cbv_methods() {
95 let re = compile(find_rule("django-fbv").unwrap());
96 assert!(!re.is_match("def get(self, request, *args, **kwargs):"));
97 }
98
99 #[test]
100 fn find_rule_returns_none_for_unknown_id() {
101 assert!(find_rule("not-a-rule").is_none());
102 }
103}