rulewright 0.1.1

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

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

#[rustfmt::skip]
const EXAMPLES: &[Example] = &[
    Example {
        label: "literal on left eq",
        code: "fn f(x: i32) { if 0 == x {} }",
        pass: false,
    },
    Example {
        label: "literal on right",
        code: "fn f(x: i32) { if x == 0 {} }",
        pass: true,
    },
    Example {
        label: "literal on left ne",
        code: "fn f(y: i32) { if 1 != y {} }",
        pass: false,
    },
    Example {
        label: "both literals",
        code: r#"fn f() { if "a" == "b" {} }"#,
        pass: true,
    },
    Example {
        label: "non-comparison",
        code: "fn f(x: i32) { let _ = 1 + x; }",
        pass: true,
    },
    Example {
        label: "yoda in test module",
        code: "#[cfg(test)]\nmod tests {\n  fn t(x: i32) { if 0 == x {} }\n}",
        pass: true,
    },
];

crate::ast_rule!(
    yoda_conditions,
    "Flag reversed comparisons like `0 == x` — prefer `x == 0`.",
    "In Rust there is no accidental assignment in conditions, so the C-style 0 == x guard is unnecessary and harder to read.",
);

fn check_yoda_conditions(ctx: &AstCtx<'_>) -> Vec<Violation> {
    ctx.nodes::<ast::BinExpr>()
        .filter(|expr| {
            !ctx.is_in_test(expr)
                && matches!(expr.op_kind(), Some(BinaryOp::CmpOp(CmpOp::Eq { .. })))
        })
        .filter_map(|expr| {
            let (left, right) = expr.sub_exprs();
            let (left, right) = (left?, right?);

            (matches!(left, ast::Expr::Literal(_)) && !matches!(right, ast::Expr::Literal(_)))
                .then(|| ctx.violation(&left, "Yoda condition — put the literal on the right side"))
        })
        .collect()
}

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