]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/clippy_lints/src/explicit_write.rs
Rollup merge of #91861 - juniorbassani:use-from-array-in-collections-examples, r...
[rust.git] / src / tools / clippy / clippy_lints / src / explicit_write.rs
1 use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg};
2 use clippy_utils::macros::FormatArgsExpn;
3 use clippy_utils::{is_expn_of, match_function_call, paths};
4 use if_chain::if_chain;
5 use rustc_errors::Applicability;
6 use rustc_hir::{Expr, ExprKind};
7 use rustc_lint::{LateContext, LateLintPass};
8 use rustc_session::{declare_lint_pass, declare_tool_lint};
9 use rustc_span::sym;
10
11 declare_clippy_lint! {
12     /// ### What it does
13     /// Checks for usage of `write!()` / `writeln()!` which can be
14     /// replaced with `(e)print!()` / `(e)println!()`
15     ///
16     /// ### Why is this bad?
17     /// Using `(e)println! is clearer and more concise
18     ///
19     /// ### Example
20     /// ```rust
21     /// # use std::io::Write;
22     /// # let bar = "furchtbar";
23     /// // this would be clearer as `eprintln!("foo: {:?}", bar);`
24     /// writeln!(&mut std::io::stderr(), "foo: {:?}", bar).unwrap();
25     /// ```
26     #[clippy::version = "pre 1.29.0"]
27     pub EXPLICIT_WRITE,
28     complexity,
29     "using the `write!()` family of functions instead of the `print!()` family of functions, when using the latter would work"
30 }
31
32 declare_lint_pass!(ExplicitWrite => [EXPLICIT_WRITE]);
33
34 impl<'tcx> LateLintPass<'tcx> for ExplicitWrite {
35     fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
36         if_chain! {
37             // match call to unwrap
38             if let ExprKind::MethodCall(unwrap_fun, [write_call], _) = expr.kind;
39             if unwrap_fun.ident.name == sym::unwrap;
40             // match call to write_fmt
41             if let ExprKind::MethodCall(write_fun, [write_recv, write_arg], _) = write_call.kind;
42             if write_fun.ident.name == sym!(write_fmt);
43             // match calls to std::io::stdout() / std::io::stderr ()
44             if let Some(dest_name) = if match_function_call(cx, write_recv, &paths::STDOUT).is_some() {
45                 Some("stdout")
46             } else if match_function_call(cx, write_recv, &paths::STDERR).is_some() {
47                 Some("stderr")
48             } else {
49                 None
50             };
51             if let Some(format_args) = FormatArgsExpn::parse(cx, write_arg);
52             then {
53                 let calling_macro =
54                     // ordering is important here, since `writeln!` uses `write!` internally
55                     if is_expn_of(write_call.span, "writeln").is_some() {
56                         Some("writeln")
57                     } else if is_expn_of(write_call.span, "write").is_some() {
58                         Some("write")
59                     } else {
60                         None
61                     };
62                 let prefix = if dest_name == "stderr" {
63                     "e"
64                 } else {
65                     ""
66                 };
67
68                 // We need to remove the last trailing newline from the string because the
69                 // underlying `fmt::write` function doesn't know whether `println!` or `print!` was
70                 // used.
71                 let (used, sugg_mac) = if let Some(macro_name) = calling_macro {
72                     (
73                         format!("{}!({}(), ...)", macro_name, dest_name),
74                         macro_name.replace("write", "print"),
75                     )
76                 } else {
77                     (
78                         format!("{}().write_fmt(...)", dest_name),
79                         "print".into(),
80                     )
81                 };
82                 let msg = format!("use of `{}.unwrap()`", used);
83                 if let [write_output] = *format_args.format_string_parts {
84                     let mut write_output = write_output.to_string();
85                     if write_output.ends_with('\n') {
86                         write_output.pop();
87                     }
88
89                     let sugg = format!("{}{}!(\"{}\")", prefix, sugg_mac, write_output.escape_default());
90                     span_lint_and_sugg(
91                         cx,
92                         EXPLICIT_WRITE,
93                         expr.span,
94                         &msg,
95                         "try this",
96                         sugg,
97                         Applicability::MachineApplicable
98                     );
99                 } else {
100                     // We don't have a proper suggestion
101                     let help = format!("consider using `{}{}!` instead", prefix, sugg_mac);
102                     span_lint_and_help(cx, EXPLICIT_WRITE, expr.span, &msg, None, &help);
103                 }
104             }
105         }
106     }
107 }