Skip to main content

sniff/
roles_surface_facade.rs

1use crate::roles::file_name;
2use crate::types::{FileRecord, MethodRecord};
3
4pub fn is_detector_facade_module(file: &FileRecord) -> bool {
5    let normalized = crate::roles::normalize_path(&file.file_path);
6    let name = file_name(&normalized);
7    let looks_like_detector = name.ends_with("_detector.py")
8        || name.ends_with("_finder.py")
9        || name.ends_with("_pipeline.py");
10    looks_like_detector
11        && file.methods.len() == 1
12        && file
13            .methods
14            .iter()
15            .all(|method| method.name.starts_with("run_") || method.name.starts_with("detect_"))
16}
17
18pub fn is_thin_wrapper_export(method: &MethodRecord) -> bool {
19    let source = method.source.trim();
20    if source.is_empty() || method.loc > 30 {
21        return false;
22    }
23
24    let lowered = source.to_lowercase();
25    let has_import = lowered.contains("from ") || lowered.contains(" import ");
26    let has_return = lowered.contains("return ");
27    let returns_alias = lowered.contains("return _");
28    let has_control_flow = lowered.contains("if ")
29        || lowered.contains("match ")
30        || lowered.contains("for ")
31        || lowered.contains("while ");
32
33    has_import && has_return && returns_alias && !has_control_flow
34}
35
36pub fn is_call_like_delegation_expression(expr: &str) -> bool {
37    let lowered = expr.to_lowercase();
38    if lowered.is_empty()
39        || lowered.contains(" if ")
40        || lowered.contains(" for ")
41        || lowered.contains(" while ")
42        || lowered.contains(" match ")
43        || lowered.contains(" try ")
44        || lowered.contains(" except ")
45        || lowered.contains(" catch ")
46        || lowered.contains(" lambda ")
47        || lowered.contains("buildstring")
48        || lowered.contains("buildlist")
49        || lowered.contains("buildmap")
50        || lowered.contains("buildset")
51        || lowered.contains("apply {")
52        || lowered.contains("also {")
53        || lowered.contains("let {")
54        || lowered.contains("run {")
55        || lowered.contains("with(")
56        || expr.contains('{')
57        || expr.contains('}')
58    {
59        return false;
60    }
61
62    expr.contains('(') || expr.contains("::")
63}
64
65pub fn is_thin_delegation_method(method: &MethodRecord) -> bool {
66    let source = method.source.trim();
67    if source.is_empty() || method.loc > 50 {
68        return false;
69    }
70
71    let meaningful_lines: Vec<&str> = source
72        .lines()
73        .map(str::trim)
74        .filter(|line| {
75            !line.is_empty()
76                && !line.starts_with('#')
77                && !line.starts_with("\"\"\"")
78                && !line.starts_with("'''")
79        })
80        .collect();
81
82    if meaningful_lines.len() == 1 {
83        let line = meaningful_lines[0].trim();
84        if let Some(return_expr) = line.strip_prefix("return ") {
85            return is_call_like_delegation_expression(return_expr.trim().trim_end_matches(';'));
86        }
87
88        if let Some(eq_index) = line.rfind('=') {
89            let Some(fun_index) = line.find("fun ") else {
90                return false;
91            };
92            let Some(close_paren_index) = line.rfind(')') else {
93                return false;
94            };
95            if eq_index < close_paren_index && eq_index > fun_index {
96                let expression = line[eq_index + 1..].trim().trim_end_matches(';');
97                return is_call_like_delegation_expression(expression);
98            }
99        }
100
101        return false;
102    }
103
104    if let Some(return_line) = meaningful_lines
105        .iter()
106        .rev()
107        .find(|line| line.trim_start().starts_with("return "))
108        .copied()
109        && let Some(return_expr) = return_line.strip_prefix("return ")
110        && is_call_like_delegation_expression(return_expr.trim().trim_end_matches(';'))
111    {
112        return true;
113    }
114
115    let lowered = source.to_lowercase();
116    if lowered.contains("fun ")
117        && let Some(eq_index) = source.rfind('=')
118    {
119        let expression = source[eq_index + 1..].replace('\n', " ");
120        if is_call_like_delegation_expression(expression.trim().trim_end_matches(';')) {
121            return true;
122        }
123    }
124
125    false
126}