]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/clippy_lints/src/empty_structs_with_brackets.rs
Rollup merge of #95446 - notseanray:master, r=Mark-Simulacrum
[rust.git] / src / tools / clippy / clippy_lints / src / empty_structs_with_brackets.rs
1 use clippy_utils::{diagnostics::span_lint_and_then, source::snippet_opt};
2 use rustc_ast::ast::{Item, ItemKind, VariantData};
3 use rustc_errors::Applicability;
4 use rustc_lexer::TokenKind;
5 use rustc_lint::{EarlyContext, EarlyLintPass};
6 use rustc_session::{declare_lint_pass, declare_tool_lint};
7 use rustc_span::Span;
8
9 declare_clippy_lint! {
10     /// ### What it does
11     /// Finds structs without fields (a so-called "empty struct") that are declared with brackets.
12     ///
13     /// ### Why is this bad?
14     /// Empty brackets after a struct declaration can be omitted.
15     ///
16     /// ### Example
17     /// ```rust
18     /// struct Cookie {}
19     /// ```
20     /// Use instead:
21     /// ```rust
22     /// struct Cookie;
23     /// ```
24     #[clippy::version = "1.62.0"]
25     pub EMPTY_STRUCTS_WITH_BRACKETS,
26     restriction,
27     "finds struct declarations with empty brackets"
28 }
29 declare_lint_pass!(EmptyStructsWithBrackets => [EMPTY_STRUCTS_WITH_BRACKETS]);
30
31 impl EarlyLintPass for EmptyStructsWithBrackets {
32     fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) {
33         let span_after_ident = item.span.with_lo(item.ident.span.hi());
34
35         if let ItemKind::Struct(var_data, _) = &item.kind
36             && has_brackets(var_data)
37             && has_no_fields(cx, var_data, span_after_ident) {
38             span_lint_and_then(
39                 cx,
40                 EMPTY_STRUCTS_WITH_BRACKETS,
41                 span_after_ident,
42                 "found empty brackets on struct declaration",
43                 |diagnostic| {
44                     diagnostic.span_suggestion_hidden(
45                         span_after_ident,
46                         "remove the brackets",
47                         ";",
48                         Applicability::MachineApplicable);
49                     },
50             );
51         }
52     }
53 }
54
55 fn has_no_ident_token(braces_span_str: &str) -> bool {
56     !rustc_lexer::tokenize(braces_span_str).any(|t| t.kind == TokenKind::Ident)
57 }
58
59 fn has_brackets(var_data: &VariantData) -> bool {
60     !matches!(var_data, VariantData::Unit(_))
61 }
62
63 fn has_no_fields(cx: &EarlyContext<'_>, var_data: &VariantData, braces_span: Span) -> bool {
64     if !var_data.fields().is_empty() {
65         return false;
66     }
67
68     // there might still be field declarations hidden from the AST
69     // (conditionally compiled code using #[cfg(..)])
70
71     let Some(braces_span_str) = snippet_opt(cx, braces_span) else {
72         return false;
73     };
74
75     has_no_ident_token(braces_span_str.as_ref())
76 }
77
78 #[cfg(test)]
79 mod unit_test {
80     use super::*;
81
82     #[test]
83     fn test_has_no_ident_token() {
84         let input = "{ field: u8 }";
85         assert!(!has_no_ident_token(input));
86
87         let input = "(u8, String);";
88         assert!(!has_no_ident_token(input));
89
90         let input = " {
91                 // test = 5
92         }
93         ";
94         assert!(has_no_ident_token(input));
95
96         let input = " ();";
97         assert!(has_no_ident_token(input));
98     }
99 }