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} ;
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 ;
66use rustc_ast:: BinOpKind ;
77use rustc_errors:: Applicability ;
88use rustc_hir:: { Block , Expr , ExprKind , Stmt , StmtKind } ;
@@ -91,37 +91,63 @@ impl CollapsibleIf {
9191 }
9292 }
9393
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)
94+ fn check_collapsible_else_if ( & self , cx : & LateContext < ' _ > , then_span : Span , else_block : & Block < ' _ > ) {
95+ if let Some ( else_) = expr_block ( else_block)
9796 && cx. tcx . hir_attrs ( else_. hir_id ) . is_empty ( )
9897 && !else_. span . from_expansion ( )
9998 && 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 ) ) )
99+ && !block_starts_with_significant_tokens ( cx, else_block, else_, self . lint_commented_code )
102100 {
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 (
101+ span_lint_and_then (
114102 cx,
115103 COLLAPSIBLE_ELSE_IF ,
116104 else_block. span ,
117105 "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,
106+ |diag| {
107+ if self . lint_commented_code {
108+ let else_open_bracket = else_block. span . split_at ( 1 ) . 0 . with_leading_whitespace ( cx) . into_span ( ) ;
109+ let else_closing_bracket = {
110+ let end = else_block. span . shrink_to_hi ( ) ;
111+ end. with_lo ( end. lo ( ) - BytePos ( 1 ) )
112+ . with_leading_whitespace ( cx)
113+ . into_span ( )
114+ } ;
115+ let sugg = vec ! [
116+ // Remove the outer else block `{`
117+ ( else_open_bracket, String :: new( ) ) ,
118+ // Remove the outer else block '}'
119+ ( else_closing_bracket, String :: new( ) ) ,
120+ ] ;
121+ diag. multipart_suggestion ( "collapse nested if block" , sugg, Applicability :: MachineApplicable ) ;
122+ return ;
123+ }
124+
125+ // Prevent "elseif"
126+ // Check that the "else" is followed by whitespace
127+ let up_to_else = then_span. between ( else_block. span ) ;
128+ let requires_space = if let Some ( c) = snippet ( cx, up_to_else, ".." ) . chars ( ) . last ( ) {
129+ !c. is_whitespace ( )
130+ } else {
131+ false
132+ } ;
133+ let mut applicability = Applicability :: MachineApplicable ;
134+ diag. span_suggestion (
135+ else_block. span ,
136+ "collapse nested if block" ,
137+ format ! (
138+ "{}{}" ,
139+ if requires_space { " " } else { "" } ,
140+ snippet_block_with_applicability(
141+ cx,
142+ else_. span,
143+ ".." ,
144+ Some ( else_block. span) ,
145+ & mut applicability
146+ )
147+ ) ,
148+ applicability,
149+ ) ;
150+ } ,
125151 ) ;
126152 }
127153 }
@@ -133,7 +159,7 @@ impl CollapsibleIf {
133159 && self . eligible_condition ( cx, check_inner)
134160 && let ctxt = expr. span . ctxt ( )
135161 && inner. span . ctxt ( ) == ctxt
136- && ( self . lint_commented_code || ! block_starts_with_comment ( cx, then) )
162+ && ! block_starts_with_significant_tokens ( cx, then, inner , self . lint_commented_code )
137163 {
138164 span_lint_and_then (
139165 cx,
@@ -182,7 +208,7 @@ impl LateLintPass<'_> for CollapsibleIf {
182208 if let Some ( else_) = else_
183209 && let ExprKind :: Block ( else_, None ) = else_. kind
184210 {
185- Self :: check_collapsible_else_if ( cx, then. span , else_) ;
211+ self . check_collapsible_else_if ( cx, then. span , else_) ;
186212 } else if else_. is_none ( )
187213 && self . eligible_condition ( cx, cond)
188214 && let ExprKind :: Block ( then, None ) = then. kind
@@ -193,12 +219,16 @@ impl LateLintPass<'_> for CollapsibleIf {
193219 }
194220}
195221
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 ( "/*" )
222+ // Check that nothing significant can be found but whitespaces between the initial `{` of `block`
223+ // and the beginning of `stop_at`.
224+ fn block_starts_with_significant_tokens (
225+ cx : & LateContext < ' _ > ,
226+ block : & Block < ' _ > ,
227+ stop_at : & Expr < ' _ > ,
228+ lint_commented_code : bool ,
229+ ) -> bool {
230+ let span = block. span . split_at ( 1 ) . 1 . until ( stop_at. span ) ;
231+ span_contains_non_whitespace ( cx, span, lint_commented_code)
202232}
203233
204234/// If `block` is a block with either one expression or a statement containing an expression,
0 commit comments