about summary refs log tree commit diff
path: root/clippy_lints/src/empty_structs_with_brackets.rs
diff options
context:
space:
mode:
Diffstat (limited to 'clippy_lints/src/empty_structs_with_brackets.rs')
-rw-r--r--clippy_lints/src/empty_structs_with_brackets.rs99
1 files changed, 99 insertions, 0 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..e3f33c1a4a3
--- /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 ("unit-like structs") 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,
+    style,
+    "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
+            && !is_unit_like_struct(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 is_unit_like_struct(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));
+    }
+}