1 use crate::utils::{snippet_with_applicability, span_lint, span_lint_and_sugg, span_lint_and_then};
2 use rustc::lint::{EarlyContext, EarlyLintPass, LintArray, LintPass};
3 use rustc::{declare_lint_pass, declare_tool_lint};
4 use rustc_errors::Applicability;
5 use rustc_parse::parser;
9 use syntax::tokenstream::TokenStream;
10 use syntax_pos::{BytePos, Span};
12 declare_clippy_lint! {
13 /// **What it does:** This lint warns when you use `println!("")` to
16 /// **Why is this bad?** You should use `println!()`, which is simpler.
18 /// **Known problems:** None.
24 pub PRINTLN_EMPTY_STRING,
26 "using `println!(\"\")` with an empty string"
29 declare_clippy_lint! {
30 /// **What it does:** This lint warns when you use `print!()` with a format
32 /// ends in a newline.
34 /// **Why is this bad?** You should use `println!()` instead, which appends the
37 /// **Known problems:** None.
41 /// # let name = "World";
42 /// print!("Hello {}!\n", name);
44 /// use println!() instead
46 /// # let name = "World";
47 /// println!("Hello {}!", name);
49 pub PRINT_WITH_NEWLINE,
51 "using `print!()` with a format string that ends in a single newline"
54 declare_clippy_lint! {
55 /// **What it does:** Checks for printing on *stdout*. The purpose of this lint
56 /// is to catch debugging remnants.
58 /// **Why is this bad?** People often print on *stdout* while debugging an
59 /// application and might forget to remove those prints afterward.
61 /// **Known problems:** Only catches `print!` and `println!` calls.
65 /// println!("Hello world!");
72 declare_clippy_lint! {
73 /// **What it does:** Checks for use of `Debug` formatting. The purpose of this
74 /// lint is to catch debugging remnants.
76 /// **Why is this bad?** The purpose of the `Debug` trait is to facilitate
77 /// debugging Rust code. It should not be used in user-facing output.
81 /// # let foo = "bar";
82 /// println!("{:?}", foo);
86 "use of `Debug`-based formatting"
89 declare_clippy_lint! {
90 /// **What it does:** This lint warns about the use of literals as `print!`/`println!` args.
92 /// **Why is this bad?** Using literals as `println!` args is inefficient
93 /// (c.f., https://github.com/matthiaskrgr/rust-str-bench) and unnecessary
94 /// (i.e., just put the literal in the format string)
96 /// **Known problems:** Will also warn with macro calls as arguments that expand to literals
97 /// -- e.g., `println!("{}", env!("FOO"))`.
101 /// println!("{}", "foo");
103 /// use the literal without formatting:
109 "printing a literal with a format string"
112 declare_clippy_lint! {
113 /// **What it does:** This lint warns when you use `writeln!(buf, "")` to
116 /// **Why is this bad?** You should use `writeln!(buf)`, which is simpler.
118 /// **Known problems:** None.
122 /// # use std::fmt::Write;
123 /// # let mut buf = String::new();
124 /// writeln!(buf, "");
126 pub WRITELN_EMPTY_STRING,
128 "using `writeln!(buf, \"\")` with an empty string"
131 declare_clippy_lint! {
132 /// **What it does:** This lint warns when you use `write!()` with a format
134 /// ends in a newline.
136 /// **Why is this bad?** You should use `writeln!()` instead, which appends the
139 /// **Known problems:** None.
143 /// # use std::fmt::Write;
144 /// # let mut buf = String::new();
145 /// # let name = "World";
146 /// write!(buf, "Hello {}!\n", name);
148 pub WRITE_WITH_NEWLINE,
150 "using `write!()` with a format string that ends in a single newline"
153 declare_clippy_lint! {
154 /// **What it does:** This lint warns about the use of literals as `write!`/`writeln!` args.
156 /// **Why is this bad?** Using literals as `writeln!` args is inefficient
157 /// (c.f., https://github.com/matthiaskrgr/rust-str-bench) and unnecessary
158 /// (i.e., just put the literal in the format string)
160 /// **Known problems:** Will also warn with macro calls as arguments that expand to literals
161 /// -- e.g., `writeln!(buf, "{}", env!("FOO"))`.
165 /// # use std::fmt::Write;
166 /// # let mut buf = String::new();
167 /// writeln!(buf, "{}", "foo");
171 "writing a literal with a format string"
174 declare_lint_pass!(Write => [
176 PRINTLN_EMPTY_STRING,
181 WRITELN_EMPTY_STRING,
185 impl EarlyLintPass for Write {
186 fn check_mac(&mut self, cx: &EarlyContext<'_>, mac: &Mac) {
187 if mac.path == sym!(println) {
188 span_lint(cx, PRINT_STDOUT, mac.span, "use of `println!`");
189 if let (Some(fmt_str), _) = check_tts(cx, &mac.tts, false) {
190 if fmt_str.contents.is_empty() {
193 PRINTLN_EMPTY_STRING,
195 "using `println!(\"\")`",
197 "println!()".to_string(),
198 Applicability::MachineApplicable,
202 } else if mac.path == sym!(print) {
203 span_lint(cx, PRINT_STDOUT, mac.span, "use of `print!`");
204 if let (Some(fmt_str), _) = check_tts(cx, &mac.tts, false) {
205 if check_newlines(&fmt_str) {
210 "using `print!()` with a format string that ends in a single newline",
212 err.multipart_suggestion(
213 "use `println!` instead",
215 (mac.path.span, String::from("println")),
216 (fmt_str.newline_span(), String::new()),
218 Applicability::MachineApplicable,
224 } else if mac.path == sym!(write) {
225 if let (Some(fmt_str), _) = check_tts(cx, &mac.tts, true) {
226 if check_newlines(&fmt_str) {
231 "using `write!()` with a format string that ends in a single newline",
233 err.multipart_suggestion(
234 "use `writeln!()` instead",
236 (mac.path.span, String::from("writeln")),
237 (fmt_str.newline_span(), String::new()),
239 Applicability::MachineApplicable,
245 } else if mac.path == sym!(writeln) {
246 if let (Some(fmt_str), expr) = check_tts(cx, &mac.tts, true) {
247 if fmt_str.contents.is_empty() {
248 let mut applicability = Applicability::MachineApplicable;
249 let suggestion = expr.map_or_else(
251 applicability = Applicability::HasPlaceholders;
254 move |expr| snippet_with_applicability(cx, expr.span, "v", &mut applicability),
259 WRITELN_EMPTY_STRING,
261 format!("using `writeln!({}, \"\")`", suggestion).as_str(),
263 format!("writeln!({})", suggestion),
272 /// The arguments of a `print[ln]!` or `write[ln]!` invocation.
274 /// The contents of the format string (inside the quotes).
277 /// The span of the format string, including quotes, the raw marker, and any raw hashes.
282 /// Given a format string that ends in a newline and its span, calculates the span of the
284 fn newline_span(&self) -> Span {
287 let newline_sp_hi = sp.hi()
289 StrStyle::Cooked => BytePos(1),
290 StrStyle::Raw(hashes) => BytePos((1 + hashes).into()),
293 let newline_sp_len = if self.contents.ends_with('\n') {
295 } else if self.contents.ends_with(r"\n") {
298 panic!("expected format string to contain a newline");
301 sp.with_lo(newline_sp_hi - newline_sp_len).with_hi(newline_sp_hi)
305 /// Checks the arguments of `print[ln]!` and `write[ln]!` calls. It will return a tuple of two
306 /// `Option`s. The first `Option` of the tuple is the macro's format string. It includes
307 /// the contents of the string, whether it's a raw string, and the span of the literal in the
308 /// source. The second `Option` in the tuple is, in the `write[ln]!` case, the expression the
309 /// `format_str` should be written to.
313 /// Calling this function on
315 /// # use std::fmt::Write;
316 /// # let mut buf = String::new();
317 /// # let something = "something";
318 /// writeln!(buf, "string to write: {}", something);
322 /// (Some("string to write: {}"), Some(buf))
324 #[allow(clippy::too_many_lines)]
325 fn check_tts<'a>(cx: &EarlyContext<'a>, tts: &TokenStream, is_write: bool) -> (Option<FmtStr>, Option<Expr>) {
327 let tts = tts.clone();
329 let mut parser = parser::Parser::new(&cx.sess.parse_sess, tts, None, false, false, None);
330 let mut expr: Option<Expr> = None;
332 expr = match parser.parse_expr().map_err(|mut err| err.cancel()) {
333 Ok(p) => Some(p.into_inner()),
334 Err(_) => return (None, None),
336 // might be `writeln!(foo)`
337 if parser.expect(&token::Comma).map_err(|mut err| err.cancel()).is_err() {
342 let (fmtstr, fmtstyle) = match parser.parse_str().map_err(|mut err| err.cancel()) {
343 Ok((fmtstr, fmtstyle)) => (fmtstr.to_string(), fmtstyle),
344 Err(_) => return (None, expr),
346 let fmtspan = parser.prev_span;
347 let tmp = fmtstr.clone();
348 let mut args = vec![];
349 let mut fmt_parser = Parser::new(&tmp, None, Vec::new(), false);
350 while let Some(piece) = fmt_parser.next() {
351 if !fmt_parser.errors.is_empty() {
354 if let Piece::NextArgument(arg) = piece {
355 if arg.format.ty == "?" {
356 // FIXME: modify rustc's fmt string parser to give us the current span
357 span_lint(cx, USE_DEBUG, parser.prev_span, "use of `Debug`-based formatting");
362 let lint = if is_write { WRITE_LITERAL } else { PRINT_LITERAL };
365 const SIMPLE: FormatSpec<'_> = FormatSpec {
369 precision: CountImplied,
370 precision_span: None,
376 if !parser.eat(&token::Comma) {
386 let token_expr = if let Ok(expr) = parser.parse_expr().map_err(|mut err| err.cancel()) {
398 match &token_expr.kind {
399 ExprKind::Lit(_) => {
400 let mut all_simple = true;
401 let mut seen = false;
404 ArgumentImplicitlyIs(n) | ArgumentIs(n) => {
406 all_simple &= arg.format == SIMPLE;
410 ArgumentNamed(_) => {},
413 if all_simple && seen {
414 span_lint(cx, lint, token_expr.span, "literal with an empty format string");
418 ExprKind::Assign(lhs, rhs) => {
419 if let ExprKind::Lit(_) = rhs.kind {
420 if let ExprKind::Path(_, p) = &lhs.kind {
421 let mut all_simple = true;
422 let mut seen = false;
425 ArgumentImplicitlyIs(_) | ArgumentIs(_) => {},
426 ArgumentNamed(name) => {
429 all_simple &= arg.format == SIMPLE;
434 if all_simple && seen {
435 span_lint(cx, lint, rhs.span, "literal with an empty format string");
445 /// Checks if the format string constains a single newline that terminates it.
447 /// Literal and escaped newlines are both checked (only literal for raw strings).
448 fn check_newlines(fmt_str: &FmtStr) -> bool {
449 let s = &fmt_str.contents;
451 if s.ends_with('\n') {
453 } else if let StrStyle::Raw(_) = fmt_str.style {
461 let bytes = s.as_bytes();
462 if bytes[bytes.len() - 2] != b'\\' || bytes[bytes.len() - 1] != b'n' {
466 let mut escaping = false;
467 for (index, &byte) in bytes.iter().enumerate() {
470 return index == bytes.len() - 1;
473 } else if byte == b'\\' {