sniff-cli 0.1.1

An exhaustive LLM-backed slop finder for codebases
Documentation
use super::*;

#[test]
fn test_python_namespace_import_resolution() {
    let dir = unique_tag("temp_python_namespace");
    let pkg_dir = format!("{}/pkg", dir);
    fs::create_dir_all(&pkg_dir).unwrap();

    let helpers_file = write_temp_file(
        &pkg_dir,
        "helpers.py",
        r#"
def process_data():
    pass
"#,
    );
    let consumer_file = write_temp_file(
        &dir,
        "consumer.py",
        r#"
import pkg.helpers as helpers

def main():
    helpers.process_data()
"#,
    );

    let mut graph = SymbolGraph::new(&dir);
    graph.add_file(parse_file_symbols(&helpers_file));
    graph.add_file(parse_file_symbols(&consumer_file));
    graph.resolve_all();

    fs::remove_dir_all(&dir).ok();

    let consumer_symbols = graph.files.get(&consumer_file).unwrap();
    let helper_refs: Vec<_> = consumer_symbols
        .references
        .iter()
        .filter(|r| r.name == "helpers.process_data")
        .collect();
    assert_eq!(helper_refs.len(), 1);

    let resolved = helper_refs[0].resolved_symbol.as_ref().unwrap();
    match resolved {
        ResolvedSymbol::External {
            file_path,
            symbol_name,
        } => {
            assert_eq!(normalize_path(file_path), normalize_path(&helpers_file));
            assert_eq!(symbol_name, "process_data");
        }
        _ => panic!("Expected Python namespace import resolution"),
    }
}

#[test]
fn test_python_src_layout_absolute_import_resolution() {
    let dir = unique_tag("temp_python_src_layout");
    let src_dir = format!("{}/src", dir);
    let pkg_dir = format!("{}/bumpkin/analysis", src_dir);
    fs::create_dir_all(&pkg_dir).unwrap();

    let helpers_file = write_temp_file(
        &pkg_dir,
        "finding_python_signatures.py",
        r#"
def extract_python_signatures():
    pass
"#,
    );
    let consumer_file = write_temp_file(
        &src_dir,
        "consumer.py",
        r#"
import bumpkin.analysis.finding_python_signatures as sigs

def main():
    sigs.extract_python_signatures()
"#,
    );

    let mut graph = SymbolGraph::new(&dir);
    graph.add_file(parse_file_symbols(&helpers_file));
    graph.add_file(parse_file_symbols(&consumer_file));
    graph.resolve_all();

    fs::remove_dir_all(&dir).ok();

    let consumer_symbols = graph.files.get(&consumer_file).unwrap();
    let refs: Vec<_> = consumer_symbols
        .references
        .iter()
        .filter(|r| r.name == "sigs.extract_python_signatures")
        .collect();
    assert_eq!(refs.len(), 1);

    let resolved = refs[0].resolved_symbol.as_ref().unwrap();
    match resolved {
        ResolvedSymbol::External {
            file_path,
            symbol_name,
        } => {
            assert_eq!(normalize_path(file_path), normalize_path(&helpers_file));
            assert_eq!(symbol_name, "extract_python_signatures");
        }
        _ => panic!("Expected Python src-layout absolute import resolution"),
    }
}

#[test]
fn test_python_src_subdir_absolute_import_resolution() {
    let dir = unique_tag("temp_python_src_subdir");
    let analysis_dir = format!("{}/src/bumpkin/analysis", dir);
    fs::create_dir_all(&analysis_dir).unwrap();

    let helpers_file = write_temp_file(
        &analysis_dir,
        "finding_python_signatures.py",
        r#"
def extract_python_signatures():
    pass
"#,
    );
    let consumer_file = write_temp_file(
        &analysis_dir,
        "finding_python_detection_context.py",
        r#"
from bumpkin.analysis import (
    finding_python_signatures,
    finding_python_surface,
)

_extract_python_signatures = finding_python_signatures.extract_python_signatures
"#,
    );

    let mut graph = SymbolGraph::new(&analysis_dir);
    graph.add_file(parse_file_symbols(&helpers_file));
    graph.add_file(parse_file_symbols(&consumer_file));
    graph.resolve_all();

    fs::remove_dir_all(&dir).ok();

    let consumer_symbols = graph.files.get(&consumer_file).unwrap();
    let refs: Vec<_> = consumer_symbols
        .references
        .iter()
        .filter(|r| r.name == "finding_python_signatures.extract_python_signatures")
        .collect();
    assert_eq!(refs.len(), 1);

    let resolved = refs[0].resolved_symbol.as_ref().unwrap();
    match resolved {
        ResolvedSymbol::External {
            file_path,
            symbol_name,
        } => {
            assert_eq!(normalize_path(file_path), normalize_path(&helpers_file));
            assert_eq!(symbol_name, "extract_python_signatures");
        }
        _ => panic!("Expected Python src-subdir absolute import resolution"),
    }
}

#[test]
fn test_python_top_level_alias_reference_resolution() {
    let dir = unique_tag("temp_python_top_level_alias");
    let src_dir = format!("{}/src", dir);
    let pkg_dir = format!("{}/bumpkin/analysis", src_dir);
    fs::create_dir_all(&pkg_dir).unwrap();

    let helpers_file = write_temp_file(
        &pkg_dir,
        "finding_python_signatures.py",
        r#"
def extract_python_signatures():
    pass
"#,
    );
    let consumer_file = write_temp_file(
        &pkg_dir,
        "finding_python_detection_context.py",
        r#"
import bumpkin.analysis.finding_python_signatures as finding_python_signatures

_extract_python_signatures = finding_python_signatures.extract_python_signatures
"#,
    );

    let mut graph = SymbolGraph::new(&dir);
    graph.add_file(parse_file_symbols(&helpers_file));
    graph.add_file(parse_file_symbols(&consumer_file));
    graph.resolve_all();

    fs::remove_dir_all(&dir).ok();

    let consumer_symbols = graph.files.get(&consumer_file).unwrap();
    let refs: Vec<_> = consumer_symbols
        .references
        .iter()
        .filter(|r| r.name == "finding_python_signatures.extract_python_signatures")
        .collect();
    assert_eq!(refs.len(), 1);

    let resolved = refs[0].resolved_symbol.as_ref().unwrap();
    match resolved {
        ResolvedSymbol::External {
            file_path,
            symbol_name,
        } => {
            assert_eq!(normalize_path(file_path), normalize_path(&helpers_file));
            assert_eq!(symbol_name, "extract_python_signatures");
        }
        _ => panic!("Expected Python top-level alias resolution"),
    }
}

#[test]
fn test_python_multiline_from_import_top_level_alias_resolution() {
    let dir = unique_tag("temp_python_multiline_alias");
    let src_dir = format!("{}/src", dir);
    let pkg_dir = format!("{}/bumpkin/analysis", src_dir);
    fs::create_dir_all(&pkg_dir).unwrap();

    let helpers_file = write_temp_file(
        &pkg_dir,
        "finding_python_signatures.py",
        r#"
def extract_python_signatures():
    pass
"#,
    );
    let consumer_file = write_temp_file(
        &pkg_dir,
        "finding_python_detection_context.py",
        r#"
from bumpkin.analysis import (
    finding_python_signatures,
    finding_python_surface,
)

_extract_python_signatures = finding_python_signatures.extract_python_signatures
"#,
    );

    let mut graph = SymbolGraph::new(&dir);
    graph.add_file(parse_file_symbols(&helpers_file));
    graph.add_file(parse_file_symbols(&consumer_file));
    graph.resolve_all();

    fs::remove_dir_all(&dir).ok();

    let consumer_symbols = graph.files.get(&consumer_file).unwrap();
    let refs: Vec<_> = consumer_symbols
        .references
        .iter()
        .filter(|r| r.name == "finding_python_signatures.extract_python_signatures")
        .collect();
    assert_eq!(refs.len(), 1);

    let resolved = refs[0].resolved_symbol.as_ref().unwrap();
    match resolved {
        ResolvedSymbol::External {
            file_path,
            symbol_name,
        } => {
            assert_eq!(normalize_path(file_path), normalize_path(&helpers_file));
            assert_eq!(symbol_name, "extract_python_signatures");
        }
        _ => panic!("Expected Python multiline import alias resolution"),
    }
}