about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md1
-rw-r--r--clippy_lints/src/attrs.rs57
-rw-r--r--clippy_lints/src/declared_lints.rs1
-rw-r--r--tests/ui/cfg_features.rs12
-rw-r--r--tests/ui/cfg_features.stderr28
5 files changed, 99 insertions, 0 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8b609b47d81..fd619368e8f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4947,6 +4947,7 @@ Released 2018-09-13
 [`match_wild_err_arm`]: https://rust-lang.github.io/rust-clippy/master/index.html#match_wild_err_arm
 [`match_wildcard_for_single_variants`]: https://rust-lang.github.io/rust-clippy/master/index.html#match_wildcard_for_single_variants
 [`maybe_infinite_iter`]: https://rust-lang.github.io/rust-clippy/master/index.html#maybe_infinite_iter
+[`maybe_misused_cfg`]: https://rust-lang.github.io/rust-clippy/master/index.html#maybe_misused_cfg
 [`mem_discriminant_non_enum`]: https://rust-lang.github.io/rust-clippy/master/index.html#mem_discriminant_non_enum
 [`mem_forget`]: https://rust-lang.github.io/rust-clippy/master/index.html#mem_forget
 [`mem_replace_option_with_none`]: https://rust-lang.github.io/rust-clippy/master/index.html#mem_replace_option_with_none
diff --git a/clippy_lints/src/attrs.rs b/clippy_lints/src/attrs.rs
index 897495ba108..891ee8af82b 100644
--- a/clippy_lints/src/attrs.rs
+++ b/clippy_lints/src/attrs.rs
@@ -362,6 +362,32 @@ declare_clippy_lint! {
     "ensure that all `cfg(any())` and `cfg(all())` have more than one condition"
 }
 
+declare_clippy_lint! {
+    /// ### What it does
+    /// Checks for `#[cfg(features = "...")]` and suggests to replace it with
+    /// `#[cfg(feature = "...")]`.
+    ///
+    /// ### Why is this bad?
+    /// Misspelling `feature` as `features` can be sometimes hard to spot. It
+    /// may cause conditional compilation not work quitely.
+    ///
+    /// ### Example
+    /// ```rust
+    /// #[cfg(features = "some-feature")]
+    /// fn conditional() { }
+    /// ```
+    ///
+    /// Use instead:
+    /// ```rust
+    /// #[cfg(feature = "some-feature")]
+    /// fn conditional() { }
+    /// ```
+    #[clippy::version = "1.69.0"]
+    pub MAYBE_MISUSED_CFG,
+    suspicious,
+    "prevent from misusing the wrong attr name"
+}
+
 declare_lint_pass!(Attributes => [
     ALLOW_ATTRIBUTES_WITHOUT_REASON,
     INLINE_ALWAYS,
@@ -676,6 +702,7 @@ impl_lint_pass!(EarlyAttributes => [
     EMPTY_LINE_AFTER_OUTER_ATTR,
     EMPTY_LINE_AFTER_DOC_COMMENTS,
     NON_MINIMAL_CFG,
+    MAYBE_MISUSED_CFG,
 ]);
 
 impl EarlyLintPass for EarlyAttributes {
@@ -687,6 +714,7 @@ impl EarlyLintPass for EarlyAttributes {
         check_deprecated_cfg_attr(cx, attr, &self.msrv);
         check_mismatched_target_os(cx, attr);
         check_minimal_cfg_condition(cx, attr);
+        check_misused_cfg(cx, attr);
     }
 
     extract_msrv_attr!(EarlyContext);
@@ -810,6 +838,27 @@ fn check_nested_cfg(cx: &EarlyContext<'_>, items: &[NestedMetaItem]) {
     }
 }
 
+fn check_nested_misused_cfg(cx: &EarlyContext<'_>, items: &[NestedMetaItem]) {
+    for item in items.iter() {
+        if let NestedMetaItem::MetaItem(meta) = item {
+            if meta.has_name(sym!(features)) && let Some(val) = meta.value_str() {
+                span_lint_and_sugg(
+                    cx,
+                    MAYBE_MISUSED_CFG,
+                    meta.span,
+                    "feature may misspelled as features",
+                    "use",
+                    format!("feature = \"{val}\""),
+                    Applicability::MaybeIncorrect,
+                );
+            }
+            if let MetaItemKind::List(list) = &meta.kind {
+                check_nested_misused_cfg(cx, list);
+            }
+        }
+    }
+}
+
 fn check_minimal_cfg_condition(cx: &EarlyContext<'_>, attr: &Attribute) {
     if attr.has_name(sym::cfg) &&
         let Some(items) = attr.meta_item_list()
@@ -818,6 +867,14 @@ fn check_minimal_cfg_condition(cx: &EarlyContext<'_>, attr: &Attribute) {
     }
 }
 
+fn check_misused_cfg(cx: &EarlyContext<'_>, attr: &Attribute) {
+    if attr.has_name(sym::cfg) &&
+        let Some(items) = attr.meta_item_list()
+    {
+        check_nested_misused_cfg(cx, &items);
+    }
+}
+
 fn check_mismatched_target_os(cx: &EarlyContext<'_>, attr: &Attribute) {
     fn find_os(name: &str) -> Option<&'static str> {
         UNIX_SYSTEMS
diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs
index 15ff8be0fd9..a0b8e055f1d 100644
--- a/clippy_lints/src/declared_lints.rs
+++ b/clippy_lints/src/declared_lints.rs
@@ -51,6 +51,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
     crate::attrs::EMPTY_LINE_AFTER_DOC_COMMENTS_INFO,
     crate::attrs::EMPTY_LINE_AFTER_OUTER_ATTR_INFO,
     crate::attrs::INLINE_ALWAYS_INFO,
+    crate::attrs::MAYBE_MISUSED_CFG_INFO,
     crate::attrs::MISMATCHED_TARGET_OS_INFO,
     crate::attrs::NON_MINIMAL_CFG_INFO,
     crate::attrs::USELESS_ATTRIBUTE_INFO,
diff --git a/tests/ui/cfg_features.rs b/tests/ui/cfg_features.rs
new file mode 100644
index 00000000000..bc4109c2c89
--- /dev/null
+++ b/tests/ui/cfg_features.rs
@@ -0,0 +1,12 @@
+#![warn(clippy::maybe_misused_cfg)]
+
+fn main() {
+    #[cfg(features = "not-really-a-feature")]
+    let _ = 1 + 2;
+
+    #[cfg(all(feature = "right", features = "wrong"))]
+    let _ = 1 + 2;
+
+    #[cfg(all(features = "wrong1", any(feature = "right", features = "wrong2", feature, features)))]
+    let _ = 1 + 2;
+}
diff --git a/tests/ui/cfg_features.stderr b/tests/ui/cfg_features.stderr
new file mode 100644
index 00000000000..00405985d48
--- /dev/null
+++ b/tests/ui/cfg_features.stderr
@@ -0,0 +1,28 @@
+error: feature may misspelled as features
+  --> $DIR/cfg_features.rs:4:11
+   |
+LL |     #[cfg(features = "not-really-a-feature")]
+   |           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `feature = "not-really-a-feature"`
+   |
+   = note: `-D clippy::maybe-misused-cfg` implied by `-D warnings`
+
+error: feature may misspelled as features
+  --> $DIR/cfg_features.rs:7:34
+   |
+LL |     #[cfg(all(feature = "right", features = "wrong"))]
+   |                                  ^^^^^^^^^^^^^^^^^^ help: use: `feature = "wrong"`
+
+error: feature may misspelled as features
+  --> $DIR/cfg_features.rs:10:15
+   |
+LL |     #[cfg(all(features = "wrong1", any(feature = "right", features = "wrong2", feature, features)))]
+   |               ^^^^^^^^^^^^^^^^^^^ help: use: `feature = "wrong1"`
+
+error: feature may misspelled as features
+  --> $DIR/cfg_features.rs:10:59
+   |
+LL |     #[cfg(all(features = "wrong1", any(feature = "right", features = "wrong2", feature, features)))]
+   |                                                           ^^^^^^^^^^^^^^^^^^^ help: use: `feature = "wrong2"`
+
+error: aborting due to 4 previous errors
+