about summary refs log tree commit diff
diff options
context:
space:
mode:
authorJason Newcomb <jsnewcomb@pm.me>2021-03-01 16:31:04 -0500
committerJason Newcomb <jsnewcomb@pm.me>2021-03-16 12:13:46 -0400
commita7fa2a6fa88477ab2542bb15347e3085d9547f95 (patch)
treed285c45ffabcf00104c6b7d9b62c3b42103618ae
parent4c1047167d20460dcb84e3d947787ce91d5fd0d4 (diff)
downloadrust-a7fa2a6fa88477ab2542bb15347e3085d9547f95.tar.gz
rust-a7fa2a6fa88477ab2542bb15347e3085d9547f95.zip
Add suggestion to `write_literal` and `print_literal`
Don't lint on a mixture of raw and regular strings
Fix spans in format strings
-rw-r--r--clippy_lints/src/write.rs62
-rw-r--r--tests/ui/print_literal.stderr70
-rw-r--r--tests/ui/write_literal.stderr70
-rw-r--r--tests/ui/write_literal_2.rs27
-rw-r--r--tests/ui/write_literal_2.stderr106
5 files changed, 305 insertions, 30 deletions
diff --git a/clippy_lints/src/write.rs b/clippy_lints/src/write.rs
index 210ddc7465f..e416eab7914 100644
--- a/clippy_lints/src/write.rs
+++ b/clippy_lints/src/write.rs
@@ -1,10 +1,11 @@
 use std::borrow::Cow;
-use std::ops::Range;
+use std::iter;
+use std::ops::{Deref, Range};
 
 use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg, span_lint_and_then};
 use clippy_utils::source::{snippet_opt, snippet_with_applicability};
-use rustc_ast::ast::{Expr, ExprKind, ImplKind, Item, ItemKind, LitKind, MacCall, Path, StrLit, StrStyle};
-use rustc_ast::token;
+use rustc_ast::ast::{Expr, ExprKind, ImplKind, Item, ItemKind, MacCall, Path, StrLit, StrStyle};
+use rustc_ast::token::{self, LitKind};
 use rustc_ast::tokenstream::TokenStream;
 use rustc_errors::Applicability;
 use rustc_lexer::unescape::{self, EscapeError};
@@ -438,7 +439,7 @@ impl Write {
     fn parse_fmt_string(&self, cx: &EarlyContext<'_>, str: &StrLit) -> Option<SimpleFormatArgs> {
         use rustc_parse_format::{ParseMode, Parser, Piece};
 
-        let str_sym = str.symbol.as_str();
+        let str_sym = str.symbol_unescaped.as_str();
         let style = match str.style {
             StrStyle::Cooked => None,
             StrStyle::Raw(n) => Some(n as usize),
@@ -514,21 +515,17 @@ impl Write {
             if !parser.eat(&token::Comma) {
                 return (Some(fmtstr), expr);
             }
+
+            let comma_span = parser.prev_token.span;
             let token_expr = if let Ok(expr) = parser.parse_expr().map_err(|mut err| err.cancel()) {
                 expr
             } else {
                 return (Some(fmtstr), None);
             };
-            let (fmt_spans, span) = match &token_expr.kind {
-                ExprKind::Lit(lit) if !matches!(lit.kind, LitKind::Int(..) | LitKind::Float(..)) => {
-                    (unnamed_args.next().unwrap_or(&[]), token_expr.span)
-                },
+            let (fmt_spans, lit) = match &token_expr.kind {
+                ExprKind::Lit(lit) => (unnamed_args.next().unwrap_or(&[]), lit),
                 ExprKind::Assign(lhs, rhs, _) => match (&lhs.kind, &rhs.kind) {
-                    (ExprKind::Path(_, p), ExprKind::Lit(lit))
-                        if !matches!(lit.kind, LitKind::Int(..) | LitKind::Float(..)) =>
-                    {
-                        (args.get_named(p), rhs.span)
-                    },
+                    (ExprKind::Path(_, p), ExprKind::Lit(lit)) => (args.get_named(p), lit),
                     _ => continue,
                 },
                 _ => {
@@ -537,8 +534,45 @@ impl Write {
                 },
             };
 
+            let replacement: String = match lit.token.kind {
+                LitKind::Integer | LitKind::Float | LitKind::Err => continue,
+                LitKind::StrRaw(_) | LitKind::ByteStrRaw(_) if matches!(fmtstr.style, StrStyle::Raw(_)) => {
+                    lit.token.symbol.as_str().replace("{", "{{").replace("}", "}}")
+                },
+                LitKind::Str | LitKind::ByteStr if matches!(fmtstr.style, StrStyle::Cooked) => {
+                    lit.token.symbol.as_str().replace("{", "{{").replace("}", "}}")
+                },
+                LitKind::StrRaw(_) | LitKind::Str | LitKind::ByteStrRaw(_) | LitKind::ByteStr => continue,
+                LitKind::Byte | LitKind::Char => match lit.token.symbol.as_str().deref() {
+                    "\"" if matches!(fmtstr.style, StrStyle::Cooked) => "\\\"",
+                    "\"" if matches!(fmtstr.style, StrStyle::Raw(0)) => continue,
+                    "\\\\" if matches!(fmtstr.style, StrStyle::Raw(_)) => "\\",
+                    "\\'" => "'",
+                    "{" => "{{",
+                    "}" => "}}",
+                    x if matches!(fmtstr.style, StrStyle::Raw(_)) && x.starts_with("\\") => continue,
+                    x => x,
+                }
+                .into(),
+                LitKind::Bool => lit.token.symbol.as_str().deref().into(),
+            };
+
             if !fmt_spans.is_empty() {
-                span_lint(cx, lint, span, "literal with an empty format string");
+                span_lint_and_then(
+                    cx,
+                    lint,
+                    token_expr.span,
+                    "literal with an empty format string",
+                    |diag| {
+                        diag.multipart_suggestion(
+                            "try this",
+                            iter::once((comma_span.to(token_expr.span), String::new()))
+                                .chain(fmt_spans.iter().cloned().zip(iter::repeat(replacement)))
+                                .collect(),
+                            Applicability::MachineApplicable,
+                        );
+                    },
+                );
             }
         }
     }
diff --git a/tests/ui/print_literal.stderr b/tests/ui/print_literal.stderr
index e284aece236..54a4084c89e 100644
--- a/tests/ui/print_literal.stderr
+++ b/tests/ui/print_literal.stderr
@@ -5,66 +5,120 @@ LL |     print!("Hello {}", "world");
    |                        ^^^^^^^
    |
    = note: `-D clippy::print-literal` implied by `-D warnings`
+help: try this
+   |
+LL |     print!("Hello world");
+   |                   ^^^^^--
 
 error: literal with an empty format string
   --> $DIR/print_literal.rs:26:36
    |
 LL |     println!("Hello {} {}", world, "world");
    |                                    ^^^^^^^
+   |
+help: try this
+   |
+LL |     println!("Hello {} world", world);
+   |                        ^^^^^       --
 
 error: literal with an empty format string
   --> $DIR/print_literal.rs:27:26
    |
 LL |     println!("Hello {}", "world");
    |                          ^^^^^^^
+   |
+help: try this
+   |
+LL |     println!("Hello world");
+   |                     ^^^^^--
 
 error: literal with an empty format string
   --> $DIR/print_literal.rs:32:25
    |
 LL |     println!("{0} {1}", "hello", "world");
    |                         ^^^^^^^
+   |
+help: try this
+   |
+LL |     println!("hello {1}", "world");
+   |               ^^^^^    --
 
 error: literal with an empty format string
   --> $DIR/print_literal.rs:32:34
    |
 LL |     println!("{0} {1}", "hello", "world");
    |                                  ^^^^^^^
+   |
+help: try this
+   |
+LL |     println!("{0} world", "hello");
+   |                   ^^^^^         --
 
 error: literal with an empty format string
   --> $DIR/print_literal.rs:33:25
    |
 LL |     println!("{1} {0}", "hello", "world");
    |                         ^^^^^^^
+   |
+help: try this
+   |
+LL |     println!("{1} hello", "world");
+   |                   ^^^^^--
 
 error: literal with an empty format string
   --> $DIR/print_literal.rs:33:34
    |
 LL |     println!("{1} {0}", "hello", "world");
    |                                  ^^^^^^^
+   |
+help: try this
+   |
+LL |     println!("world {0}", "hello");
+   |               ^^^^^             --
 
 error: literal with an empty format string
-  --> $DIR/print_literal.rs:36:35
+  --> $DIR/print_literal.rs:36:29
    |
 LL |     println!("{foo} {bar}", foo = "hello", bar = "world");
-   |                                   ^^^^^^^
+   |                             ^^^^^^^^^^^^^
+   |
+help: try this
+   |
+LL |     println!("hello {bar}", bar = "world");
+   |               ^^^^^      --
 
 error: literal with an empty format string
-  --> $DIR/print_literal.rs:36:50
+  --> $DIR/print_literal.rs:36:44
    |
 LL |     println!("{foo} {bar}", foo = "hello", bar = "world");
-   |                                                  ^^^^^^^
+   |                                            ^^^^^^^^^^^^^
+   |
+help: try this
+   |
+LL |     println!("{foo} world", foo = "hello");
+   |                     ^^^^^               --
 
 error: literal with an empty format string
-  --> $DIR/print_literal.rs:37:35
+  --> $DIR/print_literal.rs:37:29
    |
 LL |     println!("{bar} {foo}", foo = "hello", bar = "world");
-   |                                   ^^^^^^^
+   |                             ^^^^^^^^^^^^^
+   |
+help: try this
+   |
+LL |     println!("{bar} hello", bar = "world");
+   |                     ^^^^^--
 
 error: literal with an empty format string
-  --> $DIR/print_literal.rs:37:50
+  --> $DIR/print_literal.rs:37:44
    |
 LL |     println!("{bar} {foo}", foo = "hello", bar = "world");
-   |                                                  ^^^^^^^
+   |                                            ^^^^^^^^^^^^^
+   |
+help: try this
+   |
+LL |     println!("world {foo}", foo = "hello");
+   |               ^^^^^                     --
 
 error: aborting due to 11 previous errors
 
diff --git a/tests/ui/write_literal.stderr b/tests/ui/write_literal.stderr
index e54d89ecf29..507a78e8280 100644
--- a/tests/ui/write_literal.stderr
+++ b/tests/ui/write_literal.stderr
@@ -5,66 +5,120 @@ LL |     write!(&mut v, "Hello {}", "world");
    |                                ^^^^^^^
    |
    = note: `-D clippy::write-literal` implied by `-D warnings`
+help: try this
+   |
+LL |     write!(&mut v, "Hello world");
+   |                           ^^^^^--
 
 error: literal with an empty format string
   --> $DIR/write_literal.rs:31:44
    |
 LL |     writeln!(&mut v, "Hello {} {}", world, "world");
    |                                            ^^^^^^^
+   |
+help: try this
+   |
+LL |     writeln!(&mut v, "Hello {} world", world);
+   |                                ^^^^^       --
 
 error: literal with an empty format string
   --> $DIR/write_literal.rs:32:34
    |
 LL |     writeln!(&mut v, "Hello {}", "world");
    |                                  ^^^^^^^
+   |
+help: try this
+   |
+LL |     writeln!(&mut v, "Hello world");
+   |                             ^^^^^--
 
 error: literal with an empty format string
   --> $DIR/write_literal.rs:37:33
    |
 LL |     writeln!(&mut v, "{0} {1}", "hello", "world");
    |                                 ^^^^^^^
+   |
+help: try this
+   |
+LL |     writeln!(&mut v, "hello {1}", "world");
+   |                       ^^^^^    --
 
 error: literal with an empty format string
   --> $DIR/write_literal.rs:37:42
    |
 LL |     writeln!(&mut v, "{0} {1}", "hello", "world");
    |                                          ^^^^^^^
+   |
+help: try this
+   |
+LL |     writeln!(&mut v, "{0} world", "hello");
+   |                           ^^^^^         --
 
 error: literal with an empty format string
   --> $DIR/write_literal.rs:38:33
    |
 LL |     writeln!(&mut v, "{1} {0}", "hello", "world");
    |                                 ^^^^^^^
+   |
+help: try this
+   |
+LL |     writeln!(&mut v, "{1} hello", "world");
+   |                           ^^^^^--
 
 error: literal with an empty format string
   --> $DIR/write_literal.rs:38:42
    |
 LL |     writeln!(&mut v, "{1} {0}", "hello", "world");
    |                                          ^^^^^^^
+   |
+help: try this
+   |
+LL |     writeln!(&mut v, "world {0}", "hello");
+   |                       ^^^^^             --
 
 error: literal with an empty format string
-  --> $DIR/write_literal.rs:41:43
+  --> $DIR/write_literal.rs:41:37
    |
 LL |     writeln!(&mut v, "{foo} {bar}", foo = "hello", bar = "world");
-   |                                           ^^^^^^^
+   |                                     ^^^^^^^^^^^^^
+   |
+help: try this
+   |
+LL |     writeln!(&mut v, "hello {bar}", bar = "world");
+   |                       ^^^^^      --
 
 error: literal with an empty format string
-  --> $DIR/write_literal.rs:41:58
+  --> $DIR/write_literal.rs:41:52
    |
 LL |     writeln!(&mut v, "{foo} {bar}", foo = "hello", bar = "world");
-   |                                                          ^^^^^^^
+   |                                                    ^^^^^^^^^^^^^
+   |
+help: try this
+   |
+LL |     writeln!(&mut v, "{foo} world", foo = "hello");
+   |                             ^^^^^               --
 
 error: literal with an empty format string
-  --> $DIR/write_literal.rs:42:43
+  --> $DIR/write_literal.rs:42:37
    |
 LL |     writeln!(&mut v, "{bar} {foo}", foo = "hello", bar = "world");
-   |                                           ^^^^^^^
+   |                                     ^^^^^^^^^^^^^
+   |
+help: try this
+   |
+LL |     writeln!(&mut v, "{bar} hello", bar = "world");
+   |                             ^^^^^--
 
 error: literal with an empty format string
-  --> $DIR/write_literal.rs:42:58
+  --> $DIR/write_literal.rs:42:52
    |
 LL |     writeln!(&mut v, "{bar} {foo}", foo = "hello", bar = "world");
-   |                                                          ^^^^^^^
+   |                                                    ^^^^^^^^^^^^^
+   |
+help: try this
+   |
+LL |     writeln!(&mut v, "world {foo}", foo = "hello");
+   |                       ^^^^^                     --
 
 error: aborting due to 11 previous errors
 
diff --git a/tests/ui/write_literal_2.rs b/tests/ui/write_literal_2.rs
new file mode 100644
index 00000000000..f341e8215e1
--- /dev/null
+++ b/tests/ui/write_literal_2.rs
@@ -0,0 +1,27 @@
+#![allow(unused_must_use)]
+#![warn(clippy::write_literal)]
+
+use std::io::Write;
+
+fn main() {
+    let mut v = Vec::new();
+
+    writeln!(&mut v, "{}", "{hello}");
+    writeln!(&mut v, r"{}", r"{hello}");
+    writeln!(&mut v, "{}", '\'');
+    writeln!(&mut v, "{}", '"');
+    writeln!(&mut v, r"{}", '"'); // don't lint
+    writeln!(&mut v, r"{}", '\'');
+    writeln!(
+        &mut v,
+        "some {}",
+        "hello \
+        world!"
+    );
+    writeln!(
+        &mut v,
+        "some {}\
+        {} \\ {}",
+        "1", "2", "3",
+    );
+}
diff --git a/tests/ui/write_literal_2.stderr b/tests/ui/write_literal_2.stderr
new file mode 100644
index 00000000000..5b488358011
--- /dev/null
+++ b/tests/ui/write_literal_2.stderr
@@ -0,0 +1,106 @@
+error: literal with an empty format string
+  --> $DIR/write_literal_2.rs:9:28
+   |
+LL |     writeln!(&mut v, "{}", "{hello}");
+   |                            ^^^^^^^^^
+   |
+   = note: `-D clippy::write-literal` implied by `-D warnings`
+help: try this
+   |
+LL |     writeln!(&mut v, "{{hello}}");
+   |                       ^^^^^^^^^--
+
+error: literal with an empty format string
+  --> $DIR/write_literal_2.rs:10:29
+   |
+LL |     writeln!(&mut v, r"{}", r"{hello}");
+   |                             ^^^^^^^^^^
+   |
+help: try this
+   |
+LL |     writeln!(&mut v, r"{{hello}}");
+   |                        ^^^^^^^^^--
+
+error: literal with an empty format string
+  --> $DIR/write_literal_2.rs:11:28
+   |
+LL |     writeln!(&mut v, "{}", '/'');
+   |                            ^^^^
+   |
+help: try this
+   |
+LL |     writeln!(&mut v, "'");
+   |                       ^--
+
+error: literal with an empty format string
+  --> $DIR/write_literal_2.rs:12:28
+   |
+LL |     writeln!(&mut v, "{}", '"');
+   |                            ^^^
+   |
+help: try this
+   |
+LL |     writeln!(&mut v, "/"");
+   |                       ^^--
+
+error: literal with an empty format string
+  --> $DIR/write_literal_2.rs:14:29
+   |
+LL |     writeln!(&mut v, r"{}", '/'');
+   |                             ^^^^
+   |
+help: try this
+   |
+LL |     writeln!(&mut v, r"'");
+   |                        ^--
+
+error: literal with an empty format string
+  --> $DIR/write_literal_2.rs:18:9
+   |
+LL | /         "hello /
+LL | |         world!"
+   | |_______________^
+   |
+help: try this
+   |
+LL |         "some hello /
+LL |         world!"
+   |
+
+error: literal with an empty format string
+  --> $DIR/write_literal_2.rs:25:9
+   |
+LL |         "1", "2", "3",
+   |         ^^^
+   |
+help: try this
+   |
+LL |         "some 1{} / {}", "2", "3",
+   |               ^        --
+
+error: literal with an empty format string
+  --> $DIR/write_literal_2.rs:25:14
+   |
+LL |         "1", "2", "3",
+   |              ^^^
+   |
+help: try this
+   |
+LL |         2 / {}",
+LL |         "1", "3",
+   |
+
+error: literal with an empty format string
+  --> $DIR/write_literal_2.rs:25:19
+   |
+LL |         "1", "2", "3",
+   |                   ^^^
+   |
+help: try this
+   |
+LL |         {} / 3",
+LL |         "1", "2",
+   |
+
+error: aborting due to 9 previous errors
+