1 use crate::utils::{snippet_with_applicability, span_lint, span_lint_and_sugg};
2 use rustc::lint::{EarlyContext, EarlyLintPass, LintArray, LintPass};
3 use rustc::{declare_lint_pass, declare_tool_lint};
4 use rustc_errors::Applicability;
7 use syntax::parse::{parser, token};
8 use syntax::tokenstream::{TokenStream, TokenTree};
10 declare_clippy_lint! {
11 /// **What it does:** This lint warns when you use `println!("")` to
14 /// **Why is this bad?** You should use `println!()`, which is simpler.
16 /// **Known problems:** None.
22 pub PRINTLN_EMPTY_STRING,
24 "using `println!(\"\")` with an empty string"
27 declare_clippy_lint! {
28 /// **What it does:** This lint warns when you use `print!()` with a format
30 /// ends in a newline.
32 /// **Why is this bad?** You should use `println!()` instead, which appends the
35 /// **Known problems:** None.
39 /// # let name = "World";
40 /// print!("Hello {}!\n", name);
42 /// use println!() instead
44 /// # let name = "World";
45 /// println!("Hello {}!", name);
47 pub PRINT_WITH_NEWLINE,
49 "using `print!()` with a format string that ends in a single newline"
52 declare_clippy_lint! {
53 /// **What it does:** Checks for printing on *stdout*. The purpose of this lint
54 /// is to catch debugging remnants.
56 /// **Why is this bad?** People often print on *stdout* while debugging an
57 /// application and might forget to remove those prints afterward.
59 /// **Known problems:** Only catches `print!` and `println!` calls.
63 /// println!("Hello world!");
70 declare_clippy_lint! {
71 /// **What it does:** Checks for use of `Debug` formatting. The purpose of this
72 /// lint is to catch debugging remnants.
74 /// **Why is this bad?** The purpose of the `Debug` trait is to facilitate
75 /// debugging Rust code. It should not be used in in user-facing output.
79 /// println!("{:?}", foo);
83 "use of `Debug`-based formatting"
86 declare_clippy_lint! {
87 /// **What it does:** This lint warns about the use of literals as `print!`/`println!` args.
89 /// **Why is this bad?** Using literals as `println!` args is inefficient
90 /// (c.f., https://github.com/matthiaskrgr/rust-str-bench) and unnecessary
91 /// (i.e., just put the literal in the format string)
93 /// **Known problems:** Will also warn with macro calls as arguments that expand to literals
94 /// -- e.g., `println!("{}", env!("FOO"))`.
98 /// println!("{}", "foo");
100 /// use the literal without formatting:
106 "printing a literal with a format string"
109 declare_clippy_lint! {
110 /// **What it does:** This lint warns when you use `writeln!(buf, "")` to
113 /// **Why is this bad?** You should use `writeln!(buf)`, which is simpler.
115 /// **Known problems:** None.
119 /// # use std::fmt::Write;
120 /// # let mut buf = String::new();
121 /// writeln!(buf, "");
123 pub WRITELN_EMPTY_STRING,
125 "using `writeln!(buf, \"\")` with an empty string"
128 declare_clippy_lint! {
129 /// **What it does:** This lint warns when you use `write!()` with a format
131 /// ends in a newline.
133 /// **Why is this bad?** You should use `writeln!()` instead, which appends the
136 /// **Known problems:** None.
140 /// # use std::fmt::Write;
141 /// # let mut buf = String::new();
142 /// # let name = "World";
143 /// write!(buf, "Hello {}!\n", name);
145 pub WRITE_WITH_NEWLINE,
147 "using `write!()` with a format string that ends in a single newline"
150 declare_clippy_lint! {
151 /// **What it does:** This lint warns about the use of literals as `write!`/`writeln!` args.
153 /// **Why is this bad?** Using literals as `writeln!` args is inefficient
154 /// (c.f., https://github.com/matthiaskrgr/rust-str-bench) and unnecessary
155 /// (i.e., just put the literal in the format string)
157 /// **Known problems:** Will also warn with macro calls as arguments that expand to literals
158 /// -- e.g., `writeln!(buf, "{}", env!("FOO"))`.
162 /// # use std::fmt::Write;
163 /// # let mut buf = String::new();
164 /// writeln!(buf, "{}", "foo");
168 "writing a literal with a format string"
171 declare_lint_pass!(Write => [
173 PRINTLN_EMPTY_STRING,
178 WRITELN_EMPTY_STRING,
182 impl EarlyLintPass for Write {
183 fn check_mac(&mut self, cx: &EarlyContext<'_>, mac: &Mac) {
184 if mac.node.path == "println" {
185 span_lint(cx, PRINT_STDOUT, mac.span, "use of `println!`");
186 if let Some(fmtstr) = check_tts(cx, &mac.node.tts, false).0 {
190 PRINTLN_EMPTY_STRING,
192 "using `println!(\"\")`",
194 "println!()".to_string(),
195 Applicability::MachineApplicable,
199 } else if mac.node.path == "print" {
200 span_lint(cx, PRINT_STDOUT, mac.span, "use of `print!`");
201 if let (Some(fmtstr), _, is_raw) = check_tts(cx, &mac.node.tts, false) {
202 if check_newlines(&fmtstr, is_raw) {
207 "using `print!()` with a format string that ends in a \
208 single newline, consider using `println!()` instead",
212 } else if mac.node.path == "write" {
213 if let (Some(fmtstr), _, is_raw) = check_tts(cx, &mac.node.tts, true) {
214 if check_newlines(&fmtstr, is_raw) {
219 "using `write!()` with a format string that ends in a \
220 single newline, consider using `writeln!()` instead",
224 } else if mac.node.path == "writeln" {
225 let check_tts = check_tts(cx, &mac.node.tts, true);
226 if let Some(fmtstr) = check_tts.0 {
228 let mut applicability = Applicability::MachineApplicable;
229 let suggestion = check_tts.1.map_or_else(
231 applicability = Applicability::HasPlaceholders;
234 move |expr| snippet_with_applicability(cx, expr.span, "v", &mut applicability),
239 WRITELN_EMPTY_STRING,
241 format!("using `writeln!({}, \"\")`", suggestion).as_str(),
243 format!("writeln!({})", suggestion),
252 /// Checks the arguments of `print[ln]!` and `write[ln]!` calls. It will return a tuple of two
253 /// options and a bool. The first part of the tuple is `format_str` of the macros. The second part
254 /// of the tuple is in the `write[ln]!` case the expression the `format_str` should be written to.
255 /// The final part is a boolean flag indicating if the string is a raw string.
259 /// Calling this function on
261 /// # use std::fmt::Write;
262 /// # let mut buf = String::new();
263 /// # let something = "something";
264 /// writeln!(buf, "string to write: {}", something);
268 /// (Some("string to write: {}"), Some(buf), false)
270 fn check_tts<'a>(cx: &EarlyContext<'a>, tts: &TokenStream, is_write: bool) -> (Option<String>, Option<Expr>, bool) {
272 let tts = tts.clone();
273 let mut is_raw = false;
274 if let TokenStream(Some(tokens)) = &tts {
275 for token in tokens.iter() {
276 if let (TokenTree::Token(_, token::Token::Literal(lit, _)), _) = token {
278 token::Lit::Str_(_) => break,
279 token::Lit::StrRaw(_, _) => {
288 let mut parser = parser::Parser::new(&cx.sess.parse_sess, tts, None, false, false);
289 let mut expr: Option<Expr> = None;
291 expr = match parser.parse_expr().map_err(|mut err| err.cancel()) {
292 Ok(p) => Some(p.into_inner()),
293 Err(_) => return (None, None, is_raw),
295 // might be `writeln!(foo)`
296 if parser.expect(&token::Comma).map_err(|mut err| err.cancel()).is_err() {
297 return (None, expr, is_raw);
301 let fmtstr = match parser.parse_str().map_err(|mut err| err.cancel()) {
302 Ok(token) => token.0.to_string(),
303 Err(_) => return (None, expr, is_raw),
305 let tmp = fmtstr.clone();
306 let mut args = vec![];
307 let mut fmt_parser = Parser::new(&tmp, None, Vec::new(), false);
308 while let Some(piece) = fmt_parser.next() {
309 if !fmt_parser.errors.is_empty() {
310 return (None, expr, is_raw);
312 if let Piece::NextArgument(arg) = piece {
313 if arg.format.ty == "?" {
314 // FIXME: modify rustc's fmt string parser to give us the current span
315 span_lint(cx, USE_DEBUG, parser.prev_span, "use of `Debug`-based formatting");
320 let lint = if is_write { WRITE_LITERAL } else { PRINT_LITERAL };
323 const SIMPLE: FormatSpec<'_> = FormatSpec {
327 precision: CountImplied,
331 if !parser.eat(&token::Comma) {
332 return (Some(fmtstr), expr, is_raw);
334 let token_expr = match parser.parse_expr().map_err(|mut err| err.cancel()) {
336 Err(_) => return (Some(fmtstr), None, is_raw),
338 match &token_expr.node {
339 ExprKind::Lit(_) => {
340 let mut all_simple = true;
341 let mut seen = false;
344 ArgumentImplicitlyIs(n) | ArgumentIs(n) => {
346 all_simple &= arg.format == SIMPLE;
350 ArgumentNamed(_) => {},
353 if all_simple && seen {
354 span_lint(cx, lint, token_expr.span, "literal with an empty format string");
358 ExprKind::Assign(lhs, rhs) => {
359 if let ExprKind::Lit(_) = rhs.node {
360 if let ExprKind::Path(_, p) = &lhs.node {
361 let mut all_simple = true;
362 let mut seen = false;
365 ArgumentImplicitlyIs(_) | ArgumentIs(_) => {},
366 ArgumentNamed(name) => {
369 all_simple &= arg.format == SIMPLE;
374 if all_simple && seen {
375 span_lint(cx, lint, rhs.span, "literal with an empty format string");
385 // Checks if `s` constains a single newline that terminates it
386 // Literal and escaped newlines are both checked (only literal for raw strings)
387 fn check_newlines(s: &str, is_raw: bool) -> bool {
388 if s.ends_with('\n') {
398 let bytes = s.as_bytes();
399 if bytes[bytes.len() - 2] != b'\\' || bytes[bytes.len() - 1] != b'n' {
403 let mut escaping = false;
404 for (index, &byte) in bytes.iter().enumerate() {
407 return index == bytes.len() - 1;
410 } else if byte == b'\\' {