Skip to main content

wowlab_tidy/languages/rust/rules/hygiene/
expect_message.rs

1use ra_ap_syntax::ast::{self, HasArgList, LiteralKind};
2
3use crate::{AstCtx, Example, Violation};
4
5#[rustfmt::skip]
6const EXAMPLES: &[Example] = &[
7    Example {
8        label: "empty expect message",
9        code: "fn f() { Some(1).expect(\"\"); }",
10        pass: false,
11    },
12    Example {
13        label: "generic expect message",
14        code: "fn f() { Some(1).expect(\"failed\"); }",
15        pass: false,
16    },
17    Example {
18        label: "good expect message",
19        code: "fn f() { Some(1).expect(\"config file must exist at /etc/app.conf\"); }",
20        pass: true,
21    },
22    Example {
23        label: "expect in test module",
24        code: "#[cfg(test)]\nmod tests {\n    fn t() { Some(1).expect(\"\"); }\n}",
25        pass: true,
26    },
27];
28
29crate::ast_rule!(
30    expect_message,
31    "Require `.expect()` to have a meaningful message, not generic ones.",
32    "Generic expect messages like 'failed' give no context in panics. Describe what was expected and why.",
33);
34
35const GENERIC_MESSAGES: &[&str] = &[
36    "",
37    "failed",
38    "error",
39    "unwrap",
40    "should not happen",
41    "unreachable",
42    "impossible",
43    "bug",
44];
45
46fn check_expect_message(ctx: &AstCtx<'_>) -> Vec<Violation> {
47    ctx.nodes::<ast::MethodCallExpr>()
48        .filter(|call| !ctx.is_in_test(call))
49        .filter_map(|call| {
50            let method = call.name_ref()?;
51
52            if method.text() != "expect" {
53                return None;
54            }
55
56            let ast::Expr::Literal(literal) = call.arg_list()?.args().next()? else {
57                return None;
58            };
59            let LiteralKind::String(text) = literal.kind() else {
60                return None;
61            };
62            let message = text.value().ok()?.into_owned();
63            let lower = message.trim().to_lowercase();
64
65            GENERIC_MESSAGES
66                .iter()
67                .any(|generic| lower == *generic)
68                .then(|| {
69                    ctx.violation(
70                        &method,
71                        format!(
72                            ".expect() with generic message {message:?} (provide a specific reason)"
73                        ),
74                    )
75                })
76        })
77        .collect()
78}
79
80crate::tidy_ast_test!(check_expect_message, {
81    crate::example_tests!(EXAMPLES, check_expect_message);
82});