sniff-cli 0.1.0

An exhaustive LLM-backed slop finder for codebases
Documentation
use crate::roles::{contains_any, file_name, normalize_path};
use crate::types::FileRecord;

fn is_route_surface_path(normalized: &str, name: &str) -> bool {
    if !normalized.contains("/app/") {
        return false;
    }

    matches!(
        name,
        "page.tsx"
            | "layout.tsx"
            | "template.tsx"
            | "loading.tsx"
            | "error.tsx"
            | "not-found.tsx"
            | "default.tsx"
            | "route.tsx"
            | "page.jsx"
            | "layout.jsx"
            | "template.jsx"
            | "loading.jsx"
            | "error.jsx"
            | "not-found.jsx"
            | "default.jsx"
            | "route.jsx"
    ) || (normalized.contains("/pages/")
        && matches!(name, "index.tsx" | "index.jsx" | "page.tsx" | "page.jsx"))
}

fn is_component_surface_path(normalized: &str) -> bool {
    contains_any(
        normalized,
        &[
            "/components/",
            "/ui/",
            "/views/",
            "/screens/",
            "/sections/",
            "/modals/",
            "/templates/",
            "/layouts/",
        ],
    )
}

fn is_root_app_shell_path(normalized: &str, name: &str) -> bool {
    matches!(name, "app.tsx" | "app.jsx")
        && (normalized.starts_with("src/") || normalized.contains("/src/"))
}

fn source_looks_like_jsx_surface(source: &str) -> bool {
    let lowered = source.to_lowercase();
    (lowered.contains("return (") || lowered.contains("return <") || lowered.contains("return\n<"))
        && source.contains('<')
}

fn is_small_kotlin_compose_surface(file: &FileRecord, normalized: &str) -> bool {
    if !(normalized.ends_with(".kt") || normalized.ends_with(".kts")) {
        return false;
    }
    if !normalized.contains("/screens/")
        && !normalized.contains("/components/")
        && !normalized.contains("/ui-compose/")
    {
        return false;
    }
    if !file.source.contains("@Composable") {
        return false;
    }
    if file.methods.len() == 1 {
        let max_method_loc = file
            .methods
            .iter()
            .map(|method| method.loc)
            .max()
            .unwrap_or(0);
        return max_method_loc <= 60;
    }

    if file.methods.len() < 4 || file.methods.len() > 20 {
        return false;
    }

    let max_method_loc = file
        .methods
        .iter()
        .map(|method| method.loc)
        .max()
        .unwrap_or(0);
    max_method_loc <= 120
}

pub fn is_presentation_surface_module(file: &FileRecord) -> bool {
    let normalized = normalize_path(&file.file_path);
    let name = file_name(&normalized);

    if is_small_kotlin_compose_surface(file, &normalized) {
        return true;
    }

    if !(normalized.ends_with(".tsx") || normalized.ends_with(".jsx")) {
        return false;
    }

    if is_route_surface_path(&normalized, name) {
        return true;
    }

    if is_root_app_shell_path(&normalized, name)
        && file.methods.len() <= 4
        && source_looks_like_jsx_surface(&file.source)
    {
        return true;
    }

    if file.methods.len() > 4 {
        return false;
    }

    is_component_surface_path(&normalized) && source_looks_like_jsx_surface(&file.source)
}