]>
Commit | Line | Data |
---|---|---|
f20569fa | 1 | use std::borrow::Cow; |
cdc7bbd5 XL |
2 | use std::iter; |
3 | use std::ops::{Deref, Range}; | |
4 | ||
5 | use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg, span_lint_and_then}; | |
6 | use clippy_utils::source::{snippet_opt, snippet_with_applicability}; | |
3c0e092e | 7 | use rustc_ast::ast::{Expr, ExprKind, Impl, Item, ItemKind, MacCall, Path, StrLit, StrStyle}; |
cdc7bbd5 | 8 | use rustc_ast::token::{self, LitKind}; |
f20569fa | 9 | use rustc_ast::tokenstream::TokenStream; |
5e7ed085 | 10 | use rustc_errors::{Applicability, DiagnosticBuilder}; |
f20569fa | 11 | use rustc_lexer::unescape::{self, EscapeError}; |
5099ac24 | 12 | use rustc_lint::{EarlyContext, EarlyLintPass, LintContext}; |
f20569fa XL |
13 | use rustc_parse::parser; |
14 | use rustc_session::{declare_tool_lint, impl_lint_pass}; | |
cdc7bbd5 | 15 | use rustc_span::symbol::{kw, Symbol}; |
04454e1e | 16 | use rustc_span::{sym, BytePos, InnerSpan, Span, DUMMY_SP}; |
f20569fa XL |
17 | |
18 | declare_clippy_lint! { | |
94222f64 XL |
19 | /// ### What it does |
20 | /// This lint warns when you use `println!("")` to | |
f20569fa XL |
21 | /// print a newline. |
22 | /// | |
94222f64 XL |
23 | /// ### Why is this bad? |
24 | /// You should use `println!()`, which is simpler. | |
f20569fa | 25 | /// |
94222f64 | 26 | /// ### Example |
f20569fa | 27 | /// ```rust |
f20569fa | 28 | /// println!(""); |
923072b8 | 29 | /// ``` |
f20569fa | 30 | /// |
923072b8 FG |
31 | /// Use instead: |
32 | /// ```rust | |
f20569fa XL |
33 | /// println!(); |
34 | /// ``` | |
a2a8927a | 35 | #[clippy::version = "pre 1.29.0"] |
f20569fa XL |
36 | pub PRINTLN_EMPTY_STRING, |
37 | style, | |
38 | "using `println!(\"\")` with an empty string" | |
39 | } | |
40 | ||
41 | declare_clippy_lint! { | |
94222f64 XL |
42 | /// ### What it does |
43 | /// This lint warns when you use `print!()` with a format | |
f20569fa XL |
44 | /// string that ends in a newline. |
45 | /// | |
94222f64 XL |
46 | /// ### Why is this bad? |
47 | /// You should use `println!()` instead, which appends the | |
f20569fa XL |
48 | /// newline. |
49 | /// | |
94222f64 | 50 | /// ### Example |
f20569fa XL |
51 | /// ```rust |
52 | /// # let name = "World"; | |
53 | /// print!("Hello {}!\n", name); | |
54 | /// ``` | |
55 | /// use println!() instead | |
56 | /// ```rust | |
57 | /// # let name = "World"; | |
58 | /// println!("Hello {}!", name); | |
59 | /// ``` | |
a2a8927a | 60 | #[clippy::version = "pre 1.29.0"] |
f20569fa XL |
61 | pub PRINT_WITH_NEWLINE, |
62 | style, | |
63 | "using `print!()` with a format string that ends in a single newline" | |
64 | } | |
65 | ||
66 | declare_clippy_lint! { | |
94222f64 XL |
67 | /// ### What it does |
68 | /// Checks for printing on *stdout*. The purpose of this lint | |
f20569fa XL |
69 | /// is to catch debugging remnants. |
70 | /// | |
94222f64 XL |
71 | /// ### Why is this bad? |
72 | /// People often print on *stdout* while debugging an | |
f20569fa XL |
73 | /// application and might forget to remove those prints afterward. |
74 | /// | |
94222f64 | 75 | /// ### Known problems |
a2a8927a XL |
76 | /// * Only catches `print!` and `println!` calls. |
77 | /// * The lint level is unaffected by crate attributes. The level can still | |
78 | /// be set for functions, modules and other items. To change the level for | |
79 | /// the entire crate, please use command line flags. More information and a | |
80 | /// configuration example can be found in [clippy#6610]. | |
81 | /// | |
82 | /// [clippy#6610]: https://github.com/rust-lang/rust-clippy/issues/6610#issuecomment-977120558 | |
f20569fa | 83 | /// |
94222f64 | 84 | /// ### Example |
f20569fa XL |
85 | /// ```rust |
86 | /// println!("Hello world!"); | |
87 | /// ``` | |
a2a8927a | 88 | #[clippy::version = "pre 1.29.0"] |
f20569fa XL |
89 | pub PRINT_STDOUT, |
90 | restriction, | |
91 | "printing on stdout" | |
92 | } | |
93 | ||
94 | declare_clippy_lint! { | |
94222f64 XL |
95 | /// ### What it does |
96 | /// Checks for printing on *stderr*. The purpose of this lint | |
f20569fa XL |
97 | /// is to catch debugging remnants. |
98 | /// | |
94222f64 XL |
99 | /// ### Why is this bad? |
100 | /// People often print on *stderr* while debugging an | |
f20569fa XL |
101 | /// application and might forget to remove those prints afterward. |
102 | /// | |
94222f64 | 103 | /// ### Known problems |
a2a8927a XL |
104 | /// * Only catches `eprint!` and `eprintln!` calls. |
105 | /// * The lint level is unaffected by crate attributes. The level can still | |
106 | /// be set for functions, modules and other items. To change the level for | |
107 | /// the entire crate, please use command line flags. More information and a | |
108 | /// configuration example can be found in [clippy#6610]. | |
109 | /// | |
110 | /// [clippy#6610]: https://github.com/rust-lang/rust-clippy/issues/6610#issuecomment-977120558 | |
f20569fa | 111 | /// |
94222f64 | 112 | /// ### Example |
f20569fa XL |
113 | /// ```rust |
114 | /// eprintln!("Hello world!"); | |
115 | /// ``` | |
a2a8927a | 116 | #[clippy::version = "1.50.0"] |
f20569fa XL |
117 | pub PRINT_STDERR, |
118 | restriction, | |
119 | "printing on stderr" | |
120 | } | |
121 | ||
122 | declare_clippy_lint! { | |
94222f64 XL |
123 | /// ### What it does |
124 | /// Checks for use of `Debug` formatting. The purpose of this | |
f20569fa XL |
125 | /// lint is to catch debugging remnants. |
126 | /// | |
94222f64 XL |
127 | /// ### Why is this bad? |
128 | /// The purpose of the `Debug` trait is to facilitate | |
f20569fa XL |
129 | /// debugging Rust code. It should not be used in user-facing output. |
130 | /// | |
94222f64 | 131 | /// ### Example |
f20569fa XL |
132 | /// ```rust |
133 | /// # let foo = "bar"; | |
134 | /// println!("{:?}", foo); | |
135 | /// ``` | |
a2a8927a | 136 | #[clippy::version = "pre 1.29.0"] |
f20569fa XL |
137 | pub USE_DEBUG, |
138 | restriction, | |
139 | "use of `Debug`-based formatting" | |
140 | } | |
141 | ||
142 | declare_clippy_lint! { | |
94222f64 XL |
143 | /// ### What it does |
144 | /// This lint warns about the use of literals as `print!`/`println!` args. | |
f20569fa | 145 | /// |
94222f64 XL |
146 | /// ### Why is this bad? |
147 | /// Using literals as `println!` args is inefficient | |
f20569fa XL |
148 | /// (c.f., https://github.com/matthiaskrgr/rust-str-bench) and unnecessary |
149 | /// (i.e., just put the literal in the format string) | |
150 | /// | |
94222f64 XL |
151 | /// ### Known problems |
152 | /// Will also warn with macro calls as arguments that expand to literals | |
f20569fa XL |
153 | /// -- e.g., `println!("{}", env!("FOO"))`. |
154 | /// | |
94222f64 | 155 | /// ### Example |
f20569fa XL |
156 | /// ```rust |
157 | /// println!("{}", "foo"); | |
158 | /// ``` | |
159 | /// use the literal without formatting: | |
160 | /// ```rust | |
161 | /// println!("foo"); | |
162 | /// ``` | |
a2a8927a | 163 | #[clippy::version = "pre 1.29.0"] |
f20569fa XL |
164 | pub PRINT_LITERAL, |
165 | style, | |
166 | "printing a literal with a format string" | |
167 | } | |
168 | ||
169 | declare_clippy_lint! { | |
94222f64 XL |
170 | /// ### What it does |
171 | /// This lint warns when you use `writeln!(buf, "")` to | |
f20569fa XL |
172 | /// print a newline. |
173 | /// | |
94222f64 XL |
174 | /// ### Why is this bad? |
175 | /// You should use `writeln!(buf)`, which is simpler. | |
f20569fa | 176 | /// |
94222f64 | 177 | /// ### Example |
f20569fa XL |
178 | /// ```rust |
179 | /// # use std::fmt::Write; | |
180 | /// # let mut buf = String::new(); | |
f20569fa | 181 | /// writeln!(buf, ""); |
923072b8 | 182 | /// ``` |
f20569fa | 183 | /// |
923072b8 FG |
184 | /// Use instead: |
185 | /// ```rust | |
186 | /// # use std::fmt::Write; | |
187 | /// # let mut buf = String::new(); | |
f20569fa XL |
188 | /// writeln!(buf); |
189 | /// ``` | |
a2a8927a | 190 | #[clippy::version = "pre 1.29.0"] |
f20569fa XL |
191 | pub WRITELN_EMPTY_STRING, |
192 | style, | |
193 | "using `writeln!(buf, \"\")` with an empty string" | |
194 | } | |
195 | ||
196 | declare_clippy_lint! { | |
94222f64 XL |
197 | /// ### What it does |
198 | /// This lint warns when you use `write!()` with a format | |
f20569fa XL |
199 | /// string that |
200 | /// ends in a newline. | |
201 | /// | |
94222f64 XL |
202 | /// ### Why is this bad? |
203 | /// You should use `writeln!()` instead, which appends the | |
f20569fa XL |
204 | /// newline. |
205 | /// | |
94222f64 | 206 | /// ### Example |
f20569fa XL |
207 | /// ```rust |
208 | /// # use std::fmt::Write; | |
209 | /// # let mut buf = String::new(); | |
210 | /// # let name = "World"; | |
f20569fa | 211 | /// write!(buf, "Hello {}!\n", name); |
923072b8 | 212 | /// ``` |
f20569fa | 213 | /// |
923072b8 FG |
214 | /// Use instead: |
215 | /// ```rust | |
216 | /// # use std::fmt::Write; | |
217 | /// # let mut buf = String::new(); | |
218 | /// # let name = "World"; | |
f20569fa XL |
219 | /// writeln!(buf, "Hello {}!", name); |
220 | /// ``` | |
a2a8927a | 221 | #[clippy::version = "pre 1.29.0"] |
f20569fa XL |
222 | pub WRITE_WITH_NEWLINE, |
223 | style, | |
224 | "using `write!()` with a format string that ends in a single newline" | |
225 | } | |
226 | ||
227 | declare_clippy_lint! { | |
94222f64 XL |
228 | /// ### What it does |
229 | /// This lint warns about the use of literals as `write!`/`writeln!` args. | |
f20569fa | 230 | /// |
94222f64 XL |
231 | /// ### Why is this bad? |
232 | /// Using literals as `writeln!` args is inefficient | |
f20569fa XL |
233 | /// (c.f., https://github.com/matthiaskrgr/rust-str-bench) and unnecessary |
234 | /// (i.e., just put the literal in the format string) | |
235 | /// | |
94222f64 XL |
236 | /// ### Known problems |
237 | /// Will also warn with macro calls as arguments that expand to literals | |
f20569fa XL |
238 | /// -- e.g., `writeln!(buf, "{}", env!("FOO"))`. |
239 | /// | |
94222f64 | 240 | /// ### Example |
f20569fa XL |
241 | /// ```rust |
242 | /// # use std::fmt::Write; | |
243 | /// # let mut buf = String::new(); | |
f20569fa | 244 | /// writeln!(buf, "{}", "foo"); |
923072b8 | 245 | /// ``` |
f20569fa | 246 | /// |
923072b8 FG |
247 | /// Use instead: |
248 | /// ```rust | |
249 | /// # use std::fmt::Write; | |
250 | /// # let mut buf = String::new(); | |
f20569fa XL |
251 | /// writeln!(buf, "foo"); |
252 | /// ``` | |
a2a8927a | 253 | #[clippy::version = "pre 1.29.0"] |
f20569fa XL |
254 | pub WRITE_LITERAL, |
255 | style, | |
256 | "writing a literal with a format string" | |
257 | } | |
258 | ||
259 | #[derive(Default)] | |
260 | pub struct Write { | |
261 | in_debug_impl: bool, | |
262 | } | |
263 | ||
264 | impl_lint_pass!(Write => [ | |
265 | PRINT_WITH_NEWLINE, | |
266 | PRINTLN_EMPTY_STRING, | |
267 | PRINT_STDOUT, | |
268 | PRINT_STDERR, | |
269 | USE_DEBUG, | |
270 | PRINT_LITERAL, | |
271 | WRITE_WITH_NEWLINE, | |
272 | WRITELN_EMPTY_STRING, | |
273 | WRITE_LITERAL | |
274 | ]); | |
275 | ||
276 | impl EarlyLintPass for Write { | |
277 | fn check_item(&mut self, _: &EarlyContext<'_>, item: &Item) { | |
3c0e092e | 278 | if let ItemKind::Impl(box Impl { |
f20569fa XL |
279 | of_trait: Some(trait_ref), |
280 | .. | |
281 | }) = &item.kind | |
282 | { | |
283 | let trait_name = trait_ref | |
284 | .path | |
285 | .segments | |
286 | .iter() | |
287 | .last() | |
288 | .expect("path has at least one segment") | |
289 | .ident | |
290 | .name; | |
291 | if trait_name == sym::Debug { | |
292 | self.in_debug_impl = true; | |
293 | } | |
294 | } | |
295 | } | |
296 | ||
297 | fn check_item_post(&mut self, _: &EarlyContext<'_>, _: &Item) { | |
298 | self.in_debug_impl = false; | |
299 | } | |
300 | ||
301 | fn check_mac(&mut self, cx: &EarlyContext<'_>, mac: &MacCall) { | |
302 | fn is_build_script(cx: &EarlyContext<'_>) -> bool { | |
303 | // Cargo sets the crate name for build scripts to `build_script_build` | |
5099ac24 | 304 | cx.sess() |
f20569fa XL |
305 | .opts |
306 | .crate_name | |
307 | .as_ref() | |
308 | .map_or(false, |crate_name| crate_name == "build_script_build") | |
309 | } | |
310 | ||
311 | if mac.path == sym!(print) { | |
312 | if !is_build_script(cx) { | |
313 | span_lint(cx, PRINT_STDOUT, mac.span(), "use of `print!`"); | |
314 | } | |
315 | self.lint_print_with_newline(cx, mac); | |
316 | } else if mac.path == sym!(println) { | |
317 | if !is_build_script(cx) { | |
318 | span_lint(cx, PRINT_STDOUT, mac.span(), "use of `println!`"); | |
319 | } | |
320 | self.lint_println_empty_string(cx, mac); | |
321 | } else if mac.path == sym!(eprint) { | |
322 | span_lint(cx, PRINT_STDERR, mac.span(), "use of `eprint!`"); | |
323 | self.lint_print_with_newline(cx, mac); | |
324 | } else if mac.path == sym!(eprintln) { | |
325 | span_lint(cx, PRINT_STDERR, mac.span(), "use of `eprintln!`"); | |
326 | self.lint_println_empty_string(cx, mac); | |
327 | } else if mac.path == sym!(write) { | |
17df50a5 | 328 | if let (Some(fmt_str), dest) = self.check_tts(cx, mac.args.inner_tokens(), true) { |
f20569fa | 329 | if check_newlines(&fmt_str) { |
17df50a5 XL |
330 | let (nl_span, only_nl) = newline_span(&fmt_str); |
331 | let nl_span = match (dest, only_nl) { | |
332 | // Special case of `write!(buf, "\n")`: Mark everything from the end of | |
333 | // `buf` for removal so no trailing comma [`writeln!(buf, )`] remains. | |
94222f64 | 334 | (Some(dest_expr), true) => nl_span.with_lo(dest_expr.span.hi()), |
17df50a5 XL |
335 | _ => nl_span, |
336 | }; | |
f20569fa XL |
337 | span_lint_and_then( |
338 | cx, | |
339 | WRITE_WITH_NEWLINE, | |
340 | mac.span(), | |
341 | "using `write!()` with a format string that ends in a single newline", | |
342 | |err| { | |
343 | err.multipart_suggestion( | |
344 | "use `writeln!()` instead", | |
17df50a5 | 345 | vec![(mac.path.span, String::from("writeln")), (nl_span, String::new())], |
f20569fa XL |
346 | Applicability::MachineApplicable, |
347 | ); | |
348 | }, | |
17df50a5 | 349 | ); |
f20569fa XL |
350 | } |
351 | } | |
352 | } else if mac.path == sym!(writeln) { | |
353 | if let (Some(fmt_str), expr) = self.check_tts(cx, mac.args.inner_tokens(), true) { | |
354 | if fmt_str.symbol == kw::Empty { | |
355 | let mut applicability = Applicability::MachineApplicable; | |
f20569fa XL |
356 | let suggestion = if let Some(e) = expr { |
357 | snippet_with_applicability(cx, e.span, "v", &mut applicability) | |
358 | } else { | |
359 | applicability = Applicability::HasPlaceholders; | |
360 | Cow::Borrowed("v") | |
361 | }; | |
362 | ||
363 | span_lint_and_sugg( | |
364 | cx, | |
365 | WRITELN_EMPTY_STRING, | |
366 | mac.span(), | |
367 | format!("using `writeln!({}, \"\")`", suggestion).as_str(), | |
368 | "replace it with", | |
369 | format!("writeln!({})", suggestion), | |
370 | applicability, | |
371 | ); | |
372 | } | |
373 | } | |
374 | } | |
375 | } | |
376 | } | |
377 | ||
378 | /// Given a format string that ends in a newline and its span, calculates the span of the | |
379 | /// newline, or the format string itself if the format string consists solely of a newline. | |
17df50a5 XL |
380 | /// Return this and a boolean indicating whether it only consisted of a newline. |
381 | fn newline_span(fmtstr: &StrLit) -> (Span, bool) { | |
f20569fa | 382 | let sp = fmtstr.span; |
a2a8927a | 383 | let contents = fmtstr.symbol.as_str(); |
f20569fa | 384 | |
a2a8927a | 385 | if contents == r"\n" { |
17df50a5 | 386 | return (sp, true); |
f20569fa XL |
387 | } |
388 | ||
389 | let newline_sp_hi = sp.hi() | |
390 | - match fmtstr.style { | |
391 | StrStyle::Cooked => BytePos(1), | |
392 | StrStyle::Raw(hashes) => BytePos((1 + hashes).into()), | |
393 | }; | |
394 | ||
395 | let newline_sp_len = if contents.ends_with('\n') { | |
396 | BytePos(1) | |
397 | } else if contents.ends_with(r"\n") { | |
398 | BytePos(2) | |
399 | } else { | |
400 | panic!("expected format string to contain a newline"); | |
401 | }; | |
402 | ||
17df50a5 | 403 | (sp.with_lo(newline_sp_hi - newline_sp_len).with_hi(newline_sp_hi), false) |
f20569fa XL |
404 | } |
405 | ||
cdc7bbd5 XL |
406 | /// Stores a list of replacement spans for each argument, but only if all the replacements used an |
407 | /// empty format string. | |
408 | #[derive(Default)] | |
409 | struct SimpleFormatArgs { | |
410 | unnamed: Vec<Vec<Span>>, | |
411 | named: Vec<(Symbol, Vec<Span>)>, | |
412 | } | |
413 | impl SimpleFormatArgs { | |
414 | fn get_unnamed(&self) -> impl Iterator<Item = &[Span]> { | |
415 | self.unnamed.iter().map(|x| match x.as_slice() { | |
416 | // Ignore the dummy span added from out of order format arguments. | |
417 | [DUMMY_SP] => &[], | |
418 | x => x, | |
419 | }) | |
420 | } | |
421 | ||
422 | fn get_named(&self, n: &Path) -> &[Span] { | |
423 | self.named.iter().find(|x| *n == x.0).map_or(&[], |x| x.1.as_slice()) | |
424 | } | |
425 | ||
426 | fn push(&mut self, arg: rustc_parse_format::Argument<'_>, span: Span) { | |
427 | use rustc_parse_format::{ | |
428 | AlignUnknown, ArgumentImplicitlyIs, ArgumentIs, ArgumentNamed, CountImplied, FormatSpec, | |
429 | }; | |
430 | ||
431 | const SIMPLE: FormatSpec<'_> = FormatSpec { | |
432 | fill: None, | |
433 | align: AlignUnknown, | |
434 | flags: 0, | |
435 | precision: CountImplied, | |
436 | precision_span: None, | |
437 | width: CountImplied, | |
438 | width_span: None, | |
439 | ty: "", | |
440 | ty_span: None, | |
441 | }; | |
442 | ||
443 | match arg.position { | |
444 | ArgumentIs(n) | ArgumentImplicitlyIs(n) => { | |
445 | if self.unnamed.len() <= n { | |
446 | // Use a dummy span to mark all unseen arguments. | |
447 | self.unnamed.resize_with(n, || vec![DUMMY_SP]); | |
448 | if arg.format == SIMPLE { | |
449 | self.unnamed.push(vec![span]); | |
450 | } else { | |
451 | self.unnamed.push(Vec::new()); | |
452 | } | |
453 | } else { | |
454 | let args = &mut self.unnamed[n]; | |
455 | match (args.as_mut_slice(), arg.format == SIMPLE) { | |
456 | // A non-empty format string has been seen already. | |
457 | ([], _) => (), | |
458 | // Replace the dummy span, if it exists. | |
459 | ([dummy @ DUMMY_SP], true) => *dummy = span, | |
460 | ([_, ..], true) => args.push(span), | |
461 | ([_, ..], false) => *args = Vec::new(), | |
462 | } | |
463 | } | |
464 | }, | |
5099ac24 | 465 | ArgumentNamed(n, _) => { |
04454e1e | 466 | let n = Symbol::intern(n); |
cdc7bbd5 XL |
467 | if let Some(x) = self.named.iter_mut().find(|x| x.0 == n) { |
468 | match x.1.as_slice() { | |
469 | // A non-empty format string has been seen already. | |
470 | [] => (), | |
471 | [_, ..] if arg.format == SIMPLE => x.1.push(span), | |
472 | [_, ..] => x.1 = Vec::new(), | |
473 | } | |
474 | } else if arg.format == SIMPLE { | |
475 | self.named.push((n, vec![span])); | |
476 | } else { | |
477 | self.named.push((n, Vec::new())); | |
478 | } | |
479 | }, | |
480 | }; | |
481 | } | |
482 | } | |
483 | ||
f20569fa | 484 | impl Write { |
cdc7bbd5 XL |
485 | /// Parses a format string into a collection of spans for each argument. This only keeps track |
486 | /// of empty format arguments. Will also lint usages of debug format strings outside of debug | |
487 | /// impls. | |
488 | fn parse_fmt_string(&self, cx: &EarlyContext<'_>, str_lit: &StrLit) -> Option<SimpleFormatArgs> { | |
489 | use rustc_parse_format::{ParseMode, Parser, Piece}; | |
490 | ||
491 | let str_sym = str_lit.symbol_unescaped.as_str(); | |
492 | let style = match str_lit.style { | |
493 | StrStyle::Cooked => None, | |
494 | StrStyle::Raw(n) => Some(n as usize), | |
495 | }; | |
496 | ||
a2a8927a | 497 | let mut parser = Parser::new(str_sym, style, snippet_opt(cx, str_lit.span), false, ParseMode::Format); |
cdc7bbd5 XL |
498 | let mut args = SimpleFormatArgs::default(); |
499 | ||
500 | while let Some(arg) = parser.next() { | |
501 | let arg = match arg { | |
502 | Piece::String(_) => continue, | |
503 | Piece::NextArgument(arg) => arg, | |
504 | }; | |
505 | let span = parser | |
506 | .arg_places | |
507 | .last() | |
04454e1e | 508 | .map_or(DUMMY_SP, |&x| str_lit.span.from_inner(InnerSpan::new(x.start, x.end))); |
cdc7bbd5 XL |
509 | |
510 | if !self.in_debug_impl && arg.format.ty == "?" { | |
511 | // FIXME: modify rustc's fmt string parser to give us the current span | |
512 | span_lint(cx, USE_DEBUG, span, "use of `Debug`-based formatting"); | |
513 | } | |
514 | ||
515 | args.push(arg, span); | |
516 | } | |
517 | ||
518 | parser.errors.is_empty().then(move || args) | |
519 | } | |
520 | ||
f20569fa XL |
521 | /// Checks the arguments of `print[ln]!` and `write[ln]!` calls. It will return a tuple of two |
522 | /// `Option`s. The first `Option` of the tuple is the macro's format string. It includes | |
523 | /// the contents of the string, whether it's a raw string, and the span of the literal in the | |
524 | /// source. The second `Option` in the tuple is, in the `write[ln]!` case, the expression the | |
525 | /// `format_str` should be written to. | |
526 | /// | |
527 | /// Example: | |
528 | /// | |
529 | /// Calling this function on | |
530 | /// ```rust | |
531 | /// # use std::fmt::Write; | |
532 | /// # let mut buf = String::new(); | |
533 | /// # let something = "something"; | |
534 | /// writeln!(buf, "string to write: {}", something); | |
535 | /// ``` | |
536 | /// will return | |
537 | /// ```rust,ignore | |
538 | /// (Some("string to write: {}"), Some(buf)) | |
539 | /// ``` | |
cdc7bbd5 | 540 | fn check_tts<'a>(&self, cx: &EarlyContext<'a>, tts: TokenStream, is_write: bool) -> (Option<StrLit>, Option<Expr>) { |
5099ac24 | 541 | let mut parser = parser::Parser::new(&cx.sess().parse_sess, tts, false, None); |
cdc7bbd5 XL |
542 | let expr = if is_write { |
543 | match parser | |
544 | .parse_expr() | |
545 | .map(rustc_ast::ptr::P::into_inner) | |
5e7ed085 | 546 | .map_err(DiagnosticBuilder::cancel) |
cdc7bbd5 XL |
547 | { |
548 | // write!(e, ...) | |
549 | Ok(p) if parser.eat(&token::Comma) => Some(p), | |
550 | // write!(e) or error | |
551 | e => return (None, e.ok()), | |
f20569fa | 552 | } |
cdc7bbd5 XL |
553 | } else { |
554 | None | |
555 | }; | |
f20569fa XL |
556 | |
557 | let fmtstr = match parser.parse_str_lit() { | |
558 | Ok(fmtstr) => fmtstr, | |
559 | Err(_) => return (None, expr), | |
560 | }; | |
cdc7bbd5 XL |
561 | |
562 | let args = match self.parse_fmt_string(cx, &fmtstr) { | |
563 | Some(args) => args, | |
564 | None => return (Some(fmtstr), expr), | |
565 | }; | |
566 | ||
f20569fa | 567 | let lint = if is_write { WRITE_LITERAL } else { PRINT_LITERAL }; |
cdc7bbd5 | 568 | let mut unnamed_args = args.get_unnamed(); |
f20569fa | 569 | loop { |
f20569fa XL |
570 | if !parser.eat(&token::Comma) { |
571 | return (Some(fmtstr), expr); | |
572 | } | |
cdc7bbd5 XL |
573 | |
574 | let comma_span = parser.prev_token.span; | |
5e7ed085 | 575 | let token_expr = if let Ok(expr) = parser.parse_expr().map_err(DiagnosticBuilder::cancel) { |
f20569fa XL |
576 | expr |
577 | } else { | |
578 | return (Some(fmtstr), None); | |
579 | }; | |
cdc7bbd5 XL |
580 | let (fmt_spans, lit) = match &token_expr.kind { |
581 | ExprKind::Lit(lit) => (unnamed_args.next().unwrap_or(&[]), lit), | |
582 | ExprKind::Assign(lhs, rhs, _) => match (&lhs.kind, &rhs.kind) { | |
583 | (ExprKind::Path(_, p), ExprKind::Lit(lit)) => (args.get_named(p), lit), | |
584 | _ => continue, | |
585 | }, | |
586 | _ => { | |
587 | unnamed_args.next(); | |
588 | continue; | |
589 | }, | |
590 | }; | |
591 | ||
592 | let replacement: String = match lit.token.kind { | |
cdc7bbd5 | 593 | LitKind::StrRaw(_) | LitKind::ByteStrRaw(_) if matches!(fmtstr.style, StrStyle::Raw(_)) => { |
a2a8927a | 594 | lit.token.symbol.as_str().replace('{', "{{").replace('}', "}}") |
cdc7bbd5 XL |
595 | }, |
596 | LitKind::Str | LitKind::ByteStr if matches!(fmtstr.style, StrStyle::Cooked) => { | |
a2a8927a | 597 | lit.token.symbol.as_str().replace('{', "{{").replace('}', "}}") |
cdc7bbd5 | 598 | }, |
5e7ed085 FG |
599 | LitKind::StrRaw(_) |
600 | | LitKind::Str | |
601 | | LitKind::ByteStrRaw(_) | |
602 | | LitKind::ByteStr | |
603 | | LitKind::Integer | |
604 | | LitKind::Float | |
605 | | LitKind::Err => continue, | |
a2a8927a | 606 | LitKind::Byte | LitKind::Char => match lit.token.symbol.as_str() { |
cdc7bbd5 XL |
607 | "\"" if matches!(fmtstr.style, StrStyle::Cooked) => "\\\"", |
608 | "\"" if matches!(fmtstr.style, StrStyle::Raw(0)) => continue, | |
609 | "\\\\" if matches!(fmtstr.style, StrStyle::Raw(_)) => "\\", | |
610 | "\\'" => "'", | |
611 | "{" => "{{", | |
612 | "}" => "}}", | |
613 | x if matches!(fmtstr.style, StrStyle::Raw(_)) && x.starts_with('\\') => continue, | |
614 | x => x, | |
f20569fa | 615 | } |
cdc7bbd5 XL |
616 | .into(), |
617 | LitKind::Bool => lit.token.symbol.as_str().deref().into(), | |
618 | }; | |
619 | ||
620 | if !fmt_spans.is_empty() { | |
621 | span_lint_and_then( | |
622 | cx, | |
623 | lint, | |
624 | token_expr.span, | |
625 | "literal with an empty format string", | |
626 | |diag| { | |
627 | diag.multipart_suggestion( | |
628 | "try this", | |
629 | iter::once((comma_span.to(token_expr.span), String::new())) | |
630 | .chain(fmt_spans.iter().copied().zip(iter::repeat(replacement))) | |
631 | .collect(), | |
632 | Applicability::MachineApplicable, | |
633 | ); | |
634 | }, | |
635 | ); | |
f20569fa XL |
636 | } |
637 | } | |
638 | } | |
639 | ||
640 | fn lint_println_empty_string(&self, cx: &EarlyContext<'_>, mac: &MacCall) { | |
641 | if let (Some(fmt_str), _) = self.check_tts(cx, mac.args.inner_tokens(), false) { | |
642 | if fmt_str.symbol == kw::Empty { | |
643 | let name = mac.path.segments[0].ident.name; | |
644 | span_lint_and_sugg( | |
645 | cx, | |
646 | PRINTLN_EMPTY_STRING, | |
647 | mac.span(), | |
648 | &format!("using `{}!(\"\")`", name), | |
649 | "replace it with", | |
650 | format!("{}!()", name), | |
651 | Applicability::MachineApplicable, | |
652 | ); | |
653 | } | |
654 | } | |
655 | } | |
656 | ||
657 | fn lint_print_with_newline(&self, cx: &EarlyContext<'_>, mac: &MacCall) { | |
658 | if let (Some(fmt_str), _) = self.check_tts(cx, mac.args.inner_tokens(), false) { | |
659 | if check_newlines(&fmt_str) { | |
660 | let name = mac.path.segments[0].ident.name; | |
661 | let suggested = format!("{}ln", name); | |
662 | span_lint_and_then( | |
663 | cx, | |
664 | PRINT_WITH_NEWLINE, | |
665 | mac.span(), | |
cdc7bbd5 | 666 | &format!("using `{}!()` with a format string that ends in a single newline", name), |
f20569fa XL |
667 | |err| { |
668 | err.multipart_suggestion( | |
669 | &format!("use `{}!` instead", suggested), | |
17df50a5 | 670 | vec![(mac.path.span, suggested), (newline_span(&fmt_str).0, String::new())], |
f20569fa XL |
671 | Applicability::MachineApplicable, |
672 | ); | |
673 | }, | |
674 | ); | |
675 | } | |
676 | } | |
677 | } | |
678 | } | |
679 | ||
680 | /// Checks if the format string contains a single newline that terminates it. | |
681 | /// | |
682 | /// Literal and escaped newlines are both checked (only literal for raw strings). | |
683 | fn check_newlines(fmtstr: &StrLit) -> bool { | |
684 | let mut has_internal_newline = false; | |
685 | let mut last_was_cr = false; | |
686 | let mut should_lint = false; | |
687 | ||
a2a8927a | 688 | let contents = fmtstr.symbol.as_str(); |
f20569fa XL |
689 | |
690 | let mut cb = |r: Range<usize>, c: Result<char, EscapeError>| { | |
691 | let c = c.unwrap(); | |
692 | ||
693 | if r.end == contents.len() && c == '\n' && !last_was_cr && !has_internal_newline { | |
694 | should_lint = true; | |
695 | } else { | |
696 | last_was_cr = c == '\r'; | |
697 | if c == '\n' { | |
698 | has_internal_newline = true; | |
699 | } | |
700 | } | |
701 | }; | |
702 | ||
703 | match fmtstr.style { | |
704 | StrStyle::Cooked => unescape::unescape_literal(contents, unescape::Mode::Str, &mut cb), | |
705 | StrStyle::Raw(_) => unescape::unescape_literal(contents, unescape::Mode::RawStr, &mut cb), | |
706 | } | |
707 | ||
708 | should_lint | |
709 | } |