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