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