wowlab_tidy/languages/rust/rules/tests/
consecutive_field_asserts.rs1use 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});