1+ use clippy_config:: Conf ;
12use clippy_utils:: diagnostics:: { span_lint_and_sugg, span_lint_and_then} ;
2- use clippy_utils:: source:: { snippet, snippet_block, snippet_block_with_applicability} ;
3- use clippy_utils:: sugg:: Sugg ;
3+ use clippy_utils:: source:: {
4+ HasSession , IntoSpan as _, SpanRangeExt , snippet, snippet_block, snippet_block_with_applicability,
5+ } ;
6+ use clippy_utils:: span_contains_comment;
47use rustc_ast:: ast;
58use rustc_errors:: Applicability ;
69use rustc_lint:: { EarlyContext , EarlyLintPass } ;
7- use rustc_session:: declare_lint_pass ;
10+ use rustc_session:: impl_lint_pass ;
811use rustc_span:: Span ;
912
1013declare_clippy_lint ! {
@@ -75,17 +78,106 @@ declare_clippy_lint! {
7578 "nested `else`-`if` expressions that can be collapsed (e.g., `else { if x { ... } }`)"
7679}
7780
78- declare_lint_pass ! ( CollapsibleIf => [ COLLAPSIBLE_IF , COLLAPSIBLE_ELSE_IF ] ) ;
81+ pub struct CollapsibleIf {
82+ lint_commented_code : bool ,
83+ }
84+
85+ impl CollapsibleIf {
86+ pub fn new ( conf : & ' static Conf ) -> Self {
87+ Self {
88+ lint_commented_code : conf. lint_commented_code ,
89+ }
90+ }
91+
92+ fn check_collapsible_else_if ( cx : & EarlyContext < ' _ > , then_span : Span , else_ : & ast:: Expr ) {
93+ if let ast:: ExprKind :: Block ( ref block, _) = else_. kind
94+ && !block_starts_with_comment ( cx, block)
95+ && let Some ( else_) = expr_block ( block)
96+ && else_. attrs . is_empty ( )
97+ && !else_. span . from_expansion ( )
98+ && let ast:: ExprKind :: If ( ..) = else_. kind
99+ {
100+ // Prevent "elseif"
101+ // Check that the "else" is followed by whitespace
102+ let up_to_else = then_span. between ( 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 (
111+ cx,
112+ COLLAPSIBLE_ELSE_IF ,
113+ block. span ,
114+ "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 ( block. span) , & mut applicability)
120+ ) ,
121+ applicability,
122+ ) ;
123+ }
124+ }
125+
126+ fn check_collapsible_if_if ( & self , cx : & EarlyContext < ' _ > , expr : & ast:: Expr , check : & ast:: Expr , then : & ast:: Block ) {
127+ if let Some ( inner) = expr_block ( then)
128+ && inner. attrs . is_empty ( )
129+ && let ast:: ExprKind :: If ( check_inner, _, None ) = & inner. kind
130+ // Prevent triggering on `if c { if let a = b { .. } }`.
131+ && !matches ! ( check_inner. kind, ast:: ExprKind :: Let ( ..) )
132+ && let ctxt = expr. span . ctxt ( )
133+ && inner. span . ctxt ( ) == ctxt
134+ && let contains_comment = span_contains_comment ( cx. sess ( ) . source_map ( ) , check. span . to ( check_inner. span ) )
135+ && ( !contains_comment || self . lint_commented_code )
136+ {
137+ span_lint_and_then (
138+ cx,
139+ COLLAPSIBLE_IF ,
140+ expr. span ,
141+ "this `if` statement can be collapsed" ,
142+ |diag| {
143+ let then_open_bracket = then. span . split_at ( 1 ) . 0 . with_leading_whitespace ( cx) . into_span ( ) ;
144+ let then_closing_bracket = {
145+ let end = then. span . shrink_to_hi ( ) ;
146+ end. with_lo ( end. lo ( ) - rustc_span:: BytePos ( 1 ) )
147+ . with_leading_whitespace ( cx)
148+ . into_span ( )
149+ } ;
150+ let inner_if = inner. span . split_at ( 2 ) . 0 ;
151+ let mut sugg = vec ! [
152+ // Remove the outer then block `{`
153+ ( then_open_bracket, String :: new( ) ) ,
154+ // Remove the outer then block '}'
155+ ( then_closing_bracket, String :: new( ) ) ,
156+ // Replace inner `if` by `&&`
157+ ( inner_if, String :: from( "&&" ) ) ,
158+ ] ;
159+ sugg. extend ( parens_around ( check) ) ;
160+ sugg. extend ( parens_around ( check_inner) ) ;
161+
162+ diag. multipart_suggestion ( "collapse nested if block" , sugg, Applicability :: MachineApplicable ) ;
163+ } ,
164+ ) ;
165+ }
166+ }
167+ }
168+
169+ impl_lint_pass ! ( CollapsibleIf => [ COLLAPSIBLE_IF , COLLAPSIBLE_ELSE_IF ] ) ;
79170
80171impl EarlyLintPass for CollapsibleIf {
81172 fn check_expr ( & mut self , cx : & EarlyContext < ' _ > , expr : & ast:: Expr ) {
82173 if let ast:: ExprKind :: If ( cond, then, else_) = & expr. kind
83174 && !expr. span . from_expansion ( )
84175 {
85176 if let Some ( else_) = else_ {
86- check_collapsible_maybe_if_let ( cx, then. span , else_) ;
177+ Self :: check_collapsible_else_if ( cx, then. span , else_) ;
87178 } else if !matches ! ( cond. kind, ast:: ExprKind :: Let ( ..) ) {
88- check_collapsible_no_if_let ( cx, expr, cond, then) ;
179+ // Prevent triggering on `if c { if let a = b { .. } }`.
180+ self . check_collapsible_if_if ( cx, expr, cond, then) ;
89181 }
90182 }
91183 }
@@ -99,74 +191,6 @@ fn block_starts_with_comment(cx: &EarlyContext<'_>, expr: &ast::Block) -> bool {
99191 trimmed_block_text. starts_with ( "//" ) || trimmed_block_text. starts_with ( "/*" )
100192}
101193
102- fn check_collapsible_maybe_if_let ( cx : & EarlyContext < ' _ > , then_span : Span , else_ : & ast:: Expr ) {
103- if let ast:: ExprKind :: Block ( ref block, _) = else_. kind
104- && !block_starts_with_comment ( cx, block)
105- && let Some ( else_) = expr_block ( block)
106- && else_. attrs . is_empty ( )
107- && !else_. span . from_expansion ( )
108- && let ast:: ExprKind :: If ( ..) = else_. kind
109- {
110- // Prevent "elseif"
111- // Check that the "else" is followed by whitespace
112- let up_to_else = then_span. between ( block. span ) ;
113- let requires_space = if let Some ( c) = snippet ( cx, up_to_else, ".." ) . chars ( ) . last ( ) {
114- !c. is_whitespace ( )
115- } else {
116- false
117- } ;
118-
119- let mut applicability = Applicability :: MachineApplicable ;
120- span_lint_and_sugg (
121- cx,
122- COLLAPSIBLE_ELSE_IF ,
123- block. span ,
124- "this `else { if .. }` block can be collapsed" ,
125- "collapse nested if block" ,
126- format ! (
127- "{}{}" ,
128- if requires_space { " " } else { "" } ,
129- snippet_block_with_applicability( cx, else_. span, ".." , Some ( block. span) , & mut applicability)
130- ) ,
131- applicability,
132- ) ;
133- }
134- }
135-
136- fn check_collapsible_no_if_let ( cx : & EarlyContext < ' _ > , expr : & ast:: Expr , check : & ast:: Expr , then : & ast:: Block ) {
137- if !block_starts_with_comment ( cx, then)
138- && let Some ( inner) = expr_block ( then)
139- && inner. attrs . is_empty ( )
140- && let ast:: ExprKind :: If ( ref check_inner, ref content, None ) = inner. kind
141- // Prevent triggering on `if c { if let a = b { .. } }`.
142- && !matches ! ( check_inner. kind, ast:: ExprKind :: Let ( ..) )
143- && let ctxt = expr. span . ctxt ( )
144- && inner. span . ctxt ( ) == ctxt
145- {
146- span_lint_and_then (
147- cx,
148- COLLAPSIBLE_IF ,
149- expr. span ,
150- "this `if` statement can be collapsed" ,
151- |diag| {
152- let mut app = Applicability :: MachineApplicable ;
153- let lhs = Sugg :: ast ( cx, check, ".." , ctxt, & mut app) ;
154- let rhs = Sugg :: ast ( cx, check_inner, ".." , ctxt, & mut app) ;
155- diag. span_suggestion (
156- expr. span ,
157- "collapse nested if block" ,
158- format ! (
159- "if {} {}" ,
160- lhs. and( & rhs) ,
161- snippet_block( cx, content. span, ".." , Some ( expr. span) ) ,
162- ) ,
163- app, // snippet
164- ) ;
165- } ,
166- ) ;
167- }
168- }
169-
170194/// If the block contains only one expression, return it.
171195fn expr_block ( block : & ast:: Block ) -> Option < & ast:: Expr > {
172196 if let [ stmt] = & * block. stmts
@@ -177,3 +201,17 @@ fn expr_block(block: &ast::Block) -> Option<&ast::Expr> {
177201 None
178202 }
179203}
204+
205+ /// If the expression is a `||`, suggest parentheses around it.
206+ fn parens_around ( expr : & ast:: Expr ) -> Vec < ( Span , String ) > {
207+ if let ast:: ExprKind :: Binary ( op, _, _) = expr. kind
208+ && op. node == ast:: BinOpKind :: Or
209+ {
210+ vec ! [
211+ ( expr. span. shrink_to_lo( ) , String :: from( "(" ) ) ,
212+ ( expr. span. shrink_to_hi( ) , String :: from( ")" ) ) ,
213+ ]
214+ } else {
215+ vec ! [ ]
216+ }
217+ }
0 commit comments