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