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