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::RustStaticSummary;

pub(super) fn collect_static_summaries(root: Node<'_>, source: &str) -> Vec<RustStaticSummary> {
    let mut statics = Vec::new();
    visit_for_static_summaries(root, source, &mut statics);
    statics.sort_by(|left, right| left.line.cmp(&right.line).then(left.name.cmp(&right.name)));
    statics
}

fn visit_for_static_summaries(node: Node<'_>, source: &str, statics: &mut Vec<RustStaticSummary>) {
    if node.kind() == "static_item"
        && let Some(summary) = build_static_summary(node, source)
    {
        statics.push(summary);
    }

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

fn build_static_summary(node: Node<'_>, source: &str) -> Option<RustStaticSummary> {
    let name = node
        .child_by_field_name("name")
        .and_then(|name_node| source.get(name_node.byte_range()))?
        .trim()
        .to_string();
    let type_text = node
        .child_by_field_name("type")
        .and_then(|type_node| source.get(type_node.byte_range()))?
        .trim()
        .to_string();
    let value_text = node
        .child_by_field_name("value")
        .and_then(|value_node| source.get(value_node.byte_range()))
        .map(str::trim)
        .filter(|value| !value.is_empty())
        .map(str::to_string);

    Some(RustStaticSummary {
        line: node.start_position().row + 1,
        name,
        type_text,
        value_text,
        visibility_pub: source.get(node.byte_range()).is_some_and(|text| {
            text.trim_start().starts_with("pub ") || text.trim_start().starts_with("pub(")
        }),
        is_mut: source
            .get(node.byte_range())
            .is_some_and(|text| text.contains("static mut ")),
    })
}