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
#[path = "findings/mod.rs"]
pub(crate) mod findings;

#[cfg(test)]
pub(crate) use self::findings::{alias_lookup, call_matches_import, import_matches_item};
use self::findings::{
    doc_marker_findings, non_test_call_findings, non_test_macro_findings, rust_call_findings,
    rust_import_findings, unsafe_findings,
};

use crate::analysis::{Language, ParsedFile};
use crate::heuristics::rust::{
    api_design_file_findings, api_design_function_findings, async_file_findings,
    async_function_findings, boundary_file_findings, boundary_function_findings, domain_findings,
    module_surface_file_findings, performance_file_findings, performance_function_findings,
    runtime_file_findings, runtime_function_findings, runtime_ownership_function_findings,
    security_footguns_file_findings, security_footguns_function_findings,
    unsafe_soundness_findings,
};
use crate::heuristics::engine::{extend_file_rules, extend_function_rules};
use crate::index::RepositoryIndex;
use crate::model::Finding;

pub(crate) const BINDING_LOCATION: &str = file!();

const NON_TEST_MACRO_RULES: [(&str, &str, &str); 5] = [
    (
        "todo!",
        "todo_macro_leftover",
        "leaves todo! in non-test Rust code",
    ),
    (
        "unimplemented!",
        "unimplemented_macro_leftover",
        "leaves unimplemented! in non-test Rust code",
    ),
    (
        "dbg!",
        "dbg_macro_leftover",
        "leaves dbg! in non-test Rust code",
    ),
    (
        "panic!",
        "panic_macro_leftover",
        "leaves panic! in non-test Rust code",
    ),
    (
        "unreachable!",
        "unreachable_macro_leftover",
        "leaves unreachable! in non-test Rust code",
    ),
];

const NON_TEST_CALL_RULES: [(&str, &str, &str); 2] = [
    (
        "unwrap",
        "unwrap_in_non_test_code",
        "calls unwrap() in non-test Rust code",
    ),
    (
        "expect",
        "expect_in_non_test_code",
        "calls expect() in non-test Rust code",
    ),
];

type RustFileRule = fn(&ParsedFile) -> Vec<Finding>;
type RustFunctionRule = fn(&ParsedFile, &crate::analysis::ParsedFunction) -> Vec<Finding>;

const FILE_RULES: [RustFileRule; 4] = [
    domain_findings,
    api_design_file_findings,
    performance_file_findings,
    async_file_findings,
];

const FUNCTION_RULES: [RustFunctionRule; 6] = [
    unsafe_soundness_findings,
    api_design_function_findings,
    doc_marker_findings,
    performance_function_findings,
    async_function_findings,
    runtime_function_findings,
];

pub(super) fn evaluate_rust_findings(file: &ParsedFile, index: &RepositoryIndex) -> Vec<Finding> {
    let mut findings = Vec::new();

    extend_file_rules(&mut findings, file, &FILE_RULES);
    findings.extend(runtime_file_findings(file, index));
    findings.extend(boundary_file_findings(file));
    findings.extend(module_surface_file_findings(file));
    findings.extend(security_footguns_file_findings(file, index));

    for function in &file.functions {
        for (macro_name, rule_id, message_suffix) in NON_TEST_MACRO_RULES {
            findings.extend(non_test_macro_findings(
                file,
                function,
                macro_name,
                rule_id,
                message_suffix,
            ));
        }

        for (call_name, rule_id, message_suffix) in NON_TEST_CALL_RULES {
            findings.extend(non_test_call_findings(
                file,
                function,
                call_name,
                rule_id,
                message_suffix,
            ));
        }

        findings.extend(unsafe_findings(file, function));

        extend_function_rules(&mut findings, file, function, &FUNCTION_RULES);
        findings.extend(boundary_function_findings(file, function));
        findings.extend(runtime_ownership_function_findings(file, function));
        findings.extend(security_footguns_function_findings(file, function));
        let Some(package_name) = &file.package_name else {
            continue;
        };
        let Some(current_package) =
            index.package_for_file(Language::Rust, &file.path, package_name)
        else {
            continue;
        };
        findings.extend(rust_import_findings(
            file,
            function,
            index,
            &file.imports,
            current_package,
        ));
        findings.extend(rust_call_findings(file, function, index, &file.imports));
    }

    findings
}