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} ;
4
+ use clippy_utils:: source:: { IntoSpan as _, SpanRangeExt , snippet, snippet_block_with_applicability} ;
5
+ use clippy_utils:: { span_contains_non_whitespace, tokenize_with_text} ;
5
6
use rustc_ast:: BinOpKind ;
6
7
use rustc_errors:: Applicability ;
7
8
use rustc_hir:: { Block , Expr , ExprKind , Stmt , StmtKind } ;
9
+ use rustc_lexer:: TokenKind ;
8
10
use rustc_lint:: { LateContext , LateLintPass } ;
9
11
use rustc_session:: impl_lint_pass;
10
- use rustc_span:: Span ;
12
+ use rustc_span:: source_map:: SourceMap ;
13
+ use rustc_span:: { BytePos , Span } ;
11
14
12
15
declare_clippy_lint ! {
13
16
/// ### What it does
@@ -90,35 +93,74 @@ impl CollapsibleIf {
90
93
}
91
94
}
92
95
93
- fn check_collapsible_else_if ( cx : & LateContext < ' _ > , then_span : Span , else_block : & Block < ' _ > ) {
94
- if !block_starts_with_comment ( cx, else_block)
95
- && 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)
96
98
&& cx. tcx . hir_attrs ( else_. hir_id ) . is_empty ( )
97
99
&& !else_. span . from_expansion ( )
98
- && let ExprKind :: If ( ..) = else_. kind
100
+ && let ExprKind :: If ( else_if_cond, ..) = else_. kind
101
+ && !block_starts_with_significant_tokens ( cx, else_block, else_, self . lint_commented_code )
99
102
{
100
- // Prevent "elseif"
101
- // Check that the "else" is followed by whitespace
102
- let up_to_else = then_span. between ( else_block. span ) ;
103
- let requires_space = if let Some ( c) = snippet ( cx, up_to_else, ".." ) . chars ( ) . last ( ) {
104
- !c. is_whitespace ( )
105
- } else {
106
- false
107
- } ;
108
-
109
- let mut applicability = Applicability :: MachineApplicable ;
110
- span_lint_and_sugg (
103
+ span_lint_and_then (
111
104
cx,
112
105
COLLAPSIBLE_ELSE_IF ,
113
106
else_block. span ,
114
107
"this `else { if .. }` block can be collapsed" ,
115
- "collapse nested if block" ,
116
- format ! (
117
- "{}{}" ,
118
- if requires_space { " " } else { "" } ,
119
- snippet_block_with_applicability( cx, else_. span, ".." , Some ( else_block. span) , & mut applicability)
120
- ) ,
121
- 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
+ } ,
122
164
) ;
123
165
}
124
166
}
@@ -130,7 +172,7 @@ impl CollapsibleIf {
130
172
&& self . eligible_condition ( cx, check_inner)
131
173
&& let ctxt = expr. span . ctxt ( )
132
174
&& inner. span . ctxt ( ) == ctxt
133
- && ( self . lint_commented_code || ! block_starts_with_comment ( cx, then) )
175
+ && ! block_starts_with_significant_tokens ( cx, then, inner , self . lint_commented_code )
134
176
{
135
177
span_lint_and_then (
136
178
cx,
@@ -141,7 +183,7 @@ impl CollapsibleIf {
141
183
let then_open_bracket = then. span . split_at ( 1 ) . 0 . with_leading_whitespace ( cx) . into_span ( ) ;
142
184
let then_closing_bracket = {
143
185
let end = then. span . shrink_to_hi ( ) ;
144
- end. with_lo ( end. lo ( ) - rustc_span :: BytePos ( 1 ) )
186
+ end. with_lo ( end. lo ( ) - BytePos ( 1 ) )
145
187
. with_leading_whitespace ( cx)
146
188
. into_span ( )
147
189
} ;
@@ -179,7 +221,7 @@ impl LateLintPass<'_> for CollapsibleIf {
179
221
if let Some ( else_) = else_
180
222
&& let ExprKind :: Block ( else_, None ) = else_. kind
181
223
{
182
- Self :: check_collapsible_else_if ( cx, then. span , else_) ;
224
+ self . check_collapsible_else_if ( cx, then. span , else_) ;
183
225
} else if else_. is_none ( )
184
226
&& self . eligible_condition ( cx, cond)
185
227
&& let ExprKind :: Block ( then, None ) = then. kind
@@ -190,12 +232,16 @@ impl LateLintPass<'_> for CollapsibleIf {
190
232
}
191
233
}
192
234
193
- fn block_starts_with_comment ( cx : & LateContext < ' _ > , block : & Block < ' _ > ) -> bool {
194
- // We trim all opening braces and whitespaces and then check if the next string is a comment.
195
- let trimmed_block_text = snippet_block ( cx, block. span , ".." , None )
196
- . trim_start_matches ( |c : char | c. is_whitespace ( ) || c == '{' )
197
- . to_owned ( ) ;
198
- 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)
199
245
}
200
246
201
247
/// If `block` is a block with either one expression or a statement containing an expression,
@@ -226,3 +272,16 @@ fn parens_around(expr: &Expr<'_>) -> Vec<(Span, String)> {
226
272
vec ! [ ]
227
273
}
228
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