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