diff --git a/src/collapsible_if.rs b/src/collapsible_if.rs new file mode 100644 index 00000000000..31ac1e62be6 --- /dev/null +++ b/src/collapsible_if.rs @@ -0,0 +1,80 @@ +//! Checks for if expressions that contain only an if expression. +//! +//! For example, the lint would catch: +//! +//! ``` +//! if x { +//! if y { +//! println!("Hello world"); +//! } +//! } +//! ``` +//! +//! This lint is **warn** by default + +use rustc::plugin::Registry; +use rustc::lint::*; +use rustc::middle::def::*; +use syntax::ast::*; +use syntax::ptr::P; +use syntax::codemap::{Span, Spanned}; +use syntax::print::pprust::expr_to_string; + +declare_lint! { + pub COLLAPSIBLE_IF, + Warn, + "Warn on if expressions that can be collapsed" +} + +#[derive(Copy,Clone)] +pub struct CollapsibleIf; + +impl LintPass for CollapsibleIf { + fn get_lints(&self) -> LintArray { + lint_array!(COLLAPSIBLE_IF) + } + + fn check_expr(&mut self, cx: &Context, e: &Expr) { + if let ExprIf(ref check, ref then_block, None) = e.node { + let expr = check_block(then_block); + let expr = match expr { + Some(e) => e, + None => return + }; + if let ExprIf(ref check_inner, _, None) = expr.node { + let (check, check_inner) = (check_to_string(check), check_to_string(check_inner)); + cx.span_lint(COLLAPSIBLE_IF, e.span, + &format!("This if statement can be collapsed. Try: if {} && {}", check, check_inner)); + } + } + } +} + +fn requires_brackets(e: &Expr) -> bool { + match e.node { + ExprBinary(Spanned {node: n, ..}, _, _) if n == BiEq => false, + _ => true + } +} + +fn check_to_string(e: &Expr) -> String { + if requires_brackets(e) { + format!("({})", expr_to_string(e)) + } else { + format!("{}", expr_to_string(e)) + } +} + +fn check_block(b: &Block) -> Option<&P> { + if b.stmts.len() == 1 && b.expr.is_none() { + let stmt = &b.stmts[0]; + return match stmt.node { + StmtExpr(ref e, _) => Some(e), + _ => None + }; + } + if let Some(ref e) = b.expr { + return Some(e); + } + None +} diff --git a/src/lib.rs b/src/lib.rs index cf5def800a2..00d28deeed9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -25,6 +25,7 @@ pub mod eta_reduction; pub mod identity_op; pub mod mut_mut; pub mod len_zero; +pub mod collapsible_if; #[plugin_registrar] pub fn plugin_registrar(reg: &mut Registry) { @@ -45,6 +46,7 @@ pub fn plugin_registrar(reg: &mut Registry) { reg.register_lint_pass(box mut_mut::MutMut as LintPassObject); reg.register_lint_pass(box len_zero::LenZero as LintPassObject); reg.register_lint_pass(box misc::CmpOwned as LintPassObject); + reg.register_lint_pass(box collapsible_if::CollapsibleIf as LintPassObject); reg.register_lint_group("clippy", vec![types::BOX_VEC, types::LINKEDLIST, misc::SINGLE_MATCH, misc::STR_TO_STRING, @@ -61,5 +63,6 @@ pub fn plugin_registrar(reg: &mut Registry) { mut_mut::MUT_MUT, len_zero::LEN_ZERO, len_zero::LEN_WITHOUT_IS_EMPTY, + collapsible_if::COLLAPSIBLE_IF, ]); } diff --git a/tests/compile-fail/collapsible_if.rs b/tests/compile-fail/collapsible_if.rs new file mode 100644 index 00000000000..3aa86c893c6 --- /dev/null +++ b/tests/compile-fail/collapsible_if.rs @@ -0,0 +1,37 @@ +#![feature(plugin)] +#![plugin(clippy)] + +#[deny(collapsible_if)] +fn main() { + let x = "hello"; + let y = "world"; + if x == "hello" { //~ERROR This if statement can be collapsed + if y == "world" { + println!("Hello world!"); + } + } + + if x == "hello" || x == "world" { //~ERROR This if statement can be collapsed + if y == "world" || y == "hello" { + println!("Hello world!"); + } + } + + // Works because any if with an else statement cannot be collapsed. + if x == "hello" { + if y == "world" { + println!("Hello world!"); + } + } else { + println!("Not Hello world"); + } + + if x == "hello" { + if y == "world" { + println!("Hello world!"); + } else { + println!("Hello something else"); + } + } + +}