]> git.lizzy.rs Git - rust.git/blobdiff - clippy_lints/src/write.rs
ast/hir: Rename field-related structures
[rust.git] / clippy_lints / src / write.rs
index d9d60fffcd7ae79238566aa251c46b8ade76eaf1..553e6b000ebbc055161347f939a3060f206893b4 100644 (file)
@@ -2,7 +2,10 @@
 use std::ops::Range;
 
 use crate::utils::{snippet_with_applicability, span_lint, span_lint_and_sugg, span_lint_and_then};
-use rustc_ast::ast::{Expr, ExprKind, Item, ItemKind, MacCall, StrLit, StrStyle};
+use if_chain::if_chain;
+use rustc_ast::ast::{
+    Expr, ExprKind, ImplKind, Item, ItemKind, LitKind, MacCall, StrLit, StrStyle,
+};
 use rustc_ast::token;
 use rustc_ast::tokenstream::TokenStream;
 use rustc_errors::Applicability;
@@ -10,8 +13,8 @@
 use rustc_lint::{EarlyContext, EarlyLintPass};
 use rustc_parse::parser;
 use rustc_session::{declare_tool_lint, impl_lint_pass};
-use rustc_span::symbol::Symbol;
-use rustc_span::{BytePos, Span};
+use rustc_span::symbol::kw;
+use rustc_span::{sym, BytePos, Span};
 
 declare_clippy_lint! {
     /// **What it does:** This lint warns when you use `println!("")` to
     "printing on stdout"
 }
 
+declare_clippy_lint! {
+    /// **What it does:** Checks for printing on *stderr*. The purpose of this lint
+    /// is to catch debugging remnants.
+    ///
+    /// **Why is this bad?** 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:**
+    /// ```rust
+    /// eprintln!("Hello world!");
+    /// ```
+    pub PRINT_STDERR,
+    restriction,
+    "printing on stderr"
+}
+
 declare_clippy_lint! {
     /// **What it does:** Checks for use of `Debug` formatting. The purpose of this
     /// lint is to catch debugging remnants.
     /// ```rust
     /// # use std::fmt::Write;
     /// # let mut buf = String::new();
-    ///
     /// // Bad
     /// writeln!(buf, "");
     ///
     /// # use std::fmt::Write;
     /// # let mut buf = String::new();
     /// # let name = "World";
-    ///
     /// // Bad
     /// write!(buf, "Hello {}!\n", name);
     ///
     /// ```rust
     /// # use std::fmt::Write;
     /// # let mut buf = String::new();
-    ///
     /// // Bad
     /// writeln!(buf, "{}", "foo");
     ///
@@ -202,6 +220,7 @@ pub struct Write {
     PRINT_WITH_NEWLINE,
     PRINTLN_EMPTY_STRING,
     PRINT_STDOUT,
+    PRINT_STDERR,
     USE_DEBUG,
     PRINT_LITERAL,
     WRITE_WITH_NEWLINE,
@@ -211,10 +230,10 @@ pub struct Write {
 
 impl EarlyLintPass for Write {
     fn check_item(&mut self, _: &EarlyContext<'_>, item: &Item) {
-        if let ItemKind::Impl {
+        if let ItemKind::Impl(box ImplKind {
             of_trait: Some(trait_ref),
             ..
-        } = &item.kind
+        }) = &item.kind
         {
             let trait_name = trait_ref
                 .path
@@ -224,7 +243,7 @@ fn check_item(&mut self, _: &EarlyContext<'_>, item: &Item) {
                 .expect("path has at least one segment")
                 .ident
                 .name;
-            if trait_name == sym!(Debug) {
+            if trait_name == sym::Debug {
                 self.in_debug_impl = true;
             }
         }
@@ -244,47 +263,22 @@ fn is_build_script(cx: &EarlyContext<'_>) -> bool {
                 .map_or(false, |crate_name| crate_name == "build_script_build")
         }
 
-        if mac.path == sym!(println) {
-            if !is_build_script(cx) {
-                span_lint(cx, PRINT_STDOUT, mac.span(), "use of `println!`");
-            }
-            if let (Some(fmt_str), _) = self.check_tts(cx, mac.args.inner_tokens(), false) {
-                if fmt_str.symbol == Symbol::intern("") {
-                    span_lint_and_sugg(
-                        cx,
-                        PRINTLN_EMPTY_STRING,
-                        mac.span(),
-                        "using `println!(\"\")`",
-                        "replace it with",
-                        "println!()".to_string(),
-                        Applicability::MachineApplicable,
-                    );
-                }
-            }
-        } else if mac.path == sym!(print) {
+        if mac.path == sym!(print) {
             if !is_build_script(cx) {
                 span_lint(cx, PRINT_STDOUT, mac.span(), "use of `print!`");
             }
-            if let (Some(fmt_str), _) = self.check_tts(cx, mac.args.inner_tokens(), false) {
-                if check_newlines(&fmt_str) {
-                    span_lint_and_then(
-                        cx,
-                        PRINT_WITH_NEWLINE,
-                        mac.span(),
-                        "using `print!()` with a format string that ends in a single newline",
-                        |err| {
-                            err.multipart_suggestion(
-                                "use `println!` instead",
-                                vec![
-                                    (mac.path.span, String::from("println")),
-                                    (newline_span(&fmt_str), String::new()),
-                                ],
-                                Applicability::MachineApplicable,
-                            );
-                        },
-                    );
-                }
+            self.lint_print_with_newline(cx, mac);
+        } else if mac.path == sym!(println) {
+            if !is_build_script(cx) {
+                span_lint(cx, PRINT_STDOUT, mac.span(), "use of `println!`");
             }
+            self.lint_println_empty_string(cx, mac);
+        } else if mac.path == sym!(eprint) {
+            span_lint(cx, PRINT_STDERR, mac.span(), "use of `eprint!`");
+            self.lint_print_with_newline(cx, mac);
+        } else if mac.path == sym!(eprintln) {
+            span_lint(cx, PRINT_STDERR, mac.span(), "use of `eprintln!`");
+            self.lint_println_empty_string(cx, mac);
         } else if mac.path == sym!(write) {
             if let (Some(fmt_str), _) = self.check_tts(cx, mac.args.inner_tokens(), true) {
                 if check_newlines(&fmt_str) {
@@ -308,7 +302,7 @@ fn is_build_script(cx: &EarlyContext<'_>) -> bool {
             }
         } else if mac.path == sym!(writeln) {
             if let (Some(fmt_str), expr) = self.check_tts(cx, mac.args.inner_tokens(), true) {
-                if fmt_str.symbol == Symbol::intern("") {
+                if fmt_str.symbol == kw::Empty {
                     let mut applicability = Applicability::MachineApplicable;
                     // FIXME: remove this `#[allow(...)]` once the issue #5822 gets fixed
                     #[allow(clippy::option_if_let_else)]
@@ -382,10 +376,15 @@ impl Write {
     /// (Some("string to write: {}"), Some(buf))
     /// ```
     #[allow(clippy::too_many_lines)]
-    fn check_tts<'a>(&self, cx: &EarlyContext<'a>, tts: TokenStream, is_write: bool) -> (Option<StrLit>, Option<Expr>) {
+    fn check_tts<'a>(
+        &self,
+        cx: &EarlyContext<'a>,
+        tts: TokenStream,
+        is_write: bool,
+    ) -> (Option<StrLit>, Option<Expr>) {
         use rustc_parse_format::{
-            AlignUnknown, ArgumentImplicitlyIs, ArgumentIs, ArgumentNamed, CountImplied, FormatSpec, ParseMode, Parser,
-            Piece,
+            AlignUnknown, ArgumentImplicitlyIs, ArgumentIs, ArgumentNamed, CountImplied,
+            FormatSpec, ParseMode, Parser, Piece,
         };
 
         let mut parser = parser::Parser::new(&cx.sess.parse_sess, tts, false, None);
@@ -415,7 +414,12 @@ fn check_tts<'a>(&self, cx: &EarlyContext<'a>, tts: TokenStream, is_write: bool)
             if let Piece::NextArgument(arg) = piece {
                 if !self.in_debug_impl && arg.format.ty == "?" {
                     // FIXME: modify rustc's fmt string parser to give us the current span
-                    span_lint(cx, USE_DEBUG, parser.prev_token.span, "use of `Debug`-based formatting");
+                    span_lint(
+                        cx,
+                        USE_DEBUG,
+                        parser.prev_token.span,
+                        "use of `Debug`-based formatting",
+                    );
                 }
                 args.push(arg);
             }
@@ -443,7 +447,9 @@ fn check_tts<'a>(&self, cx: &EarlyContext<'a>, tts: TokenStream, is_write: bool)
                 return (Some(fmtstr), None);
             };
             match &token_expr.kind {
-                ExprKind::Lit(_) => {
+                ExprKind::Lit(lit)
+                    if !matches!(lit.kind, LitKind::Int(..) | LitKind::Float(..)) =>
+                {
                     let mut all_simple = true;
                     let mut seen = false;
                     for arg in &args {
@@ -453,18 +459,21 @@ fn check_tts<'a>(&self, cx: &EarlyContext<'a>, tts: TokenStream, is_write: bool)
                                     all_simple &= arg.format == SIMPLE;
                                     seen = true;
                                 }
-                            },
-                            ArgumentNamed(_) => {},
+                            }
+                            ArgumentNamed(_) => {}
                         }
                     }
                     if all_simple && seen {
                         span_lint(cx, lint, token_expr.span, "literal with an empty format string");
                     }
                     idx += 1;
-                },
+                }
                 ExprKind::Assign(lhs, rhs, _) => {
-                    if let ExprKind::Lit(_) = rhs.kind {
-                        if let ExprKind::Path(_, p) = &lhs.kind {
+                    if_chain! {
+                        if let ExprKind::Lit(ref lit) = rhs.kind;
+                        if !matches!(lit.kind, LitKind::Int(..) | LitKind::Float(..));
+                        if let ExprKind::Path(_, p) = &lhs.kind;
+                        then {
                             let mut all_simple = true;
                             let mut seen = false;
                             for arg in &args {
@@ -483,11 +492,56 @@ fn check_tts<'a>(&self, cx: &EarlyContext<'a>, tts: TokenStream, is_write: bool)
                             }
                         }
                     }
-                },
+                }
                 _ => idx += 1,
             }
         }
     }
+
+    fn lint_println_empty_string(&self, cx: &EarlyContext<'_>, mac: &MacCall) {
+        if let (Some(fmt_str), _) = self.check_tts(cx, mac.args.inner_tokens(), false) {
+            if fmt_str.symbol == kw::Empty {
+                let name = mac.path.segments[0].ident.name;
+                span_lint_and_sugg(
+                    cx,
+                    PRINTLN_EMPTY_STRING,
+                    mac.span(),
+                    &format!("using `{}!(\"\")`", name),
+                    "replace it with",
+                    format!("{}!()", name),
+                    Applicability::MachineApplicable,
+                );
+            }
+        }
+    }
+
+    fn lint_print_with_newline(&self, cx: &EarlyContext<'_>, mac: &MacCall) {
+        if let (Some(fmt_str), _) = self.check_tts(cx, mac.args.inner_tokens(), false) {
+            if check_newlines(&fmt_str) {
+                let name = mac.path.segments[0].ident.name;
+                let suggested = format!("{}ln", name);
+                span_lint_and_then(
+                    cx,
+                    PRINT_WITH_NEWLINE,
+                    mac.span(),
+                    &format!(
+                        "using `{}!()` with a format string that ends in a single newline",
+                        name
+                    ),
+                    |err| {
+                        err.multipart_suggestion(
+                            &format!("use `{}!` instead", suggested),
+                            vec![
+                                (mac.path.span, suggested),
+                                (newline_span(&fmt_str), String::new()),
+                            ],
+                            Applicability::MachineApplicable,
+                        );
+                    },
+                );
+            }
+        }
+    }
 }
 
 /// Checks if the format string contains a single newline that terminates it.