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