Skip to content

Commit 0584b1f

Browse files
committed
Lint more cases in collapsible_else_if
1 parent 5290b1e commit 0584b1f

File tree

12 files changed

+225
-22
lines changed

12 files changed

+225
-22
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6529,6 +6529,7 @@ Released 2018-09-13
65296529
[`ignore-interior-mutability`]: https://doc.rust-lang.org/clippy/lint_configuration.html#ignore-interior-mutability
65306530
[`large-error-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#large-error-threshold
65316531
[`lint-commented-code`]: https://doc.rust-lang.org/clippy/lint_configuration.html#lint-commented-code
6532+
[`lint-commented-else-if`]: https://doc.rust-lang.org/clippy/lint_configuration.html#lint-commented-else-if
65326533
[`literal-representation-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#literal-representation-threshold
65336534
[`matches-for-let-else`]: https://doc.rust-lang.org/clippy/lint_configuration.html#matches-for-let-else
65346535
[`max-fn-params-bools`]: https://doc.rust-lang.org/clippy/lint_configuration.html#max-fn-params-bools

book/src/lint_configuration.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -661,6 +661,17 @@ that would be collapsed.
661661
* [`collapsible_if`](https://rust-lang.github.io/rust-clippy/master/index.html#collapsible_if)
662662

663663

664+
## `lint-commented-else-if`
665+
Whether collapsible `else if` chains are linted if they contain comments inside the parts
666+
that would be collapsed.
667+
668+
**Default Value:** `false`
669+
670+
---
671+
**Affected lints:**
672+
* [`collapsible_else_if`](https://rust-lang.github.io/rust-clippy/master/index.html#collapsible_else_if)
673+
674+
664675
## `literal-representation-threshold`
665676
The lower bound for linting decimal literals
666677

clippy_config/src/conf.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -645,6 +645,10 @@ define_Conf! {
645645
/// that would be collapsed.
646646
#[lints(collapsible_if)]
647647
lint_commented_code: bool = false,
648+
/// Whether collapsible `else if` chains are linted if they contain comments inside the parts
649+
/// that would be collapsed.
650+
#[lints(collapsible_else_if)]
651+
lint_commented_else_if: bool = false,
648652
/// Whether to suggest reordering constructor fields when initializers are present.
649653
/// DEPRECATED CONFIGURATION: lint-inconsistent-struct-field-initializers
650654
///

clippy_lints/src/collapsible_if.rs

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
use clippy_config::Conf;
22
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
33
use clippy_utils::msrvs::{self, Msrv};
4-
use clippy_utils::source::{IntoSpan as _, SpanRangeExt, snippet, snippet_block, snippet_block_with_applicability};
5-
use clippy_utils::span_contains_non_comment;
4+
use clippy_utils::source::{IntoSpan as _, SpanRangeExt, snippet, snippet_block_with_applicability};
5+
use clippy_utils::span_contains_non_whitespace;
66
use rustc_ast::BinOpKind;
77
use rustc_errors::Applicability;
88
use rustc_hir::{Block, Expr, ExprKind, Stmt, StmtKind};
@@ -81,24 +81,24 @@ declare_clippy_lint! {
8181
pub struct CollapsibleIf {
8282
msrv: Msrv,
8383
lint_commented_code: bool,
84+
lint_commented_else_if: bool,
8485
}
8586

8687
impl CollapsibleIf {
8788
pub fn new(conf: &'static Conf) -> Self {
8889
Self {
8990
msrv: conf.msrv,
9091
lint_commented_code: conf.lint_commented_code,
92+
lint_commented_else_if: conf.lint_commented_else_if,
9193
}
9294
}
9395

94-
fn check_collapsible_else_if(cx: &LateContext<'_>, then_span: Span, else_block: &Block<'_>) {
95-
if !block_starts_with_comment(cx, else_block)
96-
&& let Some(else_) = expr_block(else_block)
96+
fn check_collapsible_else_if(&self, cx: &LateContext<'_>, then_span: Span, else_block: &Block<'_>) {
97+
if let Some(else_) = expr_block(else_block)
9798
&& cx.tcx.hir_attrs(else_.hir_id).is_empty()
9899
&& !else_.span.from_expansion()
99100
&& let ExprKind::If(..) = else_.kind
100-
&& let up_to_if = else_block.span.until(else_.span)
101-
&& !span_contains_non_comment(cx, up_to_if.with_lo(BytePos(up_to_if.lo().0 + 1)))
101+
&& !block_starts_with_significant_tokens(cx, else_block, else_, self.lint_commented_else_if)
102102
{
103103
// Prevent "elseif"
104104
// Check that the "else" is followed by whitespace
@@ -133,7 +133,7 @@ impl CollapsibleIf {
133133
&& self.eligible_condition(cx, check_inner)
134134
&& let ctxt = expr.span.ctxt()
135135
&& inner.span.ctxt() == ctxt
136-
&& (self.lint_commented_code || !block_starts_with_comment(cx, then))
136+
&& !block_starts_with_significant_tokens(cx, then, inner, self.lint_commented_code)
137137
{
138138
span_lint_and_then(
139139
cx,
@@ -182,7 +182,7 @@ impl LateLintPass<'_> for CollapsibleIf {
182182
if let Some(else_) = else_
183183
&& let ExprKind::Block(else_, None) = else_.kind
184184
{
185-
Self::check_collapsible_else_if(cx, then.span, else_);
185+
self.check_collapsible_else_if(cx, then.span, else_);
186186
} else if else_.is_none()
187187
&& self.eligible_condition(cx, cond)
188188
&& let ExprKind::Block(then, None) = then.kind
@@ -193,12 +193,16 @@ impl LateLintPass<'_> for CollapsibleIf {
193193
}
194194
}
195195

196-
fn block_starts_with_comment(cx: &LateContext<'_>, block: &Block<'_>) -> bool {
197-
// We trim all opening braces and whitespaces and then check if the next string is a comment.
198-
let trimmed_block_text = snippet_block(cx, block.span, "..", None)
199-
.trim_start_matches(|c: char| c.is_whitespace() || c == '{')
200-
.to_owned();
201-
trimmed_block_text.starts_with("//") || trimmed_block_text.starts_with("/*")
196+
// Check that nothing significant can be found but whitespaces between the initial `{` of `block`
197+
// and the beginning of `stop_at`.
198+
fn block_starts_with_significant_tokens(
199+
cx: &LateContext<'_>,
200+
block: &Block<'_>,
201+
stop_at: &Expr<'_>,
202+
lint_commented_code: bool,
203+
) -> bool {
204+
let span = block.span.split_at(1).1.until(stop_at.span);
205+
span_contains_non_whitespace(cx, span, lint_commented_code)
202206
}
203207

204208
/// If `block` is a block with either one expression or a statement containing an expression,

clippy_utils/src/lib.rs

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2788,16 +2788,19 @@ pub fn span_contains_comment(sm: &SourceMap, span: Span) -> bool {
27882788
});
27892789
}
27902790

2791-
/// Checks whether a given span has any non-comment token. This checks for all types of token other
2792-
/// than line comment "//", block comment "/**", doc "///" "//!" and whitespace
2791+
/// Checks whether a given span has any significant token. A significant token is a non-whitespace
2792+
/// token, including comments unless `skip_comments` is set.
27932793
/// This is useful to determine if there are any actual code tokens in the span that are omitted in
27942794
/// the late pass, such as platform-specific code.
2795-
pub fn span_contains_non_comment(cx: &impl source::HasSession, span: Span) -> bool {
2796-
matches!(span.get_source_text(cx), Some(snippet) if tokenize_with_text(&snippet).any(|(token, _, _)| {
2797-
!matches!(token, TokenKind::LineComment { .. } | TokenKind::BlockComment { .. } | TokenKind::Whitespace)
2798-
}))
2795+
pub fn span_contains_non_whitespace(cx: &impl source::HasSession, span: Span, skip_comments: bool) -> bool {
2796+
matches!(span.get_source_text(cx), Some(snippet) if tokenize_with_text(&snippet).any(|(token, _, _)|
2797+
match token {
2798+
TokenKind::Whitespace => false,
2799+
TokenKind::BlockComment { .. } | TokenKind::LineComment { .. } => !skip_comments,
2800+
_ => true,
2801+
}
2802+
))
27992803
}
2800-
28012804
/// Returns all the comments a given span contains
28022805
///
28032806
/// Comments are returned wrapped with their relevant delimiters
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
lint-commented-code = true
2+
lint-commented-else-if = true
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
#![allow(clippy::eq_op, clippy::nonminimal_bool)]
2+
3+
#[rustfmt::skip]
4+
#[warn(clippy::collapsible_if)]
5+
fn main() {
6+
let (x, y) = ("hello", "world");
7+
8+
if x == "hello" {
9+
todo!()
10+
} else if y == "world" {
11+
println!("Hello world!");
12+
}
13+
//~^^^^^^ collapsible_else_if
14+
15+
if x == "hello" {
16+
todo!()
17+
} else if y == "world" {
18+
println!("Hello world!");
19+
}
20+
//~^^^^^ collapsible_else_if
21+
22+
if x == "hello" {
23+
todo!()
24+
} else if y == "world" {
25+
println!("Hello world!");
26+
}
27+
//~^^^^^^ collapsible_else_if
28+
29+
if x == "hello" {
30+
todo!()
31+
} else if y == "world" {
32+
println!("Hello world!");
33+
}
34+
//~^^^^^ collapsible_else_if
35+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
#![allow(clippy::eq_op, clippy::nonminimal_bool)]
2+
3+
#[rustfmt::skip]
4+
#[warn(clippy::collapsible_if)]
5+
fn main() {
6+
let (x, y) = ("hello", "world");
7+
8+
if x == "hello" {
9+
todo!()
10+
} else {
11+
// Comment must be kept
12+
if y == "world" {
13+
println!("Hello world!");
14+
}
15+
}
16+
//~^^^^^^ collapsible_else_if
17+
18+
if x == "hello" {
19+
todo!()
20+
} else { // Inner comment
21+
if y == "world" {
22+
println!("Hello world!");
23+
}
24+
}
25+
//~^^^^^ collapsible_else_if
26+
27+
if x == "hello" {
28+
todo!()
29+
} else {
30+
/* Inner comment */
31+
if y == "world" {
32+
println!("Hello world!");
33+
}
34+
}
35+
//~^^^^^^ collapsible_else_if
36+
37+
if x == "hello" {
38+
todo!()
39+
} else { /* Inner comment */
40+
if y == "world" {
41+
println!("Hello world!");
42+
}
43+
}
44+
//~^^^^^ collapsible_else_if
45+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
error: this `else { if .. }` block can be collapsed
2+
--> tests/ui-toml/collapsible_if/collapsible_else_if.rs:10:12
3+
|
4+
LL | } else {
5+
| ____________^
6+
LL | | // Comment must be kept
7+
LL | | if y == "world" {
8+
LL | | println!("Hello world!");
9+
LL | | }
10+
LL | | }
11+
| |_____^
12+
|
13+
= note: `-D clippy::collapsible-else-if` implied by `-D warnings`
14+
= help: to override `-D warnings` add `#[allow(clippy::collapsible_else_if)]`
15+
help: collapse nested if block
16+
|
17+
LL ~ } else if y == "world" {
18+
LL + println!("Hello world!");
19+
LL + }
20+
|
21+
22+
error: this `else { if .. }` block can be collapsed
23+
--> tests/ui-toml/collapsible_if/collapsible_else_if.rs:20:12
24+
|
25+
LL | } else { // Inner comment
26+
| ____________^
27+
LL | | if y == "world" {
28+
LL | | println!("Hello world!");
29+
LL | | }
30+
LL | | }
31+
| |_____^
32+
|
33+
help: collapse nested if block
34+
|
35+
LL ~ } else if y == "world" {
36+
LL + println!("Hello world!");
37+
LL + }
38+
|
39+
40+
error: this `else { if .. }` block can be collapsed
41+
--> tests/ui-toml/collapsible_if/collapsible_else_if.rs:29:12
42+
|
43+
LL | } else {
44+
| ____________^
45+
LL | | /* Inner comment */
46+
LL | | if y == "world" {
47+
LL | | println!("Hello world!");
48+
LL | | }
49+
LL | | }
50+
| |_____^
51+
|
52+
help: collapse nested if block
53+
|
54+
LL ~ } else if y == "world" {
55+
LL + println!("Hello world!");
56+
LL + }
57+
|
58+
59+
error: this `else { if .. }` block can be collapsed
60+
--> tests/ui-toml/collapsible_if/collapsible_else_if.rs:39:12
61+
|
62+
LL | } else { /* Inner comment */
63+
| ____________^
64+
LL | | if y == "world" {
65+
LL | | println!("Hello world!");
66+
LL | | }
67+
LL | | }
68+
| |_____^
69+
|
70+
help: collapse nested if block
71+
|
72+
LL ~ } else if y == "world" {
73+
LL + println!("Hello world!");
74+
LL + }
75+
|
76+
77+
error: aborting due to 4 previous errors
78+

tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ error: error reading Clippy's configuration file: unknown field `foobar`, expect
5050
ignore-interior-mutability
5151
large-error-threshold
5252
lint-commented-code
53+
lint-commented-else-if
5354
literal-representation-threshold
5455
matches-for-let-else
5556
max-fn-params-bools
@@ -144,6 +145,7 @@ error: error reading Clippy's configuration file: unknown field `barfoo`, expect
144145
ignore-interior-mutability
145146
large-error-threshold
146147
lint-commented-code
148+
lint-commented-else-if
147149
literal-representation-threshold
148150
matches-for-let-else
149151
max-fn-params-bools
@@ -238,6 +240,7 @@ error: error reading Clippy's configuration file: unknown field `allow_mixed_uni
238240
ignore-interior-mutability
239241
large-error-threshold
240242
lint-commented-code
243+
lint-commented-else-if
241244
literal-representation-threshold
242245
matches-for-let-else
243246
max-fn-params-bools

tests/ui/collapsible_if.fixed

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,3 +154,12 @@ fn issue14722() {
154154
None
155155
};
156156
}
157+
158+
fn issue14799() {
159+
if true {
160+
#[cfg(target_os = "freebsd")]
161+
todo!();
162+
163+
if true {}
164+
};
165+
}

tests/ui/collapsible_if.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,3 +164,12 @@ fn issue14722() {
164164
None
165165
};
166166
}
167+
168+
fn issue14799() {
169+
if true {
170+
#[cfg(target_os = "freebsd")]
171+
todo!();
172+
173+
if true {}
174+
};
175+
}

0 commit comments

Comments
 (0)