]> git.lizzy.rs Git - rust.git/blob - src/collapsible_if.rs
Merge pull request #158 from birkenfeld/iter_methods
[rust.git] / src / collapsible_if.rs
1 //! Checks for if expressions that contain only an if expression.
2 //!
3 //! For example, the lint would catch:
4 //!
5 //! ```
6 //! if x {
7 //!     if y {
8 //!         println!("Hello world");
9 //!     }
10 //! }
11 //! ```
12 //!
13 //! This lint is **warn** by default
14
15 use rustc::plugin::Registry;
16 use rustc::lint::*;
17 use rustc::middle::def::*;
18 use syntax::ast::*;
19 use syntax::ptr::P;
20 use syntax::codemap::{Span, Spanned, ExpnInfo};
21 use utils::{in_macro, span_help_and_lint, snippet};
22
23 declare_lint! {
24     pub COLLAPSIBLE_IF,
25     Warn,
26     "two nested `if`-expressions can be collapsed into one, e.g. `if x { if y { foo() } }` \
27      can be written as `if x && y { foo() }`"
28 }
29
30 #[derive(Copy,Clone)]
31 pub struct CollapsibleIf;
32
33 impl LintPass for CollapsibleIf {
34     fn get_lints(&self) -> LintArray {
35         lint_array!(COLLAPSIBLE_IF)
36     }
37
38     fn check_expr(&mut self, cx: &Context, expr: &Expr) {
39         cx.sess().codemap().with_expn_info(expr.span.expn_id,
40             |info| check_expr_expd(cx, expr, info))
41     }
42 }
43
44 fn check_expr_expd(cx: &Context, e: &Expr, info: Option<&ExpnInfo>) {
45     if in_macro(cx, info) { return; }
46
47     if let ExprIf(ref check, ref then, None) = e.node {
48         if let Some(&Expr{ node: ExprIf(ref check_inner, ref content, None), ..}) =
49             single_stmt_of_block(then) {
50                 span_help_and_lint(cx, COLLAPSIBLE_IF, e.span,
51                     "this if statement can be collapsed",
52                     &format!("try\nif {} && {} {}",
53                              check_to_string(cx, check), check_to_string(cx, check_inner),
54                              snippet(cx, content.span, "..")));
55             }
56     }
57 }
58
59 fn requires_brackets(e: &Expr) -> bool {
60     match e.node {
61         ExprBinary(Spanned {node: n, ..}, _, _) if n == BiEq => false,
62         _ => true
63     }
64 }
65
66 fn check_to_string(cx: &Context, e: &Expr) -> String {
67     if requires_brackets(e) {
68         format!("({})", snippet(cx, e.span, ".."))
69     } else {
70         format!("{}", snippet(cx, e.span, ".."))
71     }
72 }
73
74 fn single_stmt_of_block(block: &Block) -> Option<&Expr> {
75     if block.stmts.len() == 1 && block.expr.is_none() {
76         if let StmtExpr(ref expr, _) = block.stmts[0].node {
77             single_stmt_of_expr(expr)
78         } else { None }
79     } else {
80         if block.stmts.is_empty() {
81             if let Some(ref p) = block.expr { Some(&*p) } else { None }
82         } else { None }
83     }
84 }
85
86 fn single_stmt_of_expr(expr: &Expr) -> Option<&Expr> {
87     if let ExprBlock(ref block) = expr.node {
88         single_stmt_of_block(block)
89     } else { Some(expr) }
90 }