]> git.lizzy.rs Git - rust.git/commitdiff
Lint about consecutive ifs with same condition
authormcarton <cartonmartin+git@gmail.com>
Sat, 30 Jan 2016 17:03:53 +0000 (18:03 +0100)
committermcarton <cartonmartin+git@gmail.com>
Sun, 7 Feb 2016 12:26:34 +0000 (13:26 +0100)
README.md
src/copies.rs [new file with mode: 0644]
src/lib.rs
src/utils.rs
tests/compile-fail/copies.rs [new file with mode: 0755]

index 20aaa03c51c10294c582a96acd2a375b37380bf2..93921f01f6e05cc91cd81f9dc6737c58e40d0c82 100644 (file)
--- a/README.md
+++ b/README.md
@@ -47,6 +47,7 @@ name
 [for_loop_over_option](https://github.com/Manishearth/rust-clippy/wiki#for_loop_over_option)                   | warn    | for-looping over an `Option`, which is more clearly expressed as an `if let`
 [for_loop_over_result](https://github.com/Manishearth/rust-clippy/wiki#for_loop_over_result)                   | warn    | for-looping over a `Result`, which is more clearly expressed as an `if let`
 [identity_op](https://github.com/Manishearth/rust-clippy/wiki#identity_op)                                     | warn    | using identity operations, e.g. `x + 0` or `y / 1`
+[ifs_same_cond](https://github.com/Manishearth/rust-clippy/wiki#ifs_same_cond)                                 | warn    | consecutive `ifs` with the same condition
 [ineffective_bit_mask](https://github.com/Manishearth/rust-clippy/wiki#ineffective_bit_mask)                   | warn    | expressions where a bit mask will be rendered useless by a comparison, e.g. `(x | 1) > 2`
 [inline_always](https://github.com/Manishearth/rust-clippy/wiki#inline_always)                                 | warn    | `#[inline(always)]` is a bad idea in most cases
 [invalid_regex](https://github.com/Manishearth/rust-clippy/wiki#invalid_regex)                                 | deny    | finds invalid regular expressions in `Regex::new(_)` invocations
diff --git a/src/copies.rs b/src/copies.rs
new file mode 100644 (file)
index 0000000..33461a2
--- /dev/null
@@ -0,0 +1,68 @@
+use rustc::lint::*;
+use rustc_front::hir::*;
+use utils::{get_parent_expr, is_exp_equal, span_lint};
+
+/// **What it does:** This lint checks for consecutive `ifs` with the same condition. This lint is
+/// `Warn` by default.
+///
+/// **Why is this bad?** This is probably a copy & paste error.
+///
+/// **Known problems:** Hopefully none.
+///
+/// **Example:** `if a == b { .. } else if a == b { .. }`
+declare_lint! {
+    pub IFS_SAME_COND,
+    Warn,
+    "consecutive `ifs` with the same condition"
+}
+
+#[derive(Copy, Clone, Debug)]
+pub struct CopyAndPaste;
+
+impl LintPass for CopyAndPaste {
+    fn get_lints(&self) -> LintArray {
+        lint_array![
+            IFS_SAME_COND
+        ]
+    }
+}
+
+impl LateLintPass for CopyAndPaste {
+    fn check_expr(&mut self, cx: &LateContext, expr: &Expr) {
+        // skip ifs directly in else, it will be checked in the parent if
+        if let Some(&Expr{node: ExprIf(_, _, Some(ref else_expr)), ..}) = get_parent_expr(cx, expr) {
+            if else_expr.id == expr.id {
+                return;
+            }
+        }
+
+        let conds = condition_sequence(expr);
+
+        for (n, i) in conds.iter().enumerate() {
+            for j in conds.iter().skip(n+1) {
+                if is_exp_equal(cx, i, j) {
+                    span_lint(cx, IFS_SAME_COND, j.span, "this if as the same condition as a previous if");
+                }
+            }
+        }
+    }
+}
+
+/// Return the list of conditions expression in a sequence of `if/else`.
+/// Eg. would return `[a, b]` for the expression `if a {..} else if b {..}`.
+fn condition_sequence(mut expr: &Expr) -> Vec<&Expr> {
+    let mut result = vec![];
+
+    while let ExprIf(ref cond, _, ref else_expr) = expr.node {
+        result.push(&**cond);
+
+        if let Some(ref else_expr) = *else_expr {
+            expr = else_expr;
+        }
+        else {
+            break;
+        }
+    }
+
+    result
+}
index fa98beb8d7460f576678ea3e4c24fe9406e688ff..6b2a43db795c04d88bd04f2274318eec2cdb384b 100644 (file)
@@ -37,6 +37,7 @@ fn main() {
 
 #[macro_use]
 pub mod utils;
+pub mod copies;
 pub mod consts;
 pub mod types;
 pub mod misc;
@@ -157,6 +158,7 @@ pub fn plugin_registrar(reg: &mut Registry) {
     reg.register_late_lint_pass(box drop_ref::DropRefPass);
     reg.register_late_lint_pass(box types::AbsurdUnsignedComparisons);
     reg.register_late_lint_pass(box regex::RegexPass);
+    reg.register_late_lint_pass(box copies::CopyAndPaste);
 
     reg.register_lint_group("clippy_pedantic", vec![
         enum_glob_use::ENUM_GLOB_USE,
@@ -190,6 +192,7 @@ pub fn plugin_registrar(reg: &mut Registry) {
         block_in_if_condition::BLOCK_IN_IF_CONDITION_EXPR,
         block_in_if_condition::BLOCK_IN_IF_CONDITION_STMT,
         collapsible_if::COLLAPSIBLE_IF,
+        copies::IFS_SAME_COND,
         cyclomatic_complexity::CYCLOMATIC_COMPLEXITY,
         derive::DERIVE_HASH_NOT_EQ,
         derive::EXPL_IMPL_CLONE_ON_COPY,
index 74c1de6976f1c13cdce3867a541c89a232f681cc..72c6fe94ce290d6dba16dc202ec0c0b97b61dc69 100644 (file)
@@ -596,16 +596,38 @@ pub fn is_exp_equal(cx: &LateContext, left: &Expr, right: &Expr) -> bool {
         }
     }
     match (&left.node, &right.node) {
+        (&ExprAddrOf(ref lmut, ref le), &ExprAddrOf(ref rmut, ref re)) => {
+            lmut == rmut && is_exp_equal(cx, le, re)
+        }
+        (&ExprBinary(lop, ref ll, ref lr), &ExprBinary(rop, ref rl, ref rr)) => {
+            lop.node == rop.node && is_exp_equal(cx, ll, rl) && is_exp_equal(cx, lr, rr)
+        }
+        (&ExprCall(ref lfun, ref largs), &ExprCall(ref rfun, ref rargs)) => {
+            is_exp_equal(cx, lfun, rfun) && is_exps_equal(cx, largs, rargs)
+        }
+        (&ExprCast(ref lx, ref lt), &ExprCast(ref rx, ref rt)) => is_exp_equal(cx, lx, rx) && is_cast_ty_equal(lt, rt),
         (&ExprField(ref lfexp, ref lfident), &ExprField(ref rfexp, ref rfident)) => {
             lfident.node == rfident.node && is_exp_equal(cx, lfexp, rfexp)
         }
+        (&ExprIndex(ref la, ref li), &ExprIndex(ref ra, ref ri)) => {
+            is_exp_equal(cx, la, ra) && is_exp_equal(cx, li, ri)
+        }
         (&ExprLit(ref l), &ExprLit(ref r)) => l.node == r.node,
+        (&ExprMethodCall(ref lname, ref ltys, ref largs), &ExprMethodCall(ref rname, ref rtys, ref rargs)) => {
+            // TODO: tys
+            lname.node == rname.node && ltys.is_empty() && rtys.is_empty() && is_exps_equal(cx, largs, rargs)
+        }
         (&ExprPath(ref lqself, ref lsubpath), &ExprPath(ref rqself, ref rsubpath)) => {
             both(lqself, rqself, is_qself_equal) && is_path_equal(lsubpath, rsubpath)
         }
         (&ExprTup(ref ltup), &ExprTup(ref rtup)) => is_exps_equal(cx, ltup, rtup),
+        (&ExprTupField(ref le, li), &ExprTupField(ref re, ri)) => {
+            li.node == ri.node && is_exp_equal(cx, le, re)
+        }
+        (&ExprUnary(lop, ref le), &ExprUnary(rop, ref re)) => {
+            lop == rop && is_exp_equal(cx, le, re)
+        }
         (&ExprVec(ref l), &ExprVec(ref r)) => is_exps_equal(cx, l, r),
-        (&ExprCast(ref lx, ref lt), &ExprCast(ref rx, ref rt)) => is_exp_equal(cx, lx, rx) && is_cast_ty_equal(lt, rt),
         _ => false,
     }
 }
diff --git a/tests/compile-fail/copies.rs b/tests/compile-fail/copies.rs
new file mode 100755 (executable)
index 0000000..a29fd39
--- /dev/null
@@ -0,0 +1,31 @@
+#![feature(plugin)]
+#![plugin(clippy)]
+
+#![deny(clippy)]
+
+fn foo() -> bool { unimplemented!() }
+
+fn main() {
+    let a = 0;
+
+    if a == 1 {
+    }
+    else if a == 1 { //~ERROR this if as the same condition as a previous if
+    }
+
+    if 2*a == 1 {
+    }
+    else if 2*a == 2 {
+    }
+    else if 2*a == 1 { //~ERROR this if as the same condition as a previous if
+    }
+    else if a == 1 {
+    }
+
+    // Ok, maybe `foo` isn’t pure and this actually makes sense. But you should probably refactor
+    // this to make the intention clearer anyway.
+    if foo() {
+    }
+    else if foo() { //~ERROR this if as the same condition as a previous if
+    }
+}