|
1 | 1 | use clippy_utils::diagnostics::span_lint_and_then; |
2 | | -use clippy_utils::source::snippet_opt; |
| 2 | +use clippy_utils::source::{IntoSpan, SpanRangeExt}; |
3 | 3 | use rustc_ast::ast::{Item, ItemKind, Variant, VariantData}; |
4 | 4 | use rustc_errors::Applicability; |
5 | 5 | use rustc_lexer::TokenKind; |
6 | | -use rustc_lint::{EarlyContext, EarlyLintPass}; |
| 6 | +use rustc_lint::{EarlyContext, EarlyLintPass, Lint}; |
7 | 7 | use rustc_session::declare_lint_pass; |
8 | 8 | use rustc_span::Span; |
9 | 9 |
|
@@ -74,93 +74,78 @@ declare_lint_pass!(EmptyWithBrackets => [EMPTY_STRUCTS_WITH_BRACKETS, EMPTY_ENUM |
74 | 74 |
|
75 | 75 | impl EarlyLintPass for EmptyWithBrackets { |
76 | 76 | fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) { |
77 | | - let span_after_ident = item.span.with_lo(item.ident.span.hi()); |
78 | | - |
79 | | - if let ItemKind::Struct(var_data, _) = &item.kind |
80 | | - && has_brackets(var_data) |
81 | | - && has_no_fields(cx, var_data, span_after_ident) |
82 | | - { |
83 | | - span_lint_and_then( |
| 77 | + if let ItemKind::Struct(var_data, _) = &item.kind { |
| 78 | + check( |
84 | 79 | cx, |
| 80 | + var_data, |
| 81 | + item.span, |
| 82 | + item.ident.span, |
85 | 83 | EMPTY_STRUCTS_WITH_BRACKETS, |
86 | | - span_after_ident, |
87 | | - "found empty brackets on struct declaration", |
88 | | - |diagnostic| { |
89 | | - diagnostic.span_suggestion_hidden( |
90 | | - span_after_ident, |
91 | | - "remove the brackets", |
92 | | - ";", |
93 | | - Applicability::Unspecified, |
94 | | - ); |
95 | | - }, |
| 84 | + "non-unit struct contains no fields", |
| 85 | + true, |
96 | 86 | ); |
97 | 87 | } |
98 | 88 | } |
99 | 89 |
|
100 | 90 | fn check_variant(&mut self, cx: &EarlyContext<'_>, variant: &Variant) { |
101 | | - let span_after_ident = variant.span.with_lo(variant.ident.span.hi()); |
102 | | - |
103 | | - if has_brackets(&variant.data) && has_no_fields(cx, &variant.data, span_after_ident) { |
104 | | - span_lint_and_then( |
105 | | - cx, |
106 | | - EMPTY_ENUM_VARIANTS_WITH_BRACKETS, |
107 | | - span_after_ident, |
108 | | - "enum variant has empty brackets", |
109 | | - |diagnostic| { |
110 | | - diagnostic.span_suggestion_hidden( |
111 | | - span_after_ident, |
112 | | - "remove the brackets", |
113 | | - "", |
114 | | - Applicability::MaybeIncorrect, |
115 | | - ); |
116 | | - }, |
117 | | - ); |
118 | | - } |
| 91 | + check( |
| 92 | + cx, |
| 93 | + &variant.data, |
| 94 | + variant.span, |
| 95 | + variant.ident.span, |
| 96 | + EMPTY_ENUM_VARIANTS_WITH_BRACKETS, |
| 97 | + "non-unit variant contains no fields", |
| 98 | + false, |
| 99 | + ); |
119 | 100 | } |
120 | 101 | } |
121 | 102 |
|
122 | | -fn has_no_ident_token(braces_span_str: &str) -> bool { |
123 | | - !rustc_lexer::tokenize(braces_span_str).any(|t| t.kind == TokenKind::Ident) |
124 | | -} |
125 | | - |
126 | | -fn has_brackets(var_data: &VariantData) -> bool { |
127 | | - !matches!(var_data, VariantData::Unit(_)) |
128 | | -} |
129 | | - |
130 | | -fn has_no_fields(cx: &EarlyContext<'_>, var_data: &VariantData, braces_span: Span) -> bool { |
131 | | - if !var_data.fields().is_empty() { |
132 | | - return false; |
133 | | - } |
134 | | - |
135 | | - // there might still be field declarations hidden from the AST |
136 | | - // (conditionally compiled code using #[cfg(..)]) |
137 | | - |
138 | | - let Some(braces_span_str) = snippet_opt(cx, braces_span) else { |
139 | | - return false; |
| 103 | +fn check( |
| 104 | + cx: &EarlyContext<'_>, |
| 105 | + data: &VariantData, |
| 106 | + item_sp: Span, |
| 107 | + name_sp: Span, |
| 108 | + lint: &'static Lint, |
| 109 | + msg: &'static str, |
| 110 | + needs_semi: bool, |
| 111 | +) { |
| 112 | + let (fields, has_semi, start_char, end_char, help_msg) = match &data { |
| 113 | + VariantData::Struct { fields, .. } => (fields, false, '{', '}', "remove the braces"), |
| 114 | + VariantData::Tuple(fields, _) => (fields, needs_semi, '(', ')', "remove the parentheses"), |
| 115 | + VariantData::Unit(_) => return, |
140 | 116 | }; |
141 | | - |
142 | | - has_no_ident_token(braces_span_str.as_ref()) |
143 | | -} |
144 | | - |
145 | | -#[cfg(test)] |
146 | | -mod unit_test { |
147 | | - use super::*; |
148 | | - |
149 | | - #[test] |
150 | | - fn test_has_no_ident_token() { |
151 | | - let input = "{ field: u8 }"; |
152 | | - assert!(!has_no_ident_token(input)); |
153 | | - |
154 | | - let input = "(u8, String);"; |
155 | | - assert!(!has_no_ident_token(input)); |
156 | | - |
157 | | - let input = " { |
158 | | - // test = 5 |
159 | | - } |
160 | | - "; |
161 | | - assert!(has_no_ident_token(input)); |
162 | | - |
163 | | - let input = " ();"; |
164 | | - assert!(has_no_ident_token(input)); |
| 117 | + if fields.is_empty() |
| 118 | + && !item_sp.from_expansion() |
| 119 | + && !name_sp.from_expansion() |
| 120 | + && let name_hi = name_sp.hi() |
| 121 | + && let Some(err_range) = (name_hi..item_sp.hi()).clone().map_range(cx, |src, range| { |
| 122 | + let src = src.get(range.clone())?; |
| 123 | + let (src, end) = if has_semi { |
| 124 | + (src.strip_suffix(';')?, range.end - 1) |
| 125 | + } else { |
| 126 | + (src, range.end) |
| 127 | + }; |
| 128 | + let trimmed = src.trim_start(); |
| 129 | + let start = range.start + (src.len() - trimmed.len()); |
| 130 | + // Proc-macro check. |
| 131 | + let trimmed = trimmed.strip_prefix(start_char)?.strip_suffix(end_char)?; |
| 132 | + // Check for anything inside the brackets, including comments. |
| 133 | + rustc_lexer::tokenize(trimmed) |
| 134 | + .all(|tt| matches!(tt.kind, TokenKind::Whitespace)) |
| 135 | + .then_some(start..end) |
| 136 | + }) |
| 137 | + { |
| 138 | + span_lint_and_then(cx, lint, err_range.clone().into_span(), msg, |diagnostic| { |
| 139 | + diagnostic.span_suggestion_hidden( |
| 140 | + (name_hi..err_range.end).into_span(), |
| 141 | + help_msg, |
| 142 | + if has_semi || !needs_semi { |
| 143 | + String::new() |
| 144 | + } else { |
| 145 | + ";".into() |
| 146 | + }, |
| 147 | + Applicability::MaybeIncorrect, |
| 148 | + ); |
| 149 | + }); |
165 | 150 | } |
166 | 151 | } |
0 commit comments