1 // Copyright 2014-2018 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution.
4 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
5 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
6 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
7 // option. This file may not be copied, modified, or distributed
8 // except according to those terms.
10 use crate::rustc::lint::{EarlyContext, EarlyLintPass, LintArray, LintPass};
11 use crate::rustc::{declare_tool_lint, lint_array};
12 use crate::rustc_errors::Applicability;
13 use crate::syntax::ast::*;
14 use crate::syntax::parse::{parser, token};
15 use crate::syntax::tokenstream::{ThinTokenStream, TokenStream};
16 use crate::utils::{snippet_with_applicability, span_lint, span_lint_and_sugg};
19 /// **What it does:** This lint warns when you use `println!("")` to
22 /// **Why is this bad?** You should use `println!()`, which is simpler.
24 /// **Known problems:** None.
30 declare_clippy_lint! {
31 pub PRINTLN_EMPTY_STRING,
33 "using `println!(\"\")` with an empty string"
36 /// **What it does:** This lint warns when you use `print!()` with a format
38 /// ends in a newline.
40 /// **Why is this bad?** You should use `println!()` instead, which appends the
43 /// **Known problems:** None.
47 /// print!("Hello {}!\n", name);
49 /// use println!() instead
51 /// println!("Hello {}!", name);
53 declare_clippy_lint! {
54 pub PRINT_WITH_NEWLINE,
56 "using `print!()` with a format string that ends in a single newline"
59 /// **What it does:** Checks for printing on *stdout*. The purpose of this lint
60 /// is to catch debugging remnants.
62 /// **Why is this bad?** People often print on *stdout* while debugging an
63 /// application and might forget to remove those prints afterward.
65 /// **Known problems:** Only catches `print!` and `println!` calls.
69 /// println!("Hello world!");
71 declare_clippy_lint! {
77 /// **What it does:** Checks for use of `Debug` formatting. The purpose of this
78 /// lint is to catch debugging remnants.
80 /// **Why is this bad?** The purpose of the `Debug` trait is to facilitate
81 /// debugging Rust code. It should not be used in in user-facing output.
85 /// println!("{:?}", foo);
87 declare_clippy_lint! {
90 "use of `Debug`-based formatting"
93 /// **What it does:** This lint warns about the use of literals as `print!`/`println!` args.
95 /// **Why is this bad?** Using literals as `println!` args is inefficient
96 /// (c.f., https://github.com/matthiaskrgr/rust-str-bench) and unnecessary
97 /// (i.e., just put the literal in the format string)
99 /// **Known problems:** Will also warn with macro calls as arguments that expand to literals
100 /// -- e.g., `println!("{}", env!("FOO"))`.
104 /// println!("{}", "foo");
106 /// use the literal without formatting:
110 declare_clippy_lint! {
113 "printing a literal with a format string"
116 /// **What it does:** This lint warns when you use `writeln!(buf, "")` to
119 /// **Why is this bad?** You should use `writeln!(buf)`, which is simpler.
121 /// **Known problems:** None.
127 declare_clippy_lint! {
128 pub WRITELN_EMPTY_STRING,
130 "using `writeln!(\"\")` with an empty string"
133 /// **What it does:** This lint warns when you use `write!()` with a format
135 /// ends in a newline.
137 /// **Why is this bad?** You should use `writeln!()` instead, which appends the
140 /// **Known problems:** None.
144 /// write!(buf, "Hello {}!\n", name);
146 declare_clippy_lint! {
147 pub WRITE_WITH_NEWLINE,
149 "using `write!()` with a format string that ends in a single newline"
152 /// **What it does:** This lint warns about the use of literals as `write!`/`writeln!` args.
154 /// **Why is this bad?** Using literals as `writeln!` args is inefficient
155 /// (c.f., https://github.com/matthiaskrgr/rust-str-bench) and unnecessary
156 /// (i.e., just put the literal in the format string)
158 /// **Known problems:** Will also warn with macro calls as arguments that expand to literals
159 /// -- e.g., `writeln!(buf, "{}", env!("FOO"))`.
163 /// writeln!(buf, "{}", "foo");
165 declare_clippy_lint! {
168 "writing a literal with a format string"
171 #[derive(Copy, Clone, Debug)]
174 impl LintPass for Pass {
175 fn get_lints(&self) -> LintArray {
178 PRINTLN_EMPTY_STRING,
183 WRITELN_EMPTY_STRING,
189 impl EarlyLintPass for Pass {
190 fn check_mac(&mut self, cx: &EarlyContext<'_>, mac: &Mac) {
191 if mac.node.path == "println" {
192 span_lint(cx, PRINT_STDOUT, mac.span, "use of `println!`");
193 if let Some(fmtstr) = check_tts(cx, &mac.node.tts, false).0 {
197 PRINTLN_EMPTY_STRING,
199 "using `println!(\"\")`",
201 "println!()".to_string(),
202 Applicability::MachineApplicable,
206 } else if mac.node.path == "print" {
207 span_lint(cx, PRINT_STDOUT, mac.span, "use of `print!`");
208 if let Some(fmtstr) = check_tts(cx, &mac.node.tts, false).0 {
209 if check_newlines(&fmtstr) {
214 "using `print!()` with a format string that ends in a \
215 single newline, consider using `println!()` instead",
219 } else if mac.node.path == "write" {
220 if let Some(fmtstr) = check_tts(cx, &mac.node.tts, true).0 {
221 if check_newlines(&fmtstr) {
226 "using `write!()` with a format string that ends in a \
227 single newline, consider using `writeln!()` instead",
231 } else if mac.node.path == "writeln" {
232 let check_tts = check_tts(cx, &mac.node.tts, true);
233 if let Some(fmtstr) = check_tts.0 {
235 let mut applicability = Applicability::MachineApplicable;
236 let suggestion = check_tts.1.map_or_else(
238 applicability = Applicability::HasPlaceholders;
241 move |expr| snippet_with_applicability(cx, expr.span, "v", &mut applicability),
246 WRITELN_EMPTY_STRING,
248 format!("using `writeln!({}, \"\")`", suggestion).as_str(),
250 format!("writeln!({})", suggestion),
259 /// Checks the arguments of `print[ln]!` and `write[ln]!` calls. It will return a tuple of two
260 /// options. The first part of the tuple is `format_str` of the macros. The second part of the tuple
261 /// is in the `write[ln]!` case the expression the `format_str` should be written to.
265 /// Calling this function on
267 /// writeln!(buf, "string to write: {}", something)
271 /// (Some("string to write: {}"), Some(buf))
273 fn check_tts<'a>(cx: &EarlyContext<'a>, tts: &ThinTokenStream, is_write: bool) -> (Option<String>, Option<Expr>) {
274 use crate::fmt_macros::*;
275 let tts = TokenStream::from(tts.clone());
276 let mut parser = parser::Parser::new(&cx.sess.parse_sess, tts, None, false, false);
277 let mut expr: Option<Expr> = None;
279 expr = match parser.parse_expr().map_err(|mut err| err.cancel()) {
280 Ok(p) => Some(p.into_inner()),
281 Err(_) => return (None, None),
283 // might be `writeln!(foo)`
284 if parser.expect(&token::Comma).map_err(|mut err| err.cancel()).is_err() {
289 let fmtstr = match parser.parse_str().map_err(|mut err| err.cancel()) {
290 Ok(token) => token.0.to_string(),
291 Err(_) => return (None, expr),
293 let tmp = fmtstr.clone();
294 let mut args = vec![];
295 let mut fmt_parser = Parser::new(&tmp, None);
296 while let Some(piece) = fmt_parser.next() {
297 if !fmt_parser.errors.is_empty() {
300 if let Piece::NextArgument(arg) = piece {
301 if arg.format.ty == "?" {
302 // FIXME: modify rustc's fmt string parser to give us the current span
303 span_lint(cx, USE_DEBUG, parser.prev_span, "use of `Debug`-based formatting");
308 let lint = if is_write { WRITE_LITERAL } else { PRINT_LITERAL };
311 const SIMPLE: FormatSpec<'_> = FormatSpec {
315 precision: CountImplied,
319 if !parser.eat(&token::Comma) {
320 return (Some(fmtstr), expr);
322 let token_expr = match parser.parse_expr().map_err(|mut err| err.cancel()) {
324 Err(_) => return (Some(fmtstr), None),
326 match &token_expr.node {
327 ExprKind::Lit(_) => {
328 let mut all_simple = true;
329 let mut seen = false;
332 ArgumentImplicitlyIs(n) | ArgumentIs(n) => {
334 all_simple &= arg.format == SIMPLE;
338 ArgumentNamed(_) => {},
341 if all_simple && seen {
342 span_lint(cx, lint, token_expr.span, "literal with an empty format string");
346 ExprKind::Assign(lhs, rhs) => {
347 if let ExprKind::Lit(_) = rhs.node {
348 if let ExprKind::Path(_, p) = &lhs.node {
349 let mut all_simple = true;
350 let mut seen = false;
353 ArgumentImplicitlyIs(_) | ArgumentIs(_) => {},
354 ArgumentNamed(name) => {
357 all_simple &= arg.format == SIMPLE;
362 if all_simple && seen {
363 span_lint(cx, lint, rhs.span, "literal with an empty format string");
373 // Checks if `s` constains a single newline that terminates it
374 fn check_newlines(s: &str) -> bool {
379 let bytes = s.as_bytes();
380 if bytes[bytes.len() - 2] != b'\\' || bytes[bytes.len() - 1] != b'n' {
384 let mut escaping = false;
385 for (index, &byte) in bytes.iter().enumerate() {
388 return index == bytes.len() - 1;
391 } else if byte == b'\\' {