4 use crate::utils::{snippet_with_applicability, span_lint, span_lint_and_sugg, span_lint_and_then};
5 use rustc_ast::ast::{Expr, ExprKind, Item, ItemKind, MacCall, StrLit, StrStyle};
7 use rustc_ast::tokenstream::TokenStream;
8 use rustc_errors::Applicability;
9 use rustc_lexer::unescape::{self, EscapeError};
10 use rustc_lint::{EarlyContext, EarlyLintPass};
11 use rustc_parse::parser;
12 use rustc_session::{declare_tool_lint, impl_lint_pass};
13 use rustc_span::symbol::Symbol;
14 use rustc_span::{BytePos, Span};
16 declare_clippy_lint! {
17 /// **What it does:** This lint warns when you use `println!("")` to
20 /// **Why is this bad?** You should use `println!()`, which is simpler.
22 /// **Known problems:** None.
28 pub PRINTLN_EMPTY_STRING,
30 "using `println!(\"\")` with an empty string"
33 declare_clippy_lint! {
34 /// **What it does:** This lint warns when you use `print!()` with a format
36 /// ends in a newline.
38 /// **Why is this bad?** You should use `println!()` instead, which appends the
41 /// **Known problems:** None.
45 /// # let name = "World";
46 /// print!("Hello {}!\n", name);
48 /// use println!() instead
50 /// # let name = "World";
51 /// println!("Hello {}!", name);
53 pub PRINT_WITH_NEWLINE,
55 "using `print!()` with a format string that ends in a single newline"
58 declare_clippy_lint! {
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!");
76 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 user-facing output.
85 /// # let foo = "bar";
86 /// println!("{:?}", foo);
90 "use of `Debug`-based formatting"
93 declare_clippy_lint! {
94 /// **What it does:** This lint warns about the use of literals as `print!`/`println!` args.
96 /// **Why is this bad?** Using literals as `println!` args is inefficient
97 /// (c.f., https://github.com/matthiaskrgr/rust-str-bench) and unnecessary
98 /// (i.e., just put the literal in the format string)
100 /// **Known problems:** Will also warn with macro calls as arguments that expand to literals
101 /// -- e.g., `println!("{}", env!("FOO"))`.
105 /// println!("{}", "foo");
107 /// use the literal without formatting:
113 "printing a literal with a format string"
116 declare_clippy_lint! {
117 /// **What it does:** This lint warns when you use `writeln!(buf, "")` to
120 /// **Why is this bad?** You should use `writeln!(buf)`, which is simpler.
122 /// **Known problems:** None.
126 /// # use std::fmt::Write;
127 /// # let mut buf = String::new();
128 /// writeln!(buf, "");
130 pub WRITELN_EMPTY_STRING,
132 "using `writeln!(buf, \"\")` with an empty string"
135 declare_clippy_lint! {
136 /// **What it does:** This lint warns when you use `write!()` with a format
138 /// ends in a newline.
140 /// **Why is this bad?** You should use `writeln!()` instead, which appends the
143 /// **Known problems:** None.
147 /// # use std::fmt::Write;
148 /// # let mut buf = String::new();
149 /// # let name = "World";
150 /// write!(buf, "Hello {}!\n", name);
152 pub WRITE_WITH_NEWLINE,
154 "using `write!()` with a format string that ends in a single newline"
157 declare_clippy_lint! {
158 /// **What it does:** This lint warns about the use of literals as `write!`/`writeln!` args.
160 /// **Why is this bad?** Using literals as `writeln!` args is inefficient
161 /// (c.f., https://github.com/matthiaskrgr/rust-str-bench) and unnecessary
162 /// (i.e., just put the literal in the format string)
164 /// **Known problems:** Will also warn with macro calls as arguments that expand to literals
165 /// -- e.g., `writeln!(buf, "{}", env!("FOO"))`.
169 /// # use std::fmt::Write;
170 /// # let mut buf = String::new();
171 /// writeln!(buf, "{}", "foo");
175 "writing a literal with a format string"
183 impl_lint_pass!(Write => [
185 PRINTLN_EMPTY_STRING,
190 WRITELN_EMPTY_STRING,
194 impl EarlyLintPass for Write {
195 fn check_item(&mut self, _: &EarlyContext<'_>, item: &Item) {
196 if let ItemKind::Impl {
197 of_trait: Some(trait_ref),
201 let trait_name = trait_ref
206 .expect("path has at least one segment")
209 if trait_name == sym!(Debug) {
210 self.in_debug_impl = true;
215 fn check_item_post(&mut self, _: &EarlyContext<'_>, _: &Item) {
216 self.in_debug_impl = false;
219 fn check_mac(&mut self, cx: &EarlyContext<'_>, mac: &MacCall) {
220 if mac.path == sym!(println) {
221 span_lint(cx, PRINT_STDOUT, mac.span(), "use of `println!`");
222 if let (Some(fmt_str), _) = self.check_tts(cx, &mac.args.inner_tokens(), false) {
223 if fmt_str.symbol == Symbol::intern("") {
226 PRINTLN_EMPTY_STRING,
228 "using `println!(\"\")`",
230 "println!()".to_string(),
231 Applicability::MachineApplicable,
235 } else if mac.path == sym!(print) {
236 span_lint(cx, PRINT_STDOUT, mac.span(), "use of `print!`");
237 if let (Some(fmt_str), _) = self.check_tts(cx, &mac.args.inner_tokens(), false) {
238 if check_newlines(&fmt_str) {
243 "using `print!()` with a format string that ends in a single newline",
245 err.multipart_suggestion(
246 "use `println!` instead",
248 (mac.path.span, String::from("println")),
249 (newline_span(&fmt_str), String::new()),
251 Applicability::MachineApplicable,
257 } else if mac.path == sym!(write) {
258 if let (Some(fmt_str), _) = self.check_tts(cx, &mac.args.inner_tokens(), true) {
259 if check_newlines(&fmt_str) {
264 "using `write!()` with a format string that ends in a single newline",
266 err.multipart_suggestion(
267 "use `writeln!()` instead",
269 (mac.path.span, String::from("writeln")),
270 (newline_span(&fmt_str), String::new()),
272 Applicability::MachineApplicable,
278 } else if mac.path == sym!(writeln) {
279 if let (Some(fmt_str), expr) = self.check_tts(cx, &mac.args.inner_tokens(), true) {
280 if fmt_str.symbol == Symbol::intern("") {
281 let mut applicability = Applicability::MachineApplicable;
282 let suggestion = match expr {
283 Some(expr) => snippet_with_applicability(cx, expr.span, "v", &mut applicability),
285 applicability = Applicability::HasPlaceholders;
292 WRITELN_EMPTY_STRING,
294 format!("using `writeln!({}, \"\")`", suggestion).as_str(),
296 format!("writeln!({})", suggestion),
305 /// Given a format string that ends in a newline and its span, calculates the span of the
307 fn newline_span(fmtstr: &StrLit) -> Span {
308 let sp = fmtstr.span;
309 let contents = &fmtstr.symbol.as_str();
311 let newline_sp_hi = sp.hi()
312 - match fmtstr.style {
313 StrStyle::Cooked => BytePos(1),
314 StrStyle::Raw(hashes) => BytePos((1 + hashes).into()),
317 let newline_sp_len = if contents.ends_with('\n') {
319 } else if contents.ends_with(r"\n") {
322 panic!("expected format string to contain a newline");
325 sp.with_lo(newline_sp_hi - newline_sp_len).with_hi(newline_sp_hi)
329 /// Checks the arguments of `print[ln]!` and `write[ln]!` calls. It will return a tuple of two
330 /// `Option`s. The first `Option` of the tuple is the macro's format string. It includes
331 /// the contents of the string, whether it's a raw string, and the span of the literal in the
332 /// source. The second `Option` in the tuple is, in the `write[ln]!` case, the expression the
333 /// `format_str` should be written to.
337 /// Calling this function on
339 /// # use std::fmt::Write;
340 /// # let mut buf = String::new();
341 /// # let something = "something";
342 /// writeln!(buf, "string to write: {}", something);
346 /// (Some("string to write: {}"), Some(buf))
348 #[allow(clippy::too_many_lines)]
351 cx: &EarlyContext<'a>,
354 ) -> (Option<StrLit>, Option<Expr>) {
355 use rustc_parse_format::{
356 AlignUnknown, ArgumentImplicitlyIs, ArgumentIs, ArgumentNamed, CountImplied, FormatSpec, ParseMode, Parser,
359 let tts = tts.clone();
361 let mut parser = parser::Parser::new(&cx.sess.parse_sess, tts, false, None);
362 let mut expr: Option<Expr> = None;
364 expr = match parser.parse_expr().map_err(|mut err| err.cancel()) {
365 Ok(p) => Some(p.into_inner()),
366 Err(_) => return (None, None),
368 // might be `writeln!(foo)`
369 if parser.expect(&token::Comma).map_err(|mut err| err.cancel()).is_err() {
374 let fmtstr = match parser.parse_str_lit() {
375 Ok(fmtstr) => fmtstr,
376 Err(_) => return (None, expr),
378 let tmp = fmtstr.symbol.as_str();
379 let mut args = vec![];
380 let mut fmt_parser = Parser::new(&tmp, None, None, false, ParseMode::Format);
381 while let Some(piece) = fmt_parser.next() {
382 if !fmt_parser.errors.is_empty() {
385 if let Piece::NextArgument(arg) = piece {
386 if !self.in_debug_impl && arg.format.ty == "?" {
387 // FIXME: modify rustc's fmt string parser to give us the current span
388 span_lint(cx, USE_DEBUG, parser.prev_token.span, "use of `Debug`-based formatting");
393 let lint = if is_write { WRITE_LITERAL } else { PRINT_LITERAL };
396 const SIMPLE: FormatSpec<'_> = FormatSpec {
400 precision: CountImplied,
401 precision_span: None,
407 if !parser.eat(&token::Comma) {
408 return (Some(fmtstr), expr);
410 let token_expr = if let Ok(expr) = parser.parse_expr().map_err(|mut err| err.cancel()) {
413 return (Some(fmtstr), None);
415 match &token_expr.kind {
416 ExprKind::Lit(_) => {
417 let mut all_simple = true;
418 let mut seen = false;
421 ArgumentImplicitlyIs(n) | ArgumentIs(n) => {
423 all_simple &= arg.format == SIMPLE;
427 ArgumentNamed(_) => {},
430 if all_simple && seen {
431 span_lint(cx, lint, token_expr.span, "literal with an empty format string");
435 ExprKind::Assign(lhs, rhs, _) => {
436 if let ExprKind::Lit(_) = rhs.kind {
437 if let ExprKind::Path(_, p) = &lhs.kind {
438 let mut all_simple = true;
439 let mut seen = false;
442 ArgumentImplicitlyIs(_) | ArgumentIs(_) => {},
443 ArgumentNamed(name) => {
446 all_simple &= arg.format == SIMPLE;
451 if all_simple && seen {
452 span_lint(cx, lint, rhs.span, "literal with an empty format string");
463 /// Checks if the format string contains a single newline that terminates it.
465 /// Literal and escaped newlines are both checked (only literal for raw strings).
466 fn check_newlines(fmtstr: &StrLit) -> bool {
467 let mut has_internal_newline = false;
468 let mut last_was_cr = false;
469 let mut should_lint = false;
471 let contents = &fmtstr.symbol.as_str();
473 let mut cb = |r: Range<usize>, c: Result<char, EscapeError>| {
476 if r.end == contents.len() && c == '\n' && !last_was_cr && !has_internal_newline {
479 last_was_cr = c == '\r';
481 has_internal_newline = true;
487 StrStyle::Cooked => unescape::unescape_literal(contents, unescape::Mode::Str, &mut cb),
488 StrStyle::Raw(_) => unescape::unescape_literal(contents, unescape::Mode::RawStr, &mut cb),