]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/collapsible_if.rs
Auto merge of #4229 - euclio:lint-doc-generation-fix, r=flip1995
[rust.git] / clippy_lints / 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 //! ```rust,ignore
6 //! if x {
7 //!     if y {
8 //!         println!("Hello world");
9 //!     }
10 //! }
11 //! ```
12 //!
13 //! This lint is **warn** by default
14
15 use if_chain::if_chain;
16 use rustc::lint::{EarlyContext, EarlyLintPass, LintArray, LintPass};
17 use rustc::{declare_lint_pass, declare_tool_lint};
18 use syntax::ast;
19
20 use crate::utils::sugg::Sugg;
21 use crate::utils::{
22     in_macro_or_desugar, snippet_block, snippet_block_with_applicability, span_lint_and_sugg, span_lint_and_then,
23 };
24 use rustc_errors::Applicability;
25
26 declare_clippy_lint! {
27     /// **What it does:** Checks for nested `if` statements which can be collapsed
28     /// by `&&`-combining their conditions and for `else { if ... }` expressions
29     /// that
30     /// can be collapsed to `else if ...`.
31     ///
32     /// **Why is this bad?** Each `if`-statement adds one level of nesting, which
33     /// makes code look more complex than it really is.
34     ///
35     /// **Known problems:** None.
36     ///
37     /// **Example:**
38     /// ```rust,ignore
39     /// if x {
40     ///     if y {
41     ///         …
42     ///     }
43     /// }
44     ///
45     /// // or
46     ///
47     /// if x {
48     ///     …
49     /// } else {
50     ///     if y {
51     ///         …
52     ///     }
53     /// }
54     /// ```
55     ///
56     /// Should be written:
57     ///
58     /// ```rust.ignore
59     /// if x && y {
60     ///     …
61     /// }
62     ///
63     /// // or
64     ///
65     /// if x {
66     ///     …
67     /// } else if y {
68     ///     …
69     /// }
70     /// ```
71     pub COLLAPSIBLE_IF,
72     style,
73     "`if`s that can be collapsed (e.g., `if x { if y { ... } }` and `else { if x { ... } }`)"
74 }
75
76 declare_lint_pass!(CollapsibleIf => [COLLAPSIBLE_IF]);
77
78 impl EarlyLintPass for CollapsibleIf {
79     fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &ast::Expr) {
80         if !in_macro_or_desugar(expr.span) {
81             check_if(cx, expr)
82         }
83     }
84 }
85
86 fn check_if(cx: &EarlyContext<'_>, expr: &ast::Expr) {
87     if let ast::ExprKind::If(check, then, else_) = &expr.node {
88         if let Some(else_) = else_ {
89             check_collapsible_maybe_if_let(cx, else_);
90         } else if let ast::ExprKind::Let(..) = check.node {
91             // Prevent triggering on `if let a = b { if c { .. } }`.
92         } else {
93             check_collapsible_no_if_let(cx, expr, check, then);
94         }
95     }
96 }
97
98 fn block_starts_with_comment(cx: &EarlyContext<'_>, expr: &ast::Block) -> bool {
99     // We trim all opening braces and whitespaces and then check if the next string is a comment.
100     let trimmed_block_text = snippet_block(cx, expr.span, "..")
101         .trim_start_matches(|c: char| c.is_whitespace() || c == '{')
102         .to_owned();
103     trimmed_block_text.starts_with("//") || trimmed_block_text.starts_with("/*")
104 }
105
106 fn check_collapsible_maybe_if_let(cx: &EarlyContext<'_>, else_: &ast::Expr) {
107     if_chain! {
108         if let ast::ExprKind::Block(ref block, _) = else_.node;
109         if !block_starts_with_comment(cx, block);
110         if let Some(else_) = expr_block(block);
111         if !in_macro_or_desugar(else_.span);
112         if let ast::ExprKind::If(..) = else_.node;
113         then {
114             let mut applicability = Applicability::MachineApplicable;
115             span_lint_and_sugg(
116                 cx,
117                 COLLAPSIBLE_IF,
118                 block.span,
119                 "this `else { if .. }` block can be collapsed",
120                 "try",
121                 snippet_block_with_applicability(cx, else_.span, "..", &mut applicability).into_owned(),
122                 applicability,
123             );
124         }
125     }
126 }
127
128 fn check_collapsible_no_if_let(cx: &EarlyContext<'_>, expr: &ast::Expr, check: &ast::Expr, then: &ast::Block) {
129     if_chain! {
130         if !block_starts_with_comment(cx, then);
131         if let Some(inner) = expr_block(then);
132         if let ast::ExprKind::If(ref check_inner, ref content, None) = inner.node;
133         then {
134             if let ast::ExprKind::Let(..) = check_inner.node {
135                 // Prevent triggering on `if c { if let a = b { .. } }`.
136                 return;
137             }
138
139             if expr.span.ctxt() != inner.span.ctxt() {
140                 return;
141             }
142             span_lint_and_then(cx, COLLAPSIBLE_IF, expr.span, "this if statement can be collapsed", |db| {
143                 let lhs = Sugg::ast(cx, check, "..");
144                 let rhs = Sugg::ast(cx, check_inner, "..");
145                 db.span_suggestion(
146                     expr.span,
147                     "try",
148                     format!(
149                         "if {} {}",
150                         lhs.and(&rhs),
151                         snippet_block(cx, content.span, ".."),
152                     ),
153                     Applicability::MachineApplicable, // snippet
154                 );
155             });
156         }
157     }
158 }
159
160 /// If the block contains only one expression, return it.
161 fn expr_block(block: &ast::Block) -> Option<&ast::Expr> {
162     let mut it = block.stmts.iter();
163
164     if let (Some(stmt), None) = (it.next(), it.next()) {
165         match stmt.node {
166             ast::StmtKind::Expr(ref expr) | ast::StmtKind::Semi(ref expr) => Some(expr),
167             _ => None,
168         }
169     } else {
170         None
171     }
172 }