Skip to content

Commit 8a47d20

Browse files
committed
Added semicolon_outside_block lint
1 parent f1ce81f commit 8a47d20

File tree

5 files changed

+252
-0
lines changed

5 files changed

+252
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2905,6 +2905,7 @@ Released 2018-09-13
29052905
[`self_named_constructors`]: https://rust-lang.github.io/rust-clippy/master/index.html#self_named_constructors
29062906
[`semicolon_if_nothing_returned`]: https://rust-lang.github.io/rust-clippy/master/index.html#semicolon_if_nothing_returned
29072907
[`semicolon_inside_block`]: https://rust-lang.github.io/rust-clippy/master/index.html#semicolon_inside_block
2908+
[`semicolon_outside_block`]: https://rust-lang.github.io/rust-clippy/master/index.html#semicolon_outside_block
29082909
[`serde_api_misuse`]: https://rust-lang.github.io/rust-clippy/master/index.html#serde_api_misuse
29092910
[`shadow_reuse`]: https://rust-lang.github.io/rust-clippy/master/index.html#shadow_reuse
29102911
[`shadow_same`]: https://rust-lang.github.io/rust-clippy/master/index.html#shadow_same

clippy_lints/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,7 @@ mod self_assignment;
332332
mod self_named_constructors;
333333
mod semicolon_if_nothing_returned;
334334
mod semicolon_inside_block;
335+
mod semicolon_outside_block;
335336
mod serde_api;
336337
mod shadow;
337338
mod single_component_path_imports;
@@ -905,6 +906,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
905906
self_named_constructors::SELF_NAMED_CONSTRUCTORS,
906907
semicolon_if_nothing_returned::SEMICOLON_IF_NOTHING_RETURNED,
907908
semicolon_inside_block::SEMICOLON_INSIDE_BLOCK,
909+
semicolon_outside_block::SEMICOLON_OUTSIDE_BLOCK,
908910
serde_api::SERDE_API_MISUSE,
909911
shadow::SHADOW_REUSE,
910912
shadow::SHADOW_SAME,
@@ -1136,6 +1138,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
11361138
LintId::of(ref_option_ref::REF_OPTION_REF),
11371139
LintId::of(semicolon_if_nothing_returned::SEMICOLON_IF_NOTHING_RETURNED),
11381140
LintId::of(semicolon_inside_block::SEMICOLON_INSIDE_BLOCK),
1141+
LintId::of(semicolon_outside_block::SEMICOLON_OUTSIDE_BLOCK),
11391142
LintId::of(shadow::SHADOW_UNRELATED),
11401143
LintId::of(strings::STRING_ADD_ASSIGN),
11411144
LintId::of(trait_bounds::TRAIT_DUPLICATION_IN_BOUNDS),
@@ -2092,6 +2095,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
20922095
store.register_late_pass(|| box float_equality_without_abs::FloatEqualityWithoutAbs);
20932096
store.register_late_pass(|| box semicolon_if_nothing_returned::SemicolonIfNothingReturned);
20942097
store.register_late_pass(|| box semicolon_inside_block::SemicolonInsideBlock);
2098+
store.register_late_pass(|| box semicolon_outside_block::SemicolonOutsideBlock);
20952099
store.register_late_pass(|| box async_yields_async::AsyncYieldsAsync);
20962100
let disallowed_methods = conf.disallowed_methods.iter().cloned().collect::<FxHashSet<_>>();
20972101
store.register_late_pass(move || box disallowed_method::DisallowedMethod::new(&disallowed_methods));
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
use crate::rustc_lint::LintContext;
2+
use clippy_utils::diagnostics::span_lint_and_sugg;
3+
use clippy_utils::in_macro;
4+
use clippy_utils::source::snippet_with_macro_callsite;
5+
use if_chain::if_chain;
6+
use rustc_errors::Applicability;
7+
use rustc_hir::ExprKind;
8+
use rustc_hir::{Block, StmtKind, ItemKind};
9+
use rustc_lint::{LateContext, LateLintPass};
10+
use rustc_session::{declare_lint_pass, declare_tool_lint};
11+
use rustc_span::BytePos;
12+
use rustc_span::Span;
13+
use std::ops::Add;
14+
15+
declare_clippy_lint! {
16+
/// **What it does:** For () returning expressions, check that the semicolon is outside the block.
17+
///
18+
/// **Why is this bad?** For consistency it's best to have the semicolon inside/outside the block. Either way is fine and this lint suggests outside the block.
19+
/// Take a look at `semicolon_inside_block` for the other alternative.
20+
///
21+
/// **Known problems:** None.
22+
///
23+
/// **Example:**
24+
///
25+
/// ```rust
26+
/// unsafe { f(x); }
27+
/// ```
28+
/// Use instead:
29+
/// ```rust
30+
/// unsafe { f(x) };
31+
/// ```
32+
pub SEMICOLON_OUTSIDE_BLOCK,
33+
pedantic,
34+
"add a semicolon outside the block"
35+
}
36+
37+
declare_lint_pass!(SemicolonOutsideBlock => [SEMICOLON_OUTSIDE_BLOCK]);
38+
39+
impl LateLintPass<'_> for SemicolonOutsideBlock {
40+
fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx Block<'tcx>) {
41+
if_chain! {
42+
if !in_macro(block.span);
43+
if block.expr.is_none();
44+
if let Some(last) = block.stmts.last();
45+
if let StmtKind::Semi(expr) = last.kind;
46+
let t_expr = cx.typeck_results().expr_ty(expr);
47+
if t_expr.is_unit();
48+
49+
// make sure that the block does not belong to a function
50+
let parent_item_id = cx.tcx.hir().get_parent_item(block.hir_id);
51+
let parent_item = cx.tcx.hir().expect_item(parent_item_id);
52+
if let ItemKind::Fn(_, _, body_id) = parent_item.kind;
53+
let item_body = cx.tcx.hir().body(body_id);
54+
if let ExprKind::Block(fn_block, _) = item_body.value.kind;
55+
if fn_block.hir_id != block.hir_id;
56+
then {
57+
// filter out other blocks and the desugared for loop
58+
if let ExprKind::Block(..) | ExprKind::DropTemps(..) = expr.kind { return }
59+
60+
// make sure we're also having the semicolon at the end of the expression...
61+
let expr_w_sem = expand_span_to_semicolon(cx, expr.span);
62+
let expr_snip = snippet_with_macro_callsite(cx, expr_w_sem, "..");
63+
let mut expr_sugg = expr_snip.to_string();
64+
expr_sugg.pop();
65+
66+
// and the block
67+
let block_w_sem = expand_span_to_semicolon(cx, block.span);
68+
let mut block_snip: String = snippet_with_macro_callsite(cx, block_w_sem, "..").to_string();
69+
if block_snip.ends_with('\n') {
70+
block_snip.pop();
71+
}
72+
73+
// retrieve the suggestion
74+
let suggestion = if block_snip.ends_with(';') {
75+
block_snip.replace(expr_snip.as_ref(), &format!("{}", expr_sugg.as_str()))
76+
} else {
77+
format!("{};", block_snip.replace(expr_snip.as_ref(), &format!("{}", expr_sugg.as_str())))
78+
};
79+
80+
span_lint_and_sugg(
81+
cx,
82+
SEMICOLON_OUTSIDE_BLOCK,
83+
if block_snip.ends_with(';') {
84+
block_w_sem
85+
} else {
86+
block.span
87+
},
88+
"consider moving the `;` outside the block for consistent formatting",
89+
"put the `;` outside the block",
90+
suggestion,
91+
Applicability::MaybeIncorrect,
92+
);
93+
}
94+
}
95+
}
96+
}
97+
98+
/// Takes a span and extzends it until after a semicolon in the last line of the span.
99+
fn expand_span_to_semicolon<'tcx>(cx: &LateContext<'tcx>, expr_span: Span) -> Span {
100+
let expr_span_with_sem = cx.sess().source_map().span_extend_to_next_char(expr_span, ';', false);
101+
expr_span_with_sem.with_hi(expr_span_with_sem.hi().add(BytePos(1)))
102+
}

tests/ui/semicolon_outside_block.rs

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
#![warn(clippy::semicolon_outside_block)]
2+
3+
unsafe fn f(arg: u32) {}
4+
5+
#[rustfmt::skip]
6+
fn main() {
7+
let x = 32;
8+
9+
unsafe { f(x); }
10+
}
11+
12+
fn foo() {
13+
let x = 32;
14+
15+
unsafe {
16+
f(x);
17+
}
18+
}
19+
20+
fn bar() {
21+
let x = 32;
22+
23+
unsafe {
24+
let _this = 1;
25+
let _is = 2;
26+
let _a = 3;
27+
let _long = 4;
28+
let _list = 5;
29+
let _of = 6;
30+
let _variables = 7;
31+
f(x);
32+
};
33+
}
34+
35+
fn get_unit() {}
36+
37+
fn moin() {
38+
{
39+
let _u = get_unit();
40+
println!("Hello");
41+
}
42+
}
43+
44+
#[rustfmt::skip]
45+
fn closure_error() {
46+
let _d = || {
47+
get_unit();
48+
};
49+
}
50+
51+
fn my_own_block() {
52+
let x: i32;
53+
{
54+
let y = 42;
55+
x = y + 1;
56+
get_unit();
57+
}
58+
assert_eq!(43, 43)
59+
}
60+
61+
fn just_get_unit() {
62+
get_unit();
63+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
error: consider moving the `;` outside the block for consistent formatting
2+
--> $DIR/semicolon_outside_block.rs:9:5
3+
|
4+
LL | unsafe { f(x); }
5+
| ^^^^^^^^^^^^^^^^ help: put the `;` outside the block: `unsafe { f(x) };`
6+
|
7+
= note: `-D clippy::semicolon-outside-block` implied by `-D warnings`
8+
9+
error: consider moving the `;` outside the block for consistent formatting
10+
--> $DIR/semicolon_outside_block.rs:15:5
11+
|
12+
LL | / unsafe {
13+
LL | | f(x);
14+
LL | | }
15+
| |_____^
16+
|
17+
help: put the `;` outside the block
18+
|
19+
LL | unsafe {
20+
LL | f(x)
21+
LL | };
22+
|
23+
24+
error: consider moving the `;` outside the block for consistent formatting
25+
--> $DIR/semicolon_outside_block.rs:23:5
26+
|
27+
LL | / unsafe {
28+
LL | | let _this = 1;
29+
LL | | let _is = 2;
30+
LL | | let _a = 3;
31+
... |
32+
LL | | f(x);
33+
LL | | };
34+
| |______^
35+
|
36+
help: put the `;` outside the block
37+
|
38+
LL | unsafe {
39+
LL | let _this = 1;
40+
LL | let _is = 2;
41+
LL | let _a = 3;
42+
LL | let _long = 4;
43+
LL | let _list = 5;
44+
...
45+
46+
error: consider moving the `;` outside the block for consistent formatting
47+
--> $DIR/semicolon_outside_block.rs:46:17
48+
|
49+
LL | let _d = || {
50+
| _________________^
51+
LL | | get_unit();
52+
LL | | };
53+
| |______^
54+
|
55+
help: put the `;` outside the block
56+
|
57+
LL | let _d = || {
58+
LL | get_unit()
59+
LL | };
60+
|
61+
62+
error: consider moving the `;` outside the block for consistent formatting
63+
--> $DIR/semicolon_outside_block.rs:53:5
64+
|
65+
LL | / {
66+
LL | | let y = 42;
67+
LL | | x = y + 1;
68+
LL | | get_unit();
69+
LL | | }
70+
| |_____^
71+
|
72+
help: put the `;` outside the block
73+
|
74+
LL | {
75+
LL | let y = 42;
76+
LL | x = y + 1;
77+
LL | get_unit()
78+
LL | };
79+
|
80+
81+
error: aborting due to 5 previous errors
82+

0 commit comments

Comments
 (0)