]> git.lizzy.rs Git - rust.git/blobdiff - clippy_lints/src/write.rs
Auto merge of #3680 - g-bartoszek:needless-bool-else-if-brackets, r=oli-obk
[rust.git] / clippy_lints / src / write.rs
index 06a4f6cb39eaeb513de984e5cf22e78c32e874e2..c8c291c8cc873a87d5b69d13531e568e8ff3a42d 100644 (file)
@@ -1,10 +1,11 @@
-use crate::utils::{snippet, span_lint, span_lint_and_sugg};
+use crate::utils::{snippet_with_applicability, span_lint, span_lint_and_sugg};
 use rustc::lint::{EarlyContext, EarlyLintPass, LintArray, LintPass};
 use rustc::{declare_tool_lint, lint_array};
+use rustc_errors::Applicability;
 use std::borrow::Cow;
 use syntax::ast::*;
 use syntax::parse::{parser, token};
-use syntax::tokenstream::{ThinTokenStream, TokenStream};
+use syntax::tokenstream::TokenStream;
 
 /// **What it does:** This lint warns when you use `println!("")` to
 /// print a newline.
@@ -189,13 +190,14 @@ fn check_mac(&mut self, cx: &EarlyContext<'_>, mac: &Mac) {
                         "using `println!(\"\")`",
                         "replace it with",
                         "println!()".to_string(),
+                        Applicability::MachineApplicable,
                     );
                 }
             }
         } else if mac.node.path == "print" {
             span_lint(cx, PRINT_STDOUT, mac.span, "use of `print!`");
             if let Some(fmtstr) = check_tts(cx, &mac.node.tts, false).0 {
-                if fmtstr.ends_with("\\n") && !fmtstr.ends_with("\\n\\n") {
+                if check_newlines(&fmtstr) {
                     span_lint(
                         cx,
                         PRINT_WITH_NEWLINE,
@@ -207,7 +209,7 @@ fn check_mac(&mut self, cx: &EarlyContext<'_>, mac: &Mac) {
             }
         } else if mac.node.path == "write" {
             if let Some(fmtstr) = check_tts(cx, &mac.node.tts, true).0 {
-                if fmtstr.ends_with("\\n") && !fmtstr.ends_with("\\n\\n") {
+                if check_newlines(&fmtstr) {
                     span_lint(
                         cx,
                         WRITE_WITH_NEWLINE,
@@ -221,9 +223,14 @@ fn check_mac(&mut self, cx: &EarlyContext<'_>, mac: &Mac) {
             let check_tts = check_tts(cx, &mac.node.tts, true);
             if let Some(fmtstr) = check_tts.0 {
                 if fmtstr == "" {
-                    let suggestion = check_tts
-                        .1
-                        .map_or(Cow::Borrowed("v"), |expr| snippet(cx, expr.span, "v"));
+                    let mut applicability = Applicability::MachineApplicable;
+                    let suggestion = check_tts.1.map_or_else(
+                        move || {
+                            applicability = Applicability::HasPlaceholders;
+                            Cow::Borrowed("v")
+                        },
+                        move |expr| snippet_with_applicability(cx, expr.span, "v", &mut applicability),
+                    );
 
                     span_lint_and_sugg(
                         cx,
@@ -232,6 +239,7 @@ fn check_mac(&mut self, cx: &EarlyContext<'_>, mac: &Mac) {
                         format!("using `writeln!({}, \"\")`", suggestion).as_str(),
                         "replace it with",
                         format!("writeln!({})", suggestion),
+                        applicability,
                     );
                 }
             }
@@ -239,8 +247,23 @@ fn check_mac(&mut self, cx: &EarlyContext<'_>, mac: &Mac) {
     }
 }
 
-fn check_tts<'a>(cx: &EarlyContext<'a>, tts: &ThinTokenStream, is_write: bool) -> (Option<String>, Option<Expr>) {
-    let tts = TokenStream::from(tts.clone());
+/// Checks the arguments of `print[ln]!` and `write[ln]!` calls. It will return a tuple of two
+/// options. The first part of the tuple is `format_str` of the macros. The second part of the tuple
+/// is in the `write[ln]!` case the expression the `format_str` should be written to.
+///
+/// Example:
+///
+/// Calling this function on
+/// ```rust,ignore
+/// writeln!(buf, "string to write: {}", something)
+/// ```
+/// will return
+/// ```rust,ignore
+/// (Some("string to write: {}"), Some(buf))
+/// ```
+fn check_tts<'a>(cx: &EarlyContext<'a>, tts: &TokenStream, is_write: bool) -> (Option<String>, Option<Expr>) {
+    use fmt_macros::*;
+    let tts = tts.clone();
     let mut parser = parser::Parser::new(&cx.sess.parse_sess, tts, None, false, false);
     let mut expr: Option<Expr> = None;
     if is_write {
@@ -258,10 +281,9 @@ fn check_tts<'a>(cx: &EarlyContext<'a>, tts: &ThinTokenStream, is_write: bool) -
         Ok(token) => token.0.to_string(),
         Err(_) => return (None, expr),
     };
-    use fmt_macros::*;
     let tmp = fmtstr.clone();
     let mut args = vec![];
-    let mut fmt_parser = Parser::new(&tmp, None);
+    let mut fmt_parser = Parser::new(&tmp, None, Vec::new(), false);
     while let Some(piece) = fmt_parser.next() {
         if !fmt_parser.errors.is_empty() {
             return (None, expr);
@@ -277,14 +299,6 @@ fn check_tts<'a>(cx: &EarlyContext<'a>, tts: &ThinTokenStream, is_write: bool) -
     let lint = if is_write { WRITE_LITERAL } else { PRINT_LITERAL };
     let mut idx = 0;
     loop {
-        if !parser.eat(&token::Comma) {
-            assert!(parser.eat(&token::Eof));
-            return (Some(fmtstr), expr);
-        }
-        let token_expr = match parser.parse_expr().map_err(|mut err| err.cancel()) {
-            Ok(expr) => expr,
-            Err(_) => return (Some(fmtstr), None),
-        };
         const SIMPLE: FormatSpec<'_> = FormatSpec {
             fill: None,
             align: AlignUnknown,
@@ -293,15 +307,24 @@ fn check_tts<'a>(cx: &EarlyContext<'a>, tts: &ThinTokenStream, is_write: bool) -
             width: CountImplied,
             ty: "",
         };
+        if !parser.eat(&token::Comma) {
+            return (Some(fmtstr), expr);
+        }
+        let token_expr = match parser.parse_expr().map_err(|mut err| err.cancel()) {
+            Ok(expr) => expr,
+            Err(_) => return (Some(fmtstr), None),
+        };
         match &token_expr.node {
             ExprKind::Lit(_) => {
                 let mut all_simple = true;
                 let mut seen = false;
                 for arg in &args {
                     match arg.position {
-                        ArgumentImplicitlyIs(n) | ArgumentIs(n) => if n == idx {
-                            all_simple &= arg.format == SIMPLE;
-                            seen = true;
+                        ArgumentImplicitlyIs(n) | ArgumentIs(n) => {
+                            if n == idx {
+                                all_simple &= arg.format == SIMPLE;
+                                seen = true;
+                            }
                         },
                         ArgumentNamed(_) => {},
                     }
@@ -319,9 +342,11 @@ fn check_tts<'a>(cx: &EarlyContext<'a>, tts: &ThinTokenStream, is_write: bool) -
                         for arg in &args {
                             match arg.position {
                                 ArgumentImplicitlyIs(_) | ArgumentIs(_) => {},
-                                ArgumentNamed(name) => if *p == name {
-                                    seen = true;
-                                    all_simple &= arg.format == SIMPLE;
+                                ArgumentNamed(name) => {
+                                    if *p == name {
+                                        seen = true;
+                                        all_simple &= arg.format == SIMPLE;
+                                    }
                                 },
                             }
                         }
@@ -335,3 +360,29 @@ fn check_tts<'a>(cx: &EarlyContext<'a>, tts: &ThinTokenStream, is_write: bool) -
         }
     }
 }
+
+// Checks if `s` constains a single newline that terminates it
+fn check_newlines(s: &str) -> bool {
+    if s.len() < 2 {
+        return false;
+    }
+
+    let bytes = s.as_bytes();
+    if bytes[bytes.len() - 2] != b'\\' || bytes[bytes.len() - 1] != b'n' {
+        return false;
+    }
+
+    let mut escaping = false;
+    for (index, &byte) in bytes.iter().enumerate() {
+        if escaping {
+            if byte == b'n' {
+                return index == bytes.len() - 1;
+            }
+            escaping = false;
+        } else if byte == b'\\' {
+            escaping = true;
+        }
+    }
+
+    false
+}