Skip to content

Commit a7fa2a6

Browse files
committed
Add suggestion to write_literal and print_literal
Don't lint on a mixture of raw and regular strings Fix spans in format strings
1 parent 4c10471 commit a7fa2a6

File tree

5 files changed

+305
-30
lines changed

5 files changed

+305
-30
lines changed

clippy_lints/src/write.rs

Lines changed: 48 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
use std::borrow::Cow;
2-
use std::ops::Range;
2+
use std::iter;
3+
use std::ops::{Deref, Range};
34

45
use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg, span_lint_and_then};
56
use clippy_utils::source::{snippet_opt, snippet_with_applicability};
6-
use rustc_ast::ast::{Expr, ExprKind, ImplKind, Item, ItemKind, LitKind, MacCall, Path, StrLit, StrStyle};
7-
use rustc_ast::token;
7+
use rustc_ast::ast::{Expr, ExprKind, ImplKind, Item, ItemKind, MacCall, Path, StrLit, StrStyle};
8+
use rustc_ast::token::{self, LitKind};
89
use rustc_ast::tokenstream::TokenStream;
910
use rustc_errors::Applicability;
1011
use rustc_lexer::unescape::{self, EscapeError};
@@ -438,7 +439,7 @@ impl Write {
438439
fn parse_fmt_string(&self, cx: &EarlyContext<'_>, str: &StrLit) -> Option<SimpleFormatArgs> {
439440
use rustc_parse_format::{ParseMode, Parser, Piece};
440441

441-
let str_sym = str.symbol.as_str();
442+
let str_sym = str.symbol_unescaped.as_str();
442443
let style = match str.style {
443444
StrStyle::Cooked => None,
444445
StrStyle::Raw(n) => Some(n as usize),
@@ -514,21 +515,17 @@ impl Write {
514515
if !parser.eat(&token::Comma) {
515516
return (Some(fmtstr), expr);
516517
}
518+
519+
let comma_span = parser.prev_token.span;
517520
let token_expr = if let Ok(expr) = parser.parse_expr().map_err(|mut err| err.cancel()) {
518521
expr
519522
} else {
520523
return (Some(fmtstr), None);
521524
};
522-
let (fmt_spans, span) = match &token_expr.kind {
523-
ExprKind::Lit(lit) if !matches!(lit.kind, LitKind::Int(..) | LitKind::Float(..)) => {
524-
(unnamed_args.next().unwrap_or(&[]), token_expr.span)
525-
},
525+
let (fmt_spans, lit) = match &token_expr.kind {
526+
ExprKind::Lit(lit) => (unnamed_args.next().unwrap_or(&[]), lit),
526527
ExprKind::Assign(lhs, rhs, _) => match (&lhs.kind, &rhs.kind) {
527-
(ExprKind::Path(_, p), ExprKind::Lit(lit))
528-
if !matches!(lit.kind, LitKind::Int(..) | LitKind::Float(..)) =>
529-
{
530-
(args.get_named(p), rhs.span)
531-
},
528+
(ExprKind::Path(_, p), ExprKind::Lit(lit)) => (args.get_named(p), lit),
532529
_ => continue,
533530
},
534531
_ => {
@@ -537,8 +534,45 @@ impl Write {
537534
},
538535
};
539536

537+
let replacement: String = match lit.token.kind {
538+
LitKind::Integer | LitKind::Float | LitKind::Err => continue,
539+
LitKind::StrRaw(_) | LitKind::ByteStrRaw(_) if matches!(fmtstr.style, StrStyle::Raw(_)) => {
540+
lit.token.symbol.as_str().replace("{", "{{").replace("}", "}}")
541+
},
542+
LitKind::Str | LitKind::ByteStr if matches!(fmtstr.style, StrStyle::Cooked) => {
543+
lit.token.symbol.as_str().replace("{", "{{").replace("}", "}}")
544+
},
545+
LitKind::StrRaw(_) | LitKind::Str | LitKind::ByteStrRaw(_) | LitKind::ByteStr => continue,
546+
LitKind::Byte | LitKind::Char => match lit.token.symbol.as_str().deref() {
547+
"\"" if matches!(fmtstr.style, StrStyle::Cooked) => "\\\"",
548+
"\"" if matches!(fmtstr.style, StrStyle::Raw(0)) => continue,
549+
"\\\\" if matches!(fmtstr.style, StrStyle::Raw(_)) => "\\",
550+
"\\'" => "'",
551+
"{" => "{{",
552+
"}" => "}}",
553+
x if matches!(fmtstr.style, StrStyle::Raw(_)) && x.starts_with("\\") => continue,
554+
x => x,
555+
}
556+
.into(),
557+
LitKind::Bool => lit.token.symbol.as_str().deref().into(),
558+
};
559+
540560
if !fmt_spans.is_empty() {
541-
span_lint(cx, lint, span, "literal with an empty format string");
561+
span_lint_and_then(
562+
cx,
563+
lint,
564+
token_expr.span,
565+
"literal with an empty format string",
566+
|diag| {
567+
diag.multipart_suggestion(
568+
"try this",
569+
iter::once((comma_span.to(token_expr.span), String::new()))
570+
.chain(fmt_spans.iter().cloned().zip(iter::repeat(replacement)))
571+
.collect(),
572+
Applicability::MachineApplicable,
573+
);
574+
},
575+
);
542576
}
543577
}
544578
}

tests/ui/print_literal.stderr

Lines changed: 62 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,66 +5,120 @@ LL | print!("Hello {}", "world");
55
| ^^^^^^^
66
|
77
= note: `-D clippy::print-literal` implied by `-D warnings`
8+
help: try this
9+
|
10+
LL | print!("Hello world");
11+
| ^^^^^--
812

913
error: literal with an empty format string
1014
--> $DIR/print_literal.rs:26:36
1115
|
1216
LL | println!("Hello {} {}", world, "world");
1317
| ^^^^^^^
18+
|
19+
help: try this
20+
|
21+
LL | println!("Hello {} world", world);
22+
| ^^^^^ --
1423

1524
error: literal with an empty format string
1625
--> $DIR/print_literal.rs:27:26
1726
|
1827
LL | println!("Hello {}", "world");
1928
| ^^^^^^^
29+
|
30+
help: try this
31+
|
32+
LL | println!("Hello world");
33+
| ^^^^^--
2034

2135
error: literal with an empty format string
2236
--> $DIR/print_literal.rs:32:25
2337
|
2438
LL | println!("{0} {1}", "hello", "world");
2539
| ^^^^^^^
40+
|
41+
help: try this
42+
|
43+
LL | println!("hello {1}", "world");
44+
| ^^^^^ --
2645

2746
error: literal with an empty format string
2847
--> $DIR/print_literal.rs:32:34
2948
|
3049
LL | println!("{0} {1}", "hello", "world");
3150
| ^^^^^^^
51+
|
52+
help: try this
53+
|
54+
LL | println!("{0} world", "hello");
55+
| ^^^^^ --
3256

3357
error: literal with an empty format string
3458
--> $DIR/print_literal.rs:33:25
3559
|
3660
LL | println!("{1} {0}", "hello", "world");
3761
| ^^^^^^^
62+
|
63+
help: try this
64+
|
65+
LL | println!("{1} hello", "world");
66+
| ^^^^^--
3867

3968
error: literal with an empty format string
4069
--> $DIR/print_literal.rs:33:34
4170
|
4271
LL | println!("{1} {0}", "hello", "world");
4372
| ^^^^^^^
73+
|
74+
help: try this
75+
|
76+
LL | println!("world {0}", "hello");
77+
| ^^^^^ --
4478

4579
error: literal with an empty format string
46-
--> $DIR/print_literal.rs:36:35
80+
--> $DIR/print_literal.rs:36:29
4781
|
4882
LL | println!("{foo} {bar}", foo = "hello", bar = "world");
49-
| ^^^^^^^
83+
| ^^^^^^^^^^^^^
84+
|
85+
help: try this
86+
|
87+
LL | println!("hello {bar}", bar = "world");
88+
| ^^^^^ --
5089

5190
error: literal with an empty format string
52-
--> $DIR/print_literal.rs:36:50
91+
--> $DIR/print_literal.rs:36:44
5392
|
5493
LL | println!("{foo} {bar}", foo = "hello", bar = "world");
55-
| ^^^^^^^
94+
| ^^^^^^^^^^^^^
95+
|
96+
help: try this
97+
|
98+
LL | println!("{foo} world", foo = "hello");
99+
| ^^^^^ --
56100

57101
error: literal with an empty format string
58-
--> $DIR/print_literal.rs:37:35
102+
--> $DIR/print_literal.rs:37:29
59103
|
60104
LL | println!("{bar} {foo}", foo = "hello", bar = "world");
61-
| ^^^^^^^
105+
| ^^^^^^^^^^^^^
106+
|
107+
help: try this
108+
|
109+
LL | println!("{bar} hello", bar = "world");
110+
| ^^^^^--
62111

63112
error: literal with an empty format string
64-
--> $DIR/print_literal.rs:37:50
113+
--> $DIR/print_literal.rs:37:44
65114
|
66115
LL | println!("{bar} {foo}", foo = "hello", bar = "world");
67-
| ^^^^^^^
116+
| ^^^^^^^^^^^^^
117+
|
118+
help: try this
119+
|
120+
LL | println!("world {foo}", foo = "hello");
121+
| ^^^^^ --
68122

69123
error: aborting due to 11 previous errors
70124

tests/ui/write_literal.stderr

Lines changed: 62 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,66 +5,120 @@ LL | write!(&mut v, "Hello {}", "world");
55
| ^^^^^^^
66
|
77
= note: `-D clippy::write-literal` implied by `-D warnings`
8+
help: try this
9+
|
10+
LL | write!(&mut v, "Hello world");
11+
| ^^^^^--
812

913
error: literal with an empty format string
1014
--> $DIR/write_literal.rs:31:44
1115
|
1216
LL | writeln!(&mut v, "Hello {} {}", world, "world");
1317
| ^^^^^^^
18+
|
19+
help: try this
20+
|
21+
LL | writeln!(&mut v, "Hello {} world", world);
22+
| ^^^^^ --
1423

1524
error: literal with an empty format string
1625
--> $DIR/write_literal.rs:32:34
1726
|
1827
LL | writeln!(&mut v, "Hello {}", "world");
1928
| ^^^^^^^
29+
|
30+
help: try this
31+
|
32+
LL | writeln!(&mut v, "Hello world");
33+
| ^^^^^--
2034

2135
error: literal with an empty format string
2236
--> $DIR/write_literal.rs:37:33
2337
|
2438
LL | writeln!(&mut v, "{0} {1}", "hello", "world");
2539
| ^^^^^^^
40+
|
41+
help: try this
42+
|
43+
LL | writeln!(&mut v, "hello {1}", "world");
44+
| ^^^^^ --
2645

2746
error: literal with an empty format string
2847
--> $DIR/write_literal.rs:37:42
2948
|
3049
LL | writeln!(&mut v, "{0} {1}", "hello", "world");
3150
| ^^^^^^^
51+
|
52+
help: try this
53+
|
54+
LL | writeln!(&mut v, "{0} world", "hello");
55+
| ^^^^^ --
3256

3357
error: literal with an empty format string
3458
--> $DIR/write_literal.rs:38:33
3559
|
3660
LL | writeln!(&mut v, "{1} {0}", "hello", "world");
3761
| ^^^^^^^
62+
|
63+
help: try this
64+
|
65+
LL | writeln!(&mut v, "{1} hello", "world");
66+
| ^^^^^--
3867

3968
error: literal with an empty format string
4069
--> $DIR/write_literal.rs:38:42
4170
|
4271
LL | writeln!(&mut v, "{1} {0}", "hello", "world");
4372
| ^^^^^^^
73+
|
74+
help: try this
75+
|
76+
LL | writeln!(&mut v, "world {0}", "hello");
77+
| ^^^^^ --
4478

4579
error: literal with an empty format string
46-
--> $DIR/write_literal.rs:41:43
80+
--> $DIR/write_literal.rs:41:37
4781
|
4882
LL | writeln!(&mut v, "{foo} {bar}", foo = "hello", bar = "world");
49-
| ^^^^^^^
83+
| ^^^^^^^^^^^^^
84+
|
85+
help: try this
86+
|
87+
LL | writeln!(&mut v, "hello {bar}", bar = "world");
88+
| ^^^^^ --
5089

5190
error: literal with an empty format string
52-
--> $DIR/write_literal.rs:41:58
91+
--> $DIR/write_literal.rs:41:52
5392
|
5493
LL | writeln!(&mut v, "{foo} {bar}", foo = "hello", bar = "world");
55-
| ^^^^^^^
94+
| ^^^^^^^^^^^^^
95+
|
96+
help: try this
97+
|
98+
LL | writeln!(&mut v, "{foo} world", foo = "hello");
99+
| ^^^^^ --
56100

57101
error: literal with an empty format string
58-
--> $DIR/write_literal.rs:42:43
102+
--> $DIR/write_literal.rs:42:37
59103
|
60104
LL | writeln!(&mut v, "{bar} {foo}", foo = "hello", bar = "world");
61-
| ^^^^^^^
105+
| ^^^^^^^^^^^^^
106+
|
107+
help: try this
108+
|
109+
LL | writeln!(&mut v, "{bar} hello", bar = "world");
110+
| ^^^^^--
62111

63112
error: literal with an empty format string
64-
--> $DIR/write_literal.rs:42:58
113+
--> $DIR/write_literal.rs:42:52
65114
|
66115
LL | writeln!(&mut v, "{bar} {foo}", foo = "hello", bar = "world");
67-
| ^^^^^^^
116+
| ^^^^^^^^^^^^^
117+
|
118+
help: try this
119+
|
120+
LL | writeln!(&mut v, "world {foo}", foo = "hello");
121+
| ^^^^^ --
68122

69123
error: aborting due to 11 previous errors
70124

tests/ui/write_literal_2.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#![allow(unused_must_use)]
2+
#![warn(clippy::write_literal)]
3+
4+
use std::io::Write;
5+
6+
fn main() {
7+
let mut v = Vec::new();
8+
9+
writeln!(&mut v, "{}", "{hello}");
10+
writeln!(&mut v, r"{}", r"{hello}");
11+
writeln!(&mut v, "{}", '\'');
12+
writeln!(&mut v, "{}", '"');
13+
writeln!(&mut v, r"{}", '"'); // don't lint
14+
writeln!(&mut v, r"{}", '\'');
15+
writeln!(
16+
&mut v,
17+
"some {}",
18+
"hello \
19+
world!"
20+
);
21+
writeln!(
22+
&mut v,
23+
"some {}\
24+
{} \\ {}",
25+
"1", "2", "3",
26+
);
27+
}

0 commit comments

Comments
 (0)