@@ -6,7 +6,11 @@ use clippy_utils::macros::{is_panic, macro_backtrace};
66use clippy_utils:: msrvs:: { self , Msrv } ;
77use clippy_utils:: source:: { first_line_of_span, is_present_in_source, snippet_opt, without_block_comments} ;
88use if_chain:: if_chain;
9- use rustc_ast:: { AttrKind , AttrStyle , Attribute , LitKind , MetaItemKind , MetaItemLit , NestedMetaItem } ;
9+ use rustc_ast:: token:: { Token , TokenKind } ;
10+ use rustc_ast:: tokenstream:: TokenTree ;
11+ use rustc_ast:: {
12+ AttrArgs , AttrArgsEq , AttrKind , AttrStyle , Attribute , LitKind , MetaItemKind , MetaItemLit , NestedMetaItem ,
13+ } ;
1014use rustc_errors:: Applicability ;
1115use rustc_hir:: {
1216 Block , Expr , ExprKind , ImplItem , ImplItemKind , Item , ItemKind , StmtKind , TraitFn , TraitItem , TraitItemKind ,
@@ -339,6 +343,41 @@ declare_clippy_lint! {
339343 "ensures that all `allow` and `expect` attributes have a reason"
340344}
341345
346+ declare_clippy_lint ! {
347+ /// ### What it does
348+ /// Checks for `#[should_panic]` attributes without specifying the expected panic message.
349+ ///
350+ /// ### Why is this bad?
351+ /// The expected panic message should be specified to ensure that the test is actually
352+ /// panicking with the expected message, and not another unrelated panic.
353+ ///
354+ /// ### Example
355+ /// ```rust
356+ /// fn random() -> i32 { 0 }
357+ ///
358+ /// #[should_panic]
359+ /// #[test]
360+ /// fn my_test() {
361+ /// let _ = 1 / random();
362+ /// }
363+ /// ```
364+ ///
365+ /// Use instead:
366+ /// ```rust
367+ /// fn random() -> i32 { 0 }
368+ ///
369+ /// #[should_panic = "attempt to divide by zero"]
370+ /// #[test]
371+ /// fn my_test() {
372+ /// let _ = 1 / random();
373+ /// }
374+ /// ```
375+ #[ clippy:: version = "1.73.0" ]
376+ pub SHOULD_PANIC_WITHOUT_EXPECT ,
377+ pedantic,
378+ "ensures that all `should_panic` attributes specify its expected panic message"
379+ }
380+
342381declare_clippy_lint ! {
343382 /// ### What it does
344383 /// Checks for `any` and `all` combinators in `cfg` with only one condition.
@@ -395,6 +434,7 @@ declare_lint_pass!(Attributes => [
395434 DEPRECATED_SEMVER ,
396435 USELESS_ATTRIBUTE ,
397436 BLANKET_CLIPPY_RESTRICTION_LINTS ,
437+ SHOULD_PANIC_WITHOUT_EXPECT ,
398438] ) ;
399439
400440impl < ' tcx > LateLintPass < ' tcx > for Attributes {
@@ -442,6 +482,9 @@ impl<'tcx> LateLintPass<'tcx> for Attributes {
442482 }
443483 }
444484 }
485+ if attr. has_name ( sym:: should_panic) {
486+ check_should_panic_reason ( cx, attr) ;
487+ }
445488 }
446489
447490 fn check_item ( & mut self , cx : & LateContext < ' tcx > , item : & ' tcx Item < ' _ > ) {
@@ -550,6 +593,35 @@ fn extract_clippy_lint(lint: &NestedMetaItem) -> Option<Symbol> {
550593 None
551594}
552595
596+ fn check_should_panic_reason ( cx : & LateContext < ' _ > , attr : & Attribute ) {
597+ if let AttrKind :: Normal ( normal_attr) = & attr. kind {
598+ if let AttrArgs :: Eq ( _, AttrArgsEq :: Hir ( _) ) = & normal_attr. item . args {
599+ // `#[should_panic = ".."]` found, good
600+ return ;
601+ }
602+
603+ if let AttrArgs :: Delimited ( args) = & normal_attr. item . args
604+ && let mut tt_iter = args. tokens . trees ( )
605+ && let Some ( TokenTree :: Token ( Token { kind : TokenKind :: Ident ( sym:: expected, _) , .. } , _) ) = tt_iter. next ( )
606+ && let Some ( TokenTree :: Token ( Token { kind : TokenKind :: Eq , .. } , _) ) = tt_iter. next ( )
607+ && let Some ( TokenTree :: Token ( Token { kind : TokenKind :: Literal ( _) , .. } , _) ) = tt_iter. next ( )
608+ {
609+ // `#[should_panic(expected = "..")]` found, good
610+ return ;
611+ }
612+
613+ span_lint_and_sugg (
614+ cx,
615+ SHOULD_PANIC_WITHOUT_EXPECT ,
616+ attr. span ,
617+ "#[should_panic] attribute without a reason" ,
618+ "consider specifying the expected panic" ,
619+ r#"#[should_panic(expected = /* panic message */)]"# . into ( ) ,
620+ Applicability :: HasPlaceholders ,
621+ ) ;
622+ }
623+ }
624+
553625fn check_clippy_lint_names ( cx : & LateContext < ' _ > , name : Symbol , items : & [ NestedMetaItem ] ) {
554626 for lint in items {
555627 if let Some ( lint_name) = extract_clippy_lint ( lint) {
0 commit comments