1
1
use clippy_config:: Conf ;
2
- use clippy_utils:: diagnostics:: { span_lint_and_sugg , span_lint_and_then} ;
2
+ use clippy_utils:: diagnostics:: span_lint_and_then;
3
3
use clippy_utils:: msrvs:: { self , Msrv } ;
4
- use clippy_utils:: source:: { IntoSpan as _, SpanRangeExt , snippet, snippet_block , snippet_block_with_applicability} ;
5
- use clippy_utils:: span_contains_non_comment ;
4
+ use clippy_utils:: source:: { IntoSpan as _, SpanRangeExt , snippet, snippet_block_with_applicability} ;
5
+ use clippy_utils:: { span_contains_non_whitespace , tokenize_with_text } ;
6
6
use rustc_ast:: BinOpKind ;
7
7
use rustc_errors:: Applicability ;
8
8
use rustc_hir:: { Block , Expr , ExprKind , Stmt , StmtKind } ;
9
+ use rustc_lexer:: TokenKind ;
9
10
use rustc_lint:: { LateContext , LateLintPass } ;
10
11
use rustc_session:: impl_lint_pass;
12
+ use rustc_span:: source_map:: SourceMap ;
11
13
use rustc_span:: { BytePos , Span } ;
12
14
13
15
declare_clippy_lint ! {
@@ -91,37 +93,74 @@ impl CollapsibleIf {
91
93
}
92
94
}
93
95
94
- fn check_collapsible_else_if ( cx : & LateContext < ' _ > , then_span : Span , else_block : & Block < ' _ > ) {
95
- if !block_starts_with_comment ( cx, else_block)
96
- && let Some ( else_) = expr_block ( else_block)
96
+ fn check_collapsible_else_if ( & self , cx : & LateContext < ' _ > , then_span : Span , else_block : & Block < ' _ > ) {
97
+ if let Some ( else_) = expr_block ( else_block)
97
98
&& cx. tcx . hir_attrs ( else_. hir_id ) . is_empty ( )
98
99
&& !else_. span . from_expansion ( )
99
- && let ExprKind :: If ( ..) = else_. kind
100
- && let up_to_if = else_block. span . until ( else_. span )
101
- && !span_contains_non_comment ( cx, up_to_if. with_lo ( BytePos ( up_to_if. lo ( ) . 0 + 1 ) ) )
100
+ && let ExprKind :: If ( else_if_cond, ..) = else_. kind
101
+ && !block_starts_with_significant_tokens ( cx, else_block, else_, self . lint_commented_code )
102
102
{
103
- // Prevent "elseif"
104
- // Check that the "else" is followed by whitespace
105
- let up_to_else = then_span. between ( else_block. span ) ;
106
- let requires_space = if let Some ( c) = snippet ( cx, up_to_else, ".." ) . chars ( ) . last ( ) {
107
- !c. is_whitespace ( )
108
- } else {
109
- false
110
- } ;
111
-
112
- let mut applicability = Applicability :: MachineApplicable ;
113
- span_lint_and_sugg (
103
+ span_lint_and_then (
114
104
cx,
115
105
COLLAPSIBLE_ELSE_IF ,
116
106
else_block. span ,
117
107
"this `else { if .. }` block can be collapsed" ,
118
- "collapse nested if block" ,
119
- format ! (
120
- "{}{}" ,
121
- if requires_space { " " } else { "" } ,
122
- snippet_block_with_applicability( cx, else_. span, ".." , Some ( else_block. span) , & mut applicability)
123
- ) ,
124
- applicability,
108
+ |diag| {
109
+ let up_to_else = then_span. between ( else_block. span ) ;
110
+ let else_before_if = else_. span . shrink_to_lo ( ) . with_hi ( else_if_cond. span . lo ( ) - BytePos ( 1 ) ) ;
111
+ if self . lint_commented_code
112
+ && let Some ( else_keyword_span) =
113
+ span_extract_keyword ( cx. tcx . sess . source_map ( ) , up_to_else, "else" )
114
+ && let Some ( else_if_keyword_span) =
115
+ span_extract_keyword ( cx. tcx . sess . source_map ( ) , else_before_if, "if" )
116
+ {
117
+ let else_keyword_span = else_keyword_span. with_leading_whitespace ( cx) . into_span ( ) ;
118
+ let else_open_bracket = else_block. span . split_at ( 1 ) . 0 . with_leading_whitespace ( cx) . into_span ( ) ;
119
+ let else_closing_bracket = {
120
+ let end = else_block. span . shrink_to_hi ( ) ;
121
+ end. with_lo ( end. lo ( ) - BytePos ( 1 ) )
122
+ . with_leading_whitespace ( cx)
123
+ . into_span ( )
124
+ } ;
125
+ let sugg = vec ! [
126
+ // Remove the outer else block `else`
127
+ ( else_keyword_span, String :: new( ) ) ,
128
+ // Replace the inner `if` by `else if`
129
+ ( else_if_keyword_span, String :: from( "else if" ) ) ,
130
+ // Remove the outer else block `{`
131
+ ( else_open_bracket, String :: new( ) ) ,
132
+ // Remove the outer else block '}'
133
+ ( else_closing_bracket, String :: new( ) ) ,
134
+ ] ;
135
+ diag. multipart_suggestion ( "collapse nested if block" , sugg, Applicability :: MachineApplicable ) ;
136
+ return ;
137
+ }
138
+
139
+ // Prevent "elseif"
140
+ // Check that the "else" is followed by whitespace
141
+ let requires_space = if let Some ( c) = snippet ( cx, up_to_else, ".." ) . chars ( ) . last ( ) {
142
+ !c. is_whitespace ( )
143
+ } else {
144
+ false
145
+ } ;
146
+ let mut applicability = Applicability :: MachineApplicable ;
147
+ diag. span_suggestion (
148
+ else_block. span ,
149
+ "collapse nested if block" ,
150
+ format ! (
151
+ "{}{}" ,
152
+ if requires_space { " " } else { "" } ,
153
+ snippet_block_with_applicability(
154
+ cx,
155
+ else_. span,
156
+ ".." ,
157
+ Some ( else_block. span) ,
158
+ & mut applicability
159
+ )
160
+ ) ,
161
+ applicability,
162
+ ) ;
163
+ } ,
125
164
) ;
126
165
}
127
166
}
@@ -133,7 +172,7 @@ impl CollapsibleIf {
133
172
&& self . eligible_condition ( cx, check_inner)
134
173
&& let ctxt = expr. span . ctxt ( )
135
174
&& inner. span . ctxt ( ) == ctxt
136
- && ( self . lint_commented_code || ! block_starts_with_comment ( cx, then) )
175
+ && ! block_starts_with_significant_tokens ( cx, then, inner , self . lint_commented_code )
137
176
{
138
177
span_lint_and_then (
139
178
cx,
@@ -182,7 +221,7 @@ impl LateLintPass<'_> for CollapsibleIf {
182
221
if let Some ( else_) = else_
183
222
&& let ExprKind :: Block ( else_, None ) = else_. kind
184
223
{
185
- Self :: check_collapsible_else_if ( cx, then. span , else_) ;
224
+ self . check_collapsible_else_if ( cx, then. span , else_) ;
186
225
} else if else_. is_none ( )
187
226
&& self . eligible_condition ( cx, cond)
188
227
&& let ExprKind :: Block ( then, None ) = then. kind
@@ -193,12 +232,16 @@ impl LateLintPass<'_> for CollapsibleIf {
193
232
}
194
233
}
195
234
196
- fn block_starts_with_comment ( cx : & LateContext < ' _ > , block : & Block < ' _ > ) -> bool {
197
- // We trim all opening braces and whitespaces and then check if the next string is a comment.
198
- let trimmed_block_text = snippet_block ( cx, block. span , ".." , None )
199
- . trim_start_matches ( |c : char | c. is_whitespace ( ) || c == '{' )
200
- . to_owned ( ) ;
201
- trimmed_block_text. starts_with ( "//" ) || trimmed_block_text. starts_with ( "/*" )
235
+ // Check that nothing significant can be found but whitespaces between the initial `{` of `block`
236
+ // and the beginning of `stop_at`.
237
+ fn block_starts_with_significant_tokens (
238
+ cx : & LateContext < ' _ > ,
239
+ block : & Block < ' _ > ,
240
+ stop_at : & Expr < ' _ > ,
241
+ lint_commented_code : bool ,
242
+ ) -> bool {
243
+ let span = block. span . split_at ( 1 ) . 1 . until ( stop_at. span ) ;
244
+ span_contains_non_whitespace ( cx, span, lint_commented_code)
202
245
}
203
246
204
247
/// If `block` is a block with either one expression or a statement containing an expression,
@@ -229,3 +272,16 @@ fn parens_around(expr: &Expr<'_>) -> Vec<(Span, String)> {
229
272
vec ! [ ]
230
273
}
231
274
}
275
+
276
+ fn span_extract_keyword ( sm : & SourceMap , span : Span , keyword : & str ) -> Option < Span > {
277
+ let snippet = sm. span_to_snippet ( span) . ok ( ) ?;
278
+ tokenize_with_text ( & snippet)
279
+ . filter ( |( t, s, _) | matches ! ( t, TokenKind :: Ident if * s == keyword) )
280
+ . map ( |( _, _, inner) | {
281
+ span. split_at ( u32:: try_from ( inner. start ) . unwrap ( ) )
282
+ . 1
283
+ . split_at ( u32:: try_from ( inner. end - inner. start ) . unwrap ( ) )
284
+ . 0
285
+ } )
286
+ . next ( )
287
+ }
0 commit comments