2017-10-20 09:45:17 -05:00
|
|
|
use std::ops::Deref;
|
2016-04-14 11:13:15 -05:00
|
|
|
use rustc::hir::*;
|
2017-09-05 04:33:04 -05:00
|
|
|
use rustc::hir::map::Node::{NodeImplItem, NodeItem};
|
2016-01-28 12:29:59 -06:00
|
|
|
use rustc::lint::*;
|
2017-09-29 12:13:21 -05:00
|
|
|
use syntax::ast::LitKind;
|
|
|
|
use syntax::symbol::InternedString;
|
2017-10-20 09:45:17 -05:00
|
|
|
use syntax_pos::Span;
|
2017-09-05 04:33:04 -05:00
|
|
|
use utils::{is_expn_of, match_def_path, match_path, resolve_node, span_lint};
|
2017-09-29 12:13:21 -05:00
|
|
|
use utils::{paths, opt_def_id};
|
2016-08-05 10:52:58 -05:00
|
|
|
|
2017-10-20 09:45:17 -05:00
|
|
|
/// **What it does:** This lint warns when you using `println!("")` to
|
|
|
|
/// print a newline.
|
|
|
|
///
|
|
|
|
/// **Why is this bad?** You should use `println!()`, which is simpler.
|
|
|
|
///
|
|
|
|
/// **Known problems:** None.
|
|
|
|
///
|
|
|
|
/// **Example:**
|
|
|
|
/// ```rust
|
|
|
|
/// println!("");
|
|
|
|
/// ```
|
|
|
|
declare_lint! {
|
|
|
|
pub PRINTLN_EMPTY_STRING,
|
|
|
|
Warn,
|
|
|
|
"using `print!()` with a format string that ends in a newline"
|
|
|
|
}
|
|
|
|
|
2017-08-09 02:30:56 -05:00
|
|
|
/// **What it does:** This lint warns when you using `print!()` with a format
|
|
|
|
/// string that
|
2016-08-05 10:52:58 -05:00
|
|
|
/// ends in a newline.
|
|
|
|
///
|
2017-08-09 02:30:56 -05:00
|
|
|
/// **Why is this bad?** You should use `println!()` instead, which appends the
|
|
|
|
/// newline.
|
2016-08-05 10:52:58 -05:00
|
|
|
///
|
|
|
|
/// **Known problems:** None.
|
|
|
|
///
|
|
|
|
/// **Example:**
|
|
|
|
/// ```rust
|
|
|
|
/// print!("Hello {}!\n", name);
|
|
|
|
/// ```
|
|
|
|
declare_lint! {
|
|
|
|
pub PRINT_WITH_NEWLINE,
|
|
|
|
Warn,
|
|
|
|
"using `print!()` with a format string that ends in a newline"
|
|
|
|
}
|
2016-01-28 12:29:59 -06:00
|
|
|
|
2016-08-06 02:55:04 -05:00
|
|
|
/// **What it does:** Checks for printing on *stdout*. The purpose of this lint
|
|
|
|
/// is to catch debugging remnants.
|
2016-01-28 12:29:59 -06:00
|
|
|
///
|
2016-08-06 02:55:04 -05:00
|
|
|
/// **Why is this bad?** People often print on *stdout* while debugging an
|
|
|
|
/// application and might forget to remove those prints afterward.
|
2016-01-28 12:29:59 -06:00
|
|
|
///
|
|
|
|
/// **Known problems:** Only catches `print!` and `println!` calls.
|
|
|
|
///
|
2016-07-15 17:25:44 -05:00
|
|
|
/// **Example:**
|
|
|
|
/// ```rust
|
|
|
|
/// println!("Hello world!");
|
|
|
|
/// ```
|
2016-01-28 12:29:59 -06:00
|
|
|
declare_lint! {
|
|
|
|
pub PRINT_STDOUT,
|
|
|
|
Allow,
|
|
|
|
"printing on stdout"
|
|
|
|
}
|
|
|
|
|
2016-08-06 02:55:04 -05:00
|
|
|
/// **What it does:** Checks for use of `Debug` formatting. The purpose of this
|
|
|
|
/// lint is to catch debugging remnants.
|
2016-02-07 11:30:57 -06:00
|
|
|
///
|
2016-08-06 02:55:04 -05:00
|
|
|
/// **Why is this bad?** The purpose of the `Debug` trait is to facilitate
|
|
|
|
/// debugging Rust code. It should not be used in in user-facing output.
|
2016-02-07 11:30:57 -06:00
|
|
|
///
|
2016-07-15 17:25:44 -05:00
|
|
|
/// **Example:**
|
|
|
|
/// ```rust
|
|
|
|
/// println!("{:?}", foo);
|
|
|
|
/// ```
|
2016-02-07 11:30:57 -06:00
|
|
|
declare_lint! {
|
|
|
|
pub USE_DEBUG,
|
|
|
|
Allow,
|
2016-08-06 03:18:36 -05:00
|
|
|
"use of `Debug`-based formatting"
|
2016-02-07 11:30:57 -06:00
|
|
|
}
|
|
|
|
|
2016-01-28 12:29:59 -06:00
|
|
|
#[derive(Copy, Clone, Debug)]
|
2016-06-10 09:17:20 -05:00
|
|
|
pub struct Pass;
|
2016-01-28 12:29:59 -06:00
|
|
|
|
2016-06-10 09:17:20 -05:00
|
|
|
impl LintPass for Pass {
|
2016-01-28 12:29:59 -06:00
|
|
|
fn get_lints(&self) -> LintArray {
|
2017-10-20 09:45:17 -05:00
|
|
|
lint_array!(PRINT_WITH_NEWLINE, PRINTLN_EMPTY_STRING, PRINT_STDOUT, USE_DEBUG)
|
2016-01-28 12:29:59 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-12-07 06:13:40 -06:00
|
|
|
impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Pass {
|
|
|
|
fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr) {
|
2016-10-22 08:57:19 -05:00
|
|
|
if_let_chain! {[
|
|
|
|
let ExprCall(ref fun, ref args) = expr.node,
|
2016-12-01 15:31:56 -06:00
|
|
|
let ExprPath(ref qpath) = fun.node,
|
2017-09-12 07:26:40 -05:00
|
|
|
let Some(fun_id) = opt_def_id(resolve_node(cx, qpath, fun.hir_id)),
|
2016-10-22 08:57:19 -05:00
|
|
|
], {
|
2016-01-28 12:29:59 -06:00
|
|
|
|
2016-10-22 08:57:19 -05:00
|
|
|
// Search for `std::io::_print(..)` which is unique in a
|
|
|
|
// `print!` expansion.
|
2017-01-13 10:04:56 -06:00
|
|
|
if match_def_path(cx.tcx, fun_id, &paths::IO_PRINT) {
|
2017-03-31 17:14:04 -05:00
|
|
|
if let Some(span) = is_expn_of(expr.span, "print") {
|
2016-10-22 08:57:19 -05:00
|
|
|
// `println!` uses `print!`.
|
2017-03-31 17:14:04 -05:00
|
|
|
let (span, name) = match is_expn_of(span, "println") {
|
2016-10-22 08:57:19 -05:00
|
|
|
Some(span) => (span, "println"),
|
|
|
|
None => (span, "print"),
|
|
|
|
};
|
2016-08-05 10:52:58 -05:00
|
|
|
|
2016-10-22 08:57:19 -05:00
|
|
|
span_lint(cx, PRINT_STDOUT, span, &format!("use of `{}!`", name));
|
2016-10-06 15:30:03 -05:00
|
|
|
|
2016-10-22 08:57:19 -05:00
|
|
|
if_let_chain!{[
|
|
|
|
// ensure we're calling Arguments::new_v1
|
|
|
|
args.len() == 1,
|
|
|
|
let ExprCall(ref args_fun, ref args_args) = args[0].node,
|
2016-12-01 15:31:56 -06:00
|
|
|
let ExprPath(ref qpath) = args_fun.node,
|
2017-09-12 07:26:40 -05:00
|
|
|
let Some(const_def_id) = opt_def_id(resolve_node(cx, qpath, args_fun.hir_id)),
|
|
|
|
match_def_path(cx.tcx, const_def_id, &paths::FMT_ARGUMENTS_NEWV1),
|
2016-10-22 08:57:19 -05:00
|
|
|
args_args.len() == 2,
|
|
|
|
let ExprAddrOf(_, ref match_expr) = args_args[1].node,
|
|
|
|
let ExprMatch(ref args, _, _) = match_expr.node,
|
|
|
|
let ExprTup(ref args) = args.node,
|
2017-09-29 12:13:21 -05:00
|
|
|
let Some((fmtstr, fmtlen)) = get_argument_fmtstr_parts(&args_args[0]),
|
2016-10-22 08:57:19 -05:00
|
|
|
], {
|
2017-10-20 09:45:17 -05:00
|
|
|
match name {
|
|
|
|
"print" => check_print(cx, span, args, fmtstr, fmtlen),
|
|
|
|
"println" => check_println(cx, span, fmtstr, fmtlen),
|
|
|
|
_ => (),
|
|
|
|
}
|
2016-10-22 08:57:19 -05:00
|
|
|
}}
|
2016-01-28 12:29:59 -06:00
|
|
|
}
|
2016-10-22 08:57:19 -05:00
|
|
|
}
|
|
|
|
// Search for something like
|
|
|
|
// `::std::fmt::ArgumentV1::new(__arg0, ::std::fmt::Debug::fmt)`
|
2017-01-13 10:04:56 -06:00
|
|
|
else if args.len() == 2 && match_def_path(cx.tcx, fun_id, &paths::FMT_ARGUMENTV1_NEW) {
|
2016-12-01 15:31:56 -06:00
|
|
|
if let ExprPath(ref qpath) = args[1].node {
|
2017-09-12 07:26:40 -05:00
|
|
|
if let Some(def_id) = opt_def_id(cx.tables.qpath_def(qpath, args[1].hir_id)) {
|
2017-09-20 16:59:23 -05:00
|
|
|
if match_def_path(cx.tcx, def_id, &paths::DEBUG_FMT_METHOD)
|
|
|
|
&& !is_in_debug_impl(cx, expr) && is_expn_of(expr.span, "panic").is_none() {
|
2017-09-12 07:26:40 -05:00
|
|
|
span_lint(cx, USE_DEBUG, args[0].span, "use of `Debug`-based formatting");
|
|
|
|
}
|
2016-02-07 11:30:57 -06:00
|
|
|
}
|
|
|
|
}
|
2016-01-28 12:29:59 -06:00
|
|
|
}
|
2016-10-22 08:57:19 -05:00
|
|
|
}}
|
2016-01-28 12:29:59 -06:00
|
|
|
}
|
|
|
|
}
|
2016-02-07 11:30:57 -06:00
|
|
|
|
2017-10-20 09:45:17 -05:00
|
|
|
// Check for print!("... \n", ...).
|
|
|
|
fn check_print<'a, 'tcx>(
|
|
|
|
cx: &LateContext<'a, 'tcx>,
|
|
|
|
span: Span,
|
|
|
|
args: &HirVec<Expr>,
|
|
|
|
fmtstr: InternedString,
|
|
|
|
fmtlen: usize,
|
|
|
|
) {
|
|
|
|
if_let_chain!{[
|
|
|
|
// check the final format string part
|
|
|
|
let Some('\n') = fmtstr.chars().last(),
|
|
|
|
|
|
|
|
// "foo{}bar" is made into two strings + one argument,
|
|
|
|
// if the format string starts with `{}` (eg. "{}foo"),
|
|
|
|
// the string array is prepended an empty string "".
|
|
|
|
// We only want to check the last string after any `{}`:
|
|
|
|
args.len() < fmtlen,
|
|
|
|
], {
|
|
|
|
span_lint(cx, PRINT_WITH_NEWLINE, span,
|
|
|
|
"using `print!()` with a format string that ends in a \
|
|
|
|
newline, consider using `println!()` instead");
|
|
|
|
}}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Check for println!("")
|
|
|
|
fn check_println<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, span: Span, fmtstr: InternedString, fmtlen: usize) {
|
|
|
|
if_let_chain!{[
|
|
|
|
// check that the string is empty
|
|
|
|
fmtlen == 1,
|
|
|
|
fmtstr.deref() == "\n",
|
|
|
|
|
|
|
|
// check the presence of that string
|
|
|
|
let Ok(snippet) = cx.sess().codemap().span_to_snippet(span),
|
|
|
|
snippet.contains("\"\""),
|
|
|
|
], {
|
|
|
|
span_lint(cx, PRINT_WITH_NEWLINE, span,
|
|
|
|
"using `println!(\"\")`, consider using `println!()` instead");
|
|
|
|
}}
|
|
|
|
}
|
|
|
|
|
2016-02-07 11:30:57 -06:00
|
|
|
fn is_in_debug_impl(cx: &LateContext, expr: &Expr) -> bool {
|
2017-02-02 10:53:28 -06:00
|
|
|
let map = &cx.tcx.hir;
|
2016-02-07 11:30:57 -06:00
|
|
|
|
2016-02-29 05:19:32 -06:00
|
|
|
// `fmt` method
|
|
|
|
if let Some(NodeImplItem(item)) = map.find(map.get_parent(expr.id)) {
|
|
|
|
// `Debug` impl
|
|
|
|
if let Some(NodeItem(item)) = map.find(map.get_parent(item.id)) {
|
2017-04-28 06:00:42 -05:00
|
|
|
if let ItemImpl(_, _, _, _, Some(ref tr), _, _) = item.node {
|
2017-08-24 17:21:46 -05:00
|
|
|
return match_path(&tr.path, &["Debug"]);
|
2016-02-07 11:30:57 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
false
|
|
|
|
}
|
2017-09-29 12:13:21 -05:00
|
|
|
|
|
|
|
/// Returns the slice of format string parts in an `Arguments::new_v1` call.
|
|
|
|
fn get_argument_fmtstr_parts(expr: &Expr) -> Option<(InternedString, usize)> {
|
|
|
|
if_let_chain! {[
|
|
|
|
let ExprAddrOf(_, ref expr) = expr.node, // &["…", "…", …]
|
|
|
|
let ExprArray(ref exprs) = expr.node,
|
|
|
|
let Some(expr) = exprs.last(),
|
|
|
|
let ExprLit(ref lit) = expr.node,
|
|
|
|
let LitKind::Str(ref lit, _) = lit.node,
|
|
|
|
], {
|
|
|
|
return Some((lit.as_str(), exprs.len()));
|
|
|
|
}}
|
|
|
|
None
|
|
|
|
}
|