]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/write.rs
Fix bugs and improve documentation
[rust.git] / clippy_lints / src / write.rs
1 // Copyright 2014-2018 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution.
3 //
4 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
5 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
6 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
7 // option. This file may not be copied, modified, or distributed
8 // except according to those terms.
9
10
11 use crate::rustc::lint::{EarlyContext, EarlyLintPass, LintArray, LintPass};
12 use crate::rustc::{declare_tool_lint, lint_array};
13 use crate::rustc_errors::Applicability;
14 use crate::syntax::ast::*;
15 use crate::syntax::parse::{parser, token};
16 use crate::syntax::tokenstream::{ThinTokenStream, TokenStream};
17 use crate::utils::{snippet, span_lint, span_lint_and_sugg};
18 use std::borrow::Cow;
19
20 /// **What it does:** This lint warns when you use `println!("")` to
21 /// print a newline.
22 ///
23 /// **Why is this bad?** You should use `println!()`, which is simpler.
24 ///
25 /// **Known problems:** None.
26 ///
27 /// **Example:**
28 /// ```rust
29 /// println!("");
30 /// ```
31 declare_clippy_lint! {
32     pub PRINTLN_EMPTY_STRING,
33     style,
34     "using `println!(\"\")` with an empty string"
35 }
36
37 /// **What it does:** This lint warns when you use `print!()` with a format
38 /// string that
39 /// ends in a newline.
40 ///
41 /// **Why is this bad?** You should use `println!()` instead, which appends the
42 /// newline.
43 ///
44 /// **Known problems:** None.
45 ///
46 /// **Example:**
47 /// ```rust
48 /// print!("Hello {}!\n", name);
49 /// ```
50 /// use println!() instead
51 /// ```rust
52 /// println!("Hello {}!", name);
53 /// ```
54 declare_clippy_lint! {
55     pub PRINT_WITH_NEWLINE,
56     style,
57     "using `print!()` with a format string that ends in a single newline"
58 }
59
60 /// **What it does:** Checks for printing on *stdout*. The purpose of this lint
61 /// is to catch debugging remnants.
62 ///
63 /// **Why is this bad?** People often print on *stdout* while debugging an
64 /// application and might forget to remove those prints afterward.
65 ///
66 /// **Known problems:** Only catches `print!` and `println!` calls.
67 ///
68 /// **Example:**
69 /// ```rust
70 /// println!("Hello world!");
71 /// ```
72 declare_clippy_lint! {
73     pub PRINT_STDOUT,
74     restriction,
75     "printing on stdout"
76 }
77
78 /// **What it does:** Checks for use of `Debug` formatting. The purpose of this
79 /// lint is to catch debugging remnants.
80 ///
81 /// **Why is this bad?** The purpose of the `Debug` trait is to facilitate
82 /// debugging Rust code. It should not be used in in user-facing output.
83 ///
84 /// **Example:**
85 /// ```rust
86 /// println!("{:?}", foo);
87 /// ```
88 declare_clippy_lint! {
89     pub USE_DEBUG,
90     restriction,
91     "use of `Debug`-based formatting"
92 }
93
94 /// **What it does:** This lint warns about the use of literals as `print!`/`println!` args.
95 ///
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)
99 ///
100 /// **Known problems:** Will also warn with macro calls as arguments that expand to literals
101 /// -- e.g., `println!("{}", env!("FOO"))`.
102 ///
103 /// **Example:**
104 /// ```rust
105 /// println!("{}", "foo");
106 /// ```
107 /// use the literal without formatting:
108 /// ```rust
109 /// println!("foo");
110 /// ```
111 declare_clippy_lint! {
112     pub PRINT_LITERAL,
113     style,
114     "printing a literal with a format string"
115 }
116
117 /// **What it does:** This lint warns when you use `writeln!(buf, "")` to
118 /// print a newline.
119 ///
120 /// **Why is this bad?** You should use `writeln!(buf)`, which is simpler.
121 ///
122 /// **Known problems:** None.
123 ///
124 /// **Example:**
125 /// ```rust
126 /// writeln!("");
127 /// ```
128 declare_clippy_lint! {
129     pub WRITELN_EMPTY_STRING,
130     style,
131     "using `writeln!(\"\")` with an empty string"
132 }
133
134 /// **What it does:** This lint warns when you use `write!()` with a format
135 /// string that
136 /// ends in a newline.
137 ///
138 /// **Why is this bad?** You should use `writeln!()` instead, which appends the
139 /// newline.
140 ///
141 /// **Known problems:** None.
142 ///
143 /// **Example:**
144 /// ```rust
145 /// write!(buf, "Hello {}!\n", name);
146 /// ```
147 declare_clippy_lint! {
148     pub WRITE_WITH_NEWLINE,
149     style,
150     "using `write!()` with a format string that ends in a single newline"
151 }
152
153 /// **What it does:** This lint warns about the use of literals as `write!`/`writeln!` args.
154 ///
155 /// **Why is this bad?** Using literals as `writeln!` args is inefficient
156 /// (c.f., https://github.com/matthiaskrgr/rust-str-bench) and unnecessary
157 /// (i.e., just put the literal in the format string)
158 ///
159 /// **Known problems:** Will also warn with macro calls as arguments that expand to literals
160 /// -- e.g., `writeln!(buf, "{}", env!("FOO"))`.
161 ///
162 /// **Example:**
163 /// ```rust
164 /// writeln!(buf, "{}", "foo");
165 /// ```
166 declare_clippy_lint! {
167     pub WRITE_LITERAL,
168     style,
169     "writing a literal with a format string"
170 }
171
172 #[derive(Copy, Clone, Debug)]
173 pub struct Pass;
174
175 impl LintPass for Pass {
176     fn get_lints(&self) -> LintArray {
177         lint_array!(
178             PRINT_WITH_NEWLINE,
179             PRINTLN_EMPTY_STRING,
180             PRINT_STDOUT,
181             USE_DEBUG,
182             PRINT_LITERAL,
183             WRITE_WITH_NEWLINE,
184             WRITELN_EMPTY_STRING,
185             WRITE_LITERAL
186         )
187     }
188 }
189
190 impl EarlyLintPass for Pass {
191     fn check_mac(&mut self, cx: &EarlyContext<'_>, mac: &Mac) {
192         if mac.node.path == "println" {
193             span_lint(cx, PRINT_STDOUT, mac.span, "use of `println!`");
194             if let Some(fmtstr) = check_tts(cx, &mac.node.tts, false).0 {
195                 if fmtstr == "" {
196                     span_lint_and_sugg(
197                         cx,
198                         PRINTLN_EMPTY_STRING,
199                         mac.span,
200                         "using `println!(\"\")`",
201                         "replace it with",
202                         "println!()".to_string(),
203                         Applicability::Unspecified,
204                     );
205                 }
206             }
207         } else if mac.node.path == "print" {
208             span_lint(cx, PRINT_STDOUT, mac.span, "use of `print!`");
209             if let Some(fmtstr) = check_tts(cx, &mac.node.tts, false).0 {
210                 if fmtstr.ends_with("\\n") &&
211                    // don't warn about strings with several `\n`s (#3126)
212                    fmtstr.matches("\\n").count() == 1
213                 {
214                     span_lint(
215                         cx,
216                         PRINT_WITH_NEWLINE,
217                         mac.span,
218                         "using `print!()` with a format string that ends in a \
219                          single newline, consider using `println!()` instead",
220                     );
221                 }
222             }
223         } else if mac.node.path == "write" {
224             if let Some(fmtstr) = check_tts(cx, &mac.node.tts, true).0 {
225                 if fmtstr.ends_with("\\n") &&
226                    // don't warn about strings with several `\n`s (#3126)
227                    fmtstr.matches("\\n").count() == 1
228                 {
229                     span_lint(
230                         cx,
231                         WRITE_WITH_NEWLINE,
232                         mac.span,
233                         "using `write!()` with a format string that ends in a \
234                          single newline, consider using `writeln!()` instead",
235                     );
236                 }
237             }
238         } else if mac.node.path == "writeln" {
239             let check_tts = check_tts(cx, &mac.node.tts, true);
240             if let Some(fmtstr) = check_tts.0 {
241                 if fmtstr == "" {
242                     let suggestion = check_tts
243                         .1
244                         .map_or(Cow::Borrowed("v"), |expr| snippet(cx, expr.span, "v"));
245
246                     span_lint_and_sugg(
247                         cx,
248                         WRITELN_EMPTY_STRING,
249                         mac.span,
250                         format!("using `writeln!({}, \"\")`", suggestion).as_str(),
251                         "replace it with",
252                         format!("writeln!({})", suggestion),
253                         Applicability::Unspecified,
254                     );
255                 }
256             }
257         }
258     }
259 }
260
261 /// Checks the arguments of `print[ln]!` and `write[ln]!` calls. It will return a tuple of two
262 /// options. The first part of the tuple is format_str of the macros. The secund part of the tuple
263 /// is in the `write[ln]!` case the expression the format_str should be written to.
264 ///
265 /// Example:
266 ///
267 /// Calling this function on
268 /// ```rust,ignore
269 /// writeln!(buf, "string to write: {}", something)
270 /// ```
271 /// will return
272 /// ```rust,ignore
273 /// (Some("string to write: {}"), Some(buf))
274 /// ```
275 fn check_tts<'a>(cx: &EarlyContext<'a>, tts: &ThinTokenStream, is_write: bool) -> (Option<String>, Option<Expr>) {
276     use crate::fmt_macros::*;
277     let tts = TokenStream::from(tts.clone());
278     let mut parser = parser::Parser::new(&cx.sess.parse_sess, tts, None, false, false);
279     let mut expr: Option<Expr> = None;
280     if is_write {
281         expr = match parser.parse_expr().map_err(|mut err| err.cancel()) {
282             Ok(p) => Some(p.into_inner()),
283             Err(_) => return (None, None),
284         };
285         // might be `writeln!(foo)`
286         if parser.expect(&token::Comma).map_err(|mut err| err.cancel()).is_err() {
287             return (None, expr);
288         }
289     }
290
291     let fmtstr = match parser.parse_str().map_err(|mut err| err.cancel()) {
292         Ok(token) => token.0.to_string(),
293         Err(_) => return (None, expr),
294     };
295     let tmp = fmtstr.clone();
296     let mut args = vec![];
297     let mut fmt_parser = Parser::new(&tmp, None);
298     while let Some(piece) = fmt_parser.next() {
299         if !fmt_parser.errors.is_empty() {
300             return (None, expr);
301         }
302         if let Piece::NextArgument(arg) = piece {
303             if arg.format.ty == "?" {
304                 // FIXME: modify rustc's fmt string parser to give us the current span
305                 span_lint(cx, USE_DEBUG, parser.prev_span, "use of `Debug`-based formatting");
306             }
307             args.push(arg);
308         }
309     }
310     let lint = if is_write { WRITE_LITERAL } else { PRINT_LITERAL };
311     let mut idx = 0;
312     loop {
313         const SIMPLE: FormatSpec<'_> = FormatSpec {
314             fill: None,
315             align: AlignUnknown,
316             flags: 0,
317             precision: CountImplied,
318             width: CountImplied,
319             ty: "",
320         };
321         if !parser.eat(&token::Comma) {
322             return (Some(fmtstr), expr);
323         }
324         let token_expr = match parser.parse_expr().map_err(|mut err| err.cancel()) {
325             Ok(expr) => expr,
326             Err(_) => return (Some(fmtstr), None),
327         };
328         match &token_expr.node {
329             ExprKind::Lit(_) => {
330                 let mut all_simple = true;
331                 let mut seen = false;
332                 for arg in &args {
333                     match arg.position {
334                         ArgumentImplicitlyIs(n) | ArgumentIs(n) => if n == idx {
335                             all_simple &= arg.format == SIMPLE;
336                             seen = true;
337                         },
338                         ArgumentNamed(_) => {},
339                     }
340                 }
341                 if all_simple && seen {
342                     span_lint(cx, lint, token_expr.span, "literal with an empty format string");
343                 }
344                 idx += 1;
345             },
346             ExprKind::Assign(lhs, rhs) => {
347                 if let ExprKind::Lit(_) = rhs.node {
348                     if let ExprKind::Path(_, p) = &lhs.node {
349                         let mut all_simple = true;
350                         let mut seen = false;
351                         for arg in &args {
352                             match arg.position {
353                                 ArgumentImplicitlyIs(_) | ArgumentIs(_) => {},
354                                 ArgumentNamed(name) => if *p == name {
355                                     seen = true;
356                                     all_simple &= arg.format == SIMPLE;
357                                 },
358                             }
359                         }
360                         if all_simple && seen {
361                             span_lint(cx, lint, rhs.span, "literal with an empty format string");
362                         }
363                     }
364                 }
365             },
366             _ => idx += 1,
367         }
368     }
369 }