pedant-core 0.13.0

Analysis engine for pedant: IR extraction, style checks, and capability detection
Documentation
use std::sync::Arc;

use crate::check_config::{CheckConfig, PatternCheck};
use crate::ir::{FileIr, IrSpan};
use crate::pattern::matches_pattern;
use crate::violation::{Violation, ViolationType};

use super::common::emit_violation;

pub(super) fn check_all_forbidden(
    ir: &FileIr,
    config: &CheckConfig,
    fp: &Arc<str>,
    violations: &mut Vec<Violation>,
) {
    check_forbidden_patterns(
        &config.forbid_attributes,
        ir.attributes.iter().map(|a| (a.span, &*a.text)),
        |p| ViolationType::ForbiddenAttribute { pattern: p },
        fp,
        violations,
    );

    check_forbidden_patterns(
        &config.forbid_types,
        ir.type_refs.iter().map(|t| (t.span, &*t.text)),
        |p| ViolationType::ForbiddenType { pattern: p },
        fp,
        violations,
    );

    check_forbidden_patterns(
        &config.forbid_calls,
        ir.method_calls.iter().map(|m| (m.span, &*m.text)),
        |p| ViolationType::ForbiddenCall { pattern: p },
        fp,
        violations,
    );

    check_forbidden_patterns(
        &config.forbid_macros,
        ir.macro_invocations.iter().map(|m| (m.span, &*m.text)),
        |p| ViolationType::ForbiddenMacro { pattern: p },
        fp,
        violations,
    );

    check_let_underscore_result(ir, config, fp, violations);
    check_unsafe(ir, config, fp, violations);
    check_inline_tests(ir, config, fp, violations);
}

fn match_forbidden(check: &PatternCheck, text: &str) -> Option<Arc<str>> {
    check
        .patterns
        .iter()
        .find(|p| matches_pattern(text, p))
        .map(Arc::clone)
}

fn check_forbidden_patterns<'a>(
    check: &PatternCheck,
    items: impl Iterator<Item = (IrSpan, &'a str)>,
    make_violation: impl Fn(Arc<str>) -> ViolationType,
    fp: &Arc<str>,
    violations: &mut Vec<Violation>,
) {
    if !check.enabled {
        return;
    }
    for (span, text) in items {
        let Some(pattern) = match_forbidden(check, text) else {
            continue;
        };
        emit_violation(violations, fp, span, make_violation(pattern), text);
    }
}

fn check_let_underscore_result(
    ir: &FileIr,
    config: &CheckConfig,
    fp: &Arc<str>,
    violations: &mut Vec<Violation>,
) {
    if !config.check_let_underscore_result {
        return;
    }
    for binding in &ir.bindings {
        if !binding.is_wildcard || !binding.has_init || binding.init_is_write_macro {
            continue;
        }
        let Some(span) = binding.span else { continue };
        emit_violation(
            violations,
            fp,
            span,
            ViolationType::LetUnderscoreResult,
            "let _ = discards a Result; handle the error or use an assertion",
        );
    }
}

fn check_unsafe(ir: &FileIr, config: &CheckConfig, fp: &Arc<str>, violations: &mut Vec<Violation>) {
    if !config.forbid_unsafe {
        return;
    }
    for site in &ir.unsafe_sites {
        if site.kind != crate::ir::UnsafeKind::Block {
            continue;
        }
        emit_violation(
            violations,
            fp,
            site.span,
            ViolationType::ForbiddenUnsafe,
            "unsafe block detected",
        );
    }
}

fn check_inline_tests(
    ir: &FileIr,
    config: &CheckConfig,
    fp: &Arc<str>,
    violations: &mut Vec<Violation>,
) {
    if !config.check_inline_tests {
        return;
    }
    for module in &ir.modules {
        if !module.is_cfg_test {
            continue;
        }
        emit_violation(
            violations,
            fp,
            module.span,
            ViolationType::InlineTests,
            format!(
                "test module `{}` should be in tests/ directory",
                module.name
            ),
        );
    }
}