|
1 | 1 | use clippy_utils::diagnostics::span_lint_and_help;
|
2 |
| -use rustc_ast::ast; |
3 |
| -use rustc_ast::{ |
4 |
| - token::{Token, TokenKind}, |
5 |
| - tokenstream::TokenTree, |
6 |
| -}; |
7 |
| -use rustc_lint::{EarlyContext, EarlyLintPass}; |
8 |
| -use rustc_session::{declare_tool_lint, impl_lint_pass}; |
| 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}; |
9 | 7 | use rustc_span::sym;
|
10 | 8 |
|
11 | 9 | declare_clippy_lint! {
|
@@ -37,93 +35,41 @@ declare_clippy_lint! {
|
37 | 35 | "checks assertions without a custom panic message"
|
38 | 36 | }
|
39 | 37 |
|
40 |
| -#[derive(Default, Clone, Debug)] |
41 |
| -pub struct MissingAssertMessage { |
42 |
| - // This field will be greater than zero if we are inside a `#[test]` or `#[cfg(test)]` |
43 |
| - test_deepnes: usize, |
44 |
| -} |
| 38 | +declare_lint_pass!(MissingAssertMessage => [MISSING_ASSERT_MESSAGE]); |
45 | 39 |
|
46 |
| -impl_lint_pass!(MissingAssertMessage => [MISSING_ASSERT_MESSAGE]); |
| 40 | +impl<'tcx> LateLintPass<'tcx> for MissingAssertMessage { |
| 41 | + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { |
| 42 | + let Some(macro_call) = root_macro_call_first_node(cx, expr) else { return }; |
| 43 | + let single_argument = match cx.tcx.get_diagnostic_name(macro_call.def_id) { |
| 44 | + Some(sym::assert_macro | sym::debug_assert_macro) => true, |
| 45 | + Some( |
| 46 | + sym::assert_eq_macro | sym::assert_ne_macro | sym::debug_assert_eq_macro | sym::debug_assert_ne_macro, |
| 47 | + ) => false, |
| 48 | + _ => return, |
| 49 | + }; |
47 | 50 |
|
48 |
| -impl EarlyLintPass for MissingAssertMessage { |
49 |
| - fn check_mac(&mut self, cx: &EarlyContext<'_>, mac_call: &ast::MacCall) { |
50 |
| - if self.test_deepnes != 0 { |
| 51 | + // This lint would be very noisy in tests, so just ignore if we're in test context |
| 52 | + if is_in_test_function(cx.tcx, expr.hir_id) || is_in_cfg_test(cx.tcx, expr.hir_id) { |
51 | 53 | return;
|
52 | 54 | }
|
53 | 55 |
|
54 |
| - let Some(last_segment) = mac_call.path.segments.last() else { return; }; |
55 |
| - let num_separators_needed = match last_segment.ident.as_str() { |
56 |
| - "assert" | "debug_assert" => 1, |
57 |
| - "assert_eq" | "assert_ne" | "debug_assert_eq" | "debug_assert_ne" => 2, |
58 |
| - _ => return, |
| 56 | + let panic_expn = if single_argument { |
| 57 | + let Some((_, panic_expn)) = find_assert_args(cx, expr, macro_call.expn) else { return }; |
| 58 | + panic_expn |
| 59 | + } else { |
| 60 | + let Some((_, _, panic_expn)) = find_assert_eq_args(cx, expr, macro_call.expn) else { return }; |
| 61 | + panic_expn |
59 | 62 | };
|
60 |
| - let num_separators = num_commas_on_arguments(mac_call); |
61 | 63 |
|
62 |
| - if num_separators < num_separators_needed { |
| 64 | + if let PanicExpn::Empty = panic_expn { |
63 | 65 | span_lint_and_help(
|
64 | 66 | cx,
|
65 | 67 | MISSING_ASSERT_MESSAGE,
|
66 |
| - mac_call.span(), |
| 68 | + macro_call.span, |
67 | 69 | "assert without any message",
|
68 | 70 | None,
|
69 | 71 | "consider describing why the failing assert is problematic",
|
70 | 72 | );
|
71 | 73 | }
|
72 | 74 | }
|
73 |
| - |
74 |
| - fn check_item(&mut self, _: &EarlyContext<'_>, item: &ast::Item) { |
75 |
| - if item.attrs.iter().any(is_a_test_attribute) { |
76 |
| - self.test_deepnes += 1; |
77 |
| - } |
78 |
| - } |
79 |
| - |
80 |
| - fn check_item_post(&mut self, _: &EarlyContext<'_>, item: &ast::Item) { |
81 |
| - if item.attrs.iter().any(is_a_test_attribute) { |
82 |
| - self.test_deepnes -= 1; |
83 |
| - } |
84 |
| - } |
85 |
| -} |
86 |
| - |
87 |
| -// Returns number of commas (excluding trailing comma) from `MacCall`'s arguments. |
88 |
| -fn num_commas_on_arguments(mac_call: &ast::MacCall) -> usize { |
89 |
| - let mut num_separators = 0; |
90 |
| - let mut is_trailing = false; |
91 |
| - for tt in mac_call.args.tokens.trees() { |
92 |
| - match tt { |
93 |
| - TokenTree::Token( |
94 |
| - Token { |
95 |
| - kind: TokenKind::Comma, |
96 |
| - span: _, |
97 |
| - }, |
98 |
| - _, |
99 |
| - ) => { |
100 |
| - num_separators += 1; |
101 |
| - is_trailing = true; |
102 |
| - }, |
103 |
| - _ => { |
104 |
| - is_trailing = false; |
105 |
| - }, |
106 |
| - } |
107 |
| - } |
108 |
| - if is_trailing { |
109 |
| - num_separators -= 1; |
110 |
| - } |
111 |
| - num_separators |
112 |
| -} |
113 |
| - |
114 |
| -// Returns true if the attribute is either a `#[test]` or a `#[cfg(test)]`. |
115 |
| -fn is_a_test_attribute(attr: &ast::Attribute) -> bool { |
116 |
| - if attr.has_name(sym::test) { |
117 |
| - return true; |
118 |
| - } |
119 |
| - |
120 |
| - if attr.has_name(sym::cfg) |
121 |
| - && let Some(items) = attr.meta_item_list() |
122 |
| - && let [item] = &*items |
123 |
| - && item.has_name(sym::test) |
124 |
| - { |
125 |
| - true |
126 |
| - } else { |
127 |
| - false |
128 |
| - } |
129 | 75 | }
|
0 commit comments