Skip to main content

wowlab_tidy/languages/rust/rules/tests/
consecutive_field_asserts.rs

1use ra_ap_syntax::{AstNode, ast};
2
3use super::{first_macro_argument, macro_name, statement_macro};
4use crate::{AstCtx, Example, Violation};
5
6#[rustfmt::skip]
7const EXAMPLES: &[Example] = &[
8    Example { label: "three field assertions", code: "fn test() { verify_that!(view.a, eq(1)); verify_that!(view.b, eq(2)); verify_that!(view.c, eq(3)); }", pass: false },
9    Example { label: "two field assertions", code: "fn test() { verify_that!(view.a, eq(1)); verify_that!(view.b, eq(2)); }", pass: true },
10    Example { label: "different receivers", code: "fn test() { verify_that!(one.a, eq(1)); verify_that!(two.b, eq(2)); verify_that!(three.c, eq(3)); }", pass: true },
11];
12
13crate::ast_rule!(
14    consecutive_field_asserts,
15    "Flag consecutive assertions on fields of the same receiver.",
16    "Same-receiver assertion runs are clearer as one `matches_pattern!` shape.",
17    Low,
18    params { threshold: i64 = 3 },
19);
20
21fn check_consecutive_field_asserts(ctx: &AstCtx<'_>) -> Vec<Violation> {
22    let threshold = ctx
23        .file
24        .config
25        .get_usize("rust_consecutive_field_asserts", &PARAMS[0]);
26    let mut violations = Vec::new();
27
28    for list in ctx.nodes::<ast::StmtList>() {
29        let mut previous = String::new();
30        let mut count = 0;
31
32        for statement in list.statements() {
33            let receiver = assertion_field_receiver(&statement).unwrap_or_default();
34
35            if !receiver.is_empty() && receiver == previous {
36                count += 1;
37            } else {
38                count = usize::from(!receiver.is_empty());
39                previous = receiver;
40            }
41
42            if count == threshold {
43                violations.push(ctx.violation(
44                    &statement,
45                    "consecutive field assertions should use `matches_pattern!` or `verify_all!`",
46                ));
47            }
48        }
49    }
50
51    violations
52}
53
54fn assertion_field_receiver(statement: &ast::Stmt) -> Option<String> {
55    let call = statement_macro(statement)?;
56
57    if !matches!(
58        macro_name(&call)?.as_str(),
59        "verify_that" | "expect_that" | "assert_eq"
60    ) {
61        return None;
62    }
63
64    let mut expression = first_macro_argument(&call)?;
65
66    loop {
67        let ast::Expr::FieldExpr(field) = expression else {
68            return None;
69        };
70        let receiver = field.expr()?;
71
72        if matches!(receiver, ast::Expr::FieldExpr(_)) {
73            expression = receiver;
74        } else {
75            return Some(receiver.syntax().text().to_string());
76        }
77    }
78}
79
80crate::tidy_ast_test!(check_consecutive_field_asserts, {
81    crate::example_tests!(EXAMPLES, check_consecutive_field_asserts);
82});