Skip to content

Commit 027dde9

Browse files
committed
Auto merge of #3781 - uniphil:write_with_newline_false_positive, r=oli-obk
Don't check [print/write]_with_newline on raw strings Some tests for #3778 and some maybe-not-the-greatest code that passes those tests! I didn't run `fmt` because a) it doesn't seem to install on nightly for me, and b) on stable it wanted to apply formatting to over 90 files. Happy to make any tweaks though! I suspect this contribution may require more than just tweaks. I'm still sort of new to rust so it may not be idiomatic, and the specific approach I took feels a little heavy-handed and brittle. I'm happy to make changes with some guidance, or equally happy if this gives a starting place for someone else to do it better :)
2 parents d5d8d7b + ef72b2c commit 027dde9

File tree

5 files changed

+120
-20
lines changed

5 files changed

+120
-20
lines changed

clippy_lints/src/write.rs

Lines changed: 39 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use rustc_errors::Applicability;
55
use std::borrow::Cow;
66
use syntax::ast::*;
77
use syntax::parse::{parser, token};
8-
use syntax::tokenstream::TokenStream;
8+
use syntax::tokenstream::{TokenStream, TokenTree};
99

1010
/// **What it does:** This lint warns when you use `println!("")` to
1111
/// print a newline.
@@ -200,8 +200,8 @@ impl EarlyLintPass for Pass {
200200
}
201201
} else if mac.node.path == "print" {
202202
span_lint(cx, PRINT_STDOUT, mac.span, "use of `print!`");
203-
if let Some(fmtstr) = check_tts(cx, &mac.node.tts, false).0 {
204-
if check_newlines(&fmtstr) {
203+
if let (Some(fmtstr), _, is_raw) = check_tts(cx, &mac.node.tts, false) {
204+
if check_newlines(&fmtstr, is_raw) {
205205
span_lint(
206206
cx,
207207
PRINT_WITH_NEWLINE,
@@ -212,8 +212,8 @@ impl EarlyLintPass for Pass {
212212
}
213213
}
214214
} else if mac.node.path == "write" {
215-
if let Some(fmtstr) = check_tts(cx, &mac.node.tts, true).0 {
216-
if check_newlines(&fmtstr) {
215+
if let (Some(fmtstr), _, is_raw) = check_tts(cx, &mac.node.tts, true) {
216+
if check_newlines(&fmtstr, is_raw) {
217217
span_lint(
218218
cx,
219219
WRITE_WITH_NEWLINE,
@@ -252,8 +252,9 @@ impl EarlyLintPass for Pass {
252252
}
253253

254254
/// Checks the arguments of `print[ln]!` and `write[ln]!` calls. It will return a tuple of two
255-
/// options. The first part of the tuple is `format_str` of the macros. The second part of the tuple
256-
/// is in the `write[ln]!` case the expression the `format_str` should be written to.
255+
/// options and a bool. The first part of the tuple is `format_str` of the macros. The second part
256+
/// of the tuple is in the `write[ln]!` case the expression the `format_str` should be written to.
257+
/// The final part is a boolean flag indicating if the string is a raw string.
257258
///
258259
/// Example:
259260
///
@@ -263,34 +264,49 @@ impl EarlyLintPass for Pass {
263264
/// ```
264265
/// will return
265266
/// ```rust,ignore
266-
/// (Some("string to write: {}"), Some(buf))
267+
/// (Some("string to write: {}"), Some(buf), false)
267268
/// ```
268-
fn check_tts<'a>(cx: &EarlyContext<'a>, tts: &TokenStream, is_write: bool) -> (Option<String>, Option<Expr>) {
269+
fn check_tts<'a>(cx: &EarlyContext<'a>, tts: &TokenStream, is_write: bool) -> (Option<String>, Option<Expr>, bool) {
269270
use fmt_macros::*;
270271
let tts = tts.clone();
272+
let mut is_raw = false;
273+
if let TokenStream(Some(tokens)) = &tts {
274+
for token in tokens.iter() {
275+
if let (TokenTree::Token(_, token::Token::Literal(lit, _)), _) = token {
276+
match lit {
277+
token::Lit::Str_(_) => break,
278+
token::Lit::StrRaw(_, _) => {
279+
is_raw = true;
280+
break;
281+
},
282+
_ => {},
283+
}
284+
}
285+
}
286+
}
271287
let mut parser = parser::Parser::new(&cx.sess.parse_sess, tts, None, false, false);
272288
let mut expr: Option<Expr> = None;
273289
if is_write {
274290
expr = match parser.parse_expr().map_err(|mut err| err.cancel()) {
275291
Ok(p) => Some(p.into_inner()),
276-
Err(_) => return (None, None),
292+
Err(_) => return (None, None, is_raw),
277293
};
278294
// might be `writeln!(foo)`
279295
if parser.expect(&token::Comma).map_err(|mut err| err.cancel()).is_err() {
280-
return (None, expr);
296+
return (None, expr, is_raw);
281297
}
282298
}
283299

284300
let fmtstr = match parser.parse_str().map_err(|mut err| err.cancel()) {
285301
Ok(token) => token.0.to_string(),
286-
Err(_) => return (None, expr),
302+
Err(_) => return (None, expr, is_raw),
287303
};
288304
let tmp = fmtstr.clone();
289305
let mut args = vec![];
290306
let mut fmt_parser = Parser::new(&tmp, None, Vec::new(), false);
291307
while let Some(piece) = fmt_parser.next() {
292308
if !fmt_parser.errors.is_empty() {
293-
return (None, expr);
309+
return (None, expr, is_raw);
294310
}
295311
if let Piece::NextArgument(arg) = piece {
296312
if arg.format.ty == "?" {
@@ -312,11 +328,11 @@ fn check_tts<'a>(cx: &EarlyContext<'a>, tts: &TokenStream, is_write: bool) -> (O
312328
ty: "",
313329
};
314330
if !parser.eat(&token::Comma) {
315-
return (Some(fmtstr), expr);
331+
return (Some(fmtstr), expr, is_raw);
316332
}
317333
let token_expr = match parser.parse_expr().map_err(|mut err| err.cancel()) {
318334
Ok(expr) => expr,
319-
Err(_) => return (Some(fmtstr), None),
335+
Err(_) => return (Some(fmtstr), None, is_raw),
320336
};
321337
match &token_expr.node {
322338
ExprKind::Lit(_) => {
@@ -366,7 +382,14 @@ fn check_tts<'a>(cx: &EarlyContext<'a>, tts: &TokenStream, is_write: bool) -> (O
366382
}
367383

368384
// Checks if `s` constains a single newline that terminates it
369-
fn check_newlines(s: &str) -> bool {
385+
// Literal and escaped newlines are both checked (only literal for raw strings)
386+
fn check_newlines(s: &str, is_raw: bool) -> bool {
387+
if s.ends_with('\n') {
388+
return true;
389+
} else if is_raw {
390+
return false;
391+
}
392+
370393
if s.len() < 2 {
371394
return false;
372395
}

tests/ui/print_with_newline.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,22 @@ fn main() {
2121
print!("Hello {} {}\n\n", "world", "#2");
2222
println!("\ndon't\nwarn\nfor\nmultiple\nnewlines\n"); // #3126
2323
println!("\nbla\n\n"); // #3126
24+
25+
// Escaping
26+
print!("\\n"); // #3514
27+
print!("\\\n"); // should fail
28+
print!("\\\\n");
29+
30+
// Raw strings
31+
print!(r"\n"); // #3778
32+
33+
// Literal newlines should also fail
34+
print!(
35+
"
36+
"
37+
);
38+
print!(
39+
r"
40+
"
41+
);
2442
}

tests/ui/print_with_newline.stderr

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,29 @@ error: using `print!()` with a format string that ends in a single newline, cons
2424
LL | print!("{}/n", 1265);
2525
| ^^^^^^^^^^^^^^^^^^^^
2626

27-
error: aborting due to 4 previous errors
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
29+
|
30+
LL | print!("//n"); // should fail
31+
| ^^^^^^^^^^^^^^
32+
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
35+
|
36+
LL | / print!(
37+
LL | | "
38+
LL | | "
39+
LL | | );
40+
| |_____^
41+
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
44+
|
45+
LL | / print!(
46+
LL | | r"
47+
LL | | "
48+
LL | | );
49+
| |_____^
50+
51+
error: aborting due to 7 previous errors
2852

tests/ui/write_with_newline.rs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,21 @@ fn main() {
2929

3030
// Escaping
3131
write!(&mut v, "\\n"); // #3514
32-
write!(&mut v, "\\\n");
32+
write!(&mut v, "\\\n"); // should fail
3333
write!(&mut v, "\\\\n");
34+
35+
// Raw strings
36+
write!(&mut v, r"\n"); // #3778
37+
38+
// Literal newlines should also fail
39+
write!(
40+
&mut v,
41+
"
42+
"
43+
);
44+
write!(
45+
&mut v,
46+
r"
47+
"
48+
);
3449
}

tests/ui/write_with_newline.stderr

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,28 @@ LL | write!(&mut v, "{}/n", 1265);
2727
error: using `write!()` with a format string that ends in a single newline, consider using `writeln!()` instead
2828
--> $DIR/write_with_newline.rs:32:5
2929
|
30-
LL | write!(&mut v, "//n");
30+
LL | write!(&mut v, "//n"); // should fail
3131
| ^^^^^^^^^^^^^^^^^^^^^^
3232

33-
error: aborting due to 5 previous errors
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
35+
|
36+
LL | / write!(
37+
LL | | &mut v,
38+
LL | | "
39+
LL | | "
40+
LL | | );
41+
| |_____^
42+
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
45+
|
46+
LL | / write!(
47+
LL | | &mut v,
48+
LL | | r"
49+
LL | | "
50+
LL | | );
51+
| |_____^
52+
53+
error: aborting due to 7 previous errors
3454

0 commit comments

Comments
 (0)