sniff-cli 0.1.2

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

pub(super) fn strip_quotes(value: &str) -> &str {
    value.trim().trim_matches('"').trim_matches('\'')
}

pub(super) fn parse_named_bindings(binding_text: &str) -> Vec<(String, String)> {
    let inner = binding_text
        .trim()
        .trim_start_matches('{')
        .trim_end_matches('}')
        .trim();

    if inner.is_empty() {
        return Vec::new();
    }

    inner
        .split(',')
        .filter_map(|part| {
            let spec = part.trim();
            if spec.is_empty() {
                return None;
            }

            let (source_name, local_name) =
                if let Some((source_name, alias_name)) = spec.split_once(" as ") {
                    (source_name.trim(), alias_name.trim())
                } else {
                    (spec, spec)
                };

            if source_name.is_empty() || local_name.is_empty() {
                return None;
            }

            Some((source_name.to_string(), local_name.to_string()))
        })
        .collect()
}

pub(super) fn parse_import(line: &str) -> Option<Vec<ImportRecord>> {
    let trimmed = line.trim().trim_end_matches(';');
    if !trimmed.starts_with("import ") || !trimmed.contains(" from ") {
        return None;
    }

    let mut parts = trimmed[7..].splitn(2, " from ");
    let spec = parts.next()?.trim();
    let source = strip_quotes(parts.next()?);
    let mut records = Vec::new();

    let mut remaining = spec;
    if let Some((default_part, named_part)) = spec.split_once(',') {
        let default_part = default_part.trim();
        if !default_part.starts_with('{') && !default_part.starts_with('*') {
            if !default_part.is_empty() {
                records.push(ImportRecord {
                    local_name: default_part.to_string(),
                    source_module: source.to_string(),
                    imported_name: "default".to_string(),
                });
            }
            remaining = named_part.trim();
        }
    }

    if remaining.starts_with("* as ") {
        let local_name = remaining.trim_start_matches("* as ").trim();
        if !local_name.is_empty() {
            records.push(ImportRecord {
                local_name: local_name.to_string(),
                source_module: source.to_string(),
                imported_name: "*".to_string(),
            });
        }
    } else if remaining.starts_with('{') {
        for (imported_name, local_name) in parse_named_bindings(remaining) {
            records.push(ImportRecord {
                local_name,
                source_module: source.to_string(),
                imported_name,
            });
        }
    } else if records.is_empty() {
        let local_name = remaining.trim();
        if !local_name.is_empty() {
            records.push(ImportRecord {
                local_name: local_name.to_string(),
                source_module: source.to_string(),
                imported_name: "default".to_string(),
            });
        }
    }

    if records.is_empty() {
        None
    } else {
        Some(records)
    }
}

pub(super) fn parse_export_from(line: &str) -> Option<Vec<ExportRecord>> {
    let trimmed = line.trim().trim_end_matches(';');
    if !trimmed.starts_with("export ") || !trimmed.contains(" from ") {
        return None;
    }

    let mut parts = trimmed[7..].splitn(2, " from ");
    let spec = parts.next()?.trim();
    let source = strip_quotes(parts.next()?);

    if spec == "*" {
        return Some(vec![ExportRecord {
            exported_name: "*".to_string(),
            local_symbol_name: "*".to_string(),
            source_module: Some(source.to_string()),
            source_symbol_name: Some("*".to_string()),
        }]);
    }

    if let Some(rest) = spec.strip_prefix("* as ") {
        let exported_name = rest.trim();
        if exported_name.is_empty() {
            return None;
        }
        return Some(vec![ExportRecord {
            exported_name: exported_name.to_string(),
            local_symbol_name: exported_name.to_string(),
            source_module: Some(source.to_string()),
            source_symbol_name: Some("*".to_string()),
        }]);
    }

    if !spec.starts_with('{') {
        return None;
    }

    let exports = parse_named_bindings(spec)
        .into_iter()
        .map(|(source_symbol_name, exported_name)| ExportRecord {
            exported_name: exported_name.clone(),
            local_symbol_name: exported_name,
            source_module: Some(source.to_string()),
            source_symbol_name: Some(source_symbol_name),
        })
        .collect();
    Some(exports)
}