sniff-cli 0.1.2

An exhaustive LLM-backed slop finder for codebases
Documentation
use super::js_ts_functions::*;
use super::js_ts_imports::*;
use super::js_ts_references::*;
use super::*;
use crate::types::Reference;

fn extract_balanced_block(lines: &[&str], start: usize) -> usize {
    super::shared::scan_block_end(lines, start)
}

#[allow(clippy::too_many_arguments)]
fn build_method_record(
    file_path: &str,
    source: &str,
    name: &str,
    start_line: usize,
    end_line: usize,
    is_exported: bool,
    param_count: usize,
    refs: Vec<String>,
    line_index: &LineIndex,
) -> MethodRecord {
    let start_off = line_index.line_starts[start_line - 1];
    let end_off = if end_line < line_index.line_starts.len() {
        line_index.line_starts[end_line]
    } else {
        source.len()
    };
    let method_source = source[start_off..end_off].to_string();
    MethodRecord {
        name: name.to_string(),
        file_path: file_path.to_string(),
        source: method_source,
        loc: end_line.saturating_sub(start_line) + 1,
        param_count,
        start_line,
        end_line,
        is_exported,
        language: "javascript".to_string(),
        nesting_depth: 0,
        references: refs
            .into_iter()
            .map(|name| Reference {
                file_path: file_path.to_string(),
                line: start_line,
                snippet: name,
            })
            .collect(),
        real_ref_count: 0,
    }
}

fn push_method(methods: &mut Vec<MethodRecord>, method: MethodRecord) {
    methods.push(method);
}

#[allow(dead_code)]
pub(super) struct OxcExtractor<'a> {
    pub source: &'a str,
    pub line_index: LineIndex,
    pub file_path: String,
    pub methods: Vec<MethodRecord>,
    pub definitions: Vec<SymbolDefinition>,
    pub imports: Vec<ImportRecord>,
    pub exports: Vec<ExportRecord>,
    pub references: Vec<SymbolReference>,
    pub scopes: Vec<HashSet<String>>,
    pub next_id: usize,
    pub current_name_hint: Option<String>,
    pub in_class: bool,
    pub in_function_body: bool,
    pub is_exported_context: bool,
}

impl<'a> OxcExtractor<'a> {
    pub(super) fn visit_program<T>(&mut self, _program: &T) {
        let lines: Vec<&str> = self.source.lines().collect();
        let mut i = 0usize;
        while i < lines.len() {
            let line = lines[i];
            let trimmed = line.trim();
            if trimmed.is_empty() || trimmed.starts_with("//") {
                i += 1;
                continue;
            }

            if let Some(imports) = parse_import(trimmed) {
                self.imports.extend(imports);
                i += 1;
                continue;
            }

            if let Some(exports) = parse_export_from(trimmed) {
                self.exports.extend(exports);
                i += 1;
                continue;
            }

            if let Some((name, is_exported, is_default_export)) = parse_export_function(trimmed) {
                let params = trimmed
                    .split_once('(')
                    .and_then(|(_, rest)| rest.split_once(')').map(|(inside, _)| inside))
                    .unwrap_or("");
                let start_line = i + 1;
                let end_idx = extract_balanced_block(&lines, i);
                let end_line = end_idx + 1;
                let _body = lines[i..=end_idx].join("\n");
                self.definitions.push(SymbolDefinition {
                    id: self.next_id,
                    name: name.clone(),
                    kind: SymbolKind::Function,
                    start_line,
                    end_line,
                    is_exported,
                    owner_type: None,
                });
                self.next_id += 1;
                if is_exported {
                    self.exports.push(ExportRecord {
                        exported_name: if is_default_export {
                            "default".to_string()
                        } else {
                            name.clone()
                        },
                        local_symbol_name: name.clone(),
                        source_module: None,
                        source_symbol_name: None,
                    });
                }
                let mut refs = Vec::new();
                for (offset, body_line) in lines[i..=end_idx].iter().enumerate() {
                    let line_no = i + offset + 1;
                    for r in collect_js_references(body_line) {
                        if r != name {
                            self.references.push(SymbolReference {
                                name: r.clone(),
                                line: line_no,
                                snippet: body_line.trim().to_string(),
                                resolved_symbol: None,
                            });
                            refs.push(r);
                        }
                    }
                }
                push_method(
                    &mut self.methods,
                    build_method_record(
                        &self.file_path,
                        self.source,
                        &name,
                        start_line,
                        end_line,
                        is_exported,
                        count_params(params),
                        refs,
                        &self.line_index,
                    ),
                );
                i = end_idx + 1;
                continue;
            }

            if let Some((name, param_count)) = parse_arrow_function(trimmed) {
                let start_line = i + 1;
                let end_idx = extract_balanced_block(&lines, i);
                let end_line = end_idx + 1;
                let _body = lines[i..=end_idx].join("\n");
                self.definitions.push(SymbolDefinition {
                    id: self.next_id,
                    name: name.clone(),
                    kind: SymbolKind::Function,
                    start_line,
                    end_line,
                    is_exported: false,
                    owner_type: None,
                });
                self.next_id += 1;
                let mut refs = Vec::new();
                for (offset, body_line) in lines[i..=end_idx].iter().enumerate() {
                    let line_no = i + offset + 1;
                    for r in collect_js_references(body_line) {
                        self.references.push(SymbolReference {
                            name: r.clone(),
                            line: line_no,
                            snippet: body_line.trim().to_string(),
                            resolved_symbol: None,
                        });
                        refs.push(r);
                    }
                }
                push_method(
                    &mut self.methods,
                    build_method_record(
                        &self.file_path,
                        self.source,
                        &name,
                        start_line,
                        end_line,
                        false,
                        param_count,
                        refs,
                        &self.line_index,
                    ),
                );
                i = end_idx + 1;
                continue;
            }

            for r in collect_js_references(trimmed) {
                self.references.push(SymbolReference {
                    name: r,
                    line: i + 1,
                    snippet: trimmed.to_string(),
                    resolved_symbol: None,
                });
            }
            i += 1;
        }
    }
}