| use clippy_config::Conf; | |
| use clippy_utils::diagnostics::span_lint; | |
| use clippy_utils::macros::{FormatArgsStorage, root_macro_call_first_node}; | |
| use clippy_utils::{is_in_test, sym}; | |
| use rustc_hir::{Expr, Impl, Item, ItemKind, OwnerId}; | |
| use rustc_lint::{LateContext, LateLintPass, LintContext as _, impl_lint_pass}; | |
| mod empty_string; | |
| mod literal; | |
| mod use_debug; | |
| mod with_newline; | |
| declare_clippy_lint! { | |
| /// ### What it does | |
| /// This lint warns about the use of literals as `print!`/`println!` args. | |
| /// | |
| /// ### Why is this bad? | |
| /// Using literals as `println!` args is [inefficient] and unnecessary | |
| /// (i.e., just put the literal in the format string) | |
| /// | |
| /// ### Example | |
| /// ```no_run | |
| /// println!("{}", "foo"); | |
| /// ``` | |
| /// use the literal without formatting: | |
| /// ```no_run | |
| /// println!("foo"); | |
| /// ``` | |
| /// | |
| /// [inefficient]: https://github.com/matthiaskrgr/rust-str-bench | |
| #[clippy::version = "pre 1.29.0"] | |
| pub PRINT_LITERAL, | |
| style, | |
| "printing a literal with a format string" | |
| } | |
| declare_clippy_lint! { | |
| /// ### What it does | |
| /// Checks for printing on *stderr*. The purpose of this lint | |
| /// is to catch debugging remnants. | |
| /// | |
| /// ### Why restrict this? | |
| /// People often print on *stderr* while debugging an | |
| /// application and might forget to remove those prints afterward. | |
| /// | |
| /// ### Known problems | |
| /// Only catches `eprint!` and `eprintln!` calls. | |
| /// | |
| /// ### Example | |
| /// ```no_run | |
| /// eprintln!("Hello world!"); | |
| /// ``` | |
| #[clippy::version = "1.50.0"] | |
| pub PRINT_STDERR, | |
| restriction, | |
| "printing on stderr" | |
| } | |
| declare_clippy_lint! { | |
| /// ### What it does | |
| /// Checks for printing on *stdout*. The purpose of this lint | |
| /// is to catch debugging remnants. | |
| /// | |
| /// ### Why restrict this? | |
| /// People often print on *stdout* while debugging an | |
| /// application and might forget to remove those prints afterward. | |
| /// | |
| /// ### Known problems | |
| /// Only catches `print!` and `println!` calls. | |
| /// | |
| /// ### Example | |
| /// ```no_run | |
| /// println!("Hello world!"); | |
| /// ``` | |
| #[clippy::version = "pre 1.29.0"] | |
| pub PRINT_STDOUT, | |
| restriction, | |
| "printing on stdout" | |
| } | |
| declare_clippy_lint! { | |
| /// ### What it does | |
| /// This lint warns when you use `print!()` with a format | |
| /// string that ends in a newline. | |
| /// | |
| /// ### Why is this bad? | |
| /// You should use `println!()` instead, which appends the | |
| /// newline. | |
| /// | |
| /// ### Example | |
| /// ```no_run | |
| /// # let name = "World"; | |
| /// print!("Hello {}!\n", name); | |
| /// ``` | |
| /// use println!() instead | |
| /// ```no_run | |
| /// # let name = "World"; | |
| /// println!("Hello {}!", name); | |
| /// ``` | |
| #[clippy::version = "pre 1.29.0"] | |
| pub PRINT_WITH_NEWLINE, | |
| style, | |
| "using `print!()` with a format string that ends in a single newline" | |
| } | |
| declare_clippy_lint! { | |
| /// ### What it does | |
| /// This lint warns when you use `println!("")` to | |
| /// print a newline. | |
| /// | |
| /// ### Why is this bad? | |
| /// You should use `println!()`, which is simpler. | |
| /// | |
| /// ### Example | |
| /// ```no_run | |
| /// println!(""); | |
| /// ``` | |
| /// | |
| /// Use instead: | |
| /// ```no_run | |
| /// println!(); | |
| /// ``` | |
| #[clippy::version = "pre 1.29.0"] | |
| pub PRINTLN_EMPTY_STRING, | |
| style, | |
| "using `println!(\"\")` with an empty string" | |
| } | |
| declare_clippy_lint! { | |
| /// ### What it does | |
| /// Checks for usage of `Debug` formatting. The purpose of this | |
| /// lint is to catch debugging remnants. | |
| /// | |
| /// ### Why restrict this? | |
| /// The purpose of the `Debug` trait is to facilitate debugging Rust code, | |
| /// and [no guarantees are made about its output][stability]. | |
| /// It should not be used in user-facing output. | |
| /// | |
| /// ### Example | |
| /// ```no_run | |
| /// # let foo = "bar"; | |
| /// println!("{:?}", foo); | |
| /// ``` | |
| /// | |
| /// [stability]: https://doc.rust-lang.org/stable/std/fmt/trait.Debug.html#stability | |
| #[clippy::version = "pre 1.29.0"] | |
| pub USE_DEBUG, | |
| restriction, | |
| "use of `Debug`-based formatting" | |
| } | |
| declare_clippy_lint! { | |
| /// ### What it does | |
| /// This lint warns about the use of literals as `write!`/`writeln!` args. | |
| /// | |
| /// ### Why is this bad? | |
| /// Using literals as `writeln!` args is [inefficient] and unnecessary | |
| /// (i.e., just put the literal in the format string) | |
| /// | |
| /// ### Example | |
| /// ```no_run | |
| /// # use std::fmt::Write; | |
| /// # let mut buf = String::new(); | |
| /// writeln!(buf, "{}", "foo"); | |
| /// ``` | |
| /// | |
| /// Use instead: | |
| /// ```no_run | |
| /// # use std::fmt::Write; | |
| /// # let mut buf = String::new(); | |
| /// writeln!(buf, "foo"); | |
| /// ``` | |
| /// | |
| /// [inefficient]: https://github.com/matthiaskrgr/rust-str-bench | |
| #[clippy::version = "pre 1.29.0"] | |
| pub WRITE_LITERAL, | |
| style, | |
| "writing a literal with a format string" | |
| } | |
| declare_clippy_lint! { | |
| /// ### What it does | |
| /// This lint warns when you use `write!()` with a format | |
| /// string that | |
| /// ends in a newline. | |
| /// | |
| /// ### Why is this bad? | |
| /// You should use `writeln!()` instead, which appends the | |
| /// newline. | |
| /// | |
| /// ### Example | |
| /// ```no_run | |
| /// # use std::fmt::Write; | |
| /// # let mut buf = String::new(); | |
| /// # let name = "World"; | |
| /// write!(buf, "Hello {}!\n", name); | |
| /// ``` | |
| /// | |
| /// Use instead: | |
| /// ```no_run | |
| /// # use std::fmt::Write; | |
| /// # let mut buf = String::new(); | |
| /// # let name = "World"; | |
| /// writeln!(buf, "Hello {}!", name); | |
| /// ``` | |
| #[clippy::version = "pre 1.29.0"] | |
| pub WRITE_WITH_NEWLINE, | |
| style, | |
| "using `write!()` with a format string that ends in a single newline" | |
| } | |
| declare_clippy_lint! { | |
| /// ### What it does | |
| /// This lint warns when you use `writeln!(buf, "")` to | |
| /// print a newline. | |
| /// | |
| /// ### Why is this bad? | |
| /// You should use `writeln!(buf)`, which is simpler. | |
| /// | |
| /// ### Example | |
| /// ```no_run | |
| /// # use std::fmt::Write; | |
| /// # let mut buf = String::new(); | |
| /// writeln!(buf, ""); | |
| /// ``` | |
| /// | |
| /// Use instead: | |
| /// ```no_run | |
| /// # use std::fmt::Write; | |
| /// # let mut buf = String::new(); | |
| /// writeln!(buf); | |
| /// ``` | |
| #[clippy::version = "pre 1.29.0"] | |
| pub WRITELN_EMPTY_STRING, | |
| style, | |
| "using `writeln!(buf, \"\")` with an empty string" | |
| } | |
| impl_lint_pass!(Write => [ | |
| PRINTLN_EMPTY_STRING, | |
| PRINT_LITERAL, | |
| PRINT_STDERR, | |
| PRINT_STDOUT, | |
| PRINT_WITH_NEWLINE, | |
| USE_DEBUG, | |
| WRITELN_EMPTY_STRING, | |
| WRITE_LITERAL, | |
| WRITE_WITH_NEWLINE, | |
| ]); | |
| pub struct Write { | |
| format_args: FormatArgsStorage, | |
| // The outermost `impl Debug` we're currently in. While we're in one, `USE_DEBUG` is deactivated | |
| outermost_debug_impl: Option<OwnerId>, | |
| allow_print_in_tests: bool, | |
| } | |
| impl Write { | |
| pub fn new(conf: &'static Conf, format_args: FormatArgsStorage) -> Self { | |
| Self { | |
| format_args, | |
| outermost_debug_impl: None, | |
| allow_print_in_tests: conf.allow_print_in_tests, | |
| } | |
| } | |
| fn in_debug_impl(&self) -> bool { | |
| self.outermost_debug_impl.is_some() | |
| } | |
| } | |
| impl<'tcx> LateLintPass<'tcx> for Write { | |
| fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) { | |
| // Only check for `impl Debug`s if we're not already in one | |
| if self.outermost_debug_impl.is_none() && is_debug_impl(cx, item) { | |
| self.outermost_debug_impl = Some(item.owner_id); | |
| } | |
| } | |
| fn check_item_post(&mut self, _cx: &LateContext<'_>, item: &Item<'_>) { | |
| // Only clear `self.outermost_debug_impl` if we're escaping the _outermost_ debug impl | |
| if self.outermost_debug_impl == Some(item.owner_id) { | |
| self.outermost_debug_impl = None; | |
| } | |
| } | |
| fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { | |
| let Some(macro_call) = root_macro_call_first_node(cx, expr) else { | |
| return; | |
| }; | |
| let Some(diag_name) = cx.tcx.get_diagnostic_name(macro_call.def_id) else { | |
| return; | |
| }; | |
| let Some(name) = diag_name.as_str().strip_suffix("_macro") else { | |
| return; | |
| }; | |
| let is_build_script = cx | |
| .sess() | |
| .opts | |
| .crate_name | |
| .as_ref() | |
| .is_some_and(|crate_name| crate_name == "build_script_build"); | |
| let allowed_in_tests = self.allow_print_in_tests && is_in_test(cx.tcx, expr.hir_id); | |
| match diag_name { | |
| sym::print_macro | sym::println_macro if !allowed_in_tests => { | |
| if !is_build_script { | |
| span_lint(cx, PRINT_STDOUT, macro_call.span, format!("use of `{name}!`")); | |
| } | |
| }, | |
| sym::eprint_macro | sym::eprintln_macro if !allowed_in_tests => { | |
| span_lint(cx, PRINT_STDERR, macro_call.span, format!("use of `{name}!`")); | |
| }, | |
| sym::write_macro | sym::writeln_macro => {}, | |
| _ => return, | |
| } | |
| if let Some(format_args) = self.format_args.get(cx, expr, macro_call.expn) { | |
| // ignore `writeln!(w)` and `write!(v, some_macro!())` | |
| if format_args.span.from_expansion() { | |
| return; | |
| } | |
| match diag_name { | |
| sym::print_macro | sym::eprint_macro | sym::write_macro => { | |
| with_newline::check(cx, format_args, ¯o_call, name); | |
| }, | |
| sym::println_macro | sym::eprintln_macro | sym::writeln_macro => { | |
| empty_string::check(cx, format_args, ¯o_call, name); | |
| }, | |
| _ => {}, | |
| } | |
| literal::check(cx, format_args, name); | |
| if !self.in_debug_impl() { | |
| use_debug::check(cx, format_args); | |
| } | |
| } | |
| } | |
| } | |
| fn is_debug_impl(cx: &LateContext<'_>, item: &Item<'_>) -> bool { | |
| if let ItemKind::Impl(Impl { | |
| of_trait: Some(of_trait), | |
| .. | |
| }) = &item.kind | |
| && let Some(trait_id) = of_trait.trait_ref.trait_def_id() | |
| { | |
| cx.tcx.is_diagnostic_item(sym::Debug, trait_id) | |
| } else { | |
| false | |
| } | |
| } |