]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/write.rs
Merge pull request #3137 from matthiaskrgr/clippy_git_version
[rust.git] / clippy_lints / src / write.rs
1 use crate::utils::{snippet, span_lint, span_lint_and_sugg};
2 use rustc::lint::{EarlyContext, EarlyLintPass, LintArray, LintPass};
3 use rustc::{declare_tool_lint, lint_array};
4 use std::borrow::Cow;
5 use syntax::ast::*;
6 use syntax::parse::{parser, token};
7 use syntax::tokenstream::{ThinTokenStream, TokenStream};
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") &&
199                    // don't warn about strings with several `\n`s (#3126)
200                    fmtstr.matches("\\n").count() == 1
201                 {
202                     span_lint(
203                         cx,
204                         PRINT_WITH_NEWLINE,
205                         mac.span,
206                         "using `print!()` with a format string that ends in a \
207                          single newline, consider using `println!()` instead",
208                     );
209                 }
210             }
211         } else if mac.node.path == "write" {
212             if let Some(fmtstr) = check_tts(cx, &mac.node.tts, true).0 {
213                 if fmtstr.ends_with("\\n") &&
214                    // don't warn about strings with several `\n`s (#3126)
215                    fmtstr.matches("\\n").count() == 1
216                 {
217                     span_lint(
218                         cx,
219                         WRITE_WITH_NEWLINE,
220                         mac.span,
221                         "using `write!()` with a format string that ends in a \
222                          single newline, consider using `writeln!()` instead",
223                     );
224                 }
225             }
226         } else if mac.node.path == "writeln" {
227             let check_tts = check_tts(cx, &mac.node.tts, true);
228             if let Some(fmtstr) = check_tts.0 {
229                 if fmtstr == "" {
230                     let suggestion = check_tts
231                         .1
232                         .map_or(Cow::Borrowed("v"), |expr| snippet(cx, expr.span, "v"));
233
234                     span_lint_and_sugg(
235                         cx,
236                         WRITELN_EMPTY_STRING,
237                         mac.span,
238                         format!("using `writeln!({}, \"\")`", suggestion).as_str(),
239                         "replace it with",
240                         format!("writeln!({})", suggestion),
241                     );
242                 }
243             }
244         }
245     }
246 }
247
248 fn check_tts<'a>(cx: &EarlyContext<'a>, tts: &ThinTokenStream, is_write: bool) -> (Option<String>, Option<Expr>) {
249     let tts = TokenStream::from(tts.clone());
250     let mut parser = parser::Parser::new(&cx.sess.parse_sess, tts, None, false, false);
251     let mut expr: Option<Expr> = None;
252     if is_write {
253         expr = match parser.parse_expr().map_err(|mut err| err.cancel()) {
254             Ok(p) => Some(p.into_inner()),
255             Err(_) => return (None, None),
256         };
257         // might be `writeln!(foo)`
258         if parser.expect(&token::Comma).map_err(|mut err| err.cancel()).is_err() {
259             return (None, expr);
260         }
261     }
262
263     let fmtstr = match parser.parse_str().map_err(|mut err| err.cancel()) {
264         Ok(token) => token.0.to_string(),
265         Err(_) => return (None, expr),
266     };
267     use fmt_macros::*;
268     let tmp = fmtstr.clone();
269     let mut args = vec![];
270     let mut fmt_parser = Parser::new(&tmp, None);
271     while let Some(piece) = fmt_parser.next() {
272         if !fmt_parser.errors.is_empty() {
273             return (None, expr);
274         }
275         if let Piece::NextArgument(arg) = piece {
276             if arg.format.ty == "?" {
277                 // FIXME: modify rustc's fmt string parser to give us the current span
278                 span_lint(cx, USE_DEBUG, parser.prev_span, "use of `Debug`-based formatting");
279             }
280             args.push(arg);
281         }
282     }
283     let lint = if is_write { WRITE_LITERAL } else { PRINT_LITERAL };
284     let mut idx = 0;
285     loop {
286         if !parser.eat(&token::Comma) {
287             assert!(parser.eat(&token::Eof));
288             return (Some(fmtstr), expr);
289         }
290         let token_expr = match parser.parse_expr().map_err(|mut err| err.cancel()) {
291             Ok(expr) => expr,
292             Err(_) => return (Some(fmtstr), None),
293         };
294         const SIMPLE: FormatSpec<'_> = FormatSpec {
295             fill: None,
296             align: AlignUnknown,
297             flags: 0,
298             precision: CountImplied,
299             width: CountImplied,
300             ty: "",
301         };
302         match &token_expr.node {
303             ExprKind::Lit(_) => {
304                 let mut all_simple = true;
305                 let mut seen = false;
306                 for arg in &args {
307                     match arg.position {
308                         ArgumentImplicitlyIs(n) | ArgumentIs(n) => if n == idx {
309                             all_simple &= arg.format == SIMPLE;
310                             seen = true;
311                         },
312                         ArgumentNamed(_) => {},
313                     }
314                 }
315                 if all_simple && seen {
316                     span_lint(cx, lint, token_expr.span, "literal with an empty format string");
317                 }
318                 idx += 1;
319             },
320             ExprKind::Assign(lhs, rhs) => {
321                 if let ExprKind::Lit(_) = rhs.node {
322                     if let ExprKind::Path(_, p) = &lhs.node {
323                         let mut all_simple = true;
324                         let mut seen = false;
325                         for arg in &args {
326                             match arg.position {
327                                 ArgumentImplicitlyIs(_) | ArgumentIs(_) => {},
328                                 ArgumentNamed(name) => if *p == name {
329                                     seen = true;
330                                     all_simple &= arg.format == SIMPLE;
331                                 },
332                             }
333                         }
334                         if all_simple && seen {
335                             span_lint(cx, lint, rhs.span, "literal with an empty format string");
336                         }
337                     }
338                 }
339             },
340             _ => idx += 1,
341         }
342     }
343 }