4 use crate::utils::{snippet_with_applicability, span_lint, span_lint_and_sugg, span_lint_and_then};
5 use rustc::lint::{EarlyContext, EarlyLintPass, LintArray, LintPass};
6 use rustc::{declare_lint_pass, declare_tool_lint};
7 use rustc_errors::Applicability;
8 use rustc_lexer::unescape::{self, EscapeError};
9 use rustc_parse::parser;
12 use syntax::tokenstream::TokenStream;
13 use syntax_pos::{BytePos, Span};
15 declare_clippy_lint! {
16 /// **What it does:** This lint warns when you use `println!("")` to
19 /// **Why is this bad?** You should use `println!()`, which is simpler.
21 /// **Known problems:** None.
27 pub PRINTLN_EMPTY_STRING,
29 "using `println!(\"\")` with an empty string"
32 declare_clippy_lint! {
33 /// **What it does:** This lint warns when you use `print!()` with a format
35 /// ends in a newline.
37 /// **Why is this bad?** You should use `println!()` instead, which appends the
40 /// **Known problems:** None.
44 /// # let name = "World";
45 /// print!("Hello {}!\n", name);
47 /// use println!() instead
49 /// # let name = "World";
50 /// println!("Hello {}!", name);
52 pub PRINT_WITH_NEWLINE,
54 "using `print!()` with a format string that ends in a single newline"
57 declare_clippy_lint! {
58 /// **What it does:** Checks for printing on *stdout*. The purpose of this lint
59 /// is to catch debugging remnants.
61 /// **Why is this bad?** People often print on *stdout* while debugging an
62 /// application and might forget to remove those prints afterward.
64 /// **Known problems:** Only catches `print!` and `println!` calls.
68 /// println!("Hello world!");
75 declare_clippy_lint! {
76 /// **What it does:** Checks for use of `Debug` formatting. The purpose of this
77 /// lint is to catch debugging remnants.
79 /// **Why is this bad?** The purpose of the `Debug` trait is to facilitate
80 /// debugging Rust code. It should not be used in user-facing output.
84 /// # let foo = "bar";
85 /// println!("{:?}", foo);
89 "use of `Debug`-based formatting"
92 declare_clippy_lint! {
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:
112 "printing a literal with a format string"
115 declare_clippy_lint! {
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.
125 /// # use std::fmt::Write;
126 /// # let mut buf = String::new();
127 /// writeln!(buf, "");
129 pub WRITELN_EMPTY_STRING,
131 "using `writeln!(buf, \"\")` with an empty string"
134 declare_clippy_lint! {
135 /// **What it does:** This lint warns when you use `write!()` with a format
137 /// ends in a newline.
139 /// **Why is this bad?** You should use `writeln!()` instead, which appends the
142 /// **Known problems:** None.
146 /// # use std::fmt::Write;
147 /// # let mut buf = String::new();
148 /// # let name = "World";
149 /// write!(buf, "Hello {}!\n", name);
151 pub WRITE_WITH_NEWLINE,
153 "using `write!()` with a format string that ends in a single newline"
156 declare_clippy_lint! {
157 /// **What it does:** This lint warns about the use of literals as `write!`/`writeln!` args.
159 /// **Why is this bad?** Using literals as `writeln!` args is inefficient
160 /// (c.f., https://github.com/matthiaskrgr/rust-str-bench) and unnecessary
161 /// (i.e., just put the literal in the format string)
163 /// **Known problems:** Will also warn with macro calls as arguments that expand to literals
164 /// -- e.g., `writeln!(buf, "{}", env!("FOO"))`.
168 /// # use std::fmt::Write;
169 /// # let mut buf = String::new();
170 /// writeln!(buf, "{}", "foo");
174 "writing a literal with a format string"
177 declare_lint_pass!(Write => [
179 PRINTLN_EMPTY_STRING,
184 WRITELN_EMPTY_STRING,
188 impl EarlyLintPass for Write {
189 fn check_mac(&mut self, cx: &EarlyContext<'_>, mac: &Mac) {
190 if mac.path == sym!(println) {
191 span_lint(cx, PRINT_STDOUT, mac.span, "use of `println!`");
192 if let (Some(fmt_str), _) = check_tts(cx, &mac.tts, false) {
193 if fmt_str.contents.is_empty() {
196 PRINTLN_EMPTY_STRING,
198 "using `println!(\"\")`",
200 "println!()".to_string(),
201 Applicability::MachineApplicable,
205 } else if mac.path == sym!(print) {
206 span_lint(cx, PRINT_STDOUT, mac.span, "use of `print!`");
207 if let (Some(fmt_str), _) = check_tts(cx, &mac.tts, false) {
208 if check_newlines(&fmt_str.contents, fmt_str.style) {
213 "using `print!()` with a format string that ends in a single newline",
215 err.multipart_suggestion(
216 "use `println!` instead",
218 (mac.path.span, String::from("println")),
219 (fmt_str.newline_span(), String::new()),
221 Applicability::MachineApplicable,
227 } else if mac.path == sym!(write) {
228 if let (Some(fmt_str), _) = check_tts(cx, &mac.tts, true) {
229 if check_newlines(&fmt_str.contents, fmt_str.style) {
234 "using `write!()` with a format string that ends in a single newline",
236 err.multipart_suggestion(
237 "use `writeln!()` instead",
239 (mac.path.span, String::from("writeln")),
240 (fmt_str.newline_span(), String::new()),
242 Applicability::MachineApplicable,
248 } else if mac.path == sym!(writeln) {
249 if let (Some(fmt_str), expr) = check_tts(cx, &mac.tts, true) {
250 if fmt_str.contents.is_empty() {
251 let mut applicability = Applicability::MachineApplicable;
252 let suggestion = expr.map_or_else(
254 applicability = Applicability::HasPlaceholders;
257 move |expr| snippet_with_applicability(cx, expr.span, "v", &mut applicability),
262 WRITELN_EMPTY_STRING,
264 format!("using `writeln!({}, \"\")`", suggestion).as_str(),
266 format!("writeln!({})", suggestion),
275 /// The arguments of a `print[ln]!` or `write[ln]!` invocation.
277 /// The contents of the format string (inside the quotes).
280 /// The span of the format string, including quotes, the raw marker, and any raw hashes.
285 /// Given a format string that ends in a newline and its span, calculates the span of the
287 fn newline_span(&self) -> Span {
290 let newline_sp_hi = sp.hi()
292 StrStyle::Cooked => BytePos(1),
293 StrStyle::Raw(hashes) => BytePos((1 + hashes).into()),
296 let newline_sp_len = if self.contents.ends_with('\n') {
298 } else if self.contents.ends_with(r"\n") {
301 panic!("expected format string to contain a newline");
304 sp.with_lo(newline_sp_hi - newline_sp_len).with_hi(newline_sp_hi)
308 /// Checks the arguments of `print[ln]!` and `write[ln]!` calls. It will return a tuple of two
309 /// `Option`s. The first `Option` of the tuple is the macro's format string. It includes
310 /// the contents of the string, whether it's a raw string, and the span of the literal in the
311 /// source. The second `Option` in the tuple is, in the `write[ln]!` case, the expression the
312 /// `format_str` should be written to.
316 /// Calling this function on
318 /// # use std::fmt::Write;
319 /// # let mut buf = String::new();
320 /// # let something = "something";
321 /// writeln!(buf, "string to write: {}", something);
325 /// (Some("string to write: {}"), Some(buf))
327 #[allow(clippy::too_many_lines)]
328 fn check_tts<'a>(cx: &EarlyContext<'a>, tts: &TokenStream, is_write: bool) -> (Option<FmtStr>, Option<Expr>) {
330 let tts = tts.clone();
332 let mut parser = parser::Parser::new(&cx.sess.parse_sess, tts, None, false, false, None);
333 let mut expr: Option<Expr> = None;
335 expr = match parser.parse_expr().map_err(|mut err| err.cancel()) {
336 Ok(p) => Some(p.into_inner()),
337 Err(_) => return (None, None),
339 // might be `writeln!(foo)`
340 if parser.expect(&token::Comma).map_err(|mut err| err.cancel()).is_err() {
345 let (fmtstr, fmtstyle) = match parser.parse_str().map_err(|mut err| err.cancel()) {
346 Ok((fmtstr, fmtstyle)) => (fmtstr.to_string(), fmtstyle),
347 Err(_) => return (None, expr),
349 let fmtspan = parser.prev_span;
350 let tmp = fmtstr.clone();
351 let mut args = vec![];
352 let mut fmt_parser = Parser::new(&tmp, None, Vec::new(), false);
353 while let Some(piece) = fmt_parser.next() {
354 if !fmt_parser.errors.is_empty() {
357 if let Piece::NextArgument(arg) = piece {
358 if arg.format.ty == "?" {
359 // FIXME: modify rustc's fmt string parser to give us the current span
360 span_lint(cx, USE_DEBUG, parser.prev_span, "use of `Debug`-based formatting");
365 let lint = if is_write { WRITE_LITERAL } else { PRINT_LITERAL };
368 const SIMPLE: FormatSpec<'_> = FormatSpec {
372 precision: CountImplied,
373 precision_span: None,
379 if !parser.eat(&token::Comma) {
389 let token_expr = if let Ok(expr) = parser.parse_expr().map_err(|mut err| err.cancel()) {
401 match &token_expr.kind {
402 ExprKind::Lit(_) => {
403 let mut all_simple = true;
404 let mut seen = false;
407 ArgumentImplicitlyIs(n) | ArgumentIs(n) => {
409 all_simple &= arg.format == SIMPLE;
413 ArgumentNamed(_) => {},
416 if all_simple && seen {
417 span_lint(cx, lint, token_expr.span, "literal with an empty format string");
421 ExprKind::Assign(lhs, rhs) => {
422 if let ExprKind::Lit(_) = rhs.kind {
423 if let ExprKind::Path(_, p) = &lhs.kind {
424 let mut all_simple = true;
425 let mut seen = false;
428 ArgumentImplicitlyIs(_) | ArgumentIs(_) => {},
429 ArgumentNamed(name) => {
432 all_simple &= arg.format == SIMPLE;
437 if all_simple && seen {
438 span_lint(cx, lint, rhs.span, "literal with an empty format string");
448 /// Checks if the format string contains a single newline that terminates it.
450 /// Literal and escaped newlines are both checked (only literal for raw strings).
451 fn check_newlines(contents: &str, style: StrStyle) -> bool {
452 let mut has_internal_newline = false;
453 let mut last_was_cr = false;
454 let mut should_lint = false;
456 let mut cb = |r: Range<usize>, c: Result<char, EscapeError>| {
459 if r.end == contents.len() && c == '\n' && !last_was_cr && !has_internal_newline {
462 last_was_cr = c == '\r';
464 has_internal_newline = true;
470 StrStyle::Cooked => unescape::unescape_str(contents, &mut cb),
471 StrStyle::Raw(_) => unescape::unescape_raw_str(contents, &mut cb),