about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2022-03-04 19:23:39 +0000
committerbors <bors@rust-lang.org>2022-03-04 19:23:39 +0000
commit48d54942f55078cd5866a96bd182bbaf2b81d0f6 (patch)
treec357a5c99c1bdc6cd876a8dae73551e0742437a5
parent53189ad190f313cbad28f8138a04e734ac052cfc (diff)
parentab6ffb6371b78d1777b90c25a44163562ae53036 (diff)
downloadrust-48d54942f55078cd5866a96bd182bbaf2b81d0f6.tar.gz
rust-48d54942f55078cd5866a96bd182bbaf2b81d0f6.zip
Auto merge of #8504 - xFrednet:8502-allow-lint-without-reason, r=flip1995
Add lint to detect `allow` attributes without reason

I was considering putting this lint into the pedantic group. However, that would result in countless warnings for existing projects. Having it in restriction also seems good to me :upside_down_face: (And now I need sleep :zzz: )

---

changelog: New lint [`allow_lint_without_reason`] (Requires the `lint_reasons` feature)

Closes: rust-lang/rust-clippy#8502
-rw-r--r--CHANGELOG.md1
-rw-r--r--clippy_lints/src/attrs.rs60
-rw-r--r--clippy_lints/src/lib.register_lints.rs1
-rw-r--r--clippy_lints/src/lib.register_restriction.rs1
-rw-r--r--tests/ui/allow_attributes_without_reason.rs14
-rw-r--r--tests/ui/allow_attributes_without_reason.stderr23
6 files changed, 99 insertions, 1 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7f18a00a9dd..e7884818596 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3042,6 +3042,7 @@ Released 2018-09-13
 <!-- lint disable no-unused-definitions -->
 <!-- begin autogenerated links to lint list -->
 [`absurd_extreme_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#absurd_extreme_comparisons
+[`allow_attributes_without_reason`]: https://rust-lang.github.io/rust-clippy/master/index.html#allow_attributes_without_reason
 [`almost_swapped`]: https://rust-lang.github.io/rust-clippy/master/index.html#almost_swapped
 [`approx_constant`]: https://rust-lang.github.io/rust-clippy/master/index.html#approx_constant
 [`as_conversions`]: https://rust-lang.github.io/rust-clippy/master/index.html#as_conversions
diff --git a/clippy_lints/src/attrs.rs b/clippy_lints/src/attrs.rs
index a58d12ddd6b..d0b03c0a9c2 100644
--- a/clippy_lints/src/attrs.rs
+++ b/clippy_lints/src/attrs.rs
@@ -255,7 +255,38 @@ declare_clippy_lint! {
     "usage of `cfg(operating_system)` instead of `cfg(target_os = \"operating_system\")`"
 }
 
+declare_clippy_lint! {
+    /// ### What it does
+    /// Checks for attributes that allow lints without a reason.
+    ///
+    /// (This requires the `lint_reasons` feature)
+    ///
+    /// ### Why is this bad?
+    /// Allowing a lint should always have a reason. This reason should be documented to
+    /// ensure that others understand the reasoning
+    ///
+    /// ### Example
+    /// Bad:
+    /// ```rust
+    /// #![feature(lint_reasons)]
+    ///
+    /// #![allow(clippy::some_lint)]
+    /// ```
+    ///
+    /// Good:
+    /// ```rust
+    /// #![feature(lint_reasons)]
+    ///
+    /// #![allow(clippy::some_lint, reason = "False positive rust-lang/rust-clippy#1002020")]
+    /// ```
+    #[clippy::version = "1.61.0"]
+    pub ALLOW_ATTRIBUTES_WITHOUT_REASON,
+    restriction,
+    "ensures that all `allow` and `expect` attributes have a reason"
+}
+
 declare_lint_pass!(Attributes => [
+    ALLOW_ATTRIBUTES_WITHOUT_REASON,
     INLINE_ALWAYS,
     DEPRECATED_SEMVER,
     USELESS_ATTRIBUTE,
@@ -269,6 +300,9 @@ impl<'tcx> LateLintPass<'tcx> for Attributes {
                 if is_lint_level(ident.name) {
                     check_clippy_lint_names(cx, ident.name, items);
                 }
+                if matches!(ident.name, sym::allow | sym::expect) {
+                    check_lint_reason(cx, ident.name, items, attr);
+                }
                 if items.is_empty() || !attr.has_name(sym::deprecated) {
                     return;
                 }
@@ -404,6 +438,30 @@ fn check_clippy_lint_names(cx: &LateContext<'_>, name: Symbol, items: &[NestedMe
     }
 }
 
+fn check_lint_reason(cx: &LateContext<'_>, name: Symbol, items: &[NestedMetaItem], attr: &'_ Attribute) {
+    // Check for the feature
+    if !cx.tcx.sess.features_untracked().lint_reasons {
+        return;
+    }
+
+    // Check if the reason is present
+    if let Some(item) = items.last().and_then(NestedMetaItem::meta_item)
+        && let MetaItemKind::NameValue(_) = &item.kind
+        && item.path == sym::reason
+    {
+        return;
+    }
+
+    span_lint_and_help(
+        cx,
+        ALLOW_ATTRIBUTES_WITHOUT_REASON,
+        attr.span,
+        &format!("`{}` attribute without specifying a reason", name.as_str()),
+        None,
+        "try adding a reason at the end with `, reason = \"..\"`",
+    );
+}
+
 fn is_relevant_item(cx: &LateContext<'_>, item: &Item<'_>) -> bool {
     if let ItemKind::Fn(_, _, eid) = item.kind {
         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) {
 }
 
 fn is_lint_level(symbol: Symbol) -> bool {
-    matches!(symbol, sym::allow | sym::warn | sym::deny | sym::forbid)
+    matches!(symbol, sym::allow | sym::expect | sym::warn | sym::deny | sym::forbid)
 }
diff --git a/clippy_lints/src/lib.register_lints.rs b/clippy_lints/src/lib.register_lints.rs
index d248c2a41ee..8bfa1dfe585 100644
--- a/clippy_lints/src/lib.register_lints.rs
+++ b/clippy_lints/src/lib.register_lints.rs
@@ -44,6 +44,7 @@ store.register_lints(&[
     assign_ops::ASSIGN_OP_PATTERN,
     assign_ops::MISREFACTORED_ASSIGN_OP,
     async_yields_async::ASYNC_YIELDS_ASYNC,
+    attrs::ALLOW_ATTRIBUTES_WITHOUT_REASON,
     attrs::BLANKET_CLIPPY_RESTRICTION_LINTS,
     attrs::DEPRECATED_CFG_ATTR,
     attrs::DEPRECATED_SEMVER,
diff --git a/clippy_lints/src/lib.register_restriction.rs b/clippy_lints/src/lib.register_restriction.rs
index f89f35b885c..4e30fc38197 100644
--- a/clippy_lints/src/lib.register_restriction.rs
+++ b/clippy_lints/src/lib.register_restriction.rs
@@ -8,6 +8,7 @@ store.register_group(true, "clippy::restriction", Some("clippy_restriction"), ve
     LintId::of(as_conversions::AS_CONVERSIONS),
     LintId::of(asm_syntax::INLINE_ASM_X86_ATT_SYNTAX),
     LintId::of(asm_syntax::INLINE_ASM_X86_INTEL_SYNTAX),
+    LintId::of(attrs::ALLOW_ATTRIBUTES_WITHOUT_REASON),
     LintId::of(casts::FN_TO_NUMERIC_CAST_ANY),
     LintId::of(create_dir::CREATE_DIR),
     LintId::of(dbg_macro::DBG_MACRO),
diff --git a/tests/ui/allow_attributes_without_reason.rs b/tests/ui/allow_attributes_without_reason.rs
new file mode 100644
index 00000000000..1a0d4e88657
--- /dev/null
+++ b/tests/ui/allow_attributes_without_reason.rs
@@ -0,0 +1,14 @@
+#![feature(lint_reasons)]
+#![deny(clippy::allow_attributes_without_reason)]
+
+// These should trigger the lint
+#[allow(dead_code)]
+#[allow(dead_code, deprecated)]
+// These should be fine
+#[allow(dead_code, reason = "This should be allowed")]
+#[warn(dyn_drop, reason = "Warnings can also have reasons")]
+#[warn(deref_nullptr)]
+#[deny(deref_nullptr)]
+#[forbid(deref_nullptr)]
+
+fn main() {}
diff --git a/tests/ui/allow_attributes_without_reason.stderr b/tests/ui/allow_attributes_without_reason.stderr
new file mode 100644
index 00000000000..cd040a144aa
--- /dev/null
+++ b/tests/ui/allow_attributes_without_reason.stderr
@@ -0,0 +1,23 @@
+error: `allow` attribute without specifying a reason
+  --> $DIR/allow_attributes_without_reason.rs:5:1
+   |
+LL | #[allow(dead_code)]
+   | ^^^^^^^^^^^^^^^^^^^
+   |
+note: the lint level is defined here
+  --> $DIR/allow_attributes_without_reason.rs:2:9
+   |
+LL | #![deny(clippy::allow_attributes_without_reason)]
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   = help: try adding a reason at the end with `, reason = ".."`
+
+error: `allow` attribute without specifying a reason
+  --> $DIR/allow_attributes_without_reason.rs:6:1
+   |
+LL | #[allow(dead_code, deprecated)]
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = help: try adding a reason at the end with `, reason = ".."`
+
+error: aborting due to 2 previous errors
+