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