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