1
+ use clippy_config:: Conf ;
1
2
use 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;
4
7
use rustc_ast:: ast;
5
8
use rustc_errors:: Applicability ;
6
9
use rustc_lint:: { EarlyContext , EarlyLintPass } ;
7
- use rustc_session:: declare_lint_pass ;
10
+ use rustc_session:: impl_lint_pass ;
8
11
use rustc_span:: Span ;
9
12
10
13
declare_clippy_lint ! {
@@ -75,17 +78,106 @@ declare_clippy_lint! {
75
78
"nested `else`-`if` expressions that can be collapsed (e.g., `else { if x { ... } }`)"
76
79
}
77
80
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 ] ) ;
79
170
80
171
impl EarlyLintPass for CollapsibleIf {
81
172
fn check_expr ( & mut self , cx : & EarlyContext < ' _ > , expr : & ast:: Expr ) {
82
173
if let ast:: ExprKind :: If ( cond, then, else_) = & expr. kind
83
174
&& !expr. span . from_expansion ( )
84
175
{
85
176
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_) ;
87
178
} 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) ;
89
181
}
90
182
}
91
183
}
@@ -99,74 +191,6 @@ fn block_starts_with_comment(cx: &EarlyContext<'_>, expr: &ast::Block) -> bool {
99
191
trimmed_block_text. starts_with ( "//" ) || trimmed_block_text. starts_with ( "/*" )
100
192
}
101
193
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
-
170
194
/// If the block contains only one expression, return it.
171
195
fn expr_block ( block : & ast:: Block ) -> Option < & ast:: Expr > {
172
196
if let [ stmt] = & * block. stmts
@@ -177,3 +201,17 @@ fn expr_block(block: &ast::Block) -> Option<&ast::Expr> {
177
201
None
178
202
}
179
203
}
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