@@ -255,7 +255,38 @@ declare_clippy_lint! {
255255 "usage of `cfg(operating_system)` instead of `cfg(target_os = \" operating_system\" )`"
256256}
257257
258+ declare_clippy_lint ! {
259+ /// ### What it does
260+ /// Checks for attributes that allow lints without a reason.
261+ ///
262+ /// (This requires the `lint_reasons` feature)
263+ ///
264+ /// ### Why is this bad?
265+ /// Allowing a lint should always have a reason. This reason should be documented to
266+ /// ensure that others understand the reasoning
267+ ///
268+ /// ### Example
269+ /// Bad:
270+ /// ```rust
271+ /// #![feature(lint_reasons)]
272+ ///
273+ /// #![allow(clippy::some_lint)]
274+ /// ```
275+ ///
276+ /// Good:
277+ /// ```rust
278+ /// #![feature(lint_reasons)]
279+ ///
280+ /// #![allow(clippy::some_lint, reason = "False positive rust-lang/rust-clippy#1002020")]
281+ /// ```
282+ #[ clippy:: version = "1.61.0" ]
283+ pub ALLOW_ATTRIBUTES_WITHOUT_REASON ,
284+ restriction,
285+ "ensures that all `allow` and `expect` attributes have a reason"
286+ }
287+
258288declare_lint_pass ! ( Attributes => [
289+ ALLOW_ATTRIBUTES_WITHOUT_REASON ,
259290 INLINE_ALWAYS ,
260291 DEPRECATED_SEMVER ,
261292 USELESS_ATTRIBUTE ,
@@ -269,6 +300,9 @@ impl<'tcx> LateLintPass<'tcx> for Attributes {
269300 if is_lint_level ( ident. name ) {
270301 check_clippy_lint_names ( cx, ident. name , items) ;
271302 }
303+ if matches ! ( ident. name, sym:: allow | sym:: expect) {
304+ check_lint_reason ( cx, ident. name , items, attr) ;
305+ }
272306 if items. is_empty ( ) || !attr. has_name ( sym:: deprecated) {
273307 return ;
274308 }
@@ -404,6 +438,30 @@ fn check_clippy_lint_names(cx: &LateContext<'_>, name: Symbol, items: &[NestedMe
404438 }
405439}
406440
441+ fn check_lint_reason ( cx : & LateContext < ' _ > , name : Symbol , items : & [ NestedMetaItem ] , attr : & ' _ Attribute ) {
442+ // Check for the feature
443+ if !cx. tcx . sess . features_untracked ( ) . lint_reasons {
444+ return ;
445+ }
446+
447+ // Check if the reason is present
448+ if let Some ( item) = items. last ( ) . and_then ( NestedMetaItem :: meta_item)
449+ && let MetaItemKind :: NameValue ( _) = & item. kind
450+ && item. path == sym:: reason
451+ {
452+ return ;
453+ }
454+
455+ span_lint_and_help (
456+ cx,
457+ ALLOW_ATTRIBUTES_WITHOUT_REASON ,
458+ attr. span ,
459+ & format ! ( "`{}` attribute without specifying a reason" , name. as_str( ) ) ,
460+ None ,
461+ "try adding a reason at the end with `, reason = \" ..\" `" ,
462+ ) ;
463+ }
464+
407465fn is_relevant_item ( cx : & LateContext < ' _ > , item : & Item < ' _ > ) -> bool {
408466 if let ItemKind :: Fn ( _, _, eid) = item. kind {
409467 is_relevant_expr ( cx, cx. tcx . typeck_body ( eid) , & cx. tcx . hir ( ) . body ( eid) . value )
@@ -659,5 +717,5 @@ fn check_mismatched_target_os(cx: &EarlyContext<'_>, attr: &Attribute) {
659717}
660718
661719fn is_lint_level ( symbol : Symbol ) -> bool {
662- matches ! ( symbol, sym:: allow | sym:: warn | sym:: deny | sym:: forbid)
720+ matches ! ( symbol, sym:: allow | sym:: expect | sym :: warn | sym:: deny | sym:: forbid)
663721}
0 commit comments