]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/needless_continue.rs
Changes lint sugg to bitwise and operator `&`
[rust.git] / clippy_lints / src / needless_continue.rs
1 // Copyright 2014-2018 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution.
3 //
4 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
5 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
6 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
7 // option. This file may not be copied, modified, or distributed
8 // except according to those terms.
9
10 //! Checks for continue statements in loops that are redundant.
11 //!
12 //! For example, the lint would catch
13 //!
14 //! ```ignore
15 //! while condition() {
16 //!     update_condition();
17 //!     if x {
18 //!         // ...
19 //!     } else {
20 //!         continue;
21 //!     }
22 //!     println!("Hello, world");
23 //! }
24 //! ```
25 //!
26 //! And suggest something like this:
27 //!
28 //! ```ignore
29 //! while condition() {
30 //!     update_condition();
31 //!     if x {
32 //!         // ...
33 //!         println!("Hello, world");
34 //!     }
35 //! }
36 //! ```
37 //!
38 //! This lint is **warn** by default.
39 use crate::rustc::lint::{EarlyContext, EarlyLintPass, LintArray, LintPass};
40 use crate::rustc::{declare_tool_lint, lint_array};
41 use crate::syntax::ast;
42 use crate::syntax::source_map::{original_sp, DUMMY_SP};
43 use std::borrow::Cow;
44
45 use crate::utils::{in_macro, snippet, snippet_block, span_help_and_lint, trim_multiline};
46
47 /// **What it does:** The lint checks for `if`-statements appearing in loops
48 /// that contain a `continue` statement in either their main blocks or their
49 /// `else`-blocks, when omitting the `else`-block possibly with some
50 /// rearrangement of code can make the code easier to understand.
51 ///
52 /// **Why is this bad?** Having explicit `else` blocks for `if` statements
53 /// containing `continue` in their THEN branch adds unnecessary branching and
54 /// nesting to the code. Having an else block containing just `continue` can
55 /// also be better written by grouping the statements following the whole `if`
56 /// statement within the THEN block and omitting the else block completely.
57 ///
58 /// **Known problems:** None
59 ///
60 /// **Example:**
61 /// ```rust
62 /// while condition() {
63 ///     update_condition();
64 ///     if x {
65 ///         // ...
66 ///     } else {
67 ///         continue;
68 ///     }
69 ///     println!("Hello, world");
70 /// }
71 /// ```
72 ///
73 /// Could be rewritten as
74 ///
75 /// ```rust
76 /// while condition() {
77 ///     update_condition();
78 ///     if x {
79 ///         // ...
80 ///         println!("Hello, world");
81 ///     }
82 /// }
83 /// ```
84 ///
85 /// As another example, the following code
86 ///
87 /// ```rust
88 /// loop {
89 ///     if waiting() {
90 ///         continue;
91 ///     } else {
92 ///         // Do something useful
93 ///     }
94 /// }
95 /// ```
96 /// Could be rewritten as
97 ///
98 /// ```rust
99 /// loop {
100 ///     if waiting() {
101 ///         continue;
102 ///     }
103 ///     // Do something useful
104 /// }
105 /// ```
106 declare_clippy_lint! {
107     pub NEEDLESS_CONTINUE,
108     pedantic,
109     "`continue` statements that can be replaced by a rearrangement of code"
110 }
111
112 #[derive(Copy, Clone)]
113 pub struct NeedlessContinue;
114
115 impl LintPass for NeedlessContinue {
116     fn get_lints(&self) -> LintArray {
117         lint_array!(NEEDLESS_CONTINUE)
118     }
119 }
120
121 impl EarlyLintPass for NeedlessContinue {
122     fn check_expr(&mut self, ctx: &EarlyContext<'_>, expr: &ast::Expr) {
123         if !in_macro(expr.span) {
124             check_and_warn(ctx, expr);
125         }
126     }
127 }
128
129 /* This lint has to mainly deal with two cases of needless continue
130  * statements. */
131 // Case 1 [Continue inside else block]:
132 //
133 //     loop {
134 //         // region A
135 //         if cond {
136 //             // region B
137 //         } else {
138 //             continue;
139 //         }
140 //         // region C
141 //     }
142 //
143 // This code can better be written as follows:
144 //
145 //     loop {
146 //         // region A
147 //         if cond {
148 //             // region B
149 //             // region C
150 //         }
151 //     }
152 //
153 // Case 2 [Continue inside then block]:
154 //
155 //     loop {
156 //       // region A
157 //       if cond {
158 //           continue;
159 //           // potentially more code here.
160 //       } else {
161 //           // region B
162 //       }
163 //       // region C
164 //     }
165 //
166 //
167 // This snippet can be refactored to:
168 //
169 //     loop {
170 //       // region A
171 //       if !cond {
172 //           // region B
173 //           // region C
174 //       }
175 //     }
176 //
177
178 /// Given an expression, returns true if either of the following is true
179 ///
180 /// - The expression is a `continue` node.
181 /// - The expression node is a block with the first statement being a
182 /// `continue`.
183 fn needless_continue_in_else(else_expr: &ast::Expr) -> bool {
184     match else_expr.node {
185         ast::ExprKind::Block(ref else_block, _) => is_first_block_stmt_continue(else_block),
186         ast::ExprKind::Continue(_) => true,
187         _ => false,
188     }
189 }
190
191 fn is_first_block_stmt_continue(block: &ast::Block) -> bool {
192     block.stmts.get(0).map_or(false, |stmt| match stmt.node {
193         ast::StmtKind::Semi(ref e) | ast::StmtKind::Expr(ref e) => {
194             if let ast::ExprKind::Continue(_) = e.node {
195                 true
196             } else {
197                 false
198             }
199         },
200         _ => false,
201     })
202 }
203
204 /// If `expr` is a loop expression (while/while let/for/loop), calls `func` with
205 /// the AST object representing the loop block of `expr`.
206 fn with_loop_block<F>(expr: &ast::Expr, mut func: F)
207 where
208     F: FnMut(&ast::Block),
209 {
210     match expr.node {
211         ast::ExprKind::While(_, ref loop_block, _)
212         | ast::ExprKind::WhileLet(_, _, ref loop_block, _)
213         | ast::ExprKind::ForLoop(_, _, ref loop_block, _)
214         | ast::ExprKind::Loop(ref loop_block, _) => func(loop_block),
215         _ => {},
216     }
217 }
218
219 /// If `stmt` is an if expression node with an `else` branch, calls func with
220 /// the
221 /// following:
222 ///
223 /// - The `if` expression itself,
224 /// - The `if` condition expression,
225 /// - The `then` block, and
226 /// - The `else` expression.
227 fn with_if_expr<F>(stmt: &ast::Stmt, mut func: F)
228 where
229     F: FnMut(&ast::Expr, &ast::Expr, &ast::Block, &ast::Expr),
230 {
231     match stmt.node {
232         ast::StmtKind::Semi(ref e) | ast::StmtKind::Expr(ref e) => {
233             if let ast::ExprKind::If(ref cond, ref if_block, Some(ref else_expr)) = e.node {
234                 func(e, cond, if_block, else_expr);
235             }
236         },
237         _ => {},
238     }
239 }
240
241 /// A type to distinguish between the two distinct cases this lint handles.
242 #[derive(Copy, Clone, Debug)]
243 enum LintType {
244     ContinueInsideElseBlock,
245     ContinueInsideThenBlock,
246 }
247
248 /// Data we pass around for construction of help messages.
249 struct LintData<'a> {
250     /// The `if` expression encountered in the above loop.
251     if_expr: &'a ast::Expr,
252     /// The condition expression for the above `if`.
253     if_cond: &'a ast::Expr,
254     /// The `then` block of the `if` statement.
255     if_block: &'a ast::Block,
256     /// The `else` block of the `if` statement.
257     /// Note that we only work with `if` exprs that have an `else` branch.
258     else_expr: &'a ast::Expr,
259     /// The 0-based index of the `if` statement in the containing loop block.
260     stmt_idx: usize,
261     /// The statements of the loop block.
262     block_stmts: &'a [ast::Stmt],
263 }
264
265 const MSG_REDUNDANT_ELSE_BLOCK: &str = "This else block is redundant.\n";
266
267 const MSG_ELSE_BLOCK_NOT_NEEDED: &str = "There is no need for an explicit `else` block for this `if` \
268                                          expression\n";
269
270 const DROP_ELSE_BLOCK_AND_MERGE_MSG: &str = "Consider dropping the else clause and merging the code that \
271                                              follows (in the loop) with the if block, like so:\n";
272
273 const DROP_ELSE_BLOCK_MSG: &str = "Consider dropping the else clause, and moving out the code in the else \
274                                    block, like so:\n";
275
276 fn emit_warning<'a>(ctx: &EarlyContext<'_>, data: &'a LintData<'_>, header: &str, typ: LintType) {
277     // snip    is the whole *help* message that appears after the warning.
278     // message is the warning message.
279     // expr    is the expression which the lint warning message refers to.
280     let (snip, message, expr) = match typ {
281         LintType::ContinueInsideElseBlock => (
282             suggestion_snippet_for_continue_inside_else(ctx, data, header),
283             MSG_REDUNDANT_ELSE_BLOCK,
284             data.else_expr,
285         ),
286         LintType::ContinueInsideThenBlock => (
287             suggestion_snippet_for_continue_inside_if(ctx, data, header),
288             MSG_ELSE_BLOCK_NOT_NEEDED,
289             data.if_expr,
290         ),
291     };
292     span_help_and_lint(ctx, NEEDLESS_CONTINUE, expr.span, message, &snip);
293 }
294
295 fn suggestion_snippet_for_continue_inside_if<'a>(
296     ctx: &EarlyContext<'_>,
297     data: &'a LintData<'_>,
298     header: &str,
299 ) -> String {
300     let cond_code = snippet(ctx, data.if_cond.span, "..");
301
302     let if_code = format!("if {} {{\n    continue;\n}}\n", cond_code);
303     /* ^^^^--- Four spaces of indentation. */
304     // region B
305     let else_code = snippet(ctx, data.else_expr.span, "..").into_owned();
306     let else_code = erode_block(&else_code);
307     let else_code = trim_multiline(Cow::from(else_code), false);
308
309     let mut ret = String::from(header);
310     ret.push_str(&if_code);
311     ret.push_str(&else_code);
312     ret.push_str("\n...");
313     ret
314 }
315
316 fn suggestion_snippet_for_continue_inside_else<'a>(
317     ctx: &EarlyContext<'_>,
318     data: &'a LintData<'_>,
319     header: &str,
320 ) -> String {
321     let cond_code = snippet(ctx, data.if_cond.span, "..");
322     let mut if_code = format!("if {} {{\n", cond_code);
323
324     // Region B
325     let block_code = &snippet(ctx, data.if_block.span, "..").into_owned();
326     let block_code = erode_block(block_code);
327     let block_code = trim_multiline(Cow::from(block_code), false);
328
329     if_code.push_str(&block_code);
330
331     // Region C
332     // These is the code in the loop block that follows the if/else construction
333     // we are complaining about. We want to pull all of this code into the
334     // `then` block of the `if` statement.
335     let to_annex = data.block_stmts[data.stmt_idx + 1..]
336         .iter()
337         .map(|stmt| original_sp(stmt.span, DUMMY_SP))
338         .map(|span| snippet_block(ctx, span, "..").into_owned())
339         .collect::<Vec<_>>()
340         .join("\n");
341
342     let mut ret = String::from(header);
343
344     ret.push_str(&if_code);
345     ret.push_str("\n// Merged code follows...");
346     ret.push_str(&to_annex);
347     ret.push_str("\n}\n");
348     ret
349 }
350
351 fn check_and_warn<'a>(ctx: &EarlyContext<'_>, expr: &'a ast::Expr) {
352     with_loop_block(expr, |loop_block| {
353         for (i, stmt) in loop_block.stmts.iter().enumerate() {
354             with_if_expr(stmt, |if_expr, cond, then_block, else_expr| {
355                 let data = &LintData {
356                     stmt_idx: i,
357                     if_expr,
358                     if_cond: cond,
359                     if_block: then_block,
360                     else_expr,
361                     block_stmts: &loop_block.stmts,
362                 };
363                 if needless_continue_in_else(else_expr) {
364                     emit_warning(
365                         ctx,
366                         data,
367                         DROP_ELSE_BLOCK_AND_MERGE_MSG,
368                         LintType::ContinueInsideElseBlock,
369                     );
370                 } else if is_first_block_stmt_continue(then_block) {
371                     emit_warning(ctx, data, DROP_ELSE_BLOCK_MSG, LintType::ContinueInsideThenBlock);
372                 }
373             });
374         }
375     });
376 }
377
378 /// Eats at `s` from the end till a closing brace `}` is encountered, and then
379 /// continues eating till a non-whitespace character is found.
380 /// e.g., the string
381 ///
382 /// ```
383 /// {
384 ///     let x = 5;
385 /// }
386 /// ```
387 ///
388 /// is transformed to
389 ///
390 /// ```ignore
391 ///     {
392 ///         let x = 5;
393 /// ```
394 ///
395 /// NOTE: when there is no closing brace in `s`, `s` is _not_ preserved, i.e.,
396 /// an empty string will be returned in that case.
397 pub fn erode_from_back(s: &str) -> String {
398     let mut ret = String::from(s);
399     while ret.pop().map_or(false, |c| c != '}') {}
400     while let Some(c) = ret.pop() {
401         if !c.is_whitespace() {
402             ret.push(c);
403             break;
404         }
405     }
406     ret
407 }
408
409 /// Eats at `s` from the front by first skipping all leading whitespace. Then,
410 /// any number of opening braces are eaten, followed by any number of newlines.
411 /// e.g.,  the string
412 ///
413 /// ```ignore
414 ///         {
415 ///             something();
416 ///             inside_a_block();
417 ///         }
418 /// ```
419 ///
420 /// is transformed to
421 ///
422 /// ```ignore
423 ///             something();
424 ///             inside_a_block();
425 ///         }
426 /// ```
427 pub fn erode_from_front(s: &str) -> String {
428     s.chars()
429         .skip_while(|c| c.is_whitespace())
430         .skip_while(|c| *c == '{')
431         .skip_while(|c| *c == '\n')
432         .collect::<String>()
433 }
434
435 /// If `s` contains the code for a block, delimited by braces, this function
436 /// tries to get the contents of the block. If there is no closing brace
437 /// present,
438 /// an empty string is returned.
439 pub fn erode_block(s: &str) -> String {
440     erode_from_back(&erode_from_front(s))
441 }