]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/write.rs
Merge pull request #3038 from matthiaskrgr/clippy_docs
[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 /// 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 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 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) {
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) {
197                 if fmtstr.ends_with("\\n") {
198                     span_lint(cx, PRINT_WITH_NEWLINE, mac.span,
199                             "using `print!()` with a format string that ends in a \
200                             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) {
205                 if fmtstr.ends_with("\\n") {
206                     span_lint(cx, WRITE_WITH_NEWLINE, mac.span,
207                             "using `write!()` with a format string that ends in a \
208                             newline, consider using `writeln!()` instead");
209                 }
210             }
211         } else if mac.node.path == "writeln" {
212             if let Some(fmtstr) = check_tts(cx, &mac.node.tts, true) {
213                 if fmtstr == "" {
214                     span_lint_and_sugg(
215                         cx,
216                         WRITELN_EMPTY_STRING,
217                         mac.span,
218                         "using `writeln!(v, \"\")`",
219                         "replace it with",
220                         "writeln!(v)".to_string(),
221                     );
222                 }
223             }
224         }
225     }
226 }
227
228 fn check_tts<'a>(cx: &EarlyContext<'a>, tts: &ThinTokenStream, is_write: bool) -> Option<String> {
229     let tts = TokenStream::from(tts.clone());
230     let mut parser = parser::Parser::new(
231         &cx.sess.parse_sess,
232         tts,
233         None,
234         false,
235         false,
236     );
237     if is_write {
238         // skip the initial write target
239         parser.parse_expr().map_err(|mut err| err.cancel()).ok()?;
240         // might be `writeln!(foo)`
241         parser.expect(&token::Comma).map_err(|mut err| err.cancel()).ok()?;
242     }
243     let fmtstr = parser.parse_str().map_err(|mut err| err.cancel()).ok()?.0.to_string();
244     use fmt_macros::*;
245     let tmp = fmtstr.clone();
246     let mut args = vec![];
247     let mut fmt_parser = Parser::new(&tmp, None);
248     while let Some(piece) = fmt_parser.next() {
249         if !fmt_parser.errors.is_empty() {
250             return None;
251         }
252         if let Piece::NextArgument(arg) = piece {
253             if arg.format.ty == "?" {
254                 // FIXME: modify rustc's fmt string parser to give us the current span
255                 span_lint(cx, USE_DEBUG, parser.prev_span, "use of `Debug`-based formatting");
256             }
257             args.push(arg);
258         }
259     }
260     let lint = if is_write {
261         WRITE_LITERAL
262     } else {
263         PRINT_LITERAL
264     };
265     let mut idx = 0;
266     loop {
267         if !parser.eat(&token::Comma) {
268             assert!(parser.eat(&token::Eof));
269             return Some(fmtstr);
270         }
271         let expr = parser.parse_expr().map_err(|mut err| err.cancel()).ok()?;
272         const SIMPLE: FormatSpec<'_> = FormatSpec {
273             fill: None,
274             align: AlignUnknown,
275             flags: 0,
276             precision: CountImplied,
277             width: CountImplied,
278             ty: "",
279         };
280         match &expr.node {
281             ExprKind::Lit(_) => {
282                 let mut all_simple = true;
283                 let mut seen = false;
284                 for arg in &args {
285                     match arg.position {
286                         | ArgumentImplicitlyIs(n)
287                         | ArgumentIs(n)
288                         => if n == idx {
289                             all_simple &= arg.format == SIMPLE;
290                             seen = true;
291                         },
292                         ArgumentNamed(_) => {},
293                     }
294                 }
295                 if all_simple && seen {
296                     span_lint(cx, lint, expr.span, "literal with an empty format string");
297                 }
298                 idx += 1;
299             },
300             ExprKind::Assign(lhs, rhs) => {
301                 if let ExprKind::Lit(_) = rhs.node {
302                     if let ExprKind::Path(_, p) = &lhs.node {
303                         let mut all_simple = true;
304                         let mut seen = false;
305                         for arg in &args {
306                             match arg.position {
307                                 | ArgumentImplicitlyIs(_)
308                                 | ArgumentIs(_)
309                                 => {},
310                                 ArgumentNamed(name) => if *p == name {
311                                     seen = true;
312                                     all_simple &= arg.format == SIMPLE;
313                                 },
314                             }
315                         }
316                         if all_simple && seen {
317                             span_lint(cx, lint, rhs.span, "literal with an empty format string");
318                         }
319                     }
320                 }
321             },
322             _ => idx += 1,
323         }
324     }
325 }