Skip to main content

wowlab_tidy/languages/rust/rules/tests/
indexed_element_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: "two indexed assertions", code: "fn test() { verify_that!(items[0].name, eq(\"a\")); verify_that!(items[1].name, eq(\"b\")); }", pass: false },
9    Example { label: "one indexed assertion", code: "fn test() { verify_that!(items[0].name, eq(\"a\")); }", pass: true },
10    Example { label: "different collections", code: "fn test() { verify_that!(left[0].name, eq(\"a\")); verify_that!(right[1].name, eq(\"b\")); }", pass: true },
11];
12
13crate::ast_rule!(
14    indexed_element_asserts,
15    "Flag consecutive assertions on indexed elements of the same collection.",
16    "Indexed assertion runs are clearer as `elements_are!` or `unordered_elements_are!`.",
17    Low,
18    params { threshold: i64 = 2 },
19);
20
21fn check_indexed_element_asserts(ctx: &AstCtx<'_>) -> Vec<Violation> {
22    let threshold = ctx
23        .file
24        .config
25        .get_usize("rust_indexed_element_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_index_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                    "indexed assertions should use an element matcher",
46                ));
47            }
48        }
49    }
50
51    violations
52}
53
54fn assertion_index_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 expression = first_macro_argument(&call)?;
65    let index = expression
66        .syntax()
67        .descendants()
68        .find_map(ast::IndexExpr::cast)?;
69    let literal = index.index()?;
70
71    if !matches!(literal, ast::Expr::Literal(_)) {
72        return None;
73    }
74
75    Some(index.base()?.syntax().text().to_string())
76}
77
78crate::tidy_ast_test!(check_indexed_element_asserts, {
79    crate::example_tests!(EXAMPLES, check_indexed_element_asserts);
80});