]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/write.rs
Add applicability level to (nearly) every span_lint_and_sugg function
[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_with_applicability, 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::MachineApplicable,
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 mut applicability = Applicability::MachineApplicable;
243                     let suggestion = check_tts.1.map_or_else(
244                         move || {
245                             applicability = Applicability::HasPlaceholders;
246                             Cow::Borrowed("v")
247                         },
248                         move |expr| snippet_with_applicability(cx, expr.span, "v", &mut applicability),
249                     );
250
251                     span_lint_and_sugg(
252                         cx,
253                         WRITELN_EMPTY_STRING,
254                         mac.span,
255                         format!("using `writeln!({}, \"\")`", suggestion).as_str(),
256                         "replace it with",
257                         format!("writeln!({})", suggestion),
258                         applicability,
259                     );
260                 }
261             }
262         }
263     }
264 }
265
266 /// Checks the arguments of `print[ln]!` and `write[ln]!` calls. It will return a tuple of two
267 /// options. The first part of the tuple is format_str of the macros. The secund part of the tuple
268 /// is in the `write[ln]!` case the expression the format_str should be written to.
269 ///
270 /// Example:
271 ///
272 /// Calling this function on
273 /// ```rust,ignore
274 /// writeln!(buf, "string to write: {}", something)
275 /// ```
276 /// will return
277 /// ```rust,ignore
278 /// (Some("string to write: {}"), Some(buf))
279 /// ```
280 fn check_tts<'a>(cx: &EarlyContext<'a>, tts: &ThinTokenStream, is_write: bool) -> (Option<String>, Option<Expr>) {
281     use crate::fmt_macros::*;
282     let tts = TokenStream::from(tts.clone());
283     let mut parser = parser::Parser::new(&cx.sess.parse_sess, tts, None, false, false);
284     let mut expr: Option<Expr> = None;
285     if is_write {
286         expr = match parser.parse_expr().map_err(|mut err| err.cancel()) {
287             Ok(p) => Some(p.into_inner()),
288             Err(_) => return (None, None),
289         };
290         // might be `writeln!(foo)`
291         if parser.expect(&token::Comma).map_err(|mut err| err.cancel()).is_err() {
292             return (None, expr);
293         }
294     }
295
296     let fmtstr = match parser.parse_str().map_err(|mut err| err.cancel()) {
297         Ok(token) => token.0.to_string(),
298         Err(_) => return (None, expr),
299     };
300     let tmp = fmtstr.clone();
301     let mut args = vec![];
302     let mut fmt_parser = Parser::new(&tmp, None);
303     while let Some(piece) = fmt_parser.next() {
304         if !fmt_parser.errors.is_empty() {
305             return (None, expr);
306         }
307         if let Piece::NextArgument(arg) = piece {
308             if arg.format.ty == "?" {
309                 // FIXME: modify rustc's fmt string parser to give us the current span
310                 span_lint(cx, USE_DEBUG, parser.prev_span, "use of `Debug`-based formatting");
311             }
312             args.push(arg);
313         }
314     }
315     let lint = if is_write { WRITE_LITERAL } else { PRINT_LITERAL };
316     let mut idx = 0;
317     loop {
318         const SIMPLE: FormatSpec<'_> = FormatSpec {
319             fill: None,
320             align: AlignUnknown,
321             flags: 0,
322             precision: CountImplied,
323             width: CountImplied,
324             ty: "",
325         };
326         if !parser.eat(&token::Comma) {
327             return (Some(fmtstr), expr);
328         }
329         let token_expr = match parser.parse_expr().map_err(|mut err| err.cancel()) {
330             Ok(expr) => expr,
331             Err(_) => return (Some(fmtstr), None),
332         };
333         match &token_expr.node {
334             ExprKind::Lit(_) => {
335                 let mut all_simple = true;
336                 let mut seen = false;
337                 for arg in &args {
338                     match arg.position {
339                         ArgumentImplicitlyIs(n) | ArgumentIs(n) => if n == idx {
340                             all_simple &= arg.format == SIMPLE;
341                             seen = true;
342                         },
343                         ArgumentNamed(_) => {},
344                     }
345                 }
346                 if all_simple && seen {
347                     span_lint(cx, lint, token_expr.span, "literal with an empty format string");
348                 }
349                 idx += 1;
350             },
351             ExprKind::Assign(lhs, rhs) => {
352                 if let ExprKind::Lit(_) = rhs.node {
353                     if let ExprKind::Path(_, p) = &lhs.node {
354                         let mut all_simple = true;
355                         let mut seen = false;
356                         for arg in &args {
357                             match arg.position {
358                                 ArgumentImplicitlyIs(_) | ArgumentIs(_) => {},
359                                 ArgumentNamed(name) => if *p == name {
360                                     seen = true;
361                                     all_simple &= arg.format == SIMPLE;
362                                 },
363                             }
364                         }
365                         if all_simple && seen {
366                             span_lint(cx, lint, rhs.span, "literal with an empty format string");
367                         }
368                     }
369                 }
370             },
371             _ => idx += 1,
372         }
373     }
374 }