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