]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/print.rs
4a427fc79bd4dd12235860bb2fa53be9b8032a2a
[rust.git] / clippy_lints / src / print.rs
1 use rustc::hir::*;
2 use rustc::hir::map::Node::{NodeItem, NodeImplItem};
3 use rustc::lint::*;
4 use utils::paths;
5 use utils::{is_expn_of, match_def_path, resolve_node, span_lint, match_path_old};
6 use format::get_argument_fmtstr_parts;
7
8 /// **What it does:** This lint warns when you using `print!()` with a format
9 /// string that
10 /// ends in a newline.
11 ///
12 /// **Why is this bad?** You should use `println!()` instead, which appends the
13 /// newline.
14 ///
15 /// **Known problems:** None.
16 ///
17 /// **Example:**
18 /// ```rust
19 /// print!("Hello {}!\n", name);
20 /// ```
21 declare_lint! {
22     pub PRINT_WITH_NEWLINE,
23     Warn,
24     "using `print!()` with a format string that ends in a newline"
25 }
26
27 /// **What it does:** Checks for printing on *stdout*. The purpose of this lint
28 /// is to catch debugging remnants.
29 ///
30 /// **Why is this bad?** People often print on *stdout* while debugging an
31 /// application and might forget to remove those prints afterward.
32 ///
33 /// **Known problems:** Only catches `print!` and `println!` calls.
34 ///
35 /// **Example:**
36 /// ```rust
37 /// println!("Hello world!");
38 /// ```
39 declare_lint! {
40     pub PRINT_STDOUT,
41     Allow,
42     "printing on stdout"
43 }
44
45 /// **What it does:** Checks for use of `Debug` formatting. The purpose of this
46 /// lint is to catch debugging remnants.
47 ///
48 /// **Why is this bad?** The purpose of the `Debug` trait is to facilitate
49 /// debugging Rust code. It should not be used in in user-facing output.
50 ///
51 /// **Example:**
52 /// ```rust
53 /// println!("{:?}", foo);
54 /// ```
55 declare_lint! {
56     pub USE_DEBUG,
57     Allow,
58     "use of `Debug`-based formatting"
59 }
60
61 #[derive(Copy, Clone, Debug)]
62 pub struct Pass;
63
64 impl LintPass for Pass {
65     fn get_lints(&self) -> LintArray {
66         lint_array!(PRINT_WITH_NEWLINE, PRINT_STDOUT, USE_DEBUG)
67     }
68 }
69
70 impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Pass {
71     fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr) {
72         if_let_chain! {[
73             let ExprCall(ref fun, ref args) = expr.node,
74             let ExprPath(ref qpath) = fun.node,
75         ], {
76             let fun = resolve_node(cx, qpath, fun.hir_id);
77             let fun_id = fun.def_id();
78
79             // Search for `std::io::_print(..)` which is unique in a
80             // `print!` expansion.
81             if match_def_path(cx.tcx, fun_id, &paths::IO_PRINT) {
82                 if let Some(span) = is_expn_of(expr.span, "print") {
83                     // `println!` uses `print!`.
84                     let (span, name) = match is_expn_of(span, "println") {
85                         Some(span) => (span, "println"),
86                         None => (span, "print"),
87                     };
88
89                     span_lint(cx, PRINT_STDOUT, span, &format!("use of `{}!`", name));
90
91                     // Check print! with format string ending in "\n".
92                     if_let_chain!{[
93                         name == "print",
94
95                         // ensure we're calling Arguments::new_v1
96                         args.len() == 1,
97                         let ExprCall(ref args_fun, ref args_args) = args[0].node,
98                         let ExprPath(ref qpath) = args_fun.node,
99                         match_def_path(cx.tcx,
100                                        resolve_node(cx, qpath, args_fun.hir_id).def_id(),
101                                        &paths::FMT_ARGUMENTS_NEWV1),
102                         args_args.len() == 2,
103                         let ExprAddrOf(_, ref match_expr) = args_args[1].node,
104                         let ExprMatch(ref args, _, _) = match_expr.node,
105                         let ExprTup(ref args) = args.node,
106
107                         // collect the format string parts and check the last one
108                         let Some(fmtstrs) = get_argument_fmtstr_parts(cx, &args_args[0]),
109                         let Some(last_str) = fmtstrs.last(),
110                         let Some('\n') = last_str.chars().last(),
111
112                         // "foo{}bar" is made into two strings + one argument,
113                         // if the format string starts with `{}` (eg. "{}foo"),
114                         // the string array is prepended an empty string "".
115                         // We only want to check the last string after any `{}`:
116                         args.len() < fmtstrs.len(),
117                     ], {
118                         span_lint(cx, PRINT_WITH_NEWLINE, span,
119                                   "using `print!()` with a format string that ends in a \
120                                    newline, consider using `println!()` instead");
121                     }}
122                 }
123             }
124             // Search for something like
125             // `::std::fmt::ArgumentV1::new(__arg0, ::std::fmt::Debug::fmt)`
126             else if args.len() == 2 && match_def_path(cx.tcx, fun_id, &paths::FMT_ARGUMENTV1_NEW) {
127                 if let ExprPath(ref qpath) = args[1].node {
128                     let def_id = cx.tables.qpath_def(qpath, args[1].hir_id).def_id();
129                     if match_def_path(cx.tcx, def_id, &paths::DEBUG_FMT_METHOD) && !is_in_debug_impl(cx, expr) &&
130                        is_expn_of(expr.span, "panic").is_none() {
131                         span_lint(cx, USE_DEBUG, args[0].span, "use of `Debug`-based formatting");
132                     }
133                 }
134             }
135         }}
136     }
137 }
138
139 fn is_in_debug_impl(cx: &LateContext, expr: &Expr) -> bool {
140     let map = &cx.tcx.hir;
141
142     // `fmt` method
143     if let Some(NodeImplItem(item)) = map.find(map.get_parent(expr.id)) {
144         // `Debug` impl
145         if let Some(NodeItem(item)) = map.find(map.get_parent(item.id)) {
146             if let ItemImpl(_, _, _, _, Some(ref tr), _, _) = item.node {
147                 return match_path_old(&tr.path, &["Debug"]);
148             }
149         }
150     }
151
152     false
153 }