wowlab_tidy/languages/rust/rules/tests/
indexed_element_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: "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});