]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/clippy_lints/src/explicit_write.rs
Add 'src/tools/clippy/' from commit 'd2708873ef711ec8ab45df1e984ecf24a96cd369'
[rust.git] / src / tools / clippy / clippy_lints / src / explicit_write.rs
1 use crate::utils::{is_expn_of, match_function_call, paths, span_lint, span_lint_and_sugg};
2 use if_chain::if_chain;
3 use rustc_ast::ast::LitKind;
4 use rustc_errors::Applicability;
5 use rustc_hir::{BorrowKind, Expr, ExprKind};
6 use rustc_lint::{LateContext, LateLintPass};
7 use rustc_session::{declare_lint_pass, declare_tool_lint};
8
9 declare_clippy_lint! {
10     /// **What it does:** Checks for usage of `write!()` / `writeln()!` which can be
11     /// replaced with `(e)print!()` / `(e)println!()`
12     ///
13     /// **Why is this bad?** Using `(e)println! is clearer and more concise
14     ///
15     /// **Known problems:** None.
16     ///
17     /// **Example:**
18     /// ```rust
19     /// # use std::io::Write;
20     /// # let bar = "furchtbar";
21     /// // this would be clearer as `eprintln!("foo: {:?}", bar);`
22     /// writeln!(&mut std::io::stderr(), "foo: {:?}", bar).unwrap();
23     /// ```
24     pub EXPLICIT_WRITE,
25     complexity,
26     "using the `write!()` family of functions instead of the `print!()` family of functions, when using the latter would work"
27 }
28
29 declare_lint_pass!(ExplicitWrite => [EXPLICIT_WRITE]);
30
31 impl<'a, 'tcx> LateLintPass<'a, 'tcx> for ExplicitWrite {
32     fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'_>) {
33         if_chain! {
34             // match call to unwrap
35             if let ExprKind::MethodCall(ref unwrap_fun, _, ref unwrap_args) = expr.kind;
36             if unwrap_fun.ident.name == sym!(unwrap);
37             // match call to write_fmt
38             if !unwrap_args.is_empty();
39             if let ExprKind::MethodCall(ref write_fun, _, write_args) =
40                 unwrap_args[0].kind;
41             if write_fun.ident.name == sym!(write_fmt);
42             // match calls to std::io::stdout() / std::io::stderr ()
43             if !write_args.is_empty();
44             if let Some(dest_name) = if match_function_call(cx, &write_args[0], &paths::STDOUT).is_some() {
45                 Some("stdout")
46             } else if match_function_call(cx, &write_args[0], &paths::STDERR).is_some() {
47                 Some("stderr")
48             } else {
49                 None
50             };
51             then {
52                 let write_span = unwrap_args[0].span;
53                 let calling_macro =
54                     // ordering is important here, since `writeln!` uses `write!` internally
55                     if is_expn_of(write_span, "writeln").is_some() {
56                         Some("writeln")
57                     } else if is_expn_of(write_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                 if let Some(mut write_output) = write_output_string(write_args) {
72                     if write_output.ends_with('\n') {
73                         write_output.pop();
74                     }
75
76                     if let Some(macro_name) = calling_macro {
77                         span_lint_and_sugg(
78                             cx,
79                             EXPLICIT_WRITE,
80                             expr.span,
81                             &format!(
82                                 "use of `{}!({}(), ...).unwrap()`",
83                                 macro_name,
84                                 dest_name
85                             ),
86                             "try this",
87                             format!("{}{}!(\"{}\")", prefix, macro_name.replace("write", "print"), write_output.escape_default()),
88                             Applicability::MachineApplicable
89                         );
90                     } else {
91                         span_lint_and_sugg(
92                             cx,
93                             EXPLICIT_WRITE,
94                             expr.span,
95                             &format!("use of `{}().write_fmt(...).unwrap()`", dest_name),
96                             "try this",
97                             format!("{}print!(\"{}\")", prefix, write_output.escape_default()),
98                             Applicability::MachineApplicable
99                         );
100                     }
101                 } else {
102                     // We don't have a proper suggestion
103                     if let Some(macro_name) = calling_macro {
104                         span_lint(
105                             cx,
106                             EXPLICIT_WRITE,
107                             expr.span,
108                             &format!(
109                                 "use of `{}!({}(), ...).unwrap()`. Consider using `{}{}!` instead",
110                                 macro_name,
111                                 dest_name,
112                                 prefix,
113                                 macro_name.replace("write", "print")
114                             )
115                         );
116                     } else {
117                         span_lint(
118                             cx,
119                             EXPLICIT_WRITE,
120                             expr.span,
121                             &format!("use of `{}().write_fmt(...).unwrap()`. Consider using `{}print!` instead", dest_name, prefix),
122                         );
123                     }
124                 }
125
126             }
127         }
128     }
129 }
130
131 // Extract the output string from the given `write_args`.
132 fn write_output_string(write_args: &[Expr<'_>]) -> Option<String> {
133     if_chain! {
134         // Obtain the string that should be printed
135         if write_args.len() > 1;
136         if let ExprKind::Call(_, ref output_args) = write_args[1].kind;
137         if !output_args.is_empty();
138         if let ExprKind::AddrOf(BorrowKind::Ref, _, ref output_string_expr) = output_args[0].kind;
139         if let ExprKind::Array(ref string_exprs) = output_string_expr.kind;
140         // we only want to provide an automatic suggestion for simple (non-format) strings
141         if string_exprs.len() == 1;
142         if let ExprKind::Lit(ref lit) = string_exprs[0].kind;
143         if let LitKind::Str(ref write_output, _) = lit.node;
144         then {
145             return Some(write_output.to_string())
146         }
147     }
148     None
149 }