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_tool_lint, lint_array};
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 #[derive(Copy, Clone, Debug)]
174 impl LintPass for Pass {
175 fn get_lints(&self) -> LintArray {
178 PRINTLN_EMPTY_STRING,
183 WRITELN_EMPTY_STRING,
188 fn name(&self) -> &'static str {
193 impl EarlyLintPass for Pass {
194 fn check_mac(&mut self, cx: &EarlyContext<'_>, mac: &Mac) {
195 if mac.node.path == "println" {
196 span_lint(cx, PRINT_STDOUT, mac.span, "use of `println!`");
197 if let Some(fmtstr) = check_tts(cx, &mac.node.tts, false).0 {
201 PRINTLN_EMPTY_STRING,
203 "using `println!(\"\")`",
205 "println!()".to_string(),
206 Applicability::MachineApplicable,
210 } else if mac.node.path == "print" {
211 span_lint(cx, PRINT_STDOUT, mac.span, "use of `print!`");
212 if let (Some(fmtstr), _, is_raw) = check_tts(cx, &mac.node.tts, false) {
213 if check_newlines(&fmtstr, is_raw) {
218 "using `print!()` with a format string that ends in a \
219 single newline, consider using `println!()` instead",
223 } else if mac.node.path == "write" {
224 if let (Some(fmtstr), _, is_raw) = check_tts(cx, &mac.node.tts, true) {
225 if check_newlines(&fmtstr, is_raw) {
230 "using `write!()` with a format string that ends in a \
231 single newline, consider using `writeln!()` instead",
235 } else if mac.node.path == "writeln" {
236 let check_tts = check_tts(cx, &mac.node.tts, true);
237 if let Some(fmtstr) = check_tts.0 {
239 let mut applicability = Applicability::MachineApplicable;
240 let suggestion = check_tts.1.map_or_else(
242 applicability = Applicability::HasPlaceholders;
245 move |expr| snippet_with_applicability(cx, expr.span, "v", &mut applicability),
250 WRITELN_EMPTY_STRING,
252 format!("using `writeln!({}, \"\")`", suggestion).as_str(),
254 format!("writeln!({})", suggestion),
263 /// Checks the arguments of `print[ln]!` and `write[ln]!` calls. It will return a tuple of two
264 /// options and a bool. The first part of the tuple is `format_str` of the macros. The second part
265 /// of the tuple is in the `write[ln]!` case the expression the `format_str` should be written to.
266 /// The final part is a boolean flag indicating if the string is a raw string.
270 /// Calling this function on
272 /// # use std::fmt::Write;
273 /// # let mut buf = String::new();
274 /// # let something = "something";
275 /// writeln!(buf, "string to write: {}", something);
279 /// (Some("string to write: {}"), Some(buf), false)
281 fn check_tts<'a>(cx: &EarlyContext<'a>, tts: &TokenStream, is_write: bool) -> (Option<String>, Option<Expr>, bool) {
283 let tts = tts.clone();
284 let mut is_raw = false;
285 if let TokenStream(Some(tokens)) = &tts {
286 for token in tokens.iter() {
287 if let (TokenTree::Token(_, token::Token::Literal(lit, _)), _) = token {
289 token::Lit::Str_(_) => break,
290 token::Lit::StrRaw(_, _) => {
299 let mut parser = parser::Parser::new(&cx.sess.parse_sess, tts, None, false, false);
300 let mut expr: Option<Expr> = None;
302 expr = match parser.parse_expr().map_err(|mut err| err.cancel()) {
303 Ok(p) => Some(p.into_inner()),
304 Err(_) => return (None, None, is_raw),
306 // might be `writeln!(foo)`
307 if parser.expect(&token::Comma).map_err(|mut err| err.cancel()).is_err() {
308 return (None, expr, is_raw);
312 let fmtstr = match parser.parse_str().map_err(|mut err| err.cancel()) {
313 Ok(token) => token.0.to_string(),
314 Err(_) => return (None, expr, is_raw),
316 let tmp = fmtstr.clone();
317 let mut args = vec![];
318 let mut fmt_parser = Parser::new(&tmp, None, Vec::new(), false);
319 while let Some(piece) = fmt_parser.next() {
320 if !fmt_parser.errors.is_empty() {
321 return (None, expr, is_raw);
323 if let Piece::NextArgument(arg) = piece {
324 if arg.format.ty == "?" {
325 // FIXME: modify rustc's fmt string parser to give us the current span
326 span_lint(cx, USE_DEBUG, parser.prev_span, "use of `Debug`-based formatting");
331 let lint = if is_write { WRITE_LITERAL } else { PRINT_LITERAL };
334 const SIMPLE: FormatSpec<'_> = FormatSpec {
338 precision: CountImplied,
342 if !parser.eat(&token::Comma) {
343 return (Some(fmtstr), expr, is_raw);
345 let token_expr = match parser.parse_expr().map_err(|mut err| err.cancel()) {
347 Err(_) => return (Some(fmtstr), None, is_raw),
349 match &token_expr.node {
350 ExprKind::Lit(_) => {
351 let mut all_simple = true;
352 let mut seen = false;
355 ArgumentImplicitlyIs(n) | ArgumentIs(n) => {
357 all_simple &= arg.format == SIMPLE;
361 ArgumentNamed(_) => {},
364 if all_simple && seen {
365 span_lint(cx, lint, token_expr.span, "literal with an empty format string");
369 ExprKind::Assign(lhs, rhs) => {
370 if let ExprKind::Lit(_) = rhs.node {
371 if let ExprKind::Path(_, p) = &lhs.node {
372 let mut all_simple = true;
373 let mut seen = false;
376 ArgumentImplicitlyIs(_) | ArgumentIs(_) => {},
377 ArgumentNamed(name) => {
380 all_simple &= arg.format == SIMPLE;
385 if all_simple && seen {
386 span_lint(cx, lint, rhs.span, "literal with an empty format string");
396 // Checks if `s` constains a single newline that terminates it
397 // Literal and escaped newlines are both checked (only literal for raw strings)
398 fn check_newlines(s: &str, is_raw: bool) -> bool {
399 if s.ends_with('\n') {
409 let bytes = s.as_bytes();
410 if bytes[bytes.len() - 2] != b'\\' || bytes[bytes.len() - 1] != b'n' {
414 let mut escaping = false;
415 for (index, &byte) in bytes.iter().enumerate() {
418 return index == bytes.len() - 1;
421 } else if byte == b'\\' {