]> git.lizzy.rs Git - rust.git/blobdiff - clippy_lints/src/needless_continue.rs
Simplify `rustc_ast::visit::Visitor::visit_poly_trait_ref`.
[rust.git] / clippy_lints / src / needless_continue.rs
index 8835158155b6510422e0de9877e8ab85d05e7c63..98a3bce1ff38a1e9d107b54d8f725af240156c9a 100644 (file)
 //! ```
 //!
 //! This lint is **warn** by default.
-use rustc::lint::{EarlyContext, EarlyLintPass, LintArray, LintPass};
-use rustc::{declare_lint_pass, declare_tool_lint};
-use std::borrow::Cow;
-use syntax::ast;
-use syntax::source_map::{original_sp, DUMMY_SP};
-
-use crate::utils::{in_macro_or_desugar, snippet, snippet_block, span_help_and_lint, trim_multiline};
+use clippy_utils::diagnostics::span_lint_and_help;
+use clippy_utils::source::{indent_of, snippet, snippet_block};
+use rustc_ast::ast;
+use rustc_lint::{EarlyContext, EarlyLintPass, LintContext};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::Span;
 
 declare_clippy_lint! {
-    /// **What it does:** The lint checks for `if`-statements appearing in loops
+    /// ### What it does
+    /// The lint checks for `if`-statements appearing in loops
     /// that contain a `continue` statement in either their main blocks or their
     /// `else`-blocks, when omitting the `else`-block possibly with some
     /// rearrangement of code can make the code easier to understand.
     ///
-    /// **Why is this bad?** Having explicit `else` blocks for `if` statements
+    /// ### Why is this bad?
+    /// Having explicit `else` blocks for `if` statements
     /// containing `continue` in their THEN branch adds unnecessary branching and
     /// nesting to the code. Having an else block containing just `continue` can
     /// also be better written by grouping the statements following the whole `if`
     /// statement within the THEN block and omitting the else block completely.
     ///
-    /// **Known problems:** None
-    ///
-    /// **Example:**
+    /// ### Example
     /// ```rust
+    /// # fn condition() -> bool { false }
+    /// # fn update_condition() {}
+    /// # let x = false;
     /// while condition() {
     ///     update_condition();
     ///     if x {
@@ -71,6 +73,9 @@
     /// Could be rewritten as
     ///
     /// ```rust
+    /// # fn condition() -> bool { false }
+    /// # fn update_condition() {}
+    /// # let x = false;
     /// while condition() {
     ///     update_condition();
     ///     if x {
     /// As another example, the following code
     ///
     /// ```rust
+    /// # fn waiting() -> bool { false }
     /// loop {
     ///     if waiting() {
     ///         continue;
     ///     } else {
     ///         // Do something useful
     ///     }
+    ///     # break;
     /// }
     /// ```
     /// Could be rewritten as
     ///
     /// ```rust
+    /// # fn waiting() -> bool { false }
     /// loop {
     ///     if waiting() {
     ///         continue;
     ///     }
     ///     // Do something useful
+    ///     # break;
     /// }
     /// ```
+    #[clippy::version = "pre 1.29.0"]
     pub NEEDLESS_CONTINUE,
     pedantic,
     "`continue` statements that can be replaced by a rearrangement of code"
 declare_lint_pass!(NeedlessContinue => [NEEDLESS_CONTINUE]);
 
 impl EarlyLintPass for NeedlessContinue {
-    fn check_expr(&mut self, ctx: &EarlyContext<'_>, expr: &ast::Expr) {
-        if !in_macro_or_desugar(expr.span) {
-            check_and_warn(ctx, expr);
+    fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &ast::Expr) {
+        if !expr.span.from_expansion() {
+            check_and_warn(cx, expr);
         }
     }
 }
@@ -171,7 +181,7 @@ fn check_expr(&mut self, ctx: &EarlyContext<'_>, expr: &ast::Expr) {
 /// - The expression node is a block with the first statement being a
 /// `continue`.
 fn needless_continue_in_else(else_expr: &ast::Expr, label: Option<&ast::Label>) -> bool {
-    match else_expr.node {
+    match else_expr.kind {
         ast::ExprKind::Block(ref else_block, _) => is_first_block_stmt_continue(else_block, label),
         ast::ExprKind::Continue(l) => compare_labels(label, l.as_ref()),
         _ => false,
@@ -179,9 +189,9 @@ fn needless_continue_in_else(else_expr: &ast::Expr, label: Option<&ast::Label>)
 }
 
 fn is_first_block_stmt_continue(block: &ast::Block, label: Option<&ast::Label>) -> bool {
-    block.stmts.get(0).map_or(false, |stmt| match stmt.node {
+    block.stmts.get(0).map_or(false, |stmt| match stmt.kind {
         ast::StmtKind::Semi(ref e) | ast::StmtKind::Expr(ref e) => {
-            if let ast::ExprKind::Continue(ref l) = e.node {
+            if let ast::ExprKind::Continue(ref l) = e.kind {
                 compare_labels(label, l.as_ref())
             } else {
                 false
@@ -209,12 +219,11 @@ fn with_loop_block<F>(expr: &ast::Expr, mut func: F)
 where
     F: FnMut(&ast::Block, Option<&ast::Label>),
 {
-    match expr.node {
-        ast::ExprKind::While(_, ref loop_block, ref label)
-        | ast::ExprKind::WhileLet(_, _, ref loop_block, ref label)
-        | ast::ExprKind::ForLoop(_, _, ref loop_block, ref label)
-        | ast::ExprKind::Loop(ref loop_block, ref label) => func(loop_block, label.as_ref()),
-        _ => {},
+    if let ast::ExprKind::While(_, loop_block, label)
+    | ast::ExprKind::ForLoop(_, _, loop_block, label)
+    | ast::ExprKind::Loop(loop_block, label, ..) = &expr.kind
+    {
+        func(loop_block, label.as_ref());
     }
 }
 
@@ -230,9 +239,9 @@ fn with_if_expr<F>(stmt: &ast::Stmt, mut func: F)
 where
     F: FnMut(&ast::Expr, &ast::Expr, &ast::Block, &ast::Expr),
 {
-    match stmt.node {
+    match stmt.kind {
         ast::StmtKind::Semi(ref e) | ast::StmtKind::Expr(ref e) => {
-            if let ast::ExprKind::If(ref cond, ref if_block, Some(ref else_expr)) = e.node {
+            if let ast::ExprKind::If(ref cond, ref if_block, Some(ref else_expr)) = e.kind {
                 func(e, cond, if_block, else_expr);
             }
         },
@@ -261,96 +270,120 @@ struct LintData<'a> {
     /// The 0-based index of the `if` statement in the containing loop block.
     stmt_idx: usize,
     /// The statements of the loop block.
-    block_stmts: &'a [ast::Stmt],
+    loop_block: &'a ast::Block,
 }
 
-const MSG_REDUNDANT_ELSE_BLOCK: &str = "This else block is redundant.\n";
+const MSG_REDUNDANT_CONTINUE_EXPRESSION: &str = "this `continue` expression is redundant";
+
+const MSG_REDUNDANT_ELSE_BLOCK: &str = "this `else` block is redundant";
 
-const MSG_ELSE_BLOCK_NOT_NEEDED: &str = "There is no need for an explicit `else` block for this `if` \
-                                         expression\n";
+const MSG_ELSE_BLOCK_NOT_NEEDED: &str = "there is no need for an explicit `else` block for this `if` \
+                                         expression";
 
-const DROP_ELSE_BLOCK_AND_MERGE_MSG: &str = "Consider dropping the else clause and merging the code that \
-                                             follows (in the loop) with the if block, like so:\n";
+const DROP_ELSE_BLOCK_AND_MERGE_MSG: &str = "consider dropping the `else` clause and merging the code that \
+                                             follows (in the loop) with the `if` block";
 
-const DROP_ELSE_BLOCK_MSG: &str = "Consider dropping the else clause, and moving out the code in the else \
-                                   block, like so:\n";
+const DROP_ELSE_BLOCK_MSG: &str = "consider dropping the `else` clause";
 
-fn emit_warning<'a>(ctx: &EarlyContext<'_>, data: &'a LintData<'_>, header: &str, typ: LintType) {
+const DROP_CONTINUE_EXPRESSION_MSG: &str = "consider dropping the `continue` expression";
+
+fn emit_warning<'a>(cx: &EarlyContext<'_>, data: &'a LintData<'_>, header: &str, typ: LintType) {
     // snip    is the whole *help* message that appears after the warning.
     // message is the warning message.
     // expr    is the expression which the lint warning message refers to.
     let (snip, message, expr) = match typ {
         LintType::ContinueInsideElseBlock => (
-            suggestion_snippet_for_continue_inside_else(ctx, data, header),
+            suggestion_snippet_for_continue_inside_else(cx, data),
             MSG_REDUNDANT_ELSE_BLOCK,
             data.else_expr,
         ),
         LintType::ContinueInsideThenBlock => (
-            suggestion_snippet_for_continue_inside_if(ctx, data, header),
+            suggestion_snippet_for_continue_inside_if(cx, data),
             MSG_ELSE_BLOCK_NOT_NEEDED,
             data.if_expr,
         ),
     };
-    span_help_and_lint(ctx, NEEDLESS_CONTINUE, expr.span, message, &snip);
+    span_lint_and_help(
+        cx,
+        NEEDLESS_CONTINUE,
+        expr.span,
+        message,
+        None,
+        &format!("{}\n{}", header, snip),
+    );
 }
 
-fn suggestion_snippet_for_continue_inside_if<'a>(
-    ctx: &EarlyContext<'_>,
-    data: &'a LintData<'_>,
-    header: &str,
-) -> String {
-    let cond_code = snippet(ctx, data.if_cond.span, "..");
-
-    let if_code = format!("if {} {{\n    continue;\n}}\n", cond_code);
-    /* ^^^^--- Four spaces of indentation. */
-    // region B
-    let else_code = snippet(ctx, data.else_expr.span, "..").into_owned();
-    let else_code = erode_block(&else_code);
-    let else_code = trim_multiline(Cow::from(else_code), false);
-
-    let mut ret = String::from(header);
-    ret.push_str(&if_code);
-    ret.push_str(&else_code);
-    ret.push_str("\n...");
-    ret
+fn suggestion_snippet_for_continue_inside_if<'a>(cx: &EarlyContext<'_>, data: &'a LintData<'_>) -> String {
+    let cond_code = snippet(cx, data.if_cond.span, "..");
+
+    let continue_code = snippet_block(cx, data.if_block.span, "..", Some(data.if_expr.span));
+
+    let else_code = snippet_block(cx, data.else_expr.span, "..", Some(data.if_expr.span));
+
+    let indent_if = indent_of(cx, data.if_expr.span).unwrap_or(0);
+    format!(
+        "{indent}if {} {}\n{indent}{}",
+        cond_code,
+        continue_code,
+        else_code,
+        indent = " ".repeat(indent_if),
+    )
 }
 
-fn suggestion_snippet_for_continue_inside_else<'a>(
-    ctx: &EarlyContext<'_>,
-    data: &'a LintData<'_>,
-    header: &str,
-) -> String {
-    let cond_code = snippet(ctx, data.if_cond.span, "..");
-    let mut if_code = format!("if {} {{\n", cond_code);
+fn suggestion_snippet_for_continue_inside_else<'a>(cx: &EarlyContext<'_>, data: &'a LintData<'_>) -> String {
+    let cond_code = snippet(cx, data.if_cond.span, "..");
 
     // Region B
-    let block_code = &snippet(ctx, data.if_block.span, "..").into_owned();
-    let block_code = erode_block(block_code);
-    let block_code = trim_multiline(Cow::from(block_code), false);
-
-    if_code.push_str(&block_code);
+    let block_code = erode_from_back(&snippet_block(cx, data.if_block.span, "..", Some(data.if_expr.span)));
 
     // Region C
     // These is the code in the loop block that follows the if/else construction
     // we are complaining about. We want to pull all of this code into the
     // `then` block of the `if` statement.
-    let to_annex = data.block_stmts[data.stmt_idx + 1..]
+    let indent = span_of_first_expr_in_block(data.if_block)
+        .and_then(|span| indent_of(cx, span))
+        .unwrap_or(0);
+    let to_annex = data.loop_block.stmts[data.stmt_idx + 1..]
         .iter()
-        .map(|stmt| original_sp(stmt.span, DUMMY_SP))
-        .map(|span| snippet_block(ctx, span, "..").into_owned())
+        .map(|stmt| {
+            let span = cx.sess().source_map().stmt_span(stmt.span, data.loop_block.span);
+            let snip = snippet_block(cx, span, "..", None).into_owned();
+            snip.lines()
+                .map(|line| format!("{}{}", " ".repeat(indent), line))
+                .collect::<Vec<_>>()
+                .join("\n")
+        })
         .collect::<Vec<_>>()
         .join("\n");
 
-    let mut ret = String::from(header);
-
-    ret.push_str(&if_code);
-    ret.push_str("\n// Merged code follows...");
-    ret.push_str(&to_annex);
-    ret.push_str("\n}\n");
-    ret
+    let indent_if = indent_of(cx, data.if_expr.span).unwrap_or(0);
+    format!(
+        "{indent_if}if {} {}\n{indent}// merged code follows:\n{}\n{indent_if}}}",
+        cond_code,
+        block_code,
+        to_annex,
+        indent = " ".repeat(indent),
+        indent_if = " ".repeat(indent_if),
+    )
 }
 
-fn check_and_warn<'a>(ctx: &EarlyContext<'_>, expr: &'a ast::Expr) {
+fn check_and_warn<'a>(cx: &EarlyContext<'_>, expr: &'a ast::Expr) {
+    if_chain! {
+        if let ast::ExprKind::Loop(loop_block, ..) = &expr.kind;
+        if let Some(last_stmt) = loop_block.stmts.last();
+        if let ast::StmtKind::Expr(inner_expr) | ast::StmtKind::Semi(inner_expr) = &last_stmt.kind;
+        if let ast::ExprKind::Continue(_) = inner_expr.kind;
+        then {
+            span_lint_and_help(
+                cx,
+                NEEDLESS_CONTINUE,
+                last_stmt.span,
+                MSG_REDUNDANT_CONTINUE_EXPRESSION,
+                None,
+                DROP_CONTINUE_EXPRESSION_MSG,
+            );
+        }
+    }
     with_loop_block(expr, |loop_block, label| {
         for (i, stmt) in loop_block.stmts.iter().enumerate() {
             with_if_expr(stmt, |if_expr, cond, then_block, else_expr| {
@@ -360,26 +393,26 @@ fn check_and_warn<'a>(ctx: &EarlyContext<'_>, expr: &'a ast::Expr) {
                     if_cond: cond,
                     if_block: then_block,
                     else_expr,
-                    block_stmts: &loop_block.stmts,
+                    loop_block,
                 };
                 if needless_continue_in_else(else_expr, label) {
                     emit_warning(
-                        ctx,
+                        cx,
                         data,
                         DROP_ELSE_BLOCK_AND_MERGE_MSG,
                         LintType::ContinueInsideElseBlock,
                     );
                 } else if is_first_block_stmt_continue(then_block, label) {
-                    emit_warning(ctx, data, DROP_ELSE_BLOCK_MSG, LintType::ContinueInsideThenBlock);
+                    emit_warning(cx, data, DROP_ELSE_BLOCK_MSG, LintType::ContinueInsideThenBlock);
                 }
             });
         }
     });
 }
 
-/// Eats at `s` from the end till a closing brace `}` is encountered, and then
-/// continues eating till a non-whitespace character is found.
-/// e.g., the string
+/// Eats at `s` from the end till a closing brace `}` is encountered, and then continues eating
+/// till a non-whitespace character is found.  e.g., the string. If no closing `}` is present, the
+/// string will be preserved.
 ///
 /// ```rust
 /// {
@@ -389,15 +422,13 @@ fn check_and_warn<'a>(ctx: &EarlyContext<'_>, expr: &'a ast::Expr) {
 ///
 /// is transformed to
 ///
-/// ```ignore
+/// ```text
 ///     {
 ///         let x = 5;
 /// ```
-///
-/// NOTE: when there is no closing brace in `s`, `s` is _not_ preserved, i.e.,
-/// an empty string will be returned in that case.
-pub fn erode_from_back(s: &str) -> String {
-    let mut ret = String::from(s);
+#[must_use]
+fn erode_from_back(s: &str) -> String {
+    let mut ret = s.to_string();
     while ret.pop().map_or(false, |c| c != '}') {}
     while let Some(c) = ret.pop() {
         if !c.is_whitespace() {
@@ -405,39 +436,44 @@ pub fn erode_from_back(s: &str) -> String {
             break;
         }
     }
-    ret
+    if ret.is_empty() { s.to_string() } else { ret }
 }
 
-/// Eats at `s` from the front by first skipping all leading whitespace. Then,
-/// any number of opening braces are eaten, followed by any number of newlines.
-/// e.g.,  the string
-///
-/// ```ignore
-///         {
-///             something();
-///             inside_a_block();
-///         }
-/// ```
-///
-/// is transformed to
-///
-/// ```ignore
-///             something();
-///             inside_a_block();
-///         }
-/// ```
-pub fn erode_from_front(s: &str) -> String {
-    s.chars()
-        .skip_while(|c| c.is_whitespace())
-        .skip_while(|c| *c == '{')
-        .skip_while(|c| *c == '\n')
-        .collect::<String>()
+fn span_of_first_expr_in_block(block: &ast::Block) -> Option<Span> {
+    block.stmts.get(0).map(|stmt| stmt.span)
 }
 
-/// If `s` contains the code for a block, delimited by braces, this function
-/// tries to get the contents of the block. If there is no closing brace
-/// present,
-/// an empty string is returned.
-pub fn erode_block(s: &str) -> String {
-    erode_from_back(&erode_from_front(s))
+#[cfg(test)]
+mod test {
+    use super::erode_from_back;
+
+    #[test]
+    #[rustfmt::skip]
+    fn test_erode_from_back() {
+        let input = "\
+{
+    let x = 5;
+    let y = format!(\"{}\", 42);
+}";
+
+        let expected = "\
+{
+    let x = 5;
+    let y = format!(\"{}\", 42);";
+
+        let got = erode_from_back(input);
+        assert_eq!(expected, got);
+    }
+
+    #[test]
+    #[rustfmt::skip]
+    fn test_erode_from_back_no_brace() {
+        let input = "\
+let x = 5;
+let y = something();
+";
+        let expected = input;
+        let got = erode_from_back(input);
+        assert_eq!(expected, got);
+    }
 }