about summary refs log tree commit diff
path: root/clippy_lints/src
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2022-04-04 07:28:36 +0000
committerbors <bors@rust-lang.org>2022-04-04 07:28:36 +0000
commit1cec8b30fac2c13504de7e7f950bd36c497858b1 (patch)
treeb6fef68d8683d6b8c5f73abed0ab3ed53922a5e4 /clippy_lints/src
parent85b88be2a4fc46fc27d51d4fb273d65818ee63d7 (diff)
parent58833e58a643d1d3d4f2499d6395966245207e3d (diff)
downloadrust-1cec8b30fac2c13504de7e7f950bd36c497858b1.tar.gz
rust-1cec8b30fac2c13504de7e7f950bd36c497858b1.zip
Auto merge of #8594 - FoseFx:unit_like_struct_brackets, r=giraffate
add `empty_structs_with_brackets`

<!-- Thank you for making Clippy better!

We're collecting our changelog from pull request descriptions.
If your PR only includes internal changes, you can just write
`changelog: none`. Otherwise, please write a short comment
explaining your change. Also, it's helpful for us that
the lint name is put into brackets `[]` and backticks `` ` ` ``,
e.g. ``[`lint_name`]``.

If your PR fixes an issue, you can add "fixes #issue_number" into this
PR description. This way the issue will be automatically closed when
your PR is merged.

If you added a new lint, here's a checklist for things that will be
checked during review or continuous integration.

- \[ ] Followed [lint naming conventions][lint_naming]
- \[ ] Added passing UI tests (including committed `.stderr` file)
- \[ ] `cargo test` passes locally
- \[ ] Executed `cargo dev update_lints`
- \[ ] Added lint documentation
- \[ ] Run `cargo dev fmt`

[lint_naming]: https://rust-lang.github.io/rfcs/0344-conventions-galore.html#lints

Note that you can skip the above if you are just opening a WIP PR in
order to get feedback.

Delete this line and everything above before opening your PR.

--

*Please write a short comment explaining your change (or "none" for internal only changes)*
-->
Closes #8591

I'm already sorry for the massive diff :sweat_smile:

changelog: New lint [`empty_structs_with_brackets`]
Diffstat (limited to 'clippy_lints/src')
-rw-r--r--clippy_lints/src/empty_structs_with_brackets.rs99
-rw-r--r--clippy_lints/src/lib.register_lints.rs1
-rw-r--r--clippy_lints/src/lib.register_restriction.rs1
-rw-r--r--clippy_lints/src/lib.rs2
-rw-r--r--clippy_lints/src/use_self.rs4
5 files changed, 105 insertions, 2 deletions
diff --git a/clippy_lints/src/empty_structs_with_brackets.rs b/clippy_lints/src/empty_structs_with_brackets.rs
new file mode 100644
index 00000000000..fdeac8d8255
--- /dev/null
+++ b/clippy_lints/src/empty_structs_with_brackets.rs
@@ -0,0 +1,99 @@
+use clippy_utils::{diagnostics::span_lint_and_then, source::snippet_opt};
+use rustc_ast::ast::{Item, ItemKind, VariantData};
+use rustc_errors::Applicability;
+use rustc_lexer::TokenKind;
+use rustc_lint::{EarlyContext, EarlyLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::Span;
+
+declare_clippy_lint! {
+    /// ### What it does
+    /// Finds structs without fields (a so-called "empty struct") that are declared with brackets.
+    ///
+    /// ### Why is this bad?
+    /// Empty brackets after a struct declaration can be omitted.
+    ///
+    /// ### Example
+    /// ```rust
+    /// struct Cookie {}
+    /// ```
+    /// Use instead:
+    /// ```rust
+    /// struct Cookie;
+    /// ```
+    #[clippy::version = "1.62.0"]
+    pub EMPTY_STRUCTS_WITH_BRACKETS,
+    restriction,
+    "finds struct declarations with empty brackets"
+}
+declare_lint_pass!(EmptyStructsWithBrackets => [EMPTY_STRUCTS_WITH_BRACKETS]);
+
+impl EarlyLintPass for EmptyStructsWithBrackets {
+    fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) {
+        let span_after_ident = item.span.with_lo(item.ident.span.hi());
+
+        if let ItemKind::Struct(var_data, _) = &item.kind
+            && has_brackets(var_data)
+            && has_no_fields(cx, var_data, span_after_ident) {
+            span_lint_and_then(
+                cx,
+                EMPTY_STRUCTS_WITH_BRACKETS,
+                span_after_ident,
+                "found empty brackets on struct declaration",
+                |diagnostic| {
+                    diagnostic.span_suggestion_hidden(
+                        span_after_ident,
+                        "remove the brackets",
+                        ";".to_string(),
+                        Applicability::MachineApplicable);
+                    },
+            );
+        }
+    }
+}
+
+fn has_no_ident_token(braces_span_str: &str) -> bool {
+    !rustc_lexer::tokenize(braces_span_str).any(|t| t.kind == TokenKind::Ident)
+}
+
+fn has_brackets(var_data: &VariantData) -> bool {
+    !matches!(var_data, VariantData::Unit(_))
+}
+
+fn has_no_fields(cx: &EarlyContext<'_>, var_data: &VariantData, braces_span: Span) -> bool {
+    if !var_data.fields().is_empty() {
+        return false;
+    }
+
+    // there might still be field declarations hidden from the AST
+    // (conditionaly compiled code using #[cfg(..)])
+
+    let Some(braces_span_str) = snippet_opt(cx, braces_span) else {
+        return false;
+    };
+
+    has_no_ident_token(braces_span_str.as_ref())
+}
+
+#[cfg(test)]
+mod unit_test {
+    use super::*;
+
+    #[test]
+    fn test_has_no_ident_token() {
+        let input = "{ field: u8 }";
+        assert!(!has_no_ident_token(input));
+
+        let input = "(u8, String);";
+        assert!(!has_no_ident_token(input));
+
+        let input = " {
+                // test = 5
+        }
+        ";
+        assert!(has_no_ident_token(input));
+
+        let input = " ();";
+        assert!(has_no_ident_token(input));
+    }
+}
diff --git a/clippy_lints/src/lib.register_lints.rs b/clippy_lints/src/lib.register_lints.rs
index e3161795139..bba3cae45f5 100644
--- a/clippy_lints/src/lib.register_lints.rs
+++ b/clippy_lints/src/lib.register_lints.rs
@@ -129,6 +129,7 @@ store.register_lints(&[
     duration_subsec::DURATION_SUBSEC,
     else_if_without_else::ELSE_IF_WITHOUT_ELSE,
     empty_enum::EMPTY_ENUM,
+    empty_structs_with_brackets::EMPTY_STRUCTS_WITH_BRACKETS,
     entry::MAP_ENTRY,
     enum_clike::ENUM_CLIKE_UNPORTABLE_VARIANT,
     enum_variants::ENUM_VARIANT_NAMES,
diff --git a/clippy_lints/src/lib.register_restriction.rs b/clippy_lints/src/lib.register_restriction.rs
index 6ab139b2fb6..4802dd877e9 100644
--- a/clippy_lints/src/lib.register_restriction.rs
+++ b/clippy_lints/src/lib.register_restriction.rs
@@ -16,6 +16,7 @@ store.register_group(true, "clippy::restriction", Some("clippy_restriction"), ve
     LintId::of(default_union_representation::DEFAULT_UNION_REPRESENTATION),
     LintId::of(disallowed_script_idents::DISALLOWED_SCRIPT_IDENTS),
     LintId::of(else_if_without_else::ELSE_IF_WITHOUT_ELSE),
+    LintId::of(empty_structs_with_brackets::EMPTY_STRUCTS_WITH_BRACKETS),
     LintId::of(exhaustive_items::EXHAUSTIVE_ENUMS),
     LintId::of(exhaustive_items::EXHAUSTIVE_STRUCTS),
     LintId::of(exit::EXIT),
diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs
index c8b57956b1b..3138ee9bce5 100644
--- a/clippy_lints/src/lib.rs
+++ b/clippy_lints/src/lib.rs
@@ -209,6 +209,7 @@ mod drop_forget_ref;
 mod duration_subsec;
 mod else_if_without_else;
 mod empty_enum;
+mod empty_structs_with_brackets;
 mod entry;
 mod enum_clike;
 mod enum_variants;
@@ -869,6 +870,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
         })
     });
     store.register_early_pass(|| Box::new(crate_in_macro_def::CrateInMacroDef));
+    store.register_early_pass(|| Box::new(empty_structs_with_brackets::EmptyStructsWithBrackets));
     // add lints here, do not remove this comment, it's used in `new_lint`
 }
 
diff --git a/clippy_lints/src/use_self.rs b/clippy_lints/src/use_self.rs
index 09d671e1118..f8e1021af0e 100644
--- a/clippy_lints/src/use_self.rs
+++ b/clippy_lints/src/use_self.rs
@@ -34,7 +34,7 @@ declare_clippy_lint! {
     ///
     /// ### Example
     /// ```rust
-    /// struct Foo {}
+    /// struct Foo;
     /// impl Foo {
     ///     fn new() -> Foo {
     ///         Foo {}
@@ -43,7 +43,7 @@ declare_clippy_lint! {
     /// ```
     /// could be
     /// ```rust
-    /// struct Foo {}
+    /// struct Foo;
     /// impl Foo {
     ///     fn new() -> Self {
     ///         Self {}