]> git.lizzy.rs Git - rust.git/blobdiff - clippy_lints/src/write.rs
clippy: support `QPath::LangItem`
[rust.git] / clippy_lints / src / write.rs
index b8773b47a99d58d37050830d3ce7fa43bc224181..063f94582b9d14cc38f138d8914e8ce178aaae66 100644 (file)
@@ -2,15 +2,16 @@
 use std::ops::Range;
 
 use crate::utils::{snippet_with_applicability, span_lint, span_lint_and_sugg, span_lint_and_then};
-use rustc::lint::{EarlyContext, EarlyLintPass, LintArray, LintPass};
-use rustc::{declare_lint_pass, declare_tool_lint};
+use rustc_ast::ast::{Expr, ExprKind, Item, ItemKind, MacCall, StrLit, StrStyle};
+use rustc_ast::token;
+use rustc_ast::tokenstream::TokenStream;
 use rustc_errors::Applicability;
 use rustc_lexer::unescape::{self, EscapeError};
+use rustc_lint::{EarlyContext, EarlyLintPass};
 use rustc_parse::parser;
-use syntax::ast::*;
-use syntax::token;
-use syntax::tokenstream::TokenStream;
-use syntax_pos::{BytePos, Span};
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::symbol::Symbol;
+use rustc_span::{BytePos, Span};
 
 declare_clippy_lint! {
     /// **What it does:** This lint warns when you use `println!("")` to
     ///
     /// **Example:**
     /// ```rust
+    /// // Bad
     /// println!("");
+    ///
+    /// // Good
+    /// println!();
     /// ```
     pub PRINTLN_EMPTY_STRING,
     style,
@@ -31,8 +36,7 @@
 
 declare_clippy_lint! {
     /// **What it does:** This lint warns when you use `print!()` with a format
-    /// string that
-    /// ends in a newline.
+    /// string that ends in a newline.
     ///
     /// **Why is this bad?** You should use `println!()` instead, which appends the
     /// newline.
     /// ```rust
     /// # use std::fmt::Write;
     /// # let mut buf = String::new();
+    ///
+    /// // Bad
     /// writeln!(buf, "");
+    ///
+    /// // Good
+    /// writeln!(buf);
     /// ```
     pub WRITELN_EMPTY_STRING,
     style,
     /// # use std::fmt::Write;
     /// # let mut buf = String::new();
     /// # let name = "World";
+    ///
+    /// // Bad
     /// write!(buf, "Hello {}!\n", name);
+    ///
+    /// // Good
+    /// writeln!(buf, "Hello {}!", name);
     /// ```
     pub WRITE_WITH_NEWLINE,
     style,
     /// ```rust
     /// # use std::fmt::Write;
     /// # let mut buf = String::new();
+    ///
+    /// // Bad
     /// writeln!(buf, "{}", "foo");
+    ///
+    /// // Good
+    /// writeln!(buf, "foo");
     /// ```
     pub WRITE_LITERAL,
     style,
     "writing a literal with a format string"
 }
 
-declare_lint_pass!(Write => [
+#[derive(Default)]
+pub struct Write {
+    in_debug_impl: bool,
+}
+
+impl_lint_pass!(Write => [
     PRINT_WITH_NEWLINE,
     PRINTLN_EMPTY_STRING,
     PRINT_STDOUT,
 ]);
 
 impl EarlyLintPass for Write {
-    fn check_mac(&mut self, cx: &EarlyContext<'_>, mac: &Mac) {
+    fn check_item(&mut self, _: &EarlyContext<'_>, item: &Item) {
+        if let ItemKind::Impl {
+            of_trait: Some(trait_ref),
+            ..
+        } = &item.kind
+        {
+            let trait_name = trait_ref
+                .path
+                .segments
+                .iter()
+                .last()
+                .expect("path has at least one segment")
+                .ident
+                .name;
+            if trait_name == sym!(Debug) {
+                self.in_debug_impl = true;
+            }
+        }
+    }
+
+    fn check_item_post(&mut self, _: &EarlyContext<'_>, _: &Item) {
+        self.in_debug_impl = false;
+    }
+
+    fn check_mac(&mut self, cx: &EarlyContext<'_>, mac: &MacCall) {
         if mac.path == sym!(println) {
-            span_lint(cx, PRINT_STDOUT, mac.span, "use of `println!`");
-            if let (Some(fmt_str), _) = check_tts(cx, &mac.tts, false) {
-                if fmt_str.contents.is_empty() {
+            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,
+                        mac.span(),
                         "using `println!(\"\")`",
                         "replace it with",
                         "println!()".to_string(),
@@ -203,20 +251,20 @@ fn check_mac(&mut self, cx: &EarlyContext<'_>, mac: &Mac) {
                 }
             }
         } else if mac.path == sym!(print) {
-            span_lint(cx, PRINT_STDOUT, mac.span, "use of `print!`");
-            if let (Some(fmt_str), _) = check_tts(cx, &mac.tts, false) {
-                if check_newlines(&fmt_str.contents, fmt_str.style) {
+            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,
+                        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")),
-                                    (fmt_str.newline_span(), String::new()),
+                                    (newline_span(&fmt_str), String::new()),
                                 ],
                                 Applicability::MachineApplicable,
                             );
@@ -225,19 +273,19 @@ fn check_mac(&mut self, cx: &EarlyContext<'_>, mac: &Mac) {
                 }
             }
         } else if mac.path == sym!(write) {
-            if let (Some(fmt_str), _) = check_tts(cx, &mac.tts, true) {
-                if check_newlines(&fmt_str.contents, fmt_str.style) {
+            if let (Some(fmt_str), _) = self.check_tts(cx, &mac.args.inner_tokens(), true) {
+                if check_newlines(&fmt_str) {
                     span_lint_and_then(
                         cx,
                         WRITE_WITH_NEWLINE,
-                        mac.span,
+                        mac.span(),
                         "using `write!()` with a format string that ends in a single newline",
                         |err| {
                             err.multipart_suggestion(
                                 "use `writeln!()` instead",
                                 vec![
                                     (mac.path.span, String::from("writeln")),
-                                    (fmt_str.newline_span(), String::new()),
+                                    (newline_span(&fmt_str), String::new()),
                                 ],
                                 Applicability::MachineApplicable,
                             );
@@ -246,21 +294,21 @@ fn check_mac(&mut self, cx: &EarlyContext<'_>, mac: &Mac) {
                 }
             }
         } else if mac.path == sym!(writeln) {
-            if let (Some(fmt_str), expr) = check_tts(cx, &mac.tts, true) {
-                if fmt_str.contents.is_empty() {
+            if let (Some(fmt_str), expr) = self.check_tts(cx, &mac.args.inner_tokens(), true) {
+                if fmt_str.symbol == Symbol::intern("") {
                     let mut applicability = Applicability::MachineApplicable;
                     let suggestion = expr.map_or_else(
-                        move || {
+                        || {
                             applicability = Applicability::HasPlaceholders;
                             Cow::Borrowed("v")
                         },
-                        move |expr| snippet_with_applicability(cx, expr.span, "v", &mut applicability),
+                        |e| snippet_with_applicability(cx, e.span, "v", &mut Applicability::MachineApplicable),
                     );
 
                     span_lint_and_sugg(
                         cx,
                         WRITELN_EMPTY_STRING,
-                        mac.span,
+                        mac.span(),
                         format!("using `writeln!({}, \"\")`", suggestion).as_str(),
                         "replace it with",
                         format!("writeln!({})", suggestion),
@@ -272,175 +320,160 @@ fn check_mac(&mut self, cx: &EarlyContext<'_>, mac: &Mac) {
     }
 }
 
-/// The arguments of a `print[ln]!` or `write[ln]!` invocation.
-struct FmtStr {
-    /// The contents of the format string (inside the quotes).
-    contents: String,
-    style: StrStyle,
-    /// The span of the format string, including quotes, the raw marker, and any raw hashes.
-    span: Span,
-}
-
-impl FmtStr {
-    /// Given a format string that ends in a newline and its span, calculates the span of the
-    /// newline.
-    fn newline_span(&self) -> Span {
-        let sp = self.span;
-
-        let newline_sp_hi = sp.hi()
-            - match self.style {
-                StrStyle::Cooked => BytePos(1),
-                StrStyle::Raw(hashes) => BytePos((1 + hashes).into()),
-            };
+/// Given a format string that ends in a newline and its span, calculates the span of the
+/// newline.
+fn newline_span(fmtstr: &StrLit) -> Span {
+    let sp = fmtstr.span;
+    let contents = &fmtstr.symbol.as_str();
 
-        let newline_sp_len = if self.contents.ends_with('\n') {
-            BytePos(1)
-        } else if self.contents.ends_with(r"\n") {
-            BytePos(2)
-        } else {
-            panic!("expected format string to contain a newline");
+    let newline_sp_hi = sp.hi()
+        - match fmtstr.style {
+            StrStyle::Cooked => BytePos(1),
+            StrStyle::Raw(hashes) => BytePos((1 + hashes).into()),
         };
 
-        sp.with_lo(newline_sp_hi - newline_sp_len).with_hi(newline_sp_hi)
-    }
-}
+    let newline_sp_len = if contents.ends_with('\n') {
+        BytePos(1)
+    } else if contents.ends_with(r"\n") {
+        BytePos(2)
+    } else {
+        panic!("expected format string to contain a newline");
+    };
 
-/// Checks the arguments of `print[ln]!` and `write[ln]!` calls. It will return a tuple of two
-/// `Option`s. The first `Option` of the tuple is the macro's format string. It includes
-/// the contents of the string, whether it's a raw string, and the span of the literal in the
-/// source. The second `Option` in the tuple is, in the `write[ln]!` case, the expression the
-/// `format_str` should be written to.
-///
-/// Example:
-///
-/// Calling this function on
-/// ```rust
-/// # use std::fmt::Write;
-/// # let mut buf = String::new();
-/// # let something = "something";
-/// writeln!(buf, "string to write: {}", something);
-/// ```
-/// will return
-/// ```rust,ignore
-/// (Some("string to write: {}"), Some(buf))
-/// ```
-#[allow(clippy::too_many_lines)]
-fn check_tts<'a>(cx: &EarlyContext<'a>, tts: &TokenStream, is_write: bool) -> (Option<FmtStr>, Option<Expr>) {
-    use fmt_macros::*;
-    let tts = tts.clone();
+    sp.with_lo(newline_sp_hi - newline_sp_len).with_hi(newline_sp_hi)
+}
 
-    let mut parser = parser::Parser::new(&cx.sess.parse_sess, tts, None, false, false, None);
-    let mut expr: Option<Expr> = None;
-    if is_write {
-        expr = match parser.parse_expr().map_err(|mut err| err.cancel()) {
-            Ok(p) => Some(p.into_inner()),
-            Err(_) => return (None, None),
+impl Write {
+    /// Checks the arguments of `print[ln]!` and `write[ln]!` calls. It will return a tuple of two
+    /// `Option`s. The first `Option` of the tuple is the macro's format string. It includes
+    /// the contents of the string, whether it's a raw string, and the span of the literal in the
+    /// source. The second `Option` in the tuple is, in the `write[ln]!` case, the expression the
+    /// `format_str` should be written to.
+    ///
+    /// Example:
+    ///
+    /// Calling this function on
+    /// ```rust
+    /// # use std::fmt::Write;
+    /// # let mut buf = String::new();
+    /// # let something = "something";
+    /// writeln!(buf, "string to write: {}", something);
+    /// ```
+    /// will return
+    /// ```rust,ignore
+    /// (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>) {
+        use rustc_parse_format::{
+            AlignUnknown, ArgumentImplicitlyIs, ArgumentIs, ArgumentNamed, CountImplied, FormatSpec, ParseMode, Parser,
+            Piece,
         };
-        // might be `writeln!(foo)`
-        if parser.expect(&token::Comma).map_err(|mut err| err.cancel()).is_err() {
-            return (None, expr);
-        }
-    }
+        let tts = tts.clone();
 
-    let (fmtstr, fmtstyle) = match parser.parse_str().map_err(|mut err| err.cancel()) {
-        Ok((fmtstr, fmtstyle)) => (fmtstr.to_string(), fmtstyle),
-        Err(_) => return (None, expr),
-    };
-    let fmtspan = parser.prev_span;
-    let tmp = fmtstr.clone();
-    let mut args = vec![];
-    let mut fmt_parser = Parser::new(&tmp, None, Vec::new(), false);
-    while let Some(piece) = fmt_parser.next() {
-        if !fmt_parser.errors.is_empty() {
-            return (None, expr);
-        }
-        if let Piece::NextArgument(arg) = piece {
-            if arg.format.ty == "?" {
-                // FIXME: modify rustc's fmt string parser to give us the current span
-                span_lint(cx, USE_DEBUG, parser.prev_span, "use of `Debug`-based formatting");
+        let mut parser = parser::Parser::new(&cx.sess.parse_sess, tts, false, None);
+        let mut expr: Option<Expr> = None;
+        if is_write {
+            expr = match parser.parse_expr().map_err(|mut err| err.cancel()) {
+                Ok(p) => Some(p.into_inner()),
+                Err(_) => return (None, None),
+            };
+            // might be `writeln!(foo)`
+            if parser.expect(&token::Comma).map_err(|mut err| err.cancel()).is_err() {
+                return (None, expr);
             }
-            args.push(arg);
         }
-    }
-    let lint = if is_write { WRITE_LITERAL } else { PRINT_LITERAL };
-    let mut idx = 0;
-    loop {
-        const SIMPLE: FormatSpec<'_> = FormatSpec {
-            fill: None,
-            align: AlignUnknown,
-            flags: 0,
-            precision: CountImplied,
-            precision_span: None,
-            width: CountImplied,
-            width_span: None,
-            ty: "",
-            ty_span: None,
+
+        let fmtstr = match parser.parse_str_lit() {
+            Ok(fmtstr) => fmtstr,
+            Err(_) => return (None, expr),
         };
-        if !parser.eat(&token::Comma) {
-            return (
-                Some(FmtStr {
-                    contents: fmtstr,
-                    style: fmtstyle,
-                    span: fmtspan,
-                }),
-                expr,
-            );
+        let tmp = fmtstr.symbol.as_str();
+        let mut args = vec![];
+        let mut fmt_parser = Parser::new(&tmp, None, None, false, ParseMode::Format);
+        while let Some(piece) = fmt_parser.next() {
+            if !fmt_parser.errors.is_empty() {
+                return (None, expr);
+            }
+            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");
+                }
+                args.push(arg);
+            }
         }
-        let token_expr = if let Ok(expr) = parser.parse_expr().map_err(|mut err| err.cancel()) {
-            expr
-        } else {
-            return (
-                Some(FmtStr {
-                    contents: fmtstr,
-                    style: fmtstyle,
-                    span: fmtspan,
-                }),
-                None,
-            );
-        };
-        match &token_expr.kind {
-            ExprKind::Lit(_) => {
-                let mut all_simple = true;
-                let mut seen = false;
-                for arg in &args {
-                    match arg.position {
-                        ArgumentImplicitlyIs(n) | ArgumentIs(n) => {
-                            if n == idx {
-                                all_simple &= arg.format == SIMPLE;
-                                seen = true;
-                            }
-                        },
-                        ArgumentNamed(_) => {},
+        let lint = if is_write { WRITE_LITERAL } else { PRINT_LITERAL };
+        let mut idx = 0;
+        loop {
+            const SIMPLE: FormatSpec<'_> = FormatSpec {
+                fill: None,
+                align: AlignUnknown,
+                flags: 0,
+                precision: CountImplied,
+                precision_span: None,
+                width: CountImplied,
+                width_span: None,
+                ty: "",
+                ty_span: None,
+            };
+            if !parser.eat(&token::Comma) {
+                return (Some(fmtstr), expr);
+            }
+            let token_expr = if let Ok(expr) = parser.parse_expr().map_err(|mut err| err.cancel()) {
+                expr
+            } else {
+                return (Some(fmtstr), None);
+            };
+            match &token_expr.kind {
+                ExprKind::Lit(_) => {
+                    let mut all_simple = true;
+                    let mut seen = false;
+                    for arg in &args {
+                        match arg.position {
+                            ArgumentImplicitlyIs(n) | ArgumentIs(n) => {
+                                if n == idx {
+                                    all_simple &= arg.format == SIMPLE;
+                                    seen = true;
+                                }
+                            },
+                            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 {
-                        let mut all_simple = true;
-                        let mut seen = false;
-                        for arg in &args {
-                            match arg.position {
-                                ArgumentImplicitlyIs(_) | ArgumentIs(_) => {},
-                                ArgumentNamed(name) => {
-                                    if *p == name {
-                                        seen = true;
-                                        all_simple &= arg.format == SIMPLE;
-                                    }
-                                },
+                    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 {
+                            let mut all_simple = true;
+                            let mut seen = false;
+                            for arg in &args {
+                                match arg.position {
+                                    ArgumentImplicitlyIs(_) | ArgumentIs(_) => {},
+                                    ArgumentNamed(name) => {
+                                        if *p == name {
+                                            seen = true;
+                                            all_simple &= arg.format == SIMPLE;
+                                        }
+                                    },
+                                }
+                            }
+                            if all_simple && seen {
+                                span_lint(cx, lint, rhs.span, "literal with an empty format string");
                             }
-                        }
-                        if all_simple && seen {
-                            span_lint(cx, lint, rhs.span, "literal with an empty format string");
                         }
                     }
-                }
-            },
-            _ => idx += 1,
+                },
+                _ => idx += 1,
+            }
         }
     }
 }
@@ -448,11 +481,13 @@ fn check_tts<'a>(cx: &EarlyContext<'a>, tts: &TokenStream, is_write: bool) -> (O
 /// Checks if the format string contains a single newline that terminates it.
 ///
 /// Literal and escaped newlines are both checked (only literal for raw strings).
-fn check_newlines(contents: &str, style: StrStyle) -> bool {
+fn check_newlines(fmtstr: &StrLit) -> bool {
     let mut has_internal_newline = false;
     let mut last_was_cr = false;
     let mut should_lint = false;
 
+    let contents = &fmtstr.symbol.as_str();
+
     let mut cb = |r: Range<usize>, c: Result<char, EscapeError>| {
         let c = c.unwrap();
 
@@ -466,9 +501,9 @@ fn check_newlines(contents: &str, style: StrStyle) -> bool {
         }
     };
 
-    match style {
-        StrStyle::Cooked => unescape::unescape_str(contents, &mut cb),
-        StrStyle::Raw(_) => unescape::unescape_raw_str(contents, &mut cb),
+    match fmtstr.style {
+        StrStyle::Cooked => unescape::unescape_literal(contents, unescape::Mode::Str, &mut cb),
+        StrStyle::Raw(_) => unescape::unescape_literal(contents, unescape::Mode::RawStr, &mut cb),
     }
 
     should_lint