11use 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;
33use 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} ;
56use rustc_ast:: BinOpKind ;
67use rustc_errors:: Applicability ;
78use rustc_hir:: { Block , Expr , ExprKind , Stmt , StmtKind } ;
9+ use rustc_lexer:: TokenKind ;
810use rustc_lint:: { LateContext , LateLintPass } ;
911use rustc_session:: impl_lint_pass;
10- use rustc_span:: Span ;
12+ use rustc_span:: source_map:: SourceMap ;
13+ use rustc_span:: { BytePos , Span } ;
1114
1215declare_clippy_lint ! {
1316 /// ### What it does
@@ -90,35 +93,74 @@ impl CollapsibleIf {
9093 }
9194 }
9295
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)
9698 && cx. tcx . hir_attrs ( else_. hir_id ) . is_empty ( )
9799 && !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 )
99102 {
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 (
111104 cx,
112105 COLLAPSIBLE_ELSE_IF ,
113106 else_block. span ,
114107 "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+ } ,
122164 ) ;
123165 }
124166 }
@@ -130,7 +172,7 @@ impl CollapsibleIf {
130172 && self . eligible_condition ( cx, check_inner)
131173 && let ctxt = expr. span . ctxt ( )
132174 && 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 )
134176 {
135177 span_lint_and_then (
136178 cx,
@@ -141,7 +183,7 @@ impl CollapsibleIf {
141183 let then_open_bracket = then. span . split_at ( 1 ) . 0 . with_leading_whitespace ( cx) . into_span ( ) ;
142184 let then_closing_bracket = {
143185 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 ) )
145187 . with_leading_whitespace ( cx)
146188 . into_span ( )
147189 } ;
@@ -179,7 +221,7 @@ impl LateLintPass<'_> for CollapsibleIf {
179221 if let Some ( else_) = else_
180222 && let ExprKind :: Block ( else_, None ) = else_. kind
181223 {
182- Self :: check_collapsible_else_if ( cx, then. span , else_) ;
224+ self . check_collapsible_else_if ( cx, then. span , else_) ;
183225 } else if else_. is_none ( )
184226 && self . eligible_condition ( cx, cond)
185227 && let ExprKind :: Block ( then, None ) = then. kind
@@ -190,12 +232,16 @@ impl LateLintPass<'_> for CollapsibleIf {
190232 }
191233}
192234
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)
199245}
200246
201247/// 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)> {
226272 vec ! [ ]
227273 }
228274}
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