@@ -6,7 +6,11 @@ use clippy_utils::macros::{is_panic, macro_backtrace};
6
6
use clippy_utils:: msrvs:: { self , Msrv } ;
7
7
use clippy_utils:: source:: { first_line_of_span, is_present_in_source, snippet_opt, without_block_comments} ;
8
8
use 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
+ } ;
10
14
use rustc_errors:: Applicability ;
11
15
use rustc_hir:: {
12
16
Block , Expr , ExprKind , ImplItem , ImplItemKind , Item , ItemKind , StmtKind , TraitFn , TraitItem , TraitItemKind ,
@@ -339,6 +343,41 @@ declare_clippy_lint! {
339
343
"ensures that all `allow` and `expect` attributes have a reason"
340
344
}
341
345
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
+
342
381
declare_clippy_lint ! {
343
382
/// ### What it does
344
383
/// Checks for `any` and `all` combinators in `cfg` with only one condition.
@@ -395,6 +434,7 @@ declare_lint_pass!(Attributes => [
395
434
DEPRECATED_SEMVER ,
396
435
USELESS_ATTRIBUTE ,
397
436
BLANKET_CLIPPY_RESTRICTION_LINTS ,
437
+ SHOULD_PANIC_WITHOUT_EXPECT ,
398
438
] ) ;
399
439
400
440
impl < ' tcx > LateLintPass < ' tcx > for Attributes {
@@ -442,6 +482,9 @@ impl<'tcx> LateLintPass<'tcx> for Attributes {
442
482
}
443
483
}
444
484
}
485
+ if attr. has_name ( sym:: should_panic) {
486
+ check_should_panic_reason ( cx, attr) ;
487
+ }
445
488
}
446
489
447
490
fn check_item ( & mut self , cx : & LateContext < ' tcx > , item : & ' tcx Item < ' _ > ) {
@@ -550,6 +593,35 @@ fn extract_clippy_lint(lint: &NestedMetaItem) -> Option<Symbol> {
550
593
None
551
594
}
552
595
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
+
553
625
fn check_clippy_lint_names ( cx : & LateContext < ' _ > , name : Symbol , items : & [ NestedMetaItem ] ) {
554
626
for lint in items {
555
627
if let Some ( lint_name) = extract_clippy_lint ( lint) {
0 commit comments