]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/write.rs
Merge branch 'master' into issue2894
[rust.git] / clippy_lints / src / write.rs
1 use rustc::lint::*;
2 use rustc::{declare_lint, lint_array};
3 use syntax::ast::*;
4 use syntax::tokenstream::{ThinTokenStream, TokenStream};
5 use syntax::parse::{token, parser};
6 use crate::utils::{span_lint, span_lint_and_sugg};
7
8 /// **What it does:** This lint warns when you use `println!("")` to
9 /// print a newline.
10 ///
11 /// **Why is this bad?** You should use `println!()`, which is simpler.
12 ///
13 /// **Known problems:** None.
14 ///
15 /// **Example:**
16 /// ```rust
17 /// println!("");
18 /// ```
19 declare_clippy_lint! {
20     pub PRINTLN_EMPTY_STRING,
21     style,
22     "using `println!(\"\")` with an empty string"
23 }
24
25 /// **What it does:** This lint warns when you use `print!()` with a format
26 /// string that
27 /// ends in a newline.
28 ///
29 /// **Why is this bad?** You should use `println!()` instead, which appends the
30 /// newline.
31 ///
32 /// **Known problems:** None.
33 ///
34 /// **Example:**
35 /// ```rust
36 /// print!("Hello {}!\n", name);
37 /// ```
38 declare_clippy_lint! {
39     pub PRINT_WITH_NEWLINE,
40     style,
41     "using `print!()` with a format string that ends in a newline"
42 }
43
44 /// **What it does:** Checks for printing on *stdout*. The purpose of this lint
45 /// is to catch debugging remnants.
46 ///
47 /// **Why is this bad?** People often print on *stdout* while debugging an
48 /// application and might forget to remove those prints afterward.
49 ///
50 /// **Known problems:** Only catches `print!` and `println!` calls.
51 ///
52 /// **Example:**
53 /// ```rust
54 /// println!("Hello world!");
55 /// ```
56 declare_clippy_lint! {
57     pub PRINT_STDOUT,
58     restriction,
59     "printing on stdout"
60 }
61
62 /// **What it does:** Checks for use of `Debug` formatting. The purpose of this
63 /// lint is to catch debugging remnants.
64 ///
65 /// **Why is this bad?** The purpose of the `Debug` trait is to facilitate
66 /// debugging Rust code. It should not be used in in user-facing output.
67 ///
68 /// **Example:**
69 /// ```rust
70 /// println!("{:?}", foo);
71 /// ```
72 declare_clippy_lint! {
73     pub USE_DEBUG,
74     restriction,
75     "use of `Debug`-based formatting"
76 }
77
78 /// **What it does:** This lint warns about the use of literals as `print!`/`println!` args.
79 ///
80 /// **Why is this bad?** Using literals as `println!` args is inefficient
81 /// (c.f., https://github.com/matthiaskrgr/rust-str-bench) and unnecessary
82 /// (i.e., just put the literal in the format string)
83 ///
84 /// **Known problems:** Will also warn with macro calls as arguments that expand to literals
85 /// -- e.g., `println!("{}", env!("FOO"))`.
86 ///
87 /// **Example:**
88 /// ```rust
89 /// println!("{}", "foo");
90 /// ```
91 declare_clippy_lint! {
92     pub PRINT_LITERAL,
93     style,
94     "printing a literal with a format string"
95 }
96
97 /// **What it does:** This lint warns when you use `writeln!(buf, "")` to
98 /// print a newline.
99 ///
100 /// **Why is this bad?** You should use `writeln!(buf)`, which is simpler.
101 ///
102 /// **Known problems:** None.
103 ///
104 /// **Example:**
105 /// ```rust
106 /// writeln!("");
107 /// ```
108 declare_clippy_lint! {
109     pub WRITELN_EMPTY_STRING,
110     style,
111     "using `writeln!(\"\")` with an empty string"
112 }
113
114 /// **What it does:** This lint warns when you use `write!()` with a format
115 /// string that
116 /// ends in a newline.
117 ///
118 /// **Why is this bad?** You should use `writeln!()` instead, which appends the
119 /// newline.
120 ///
121 /// **Known problems:** None.
122 ///
123 /// **Example:**
124 /// ```rust
125 /// write!(buf, "Hello {}!\n", name);
126 /// ```
127 declare_clippy_lint! {
128     pub WRITE_WITH_NEWLINE,
129     style,
130     "using `write!()` with a format string that ends in a newline"
131 }
132
133 /// **What it does:** This lint warns about the use of literals as `write!`/`writeln!` args.
134 ///
135 /// **Why is this bad?** Using literals as `writeln!` args is inefficient
136 /// (c.f., https://github.com/matthiaskrgr/rust-str-bench) and unnecessary
137 /// (i.e., just put the literal in the format string)
138 ///
139 /// **Known problems:** Will also warn with macro calls as arguments that expand to literals
140 /// -- e.g., `writeln!(buf, "{}", env!("FOO"))`.
141 ///
142 /// **Example:**
143 /// ```rust
144 /// writeln!(buf, "{}", "foo");
145 /// ```
146 declare_clippy_lint! {
147     pub WRITE_LITERAL,
148     style,
149     "writing a literal with a format string"
150 }
151
152 #[derive(Copy, Clone, Debug)]
153 pub struct Pass;
154
155 impl LintPass for Pass {
156     fn get_lints(&self) -> LintArray {
157         lint_array!(
158             PRINT_WITH_NEWLINE,
159             PRINTLN_EMPTY_STRING,
160             PRINT_STDOUT,
161             USE_DEBUG,
162             PRINT_LITERAL,
163             WRITE_WITH_NEWLINE,
164             WRITELN_EMPTY_STRING,
165             WRITE_LITERAL
166         )
167     }
168 }
169
170 impl EarlyLintPass for Pass {
171     fn check_mac(&mut self, cx: &EarlyContext<'_>, mac: &Mac) {
172         if mac.node.path == "println" {
173             span_lint(cx, PRINT_STDOUT, mac.span, "use of `println!`");
174             if let Some(fmtstr) = check_tts(cx, &mac.node.tts, false) {
175                 if fmtstr == "" {
176                     span_lint_and_sugg(
177                         cx,
178                         PRINTLN_EMPTY_STRING,
179                         mac.span,
180                         "using `println!(\"\")`",
181                         "replace it with",
182                         "println!()".to_string(),
183                     );
184                 }
185             }
186         } else if mac.node.path == "print" {
187             span_lint(cx, PRINT_STDOUT, mac.span, "use of `print!`");
188             if let Some(fmtstr) = check_tts(cx, &mac.node.tts, false) {
189                 if fmtstr.ends_with("\\n") {
190                     span_lint(cx, PRINT_WITH_NEWLINE, mac.span,
191                             "using `print!()` with a format string that ends in a \
192                             newline, consider using `println!()` instead");
193                 }
194             }
195         } else if mac.node.path == "write" {
196             if let Some(fmtstr) = check_tts(cx, &mac.node.tts, true) {
197                 if fmtstr.ends_with("\\n") {
198                     span_lint(cx, WRITE_WITH_NEWLINE, mac.span,
199                             "using `write!()` with a format string that ends in a \
200                             newline, consider using `writeln!()` instead");
201                 }
202             }
203         } else if mac.node.path == "writeln" {
204             if let Some(fmtstr) = check_tts(cx, &mac.node.tts, true) {
205                 if fmtstr == "" {
206                     span_lint_and_sugg(
207                         cx,
208                         WRITELN_EMPTY_STRING,
209                         mac.span,
210                         "using `writeln!(v, \"\")`",
211                         "replace it with",
212                         "writeln!(v)".to_string(),
213                     );
214                 }
215             }
216         }
217     }
218 }
219
220 fn check_tts(cx: &EarlyContext<'a>, tts: &ThinTokenStream, is_write: bool) -> Option<String> {
221     let tts = TokenStream::from(tts.clone());
222     let mut parser = parser::Parser::new(
223         &cx.sess.parse_sess,
224         tts,
225         None,
226         false,
227         false,
228     );
229     if is_write {
230         // skip the initial write target
231         parser.parse_expr().map_err(|mut err| err.cancel()).ok()?;
232         // might be `writeln!(foo)`
233         parser.expect(&token::Comma).map_err(|mut err| err.cancel()).ok()?;
234     }
235     let fmtstr = parser.parse_str().map_err(|mut err| err.cancel()).ok()?.0.to_string();
236     use fmt_macros::*;
237     let tmp = fmtstr.clone();
238     let mut args = vec![];
239     let mut fmt_parser = Parser::new(&tmp, None);
240     while let Some(piece) = fmt_parser.next() {
241         if !fmt_parser.errors.is_empty() {
242             return None;
243         }
244         if let Piece::NextArgument(arg) = piece {
245             if arg.format.ty == "?" {
246                 // FIXME: modify rustc's fmt string parser to give us the current span
247                 span_lint(cx, USE_DEBUG, parser.prev_span, "use of `Debug`-based formatting");
248             }
249             args.push(arg);
250         }
251     }
252     let lint = if is_write {
253         WRITE_LITERAL
254     } else {
255         PRINT_LITERAL
256     };
257     let mut idx = 0;
258     loop {
259         if !parser.eat(&token::Comma) {
260             assert!(parser.eat(&token::Eof));
261             return Some(fmtstr);
262         }
263         let expr = parser.parse_expr().map_err(|mut err| err.cancel()).ok()?;
264         const SIMPLE: FormatSpec<'_> = FormatSpec {
265             fill: None,
266             align: AlignUnknown,
267             flags: 0,
268             precision: CountImplied,
269             width: CountImplied,
270             ty: "",
271         };
272         match &expr.node {
273             ExprKind::Lit(_) => {
274                 let mut all_simple = true;
275                 let mut seen = false;
276                 for arg in &args {
277                     match arg.position {
278                         | ArgumentImplicitlyIs(n)
279                         | ArgumentIs(n)
280                         => if n == idx {
281                             all_simple &= arg.format == SIMPLE;
282                             seen = true;
283                         },
284                         ArgumentNamed(_) => {},
285                     }
286                 }
287                 if all_simple && seen {
288                     span_lint(cx, lint, expr.span, "literal with an empty format string");
289                 }
290                 idx += 1;
291             },
292             ExprKind::Assign(lhs, rhs) => {
293                 if let ExprKind::Path(_, p) = &lhs.node {
294                     let mut all_simple = true;
295                     let mut seen = false;
296                     for arg in &args {
297                         match arg.position {
298                             | ArgumentImplicitlyIs(_)
299                             | ArgumentIs(_)
300                             => {},
301                             ArgumentNamed(name) => if *p == name {
302                                 seen = true;
303                                 all_simple &= arg.format == SIMPLE;
304                             },
305                         }
306                     }
307                     if all_simple && seen {
308                         span_lint(cx, lint, rhs.span, "literal with an empty format string");
309                     }
310                 }
311             },
312             _ => idx += 1,
313         }
314     }
315 }