]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/write.rs
Remove unnecssary lifetime from trait_ref_of_method
[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, token};
8 use syntax::tokenstream::TokenStream;
9 use syntax_pos::{BytePos, Span};
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(fmt_str), _) = check_tts(cx, &mac.node.tts, false) {
188                 if fmt_str.contents.is_empty() {
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(fmt_str), _) = check_tts(cx, &mac.node.tts, false) {
203                 if check_newlines(&fmt_str) {
204                     span_lint_and_then(
205                         cx,
206                         PRINT_WITH_NEWLINE,
207                         mac.span,
208                         "using `print!()` with a format string that ends in a single newline",
209                         |err| {
210                             err.multipart_suggestion(
211                                 "use `println!` instead",
212                                 vec![
213                                     (mac.node.path.span, String::from("println")),
214                                     (fmt_str.newline_span(), String::new()),
215                                 ],
216                                 Applicability::MachineApplicable,
217                             );
218                         },
219                     );
220                 }
221             }
222         } else if mac.node.path == sym!(write) {
223             if let (Some(fmt_str), _) = check_tts(cx, &mac.node.tts, true) {
224                 if check_newlines(&fmt_str) {
225                     span_lint_and_then(
226                         cx,
227                         WRITE_WITH_NEWLINE,
228                         mac.span,
229                         "using `write!()` with a format string that ends in a single newline",
230                         |err| {
231                             err.multipart_suggestion(
232                                 "use `writeln!()` instead",
233                                 vec![
234                                     (mac.node.path.span, String::from("writeln")),
235                                     (fmt_str.newline_span(), String::new()),
236                                 ],
237                                 Applicability::MachineApplicable,
238                             );
239                         },
240                     )
241                 }
242             }
243         } else if mac.node.path == sym!(writeln) {
244             if let (Some(fmt_str), expr) = check_tts(cx, &mac.node.tts, true) {
245                 if fmt_str.contents.is_empty() {
246                     let mut applicability = Applicability::MachineApplicable;
247                     let suggestion = expr.map_or_else(
248                         move || {
249                             applicability = Applicability::HasPlaceholders;
250                             Cow::Borrowed("v")
251                         },
252                         move |expr| snippet_with_applicability(cx, expr.span, "v", &mut applicability),
253                     );
254
255                     span_lint_and_sugg(
256                         cx,
257                         WRITELN_EMPTY_STRING,
258                         mac.span,
259                         format!("using `writeln!({}, \"\")`", suggestion).as_str(),
260                         "replace it with",
261                         format!("writeln!({})", suggestion),
262                         applicability,
263                     );
264                 }
265             }
266         }
267     }
268 }
269
270 /// The arguments of a `print[ln]!` or `write[ln]!` invocation.
271 struct FmtStr {
272     /// The contents of the format string (inside the quotes).
273     contents: String,
274     style: StrStyle,
275     /// The span of the format string, including quotes, the raw marker, and any raw hashes.
276     span: Span,
277 }
278
279 impl FmtStr {
280     /// Given a format string that ends in a newline and its span, calculates the span of the
281     /// newline.
282     fn newline_span(&self) -> Span {
283         let sp = self.span;
284
285         let newline_sp_hi = sp.hi()
286             - match self.style {
287                 StrStyle::Cooked => BytePos(1),
288                 StrStyle::Raw(hashes) => BytePos((1 + hashes).into()),
289             };
290
291         let newline_sp_len = if self.contents.ends_with('\n') {
292             BytePos(1)
293         } else if self.contents.ends_with(r"\n") {
294             BytePos(2)
295         } else {
296             panic!("expected format string to contain a newline");
297         };
298
299         sp.with_lo(newline_sp_hi - newline_sp_len).with_hi(newline_sp_hi)
300     }
301 }
302
303 /// Checks the arguments of `print[ln]!` and `write[ln]!` calls. It will return a tuple of two
304 /// `Option`s. The first `Option` of the tuple is the macro's format string. It includes
305 /// the contents of the string, whether it's a raw string, and the span of the literal in the
306 /// source. The second `Option` in the tuple is, in the `write[ln]!` case, the expression the
307 /// `format_str` should be written to.
308 ///
309 /// Example:
310 ///
311 /// Calling this function on
312 /// ```rust
313 /// # use std::fmt::Write;
314 /// # let mut buf = String::new();
315 /// # let something = "something";
316 /// writeln!(buf, "string to write: {}", something);
317 /// ```
318 /// will return
319 /// ```rust,ignore
320 /// (Some("string to write: {}"), Some(buf))
321 /// ```
322 fn check_tts<'a>(cx: &EarlyContext<'a>, tts: &TokenStream, is_write: bool) -> (Option<FmtStr>, Option<Expr>) {
323     use fmt_macros::*;
324     let tts = tts.clone();
325
326     let mut parser = parser::Parser::new(&cx.sess.parse_sess, tts, None, false, false, None);
327     let mut expr: Option<Expr> = None;
328     if is_write {
329         expr = match parser.parse_expr().map_err(|mut err| err.cancel()) {
330             Ok(p) => Some(p.into_inner()),
331             Err(_) => return (None, None),
332         };
333         // might be `writeln!(foo)`
334         if parser.expect(&token::Comma).map_err(|mut err| err.cancel()).is_err() {
335             return (None, expr);
336         }
337     }
338
339     let (fmtstr, fmtstyle) = match parser.parse_str().map_err(|mut err| err.cancel()) {
340         Ok((fmtstr, fmtstyle)) => (fmtstr.to_string(), fmtstyle),
341         Err(_) => return (None, expr),
342     };
343     let fmtspan = parser.prev_span;
344     let tmp = fmtstr.clone();
345     let mut args = vec![];
346     let mut fmt_parser = Parser::new(&tmp, None, Vec::new(), false);
347     while let Some(piece) = fmt_parser.next() {
348         if !fmt_parser.errors.is_empty() {
349             return (None, expr);
350         }
351         if let Piece::NextArgument(arg) = piece {
352             if arg.format.ty == "?" {
353                 // FIXME: modify rustc's fmt string parser to give us the current span
354                 span_lint(cx, USE_DEBUG, parser.prev_span, "use of `Debug`-based formatting");
355             }
356             args.push(arg);
357         }
358     }
359     let lint = if is_write { WRITE_LITERAL } else { PRINT_LITERAL };
360     let mut idx = 0;
361     loop {
362         const SIMPLE: FormatSpec<'_> = FormatSpec {
363             fill: None,
364             align: AlignUnknown,
365             flags: 0,
366             precision: CountImplied,
367             width: CountImplied,
368             ty: "",
369         };
370         if !parser.eat(&token::Comma) {
371             return (
372                 Some(FmtStr {
373                     contents: fmtstr,
374                     style: fmtstyle,
375                     span: fmtspan,
376                 }),
377                 expr,
378             );
379         }
380         let token_expr = if let Ok(expr) = parser.parse_expr().map_err(|mut err| err.cancel()) {
381             expr
382         } else {
383             return (
384                 Some(FmtStr {
385                     contents: fmtstr,
386                     style: fmtstyle,
387                     span: fmtspan,
388                 }),
389                 None,
390             );
391         };
392         match &token_expr.node {
393             ExprKind::Lit(_) => {
394                 let mut all_simple = true;
395                 let mut seen = false;
396                 for arg in &args {
397                     match arg.position {
398                         ArgumentImplicitlyIs(n) | ArgumentIs(n) => {
399                             if n == idx {
400                                 all_simple &= arg.format == SIMPLE;
401                                 seen = true;
402                             }
403                         },
404                         ArgumentNamed(_) => {},
405                     }
406                 }
407                 if all_simple && seen {
408                     span_lint(cx, lint, token_expr.span, "literal with an empty format string");
409                 }
410                 idx += 1;
411             },
412             ExprKind::Assign(lhs, rhs) => {
413                 if let ExprKind::Lit(_) = rhs.node {
414                     if let ExprKind::Path(_, p) = &lhs.node {
415                         let mut all_simple = true;
416                         let mut seen = false;
417                         for arg in &args {
418                             match arg.position {
419                                 ArgumentImplicitlyIs(_) | ArgumentIs(_) => {},
420                                 ArgumentNamed(name) => {
421                                     if *p == name {
422                                         seen = true;
423                                         all_simple &= arg.format == SIMPLE;
424                                     }
425                                 },
426                             }
427                         }
428                         if all_simple && seen {
429                             span_lint(cx, lint, rhs.span, "literal with an empty format string");
430                         }
431                     }
432                 }
433             },
434             _ => idx += 1,
435         }
436     }
437 }
438
439 /// Checks if the format string constains a single newline that terminates it.
440 ///
441 /// Literal and escaped newlines are both checked (only literal for raw strings).
442 fn check_newlines(fmt_str: &FmtStr) -> bool {
443     let s = &fmt_str.contents;
444
445     if s.ends_with('\n') {
446         return true;
447     } else if let StrStyle::Raw(_) = fmt_str.style {
448         return false;
449     }
450
451     if s.len() < 2 {
452         return false;
453     }
454
455     let bytes = s.as_bytes();
456     if bytes[bytes.len() - 2] != b'\\' || bytes[bytes.len() - 1] != b'n' {
457         return false;
458     }
459
460     let mut escaping = false;
461     for (index, &byte) in bytes.iter().enumerate() {
462         if escaping {
463             if byte == b'n' {
464                 return index == bytes.len() - 1;
465             }
466             escaping = false;
467         } else if byte == b'\\' {
468             escaping = true;
469         }
470     }
471
472     false
473 }