rulewright 0.1.1

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

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

#[rustfmt::skip]
const EXAMPLES: &[Example] = &[
    Example {
        label: "variable index",
        code: "fn f(v: Vec<i32>, i: usize) { let _ = v[i]; }",
        pass: false,
    },
    Example {
        label: "literal index",
        code: "fn f(v: Vec<i32>) { let _ = v[0]; }",
        pass: true,
    },
    Example {
        label: "indexing in test module",
        code: "#[cfg(test)]\nmod tests {\n    fn f(v: Vec<i32>, i: usize) { let _ = v[i]; }\n}",
        pass: true,
    },
    Example {
        label: "with BOUNDS comment",
        code: "fn f(v: Vec<i32>, i: usize) {\n    assert!(i < v.len());\n    // BOUNDS: the assertion above establishes i < v.len().\n    let _ = v[i];\n}",
        pass: true,
    },
];

crate::ast_rule!(
    unchecked_indexing,
    "Flag `container[expr]` indexing with non-literal indices.",
    "Indexing with a variable panics on out-of-bounds. Prefer `.get()` when failure is possible. When an established invariant makes indexing correct, place a `// BOUNDS:` comment directly above it that names the concrete check or relationship; boilerplate comments do not make the code safer.",
    Low,
    default = false,
);

fn check_unchecked_indexing(ctx: &AstCtx<'_>) -> Vec<Violation> {
    ctx.nodes::<ast::IndexExpr>()
        .filter(|index| !ctx.is_in_test(index))
        .filter_map(|index| {
            let expr = index.index()?;

            if matches!(expr, ast::Expr::Literal(ref literal) if matches!(literal.kind(), LiteralKind::IntNumber(_))) {
                return None;
            }

            let line = ctx.line_of(&expr);

            (!crate::infra::helpers::has_preceding_comment(
                ctx.file.lines,
                line,
                &["BOUNDS:"],
            ))
            .then(|| {
                ctx.violation(
                    &expr,
                    "unchecked indexing — prefer `.get()`, or document the concrete invariant directly above with `// BOUNDS:`",
                )
            })
        })
        .collect()
}

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