deslop 0.2.0

A static analyzer that spots low-context and AI-assisted code patterns across naming, concurrency, security, performance, and test quality.
Documentation
use tree_sitter::Node;

use crate::analysis::ImportSpec;

pub(crate) fn collect_imports(root: Node<'_>, source: &str) -> Vec<ImportSpec> {
    let mut imports = Vec::new();
    visit_imports(root, source, &mut imports);
    imports
}

fn visit_imports(node: Node<'_>, source: &str, imports: &mut Vec<ImportSpec>) {
    if node.kind() == "import_spec"
        && let Some(import_spec) = parse_import_spec(node, source)
    {
        imports.push(import_spec);
    }

    let mut cursor = node.walk();
    for child in node.named_children(&mut cursor) {
        visit_imports(child, source, imports);
    }
}

fn parse_import_spec(node: Node<'_>, source: &str) -> Option<ImportSpec> {
    let text = source.get(node.byte_range())?.trim();
    let mut parts = text.split_whitespace().collect::<Vec<_>>();
    let path_literal = parts.pop()?;
    let path = path_literal.trim_matches('"').to_string();
    let alias = parts
        .first()
        .map(|alias| alias.to_string())
        .unwrap_or_else(|| alias_from_path(&path));

    Some(ImportSpec {
        line: node.start_position().row + 1,
        group_line: node
            .parent()
            .map_or(node.start_position().row + 1, |parent| {
                parent.start_position().row + 1
            }),
        alias,
        path,
        namespace_path: None,
        imported_name: None,
        is_public: false,
    })
}

pub(crate) fn alias_from_path(path: &str) -> String {
    let mut segments = path.rsplit('/');
    let last = segments.next().unwrap_or(path);
    if is_go_module_major_version(last) {
        return segments.next().unwrap_or(last).to_string();
    }
    last.to_string()
}

fn is_go_module_major_version(segment: &str) -> bool {
    let mut characters = segment.chars();
    matches!(characters.next(), Some('v'))
        && characters.next().is_some()
        && characters.all(|character| character.is_ascii_digit())
}