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