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