Skip to content

Commit 01af71e

Browse files
committed
add suggestions for print/write with newline lint
1 parent d519ed4 commit 01af71e

File tree

5 files changed

+159
-50
lines changed

5 files changed

+159
-50
lines changed

clippy_lints/src/write.rs

Lines changed: 63 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
use crate::utils::{snippet_with_applicability, span_lint, span_lint_and_sugg};
1+
use crate::utils::{snippet_with_applicability, span_lint, span_lint_and_sugg, span_lint_and_then};
22
use rustc::lint::{EarlyContext, EarlyLintPass, LintArray, LintPass};
33
use rustc::{declare_lint_pass, declare_tool_lint};
44
use rustc_errors::Applicability;
55
use std::borrow::Cow;
66
use syntax::ast::*;
77
use syntax::parse::{parser, token};
88
use syntax::tokenstream::{TokenStream, TokenTree};
9-
use syntax_pos::symbol::Symbol;
9+
use syntax_pos::{symbol::Symbol, BytePos, Span};
1010

1111
declare_clippy_lint! {
1212
/// **What it does:** This lint warns when you use `println!("")` to
@@ -184,7 +184,7 @@ impl EarlyLintPass for Write {
184184
fn check_mac(&mut self, cx: &EarlyContext<'_>, mac: &Mac) {
185185
if mac.node.path == sym!(println) {
186186
span_lint(cx, PRINT_STDOUT, mac.span, "use of `println!`");
187-
if let Some(fmtstr) = check_tts(cx, &mac.node.tts, false).0 {
187+
if let Some((fmtstr, ..)) = check_tts(cx, &mac.node.tts, false).0 {
188188
if fmtstr == "" {
189189
span_lint_and_sugg(
190190
cx,
@@ -199,32 +199,50 @@ impl EarlyLintPass for Write {
199199
}
200200
} else if mac.node.path == sym!(print) {
201201
span_lint(cx, PRINT_STDOUT, mac.span, "use of `print!`");
202-
if let (Some(fmtstr), _, is_raw) = check_tts(cx, &mac.node.tts, false) {
202+
if let (Some((fmtstr, fmtstyle, fmt_sp)), _, is_raw) = check_tts(cx, &mac.node.tts, false) {
203203
if check_newlines(&fmtstr, is_raw) {
204-
span_lint(
204+
span_lint_and_then(
205205
cx,
206206
PRINT_WITH_NEWLINE,
207207
mac.span,
208-
"using `print!()` with a format string that ends in a \
209-
single newline, consider using `println!()` instead",
208+
"using `print!()` with a format string that ends in a single newline",
209+
|err| {
210+
err.multipart_suggestion(
211+
"use `println!` instead",
212+
vec![
213+
(mac.node.path.span, String::from("println")),
214+
(newline_span(&fmtstr, fmtstyle, fmt_sp), String::new()),
215+
],
216+
Applicability::MachineApplicable,
217+
);
218+
},
210219
);
211220
}
212221
}
213222
} else if mac.node.path == sym!(write) {
214-
if let (Some(fmtstr), _, is_raw) = check_tts(cx, &mac.node.tts, true) {
223+
if let (Some((fmtstr, fmtstyle, fmt_sp)), _, is_raw) = check_tts(cx, &mac.node.tts, true) {
215224
if check_newlines(&fmtstr, is_raw) {
216-
span_lint(
225+
span_lint_and_then(
217226
cx,
218227
WRITE_WITH_NEWLINE,
219228
mac.span,
220-
"using `write!()` with a format string that ends in a \
221-
single newline, consider using `writeln!()` instead",
222-
);
229+
"using `write!()` with a format string that ends in a single newline",
230+
|err| {
231+
err.multipart_suggestion(
232+
"use `writeln!()` instead",
233+
vec![
234+
(mac.node.path.span, String::from("writeln")),
235+
(newline_span(&fmtstr, fmtstyle, fmt_sp), String::new()),
236+
],
237+
Applicability::MachineApplicable,
238+
);
239+
},
240+
)
223241
}
224242
}
225243
} else if mac.node.path == sym!(writeln) {
226244
let check_tts = check_tts(cx, &mac.node.tts, true);
227-
if let Some(fmtstr) = check_tts.0 {
245+
if let Some((fmtstr, ..)) = check_tts.0 {
228246
if fmtstr == "" {
229247
let mut applicability = Applicability::MachineApplicable;
230248
let suggestion = check_tts.1.map_or_else(
@@ -250,10 +268,28 @@ impl EarlyLintPass for Write {
250268
}
251269
}
252270

271+
/// Given a format string that ends in a newline and its span, calculates the span of the newline.
272+
fn newline_span(fmt_str: &str, str_style: StrStyle, sp: Span) -> Span {
273+
let newline_sp_hi = sp.hi()
274+
- match str_style {
275+
StrStyle::Cooked => BytePos(1),
276+
StrStyle::Raw(hashes) => BytePos((1 + hashes).into()),
277+
};
278+
279+
let newline_sp_len = if fmt_str.ends_with('\n') {
280+
BytePos(1)
281+
} else {
282+
BytePos(2)
283+
};
284+
285+
sp.with_lo(newline_sp_hi - newline_sp_len).with_hi(newline_sp_hi)
286+
}
287+
253288
/// Checks the arguments of `print[ln]!` and `write[ln]!` calls. It will return a tuple of two
254-
/// options and a bool. The first part of the tuple is `format_str` of the macros. The second part
255-
/// of the tuple is in the `write[ln]!` case the expression the `format_str` should be written to.
256-
/// The final part is a boolean flag indicating if the string is a raw string.
289+
/// `Option`s. The first `Option` of the tuple is the macro's `format_str`. It includes
290+
/// the contents of the string, whether it's a raw string, and the span of the literal in the
291+
/// source. The second `Option` in the tuple is, in the `write[ln]!` case, the expression the
292+
/// `format_str` should be written to.
257293
///
258294
/// Example:
259295
///
@@ -266,9 +302,13 @@ impl EarlyLintPass for Write {
266302
/// ```
267303
/// will return
268304
/// ```rust,ignore
269-
/// (Some("string to write: {}"), Some(buf), false)
305+
/// (Some(("string to write: {}"), StrStyle::Cooked, "1:15-1:36"), Some(buf))
270306
/// ```
271-
fn check_tts<'a>(cx: &EarlyContext<'a>, tts: &TokenStream, is_write: bool) -> (Option<String>, Option<Expr>, bool) {
307+
fn check_tts<'a>(
308+
cx: &EarlyContext<'a>,
309+
tts: &TokenStream,
310+
is_write: bool,
311+
) -> (Option<(String, StrStyle, Span)>, Option<Expr>, bool) {
272312
use fmt_macros::*;
273313
let tts = tts.clone();
274314
let mut is_raw = false;
@@ -299,10 +339,11 @@ fn check_tts<'a>(cx: &EarlyContext<'a>, tts: &TokenStream, is_write: bool) -> (O
299339
}
300340
}
301341

302-
let fmtstr = match parser.parse_str().map_err(|mut err| err.cancel()) {
303-
Ok(token) => token.0.to_string(),
342+
let (fmtstr, fmtstyle) = match parser.parse_str().map_err(|mut err| err.cancel()) {
343+
Ok((fmtstr, fmtstyle)) => (fmtstr.to_string(), fmtstyle),
304344
Err(_) => return (None, expr, is_raw),
305345
};
346+
let fmtspan = parser.prev_span;
306347
let tmp = fmtstr.clone();
307348
let mut args = vec![];
308349
let mut fmt_parser = Parser::new(&tmp, None, Vec::new(), false);
@@ -330,11 +371,11 @@ fn check_tts<'a>(cx: &EarlyContext<'a>, tts: &TokenStream, is_write: bool) -> (O
330371
ty: "",
331372
};
332373
if !parser.eat(&token::Comma) {
333-
return (Some(fmtstr), expr, is_raw);
374+
return (Some((fmtstr, fmtstyle, fmtspan)), expr, is_raw);
334375
}
335376
let token_expr = match parser.parse_expr().map_err(|mut err| err.cancel()) {
336377
Ok(expr) => expr,
337-
Err(_) => return (Some(fmtstr), None, is_raw),
378+
Err(_) => return (Some((fmtstr, fmtstyle, fmtspan)), None, is_raw),
338379
};
339380
match &token_expr.node {
340381
ExprKind::Lit(_) => {

tests/ui/print_with_newline.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
// FIXME: Ideally these suggestions would be fixed via rustfix. Blocked by rust-lang/rust#53934
2+
// // run-rustfix
3+
14
#![allow(clippy::print_literal)]
25
#![warn(clippy::print_with_newline)]
36

tests/ui/print_with_newline.stderr

Lines changed: 44 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,82 @@
1-
error: using `print!()` with a format string that ends in a single newline, consider using `println!()` instead
2-
--> $DIR/print_with_newline.rs:5:5
1+
error: using `print!()` with a format string that ends in a single newline
2+
--> $DIR/print_with_newline.rs:8:5
33
|
44
LL | print!("Hello/n");
55
| ^^^^^^^^^^^^^^^^^
66
|
77
= note: `-D clippy::print-with-newline` implied by `-D warnings`
8+
help: use `println!` instead
9+
|
10+
LL | println!("Hello");
11+
| ^^^^^^^ --
812

9-
error: using `print!()` with a format string that ends in a single newline, consider using `println!()` instead
10-
--> $DIR/print_with_newline.rs:6:5
13+
error: using `print!()` with a format string that ends in a single newline
14+
--> $DIR/print_with_newline.rs:9:5
1115
|
1216
LL | print!("Hello {}/n", "world");
1317
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
18+
help: use `println!` instead
19+
|
20+
LL | println!("Hello {}", "world");
21+
| ^^^^^^^ --
1422

15-
error: using `print!()` with a format string that ends in a single newline, consider using `println!()` instead
16-
--> $DIR/print_with_newline.rs:7:5
23+
error: using `print!()` with a format string that ends in a single newline
24+
--> $DIR/print_with_newline.rs:10:5
1725
|
1826
LL | print!("Hello {} {}/n", "world", "#2");
1927
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
28+
help: use `println!` instead
29+
|
30+
LL | println!("Hello {} {}", "world", "#2");
31+
| ^^^^^^^ --
2032

21-
error: using `print!()` with a format string that ends in a single newline, consider using `println!()` instead
22-
--> $DIR/print_with_newline.rs:8:5
33+
error: using `print!()` with a format string that ends in a single newline
34+
--> $DIR/print_with_newline.rs:11:5
2335
|
2436
LL | print!("{}/n", 1265);
2537
| ^^^^^^^^^^^^^^^^^^^^
38+
help: use `println!` instead
39+
|
40+
LL | println!("{}", 1265);
41+
| ^^^^^^^ --
2642

27-
error: using `print!()` with a format string that ends in a single newline, consider using `println!()` instead
28-
--> $DIR/print_with_newline.rs:27:5
43+
error: using `print!()` with a format string that ends in a single newline
44+
--> $DIR/print_with_newline.rs:30:5
2945
|
3046
LL | print!("//n"); // should fail
3147
| ^^^^^^^^^^^^^^
48+
help: use `println!` instead
49+
|
50+
LL | println!("/"); // should fail
51+
| ^^^^^^^ --
3252

33-
error: using `print!()` with a format string that ends in a single newline, consider using `println!()` instead
34-
--> $DIR/print_with_newline.rs:34:5
53+
error: using `print!()` with a format string that ends in a single newline
54+
--> $DIR/print_with_newline.rs:37:5
3555
|
3656
LL | / print!(
3757
LL | | "
3858
LL | | "
3959
LL | | );
4060
| |_____^
61+
help: use `println!` instead
62+
|
63+
LL | println!(
64+
LL | ""
65+
|
4166

42-
error: using `print!()` with a format string that ends in a single newline, consider using `println!()` instead
43-
--> $DIR/print_with_newline.rs:38:5
67+
error: using `print!()` with a format string that ends in a single newline
68+
--> $DIR/print_with_newline.rs:41:5
4469
|
4570
LL | / print!(
4671
LL | | r"
4772
LL | | "
4873
LL | | );
4974
| |_____^
75+
help: use `println!` instead
76+
|
77+
LL | println!(
78+
LL | r""
79+
|
5080

5181
error: aborting due to 7 previous errors
5282

tests/ui/write_with_newline.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
// FIXME: Ideally these suggestions would be fixed via rustfix. Blocked by rust-lang/rust#53934
2+
// // run-rustfix
3+
14
#![allow(clippy::write_literal)]
25
#![warn(clippy::write_with_newline)]
36

tests/ui/write_with_newline.stderr

Lines changed: 46 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,86 @@
1-
error: using `write!()` with a format string that ends in a single newline, consider using `writeln!()` instead
2-
--> $DIR/write_with_newline.rs:10:5
1+
error: using `write!()` with a format string that ends in a single newline
2+
--> $DIR/write_with_newline.rs:13:5
33
|
44
LL | write!(&mut v, "Hello/n");
55
| ^^^^^^^^^^^^^^^^^^^^^^^^^
66
|
77
= note: `-D clippy::write-with-newline` implied by `-D warnings`
8+
help: use `writeln!()` instead
9+
|
10+
LL | writeln!(&mut v, "Hello");
11+
| ^^^^^^^ --
812

9-
error: using `write!()` with a format string that ends in a single newline, consider using `writeln!()` instead
10-
--> $DIR/write_with_newline.rs:11:5
13+
error: using `write!()` with a format string that ends in a single newline
14+
--> $DIR/write_with_newline.rs:14:5
1115
|
1216
LL | write!(&mut v, "Hello {}/n", "world");
1317
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
18+
help: use `writeln!()` instead
19+
|
20+
LL | writeln!(&mut v, "Hello {}", "world");
21+
| ^^^^^^^ --
1422

15-
error: using `write!()` with a format string that ends in a single newline, consider using `writeln!()` instead
16-
--> $DIR/write_with_newline.rs:12:5
23+
error: using `write!()` with a format string that ends in a single newline
24+
--> $DIR/write_with_newline.rs:15:5
1725
|
1826
LL | write!(&mut v, "Hello {} {}/n", "world", "#2");
1927
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
28+
help: use `writeln!()` instead
29+
|
30+
LL | writeln!(&mut v, "Hello {} {}", "world", "#2");
31+
| ^^^^^^^ --
2032

21-
error: using `write!()` with a format string that ends in a single newline, consider using `writeln!()` instead
22-
--> $DIR/write_with_newline.rs:13:5
33+
error: using `write!()` with a format string that ends in a single newline
34+
--> $DIR/write_with_newline.rs:16:5
2335
|
2436
LL | write!(&mut v, "{}/n", 1265);
2537
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
38+
help: use `writeln!()` instead
39+
|
40+
LL | writeln!(&mut v, "{}", 1265);
41+
| ^^^^^^^ --
2642

27-
error: using `write!()` with a format string that ends in a single newline, consider using `writeln!()` instead
28-
--> $DIR/write_with_newline.rs:32:5
43+
error: using `write!()` with a format string that ends in a single newline
44+
--> $DIR/write_with_newline.rs:35:5
2945
|
3046
LL | write!(&mut v, "//n"); // should fail
3147
| ^^^^^^^^^^^^^^^^^^^^^^
48+
help: use `writeln!()` instead
49+
|
50+
LL | writeln!(&mut v, "/"); // should fail
51+
| ^^^^^^^ --
3252

33-
error: using `write!()` with a format string that ends in a single newline, consider using `writeln!()` instead
34-
--> $DIR/write_with_newline.rs:39:5
53+
error: using `write!()` with a format string that ends in a single newline
54+
--> $DIR/write_with_newline.rs:42:5
3555
|
3656
LL | / write!(
3757
LL | | &mut v,
3858
LL | | "
3959
LL | | "
4060
LL | | );
4161
| |_____^
62+
help: use `writeln!()` instead
63+
|
64+
LL | writeln!(
65+
LL | &mut v,
66+
LL | ""
67+
|
4268

43-
error: using `write!()` with a format string that ends in a single newline, consider using `writeln!()` instead
44-
--> $DIR/write_with_newline.rs:44:5
69+
error: using `write!()` with a format string that ends in a single newline
70+
--> $DIR/write_with_newline.rs:47:5
4571
|
4672
LL | / write!(
4773
LL | | &mut v,
4874
LL | | r"
4975
LL | | "
5076
LL | | );
5177
| |_____^
78+
help: use `writeln!()` instead
79+
|
80+
LL | writeln!(
81+
LL | &mut v,
82+
LL | r""
83+
|
5284

5385
error: aborting due to 7 previous errors
5486

0 commit comments

Comments
 (0)