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