• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 use clippy_utils::diagnostics::span_lint_and_help;
2 use clippy_utils::macros::{find_assert_args, find_assert_eq_args, root_macro_call_first_node, PanicExpn};
3 use clippy_utils::{is_in_cfg_test, is_in_test_function};
4 use rustc_hir::Expr;
5 use rustc_lint::{LateContext, LateLintPass};
6 use rustc_session::{declare_lint_pass, declare_tool_lint};
7 use rustc_span::sym;
8 
9 declare_clippy_lint! {
10     /// ### What it does
11     /// Checks assertions without a custom panic message.
12     ///
13     /// ### Why is this bad?
14     /// Without a good custom message, it'd be hard to understand what went wrong when the assertion fails.
15     /// A good custom message should be more about why the failure of the assertion is problematic
16     /// and not what is failed because the assertion already conveys that.
17     ///
18     /// ### Known problems
19     /// This lint cannot check the quality of the custom panic messages.
20     /// Hence, you can suppress this lint simply by adding placeholder messages
21     /// like "assertion failed". However, we recommend coming up with good messages
22     /// that provide useful information instead of placeholder messages that
23     /// don't provide any extra information.
24     ///
25     /// ### Example
26     /// ```rust
27     /// # struct Service { ready: bool }
28     /// fn call(service: Service) {
29     ///     assert!(service.ready);
30     /// }
31     /// ```
32     /// Use instead:
33     /// ```rust
34     /// # struct Service { ready: bool }
35     /// fn call(service: Service) {
36     ///     assert!(service.ready, "`service.poll_ready()` must be called first to ensure that service is ready to receive requests");
37     /// }
38     /// ```
39     #[clippy::version = "1.70.0"]
40     pub MISSING_ASSERT_MESSAGE,
41     restriction,
42     "checks assertions without a custom panic message"
43 }
44 
45 declare_lint_pass!(MissingAssertMessage => [MISSING_ASSERT_MESSAGE]);
46 
47 impl<'tcx> LateLintPass<'tcx> for MissingAssertMessage {
check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>)48     fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
49         let Some(macro_call) = root_macro_call_first_node(cx, expr) else { return };
50         let single_argument = match cx.tcx.get_diagnostic_name(macro_call.def_id) {
51             Some(sym::assert_macro | sym::debug_assert_macro) => true,
52             Some(
53                 sym::assert_eq_macro | sym::assert_ne_macro | sym::debug_assert_eq_macro | sym::debug_assert_ne_macro,
54             ) => false,
55             _ => return,
56         };
57 
58         // This lint would be very noisy in tests, so just ignore if we're in test context
59         if is_in_test_function(cx.tcx, expr.hir_id) || is_in_cfg_test(cx.tcx, expr.hir_id) {
60             return;
61         }
62 
63         let panic_expn = if single_argument {
64             let Some((_, panic_expn)) = find_assert_args(cx, expr, macro_call.expn) else { return };
65             panic_expn
66         } else {
67             let Some((_, _, panic_expn)) = find_assert_eq_args(cx, expr, macro_call.expn) else { return };
68             panic_expn
69         };
70 
71         if let PanicExpn::Empty = panic_expn {
72             span_lint_and_help(
73                 cx,
74                 MISSING_ASSERT_MESSAGE,
75                 macro_call.span,
76                 "assert without any message",
77                 None,
78                 "consider describing why the failing assert is problematic",
79             );
80         }
81     }
82 }
83