sniff-cli 0.1.5

An exhaustive LLM-backed slop finder for codebases
Documentation
use std::collections::HashSet;

pub(super) fn collect_python_refs(line: &str, shadowed: &HashSet<String>) -> Vec<String> {
    let mut refs = Vec::new();
    let bytes = line.as_bytes();
    let mut i = 0;
    while i < bytes.len() {
        if bytes[i].is_ascii_alphabetic() || bytes[i] == b'_' {
            let start = i;
            i += 1;
            while i < bytes.len() && (bytes[i].is_ascii_alphanumeric() || bytes[i] == b'_') {
                i += 1;
            }
            let ident = &line[start..i];
            let mut j = i;
            while j < bytes.len() && bytes[j].is_ascii_whitespace() {
                j += 1;
            }
            if j < bytes.len() && bytes[j] == b'.' {
                let mut k = j + 1;
                while k < bytes.len() && bytes[k].is_ascii_whitespace() {
                    k += 1;
                }
                let member_start = k;
                while k < bytes.len() && (bytes[k].is_ascii_alphanumeric() || bytes[k] == b'_') {
                    k += 1;
                }
                if member_start < k && !shadowed.contains(ident) {
                    refs.push(format!("{}.{}", ident, &line[member_start..k]));
                }
                i = k;
                continue;
            }
            if j < bytes.len() && bytes[j] == b'(' && !shadowed.contains(ident) {
                refs.push(ident.to_string());
                continue;
            }

            // A callable can be used as a value instead of invoked here,
            // most commonly when wiring a dependency-injection seam:
            // `run_git_fn=_run_git`. Keep these references in the graph so
            // wrappers are not misclassified as orphaned or unused.
            let mut previous = start;
            while previous > 0 && bytes[previous - 1].is_ascii_whitespace() {
                previous -= 1;
            }
            let value_position =
                previous > 0 && matches!(bytes[previous - 1], b'=' | b',' | b'(' | b'[' | b'{');
            if value_position && ident.starts_with('_') && !shadowed.contains(ident) {
                refs.push(ident.to_string());
            }
            continue;
        }
        i += 1;
    }
    refs
}

#[cfg(test)]
mod tests {
    use super::collect_python_refs;
    use std::collections::HashSet;

    #[test]
    fn collects_private_callable_passed_as_dependency() {
        let refs =
            collect_python_refs("resolve_target_ref_fn=_resolve_target_ref", &HashSet::new());
        assert_eq!(refs, vec!["_resolve_target_ref"]);
    }

    #[test]
    fn does_not_collect_shadowed_callable_reference() {
        let shadowed = HashSet::from(["_resolve_target_ref".to_string()]);
        let refs = collect_python_refs("resolve_target_ref_fn=_resolve_target_ref", &shadowed);
        assert!(refs.is_empty());
    }
}