rulewright 0.1.1

Executable engineering standards for Rust workspaces
Documentation
use ra_ap_syntax::ast::{self};

use crate::{AstCtx, Example, Violation};

#[rustfmt::skip]
const EXAMPLES: &[Example] = &[
    Example {
        label: "unsafe without safety comment",
        code: "fn f() { unsafe { std::ptr::null::<u8>().read() }; }",
        pass: false,
    },
    Example {
        label: "unsafe with safety comment",
        code: "fn f() {\n    // SAFETY: pointer is valid and aligned\n    unsafe { std::ptr::null::<u8>().read() };\n}",
        pass: true,
    },
    Example {
        label: "unsafe in test module",
        code: "#[cfg(test)]\nmod tests {\n    fn t() { unsafe { std::ptr::null::<u8>().read() }; }\n}",
        pass: true,
    },
    Example {
        label: "safety comment with blank line",
        code: "fn f() {\n    // SAFETY: guaranteed valid\n\n    unsafe { std::ptr::null::<u8>().read() };\n}",
        pass: true,
    },
];

crate::ast_rule!(
    unsafe_comment,
    "Require `// SAFETY:` comment on `unsafe` blocks.",
    "Every unsafe block must document why it is sound. Without a SAFETY comment, reviewers cannot verify correctness.",
    High,
);

fn check_unsafe_comment(ctx: &AstCtx<'_>) -> Vec<Violation> {
    ctx.nodes::<ast::BlockExpr>()
        .filter(|block| block.unsafe_token().is_some() && !ctx.is_in_test(block))
        .filter_map(|block| {
            let unsafe_token = block.unsafe_token()?;
            let line = ctx
                .line_index
                .line_col(unsafe_token.text_range().start())
                .line as usize
                + 1;

            (!crate::infra::helpers::has_preceding_comment(ctx.file.lines, line, &["SAFETY:"]))
                .then(|| {
                    ctx.violation(
                        &block,
                        "unsafe block without // SAFETY: comment on a preceding line",
                    )
                })
        })
        .collect()
}

crate::rulewright_ast_test!(check_unsafe_comment, {
    crate::example_tests!(EXAMPLES, check_unsafe_comment);
});