]> git.proxmox.com Git - rustc.git/blob - src/tools/clippy/clippy_lints/src/print.rs
New upstream version 1.25.0+dfsg1
[rustc.git] / src / tools / clippy / clippy_lints / src / print.rs
1 use std::ops::Deref;
2 use rustc::hir::*;
3 use rustc::hir::map::Node::{NodeImplItem, NodeItem};
4 use rustc::lint::*;
5 use syntax::ast::LitKind;
6 use syntax::symbol::InternedString;
7 use syntax_pos::Span;
8 use utils::{is_expn_of, match_def_path, match_path, resolve_node, span_lint, span_lint_and_sugg};
9 use utils::{opt_def_id, paths};
10
11 /// **What it does:** This lint warns when you using `println!("")` to
12 /// print a newline.
13 ///
14 /// **Why is this bad?** You should use `println!()`, which is simpler.
15 ///
16 /// **Known problems:** None.
17 ///
18 /// **Example:**
19 /// ```rust
20 /// println!("");
21 /// ```
22 declare_lint! {
23 pub PRINTLN_EMPTY_STRING,
24 Warn,
25 "using `print!()` with a format string that ends in a newline"
26 }
27
28 /// **What it does:** This lint warns when you using `print!()` with a format
29 /// string that
30 /// ends in a newline.
31 ///
32 /// **Why is this bad?** You should use `println!()` instead, which appends the
33 /// newline.
34 ///
35 /// **Known problems:** None.
36 ///
37 /// **Example:**
38 /// ```rust
39 /// print!("Hello {}!\n", name);
40 /// ```
41 declare_lint! {
42 pub PRINT_WITH_NEWLINE,
43 Warn,
44 "using `print!()` with a format string that ends in a newline"
45 }
46
47 /// **What it does:** Checks for printing on *stdout*. The purpose of this lint
48 /// is to catch debugging remnants.
49 ///
50 /// **Why is this bad?** People often print on *stdout* while debugging an
51 /// application and might forget to remove those prints afterward.
52 ///
53 /// **Known problems:** Only catches `print!` and `println!` calls.
54 ///
55 /// **Example:**
56 /// ```rust
57 /// println!("Hello world!");
58 /// ```
59 declare_lint! {
60 pub PRINT_STDOUT,
61 Allow,
62 "printing on stdout"
63 }
64
65 /// **What it does:** Checks for use of `Debug` formatting. The purpose of this
66 /// lint is to catch debugging remnants.
67 ///
68 /// **Why is this bad?** The purpose of the `Debug` trait is to facilitate
69 /// debugging Rust code. It should not be used in in user-facing output.
70 ///
71 /// **Example:**
72 /// ```rust
73 /// println!("{:?}", foo);
74 /// ```
75 declare_lint! {
76 pub USE_DEBUG,
77 Allow,
78 "use of `Debug`-based formatting"
79 }
80
81 #[derive(Copy, Clone, Debug)]
82 pub struct Pass;
83
84 impl LintPass for Pass {
85 fn get_lints(&self) -> LintArray {
86 lint_array!(PRINT_WITH_NEWLINE, PRINTLN_EMPTY_STRING, PRINT_STDOUT, USE_DEBUG)
87 }
88 }
89
90 impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Pass {
91 fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr) {
92 if_chain! {
93 if let ExprCall(ref fun, ref args) = expr.node;
94 if let ExprPath(ref qpath) = fun.node;
95 if let Some(fun_id) = opt_def_id(resolve_node(cx, qpath, fun.hir_id));
96 then {
97
98 // Search for `std::io::_print(..)` which is unique in a
99 // `print!` expansion.
100 if match_def_path(cx.tcx, fun_id, &paths::IO_PRINT) {
101 if let Some(span) = is_expn_of(expr.span, "print") {
102 // `println!` uses `print!`.
103 let (span, name) = match is_expn_of(span, "println") {
104 Some(span) => (span, "println"),
105 None => (span, "print"),
106 };
107
108 span_lint(cx, PRINT_STDOUT, span, &format!("use of `{}!`", name));
109
110 if_chain! {
111 // ensure we're calling Arguments::new_v1
112 if args.len() == 1;
113 if let ExprCall(ref args_fun, ref args_args) = args[0].node;
114 if let ExprPath(ref qpath) = args_fun.node;
115 if let Some(const_def_id) = opt_def_id(resolve_node(cx, qpath, args_fun.hir_id));
116 if match_def_path(cx.tcx, const_def_id, &paths::FMT_ARGUMENTS_NEWV1);
117 if args_args.len() == 2;
118 if let ExprAddrOf(_, ref match_expr) = args_args[1].node;
119 if let ExprMatch(ref args, _, _) = match_expr.node;
120 if let ExprTup(ref args) = args.node;
121 if let Some((fmtstr, fmtlen)) = get_argument_fmtstr_parts(&args_args[0]);
122 then {
123 match name {
124 "print" => check_print(cx, span, args, fmtstr, fmtlen),
125 "println" => check_println(cx, span, fmtstr, fmtlen),
126 _ => (),
127 }
128 }
129 }
130 }
131 }
132 // Search for something like
133 // `::std::fmt::ArgumentV1::new(__arg0, ::std::fmt::Debug::fmt)`
134 else if args.len() == 2 && match_def_path(cx.tcx, fun_id, &paths::FMT_ARGUMENTV1_NEW) {
135 if let ExprPath(ref qpath) = args[1].node {
136 if let Some(def_id) = opt_def_id(cx.tables.qpath_def(qpath, args[1].hir_id)) {
137 if match_def_path(cx.tcx, def_id, &paths::DEBUG_FMT_METHOD)
138 && !is_in_debug_impl(cx, expr) && is_expn_of(expr.span, "panic").is_none() {
139 span_lint(cx, USE_DEBUG, args[0].span, "use of `Debug`-based formatting");
140 }
141 }
142 }
143 }
144 }
145 }
146 }
147 }
148
149 // Check for print!("... \n", ...).
150 fn check_print<'a, 'tcx>(
151 cx: &LateContext<'a, 'tcx>,
152 span: Span,
153 args: &HirVec<Expr>,
154 fmtstr: InternedString,
155 fmtlen: usize,
156 ) {
157 if_chain! {
158 // check the final format string part
159 if let Some('\n') = fmtstr.chars().last();
160
161 // "foo{}bar" is made into two strings + one argument,
162 // if the format string starts with `{}` (eg. "{}foo"),
163 // the string array is prepended an empty string "".
164 // We only want to check the last string after any `{}`:
165 if args.len() < fmtlen;
166 then {
167 span_lint(cx, PRINT_WITH_NEWLINE, span,
168 "using `print!()` with a format string that ends in a \
169 newline, consider using `println!()` instead");
170 }
171 }
172 }
173
174 /// Check for println!("")
175 fn check_println<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, span: Span, fmtstr: InternedString, fmtlen: usize) {
176 if_chain! {
177 // check that the string is empty
178 if fmtlen == 1;
179 if fmtstr.deref() == "\n";
180
181 // check the presence of that string
182 if let Ok(snippet) = cx.sess().codemap().span_to_snippet(span);
183 if snippet.contains("\"\"");
184 then {
185 span_lint_and_sugg(
186 cx,
187 PRINT_WITH_NEWLINE,
188 span,
189 "using `println!(\"\")`",
190 "replace it with",
191 "println!()".to_string(),
192 );
193 }
194 }
195 }
196
197 fn is_in_debug_impl(cx: &LateContext, expr: &Expr) -> bool {
198 let map = &cx.tcx.hir;
199
200 // `fmt` method
201 if let Some(NodeImplItem(item)) = map.find(map.get_parent(expr.id)) {
202 // `Debug` impl
203 if let Some(NodeItem(item)) = map.find(map.get_parent(item.id)) {
204 if let ItemImpl(_, _, _, _, Some(ref tr), _, _) = item.node {
205 return match_path(&tr.path, &["Debug"]);
206 }
207 }
208 }
209
210 false
211 }
212
213 /// Returns the slice of format string parts in an `Arguments::new_v1` call.
214 fn get_argument_fmtstr_parts(expr: &Expr) -> Option<(InternedString, usize)> {
215 if_chain! {
216 if let ExprAddrOf(_, ref expr) = expr.node; // &["…", "…", …]
217 if let ExprArray(ref exprs) = expr.node;
218 if let Some(expr) = exprs.last();
219 if let ExprLit(ref lit) = expr.node;
220 if let LitKind::Str(ref lit, _) = lit.node;
221 then {
222 return Some((lit.as_str(), exprs.len()));
223 }
224 }
225 None
226 }