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