diff --git a/README.md b/README.md index 04b0bd0e3..2673d81f8 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ A collection of lints to catch common mistakes and improve your Rust code. [Jump to usage instructions](#usage) ##Lints -There are 100 lints included in this crate: +There are 101 lints included in this crate: name | default | meaning ---------------------------------------------------------------------------------------------------------------|---------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ @@ -73,6 +73,7 @@ name [out_of_bounds_indexing](https://github.com/Manishearth/rust-clippy/wiki#out_of_bounds_indexing) | deny | out of bound constant indexing [panic_params](https://github.com/Manishearth/rust-clippy/wiki#panic_params) | warn | missing parameters in `panic!` [precedence](https://github.com/Manishearth/rust-clippy/wiki#precedence) | warn | catches operations where precedence may be unclear. See the wiki for a list of cases caught +[print_stdout](https://github.com/Manishearth/rust-clippy/wiki#print_stdout) | allow | printing on stdout [ptr_arg](https://github.com/Manishearth/rust-clippy/wiki#ptr_arg) | warn | fn arguments of the type `&Vec<...>` or `&String`, suggesting to use `&[...]` or `&str` instead, respectively [range_step_by_zero](https://github.com/Manishearth/rust-clippy/wiki#range_step_by_zero) | warn | using Range::step_by(0), which produces an infinite iterator [range_zip_with_len](https://github.com/Manishearth/rust-clippy/wiki#range_zip_with_len) | warn | zipping iterator with a range when enumerate() would do diff --git a/src/lib.rs b/src/lib.rs index 6d706888f..8b115a304 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,6 +6,7 @@ // this only exists to allow the "dogfood" integration test to work #[allow(dead_code)] +#[allow(print_stdout)] fn main() { println!("What are you doing? Don't run clippy as an executable"); } @@ -77,6 +78,7 @@ pub mod misc_early; pub mod array_indexing; pub mod panic; pub mod derive; +pub mod print; mod reexport { pub use syntax::ast::{Name, NodeId}; @@ -141,6 +143,7 @@ pub fn plugin_registrar(reg: &mut Registry) { reg.register_late_lint_pass(box strings::StringLitAsBytes); reg.register_late_lint_pass(box derive::Derive); reg.register_late_lint_pass(box types::CharLitAsU8); + reg.register_late_lint_pass(box print::PrintLint); reg.register_lint_group("clippy_pedantic", vec![ @@ -149,6 +152,7 @@ pub fn plugin_registrar(reg: &mut Registry) { methods::WRONG_PUB_SELF_CONVENTION, mut_mut::MUT_MUT, mutex_atomic::MUTEX_INTEGER, + print::PRINT_STDOUT, shadow::SHADOW_REUSE, shadow::SHADOW_SAME, shadow::SHADOW_UNRELATED, diff --git a/src/print.rs b/src/print.rs new file mode 100644 index 000000000..a47fa69b2 --- /dev/null +++ b/src/print.rs @@ -0,0 +1,49 @@ +use rustc::lint::*; +use rustc_front::hir::*; +use utils::{IO_PRINT_PATH, is_expn_of, match_path, span_lint}; + +/// **What it does:** This lint warns whenever you print on *stdout*. This lint is `Allow` by +/// default, the purpose is to catch debugging remnants. +/// +/// **Why is this bad?** People often print on *stdout* while debugging an application and might +/// forget to remove those prints afterward. +/// +/// **Known problems:** Only catches `print!` and `println!` calls. +/// +/// **Example:** `println!("Hello world!");` +declare_lint! { + pub PRINT_STDOUT, + Allow, + "printing on stdout" +} + +#[derive(Copy, Clone, Debug)] +pub struct PrintLint; + +impl LintPass for PrintLint { + fn get_lints(&self) -> LintArray { + lint_array!(PRINT_STDOUT) + } +} + +impl LateLintPass for PrintLint { + fn check_expr(&mut self, cx: &LateContext, expr: &Expr) { + if let ExprCall(ref fun, _) = expr.node { + if let ExprPath(_, ref path) = fun.node { + if match_path(path, &IO_PRINT_PATH) { + if let Some(span) = is_expn_of(cx, expr.span, "print") { + let (span, name) = match is_expn_of(cx, span, "println") { + Some(span) => (span, "println"), + None => (span, "print"), + }; + + span_lint(cx, + PRINT_STDOUT, + span, + &format!("use of `{}!`", name)); + } + } + } + } + } +} diff --git a/src/utils.rs b/src/utils.rs index 139e94dbc..c59e35c5c 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -29,6 +29,7 @@ pub const DEFAULT_TRAIT_PATH: [&'static str; 3] = ["core", "default", "Default"] pub const HASHMAP_ENTRY_PATH: [&'static str; 5] = ["std", "collections", "hash", "map", "Entry"]; pub const HASHMAP_PATH: [&'static str; 5] = ["std", "collections", "hash", "map", "HashMap"]; pub const HASH_PATH: [&'static str; 2] = ["hash", "Hash"]; +pub const IO_PRINT_PATH: [&'static str; 3] = ["std", "io", "_print"]; pub const LL_PATH: [&'static str; 3] = ["collections", "linked_list", "LinkedList"]; pub const MUTEX_PATH: [&'static str; 4] = ["std", "sync", "mutex", "Mutex"]; pub const OPEN_OPTIONS_PATH: [&'static str; 3] = ["std", "fs", "OpenOptions"]; @@ -645,3 +646,29 @@ fn is_cast_ty_equal(left: &Ty, right: &Ty) -> bool { _ => false, } } + +/// Return the pre-expansion span is this comes from a expansion of the macro `name`. +pub fn is_expn_of(cx: &LateContext, mut span: Span, name: &str) -> Option { + loop { + let span_name_span = cx.tcx.sess.codemap().with_expn_info(span.expn_id, |expn| { + expn.map(|ei| { + (ei.callee.name(), ei.call_site) + }) + }); + + return match span_name_span { + Some((mac_name, new_span)) => { + if mac_name.as_str() == name { + Some(new_span) + } + else { + span = new_span; + continue; + } + } + None => { + None + } + }; + } +} diff --git a/tests/compile-fail/print.rs b/tests/compile-fail/print.rs new file mode 100755 index 000000000..8141cdd26 --- /dev/null +++ b/tests/compile-fail/print.rs @@ -0,0 +1,11 @@ +#![feature(plugin)] +#![plugin(clippy)] + +#[deny(print_stdout)] + +fn main() { + println!("Hello"); //~ERROR use of `println!` + print!("Hello"); //~ERROR use of `print!` + + vec![1, 2]; +}