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