diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b6b12c623af..dac4a54b11fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4353,6 +4353,8 @@ Released 2018-09-13 [`self_named_constructors`]: https://rust-lang.github.io/rust-clippy/master/index.html#self_named_constructors [`self_named_module_files`]: https://rust-lang.github.io/rust-clippy/master/index.html#self_named_module_files [`semicolon_if_nothing_returned`]: https://rust-lang.github.io/rust-clippy/master/index.html#semicolon_if_nothing_returned +[`semicolon_inside_block`]: https://rust-lang.github.io/rust-clippy/master/index.html#semicolon_inside_block +[`semicolon_outside_block`]: https://rust-lang.github.io/rust-clippy/master/index.html#semicolon_outside_block [`separated_literal_suffix`]: https://rust-lang.github.io/rust-clippy/master/index.html#separated_literal_suffix [`serde_api_misuse`]: https://rust-lang.github.io/rust-clippy/master/index.html#serde_api_misuse [`shadow_reuse`]: https://rust-lang.github.io/rust-clippy/master/index.html#shadow_reuse diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index eb3210946f11..76b4a2ccf5e1 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -525,6 +525,8 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::returns::NEEDLESS_RETURN_INFO, crate::same_name_method::SAME_NAME_METHOD_INFO, crate::self_named_constructors::SELF_NAMED_CONSTRUCTORS_INFO, + crate::semicolon_block::SEMICOLON_INSIDE_BLOCK_INFO, + crate::semicolon_block::SEMICOLON_OUTSIDE_BLOCK_INFO, crate::semicolon_if_nothing_returned::SEMICOLON_IF_NOTHING_RETURNED_INFO, crate::serde_api::SERDE_API_MISUSE_INFO, crate::shadow::SHADOW_REUSE_INFO, diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 17dbd983b6c0..cbca317a1463 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -257,6 +257,7 @@ mod return_self_not_must_use; mod returns; mod same_name_method; mod self_named_constructors; +mod semicolon_block; mod semicolon_if_nothing_returned; mod serde_api; mod shadow; @@ -884,6 +885,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(|_| Box::new(from_raw_with_void_ptr::FromRawWithVoidPtr)); store.register_late_pass(|_| Box::new(suspicious_xor_used_as_pow::ConfusingXorAndPow)); store.register_late_pass(move |_| Box::new(manual_is_ascii_check::ManualIsAsciiCheck::new(msrv()))); + store.register_late_pass(|_| Box::new(semicolon_block::SemicolonBlock)); // add lints here, do not remove this comment, it's used in `new_lint` } diff --git a/clippy_lints/src/semicolon_block.rs b/clippy_lints/src/semicolon_block.rs new file mode 100644 index 000000000000..911d38f65d6c --- /dev/null +++ b/clippy_lints/src/semicolon_block.rs @@ -0,0 +1,131 @@ +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::source::snippet_with_macro_callsite; +use clippy_utils::{get_parent_expr_for_hir, get_parent_node}; +use rustc_errors::Applicability; +use rustc_hir::{Block, Expr, ExprKind, Node, Stmt, StmtKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// ### What it does + /// + /// For () returning expressions, check that the semicolon is inside the block. + /// + /// ### 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 inside the block. + /// Take a look at `semicolon_outside_block` for the other alternative. + /// + /// ### Example + /// + /// ```rust + /// # fn f(_: u32) {} + /// # let x = 0; + /// unsafe { f(x) }; + /// ``` + /// Use instead: + /// ```rust + /// # fn f(_: u32) {} + /// # let x = 0; + /// unsafe { f(x); } + /// ``` + #[clippy::version = "1.66.0"] + pub SEMICOLON_INSIDE_BLOCK, + restriction, + "add a semicolon inside the block" +} +declare_clippy_lint! { + /// ### What it does + /// + /// For () returning expressions, check that the semicolon is outside the block. + /// + /// ### 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. + /// Take a look at `semicolon_inside_block` for the other alternative. + /// + /// ### Example + /// + /// ```rust + /// # fn f(_: u32) {} + /// # let x = 0; + /// unsafe { f(x); } + /// ``` + /// Use instead: + /// ```rust + /// # fn f(_: u32) {} + /// # let x = 0; + /// unsafe { f(x) }; + /// ``` + #[clippy::version = "1.66.0"] + pub SEMICOLON_OUTSIDE_BLOCK, + restriction, + "add a semicolon outside the block" +} +declare_lint_pass!(SemicolonBlock => [SEMICOLON_INSIDE_BLOCK, SEMICOLON_OUTSIDE_BLOCK]); + +impl LateLintPass<'_> for SemicolonBlock { + fn check_block(&mut self, cx: &LateContext<'_>, block: &Block<'_>) { + semicolon_inside_block(cx, block); + semicolon_outside_block(cx, block); + } +} + +fn semicolon_inside_block(cx: &LateContext<'_>, block: &Block<'_>) { + if !block.span.from_expansion() + && let Some(tail) = block.expr + && let Some(block_expr @ Expr { kind: ExprKind::Block(_, _), ..}) = get_parent_expr_for_hir(cx, block.hir_id) + && let Some(Node::Stmt(Stmt { kind: StmtKind::Semi(_), span, .. })) = get_parent_node(cx.tcx, block_expr.hir_id) + { + let expr_snip = snippet_with_macro_callsite(cx, tail.span, ".."); + + let mut suggestion: String = snippet_with_macro_callsite(cx, block.span, "..").to_string(); + + if let Some((expr_offset, _)) = suggestion.rmatch_indices(&*expr_snip).next() { + suggestion.insert(expr_offset + expr_snip.len(), ';'); + } else { + return; + } + + span_lint_and_sugg( + cx, + SEMICOLON_INSIDE_BLOCK, + *span, + "consider moving the `;` inside the block for consistent formatting", + "put the `;` here", + suggestion, + Applicability::MaybeIncorrect, + ); + } +} + +fn semicolon_outside_block(cx: &LateContext<'_>, block: &Block<'_>) { + if !block.span.from_expansion() + && block.expr.is_none() + && let [.., Stmt { kind: StmtKind::Semi(expr), .. }] = block.stmts + && let Some(block_expr @ Expr { kind: ExprKind::Block(_, _), ..}) = get_parent_expr_for_hir(cx,block.hir_id) + && let Some(Node::Stmt(Stmt { kind: StmtKind::Expr(_), .. })) = get_parent_node(cx.tcx, block_expr.hir_id) { + let expr_snip = snippet_with_macro_callsite(cx, expr.span, ".."); + + let mut suggestion: String = snippet_with_macro_callsite(cx, block.span, "..").to_string(); + + if let Some((expr_offset, _)) = suggestion.rmatch_indices(&*expr_snip).next() + && let Some(semi_offset) = suggestion[expr_offset + expr_snip.len()..].find(';') { + suggestion.remove(expr_offset + expr_snip.len() + semi_offset); + } else { + return; + } + + suggestion.push(';'); + + span_lint_and_sugg( + cx, + SEMICOLON_OUTSIDE_BLOCK, + block.span, + "consider moving the `;` outside the block for consistent formatting", + "put the `;` outside the block", + suggestion, + Applicability::MaybeIncorrect, + ); + } +} diff --git a/tests/ui/semicolon_inside_block.fixed b/tests/ui/semicolon_inside_block.fixed new file mode 100644 index 000000000000..4cd112dd5e12 --- /dev/null +++ b/tests/ui/semicolon_inside_block.fixed @@ -0,0 +1,83 @@ +// run-rustfix +#![allow( + unused, + clippy::unused_unit, + clippy::unnecessary_operation, + clippy::no_effect, + clippy::single_element_loop +)] +#![warn(clippy::semicolon_inside_block)] + +macro_rules! m { + (()) => { + () + }; + (0) => {{ + 0 + };}; + (1) => {{ + 1; + }}; + (2) => {{ + 2; + }}; +} + +fn unit_fn_block() { + () +} + +#[rustfmt::skip] +fn main() { + { unit_fn_block() } + unsafe { unit_fn_block() } + + { + unit_fn_block() + } + + { unit_fn_block(); } + unsafe { unit_fn_block(); } + + { unit_fn_block(); } + unsafe { unit_fn_block(); } + + { unit_fn_block(); }; + unsafe { unit_fn_block(); }; + + { + unit_fn_block(); + unit_fn_block(); + } + { + unit_fn_block(); + unit_fn_block(); + } + { + unit_fn_block(); + unit_fn_block(); + }; + + { m!(()); } + { m!(()); } + { m!(()); }; + m!(0); + m!(1); + m!(2); + + for _ in [()] { + unit_fn_block(); + } + for _ in [()] { + unit_fn_block() + } + + let _d = || { + unit_fn_block(); + }; + let _d = || { + unit_fn_block() + }; + + unit_fn_block() +} diff --git a/tests/ui/semicolon_inside_block.rs b/tests/ui/semicolon_inside_block.rs new file mode 100644 index 000000000000..7512125c051d --- /dev/null +++ b/tests/ui/semicolon_inside_block.rs @@ -0,0 +1,83 @@ +// run-rustfix +#![allow( + unused, + clippy::unused_unit, + clippy::unnecessary_operation, + clippy::no_effect, + clippy::single_element_loop +)] +#![warn(clippy::semicolon_inside_block)] + +macro_rules! m { + (()) => { + () + }; + (0) => {{ + 0 + };}; + (1) => {{ + 1; + }}; + (2) => {{ + 2; + }}; +} + +fn unit_fn_block() { + () +} + +#[rustfmt::skip] +fn main() { + { unit_fn_block() } + unsafe { unit_fn_block() } + + { + unit_fn_block() + } + + { unit_fn_block() }; + unsafe { unit_fn_block() }; + + { unit_fn_block(); } + unsafe { unit_fn_block(); } + + { unit_fn_block(); }; + unsafe { unit_fn_block(); }; + + { + unit_fn_block(); + unit_fn_block() + }; + { + unit_fn_block(); + unit_fn_block(); + } + { + unit_fn_block(); + unit_fn_block(); + }; + + { m!(()) }; + { m!(()); } + { m!(()); }; + m!(0); + m!(1); + m!(2); + + for _ in [()] { + unit_fn_block(); + } + for _ in [()] { + unit_fn_block() + } + + let _d = || { + unit_fn_block(); + }; + let _d = || { + unit_fn_block() + }; + + unit_fn_block() +} diff --git a/tests/ui/semicolon_inside_block.stderr b/tests/ui/semicolon_inside_block.stderr new file mode 100644 index 000000000000..febe74b49bda --- /dev/null +++ b/tests/ui/semicolon_inside_block.stderr @@ -0,0 +1,39 @@ +error: consider moving the `;` inside the block for consistent formatting + --> $DIR/semicolon_inside_block.rs:39:5 + | +LL | { unit_fn_block() }; + | ^^^^^^^^^^^^^^^^^^^^ help: put the `;` here: `{ unit_fn_block(); }` + | + = note: `-D clippy::semicolon-inside-block` implied by `-D warnings` + +error: consider moving the `;` inside the block for consistent formatting + --> $DIR/semicolon_inside_block.rs:40:5 + | +LL | unsafe { unit_fn_block() }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: put the `;` here: `unsafe { unit_fn_block(); }` + +error: consider moving the `;` inside the block for consistent formatting + --> $DIR/semicolon_inside_block.rs:48:5 + | +LL | / { +LL | | unit_fn_block(); +LL | | unit_fn_block() +LL | | }; + | |______^ + | +help: put the `;` here + | +LL ~ { +LL + unit_fn_block(); +LL + unit_fn_block(); +LL + } + | + +error: consider moving the `;` inside the block for consistent formatting + --> $DIR/semicolon_inside_block.rs:61:5 + | +LL | { m!(()) }; + | ^^^^^^^^^^^ help: put the `;` here: `{ m!(()); }` + +error: aborting due to 4 previous errors + diff --git a/tests/ui/semicolon_outside_block.fixed b/tests/ui/semicolon_outside_block.fixed new file mode 100644 index 000000000000..5bc18faaad8e --- /dev/null +++ b/tests/ui/semicolon_outside_block.fixed @@ -0,0 +1,83 @@ +// run-rustfix +#![allow( + unused, + clippy::unused_unit, + clippy::unnecessary_operation, + clippy::no_effect, + clippy::single_element_loop +)] +#![warn(clippy::semicolon_outside_block)] + +macro_rules! m { + (()) => { + () + }; + (0) => {{ + 0 + };}; + (1) => {{ + 1; + }}; + (2) => {{ + 2; + }}; +} + +fn unit_fn_block() { + () +} + +#[rustfmt::skip] +fn main() { + { unit_fn_block() } + unsafe { unit_fn_block() } + + { + unit_fn_block() + } + + { unit_fn_block() }; + unsafe { unit_fn_block() }; + + { unit_fn_block() }; + unsafe { unit_fn_block() }; + + { unit_fn_block(); }; + unsafe { unit_fn_block(); }; + + { + unit_fn_block(); + unit_fn_block() + }; + { + unit_fn_block(); + unit_fn_block() + }; + { + unit_fn_block(); + unit_fn_block(); + }; + + { m!(()) }; + { m!(()) }; + { m!(()); }; + m!(0); + m!(1); + m!(2); + + for _ in [()] { + unit_fn_block(); + } + for _ in [()] { + unit_fn_block() + } + + let _d = || { + unit_fn_block(); + }; + let _d = || { + unit_fn_block() + }; + + unit_fn_block() +} diff --git a/tests/ui/semicolon_outside_block.rs b/tests/ui/semicolon_outside_block.rs new file mode 100644 index 000000000000..0a4293238763 --- /dev/null +++ b/tests/ui/semicolon_outside_block.rs @@ -0,0 +1,83 @@ +// run-rustfix +#![allow( + unused, + clippy::unused_unit, + clippy::unnecessary_operation, + clippy::no_effect, + clippy::single_element_loop +)] +#![warn(clippy::semicolon_outside_block)] + +macro_rules! m { + (()) => { + () + }; + (0) => {{ + 0 + };}; + (1) => {{ + 1; + }}; + (2) => {{ + 2; + }}; +} + +fn unit_fn_block() { + () +} + +#[rustfmt::skip] +fn main() { + { unit_fn_block() } + unsafe { unit_fn_block() } + + { + unit_fn_block() + } + + { unit_fn_block() }; + unsafe { unit_fn_block() }; + + { unit_fn_block(); } + unsafe { unit_fn_block(); } + + { unit_fn_block(); }; + unsafe { unit_fn_block(); }; + + { + unit_fn_block(); + unit_fn_block() + }; + { + unit_fn_block(); + unit_fn_block(); + } + { + unit_fn_block(); + unit_fn_block(); + }; + + { m!(()) }; + { m!(()); } + { m!(()); }; + m!(0); + m!(1); + m!(2); + + for _ in [()] { + unit_fn_block(); + } + for _ in [()] { + unit_fn_block() + } + + let _d = || { + unit_fn_block(); + }; + let _d = || { + unit_fn_block() + }; + + unit_fn_block() +} diff --git a/tests/ui/semicolon_outside_block.stderr b/tests/ui/semicolon_outside_block.stderr new file mode 100644 index 000000000000..fddf51208130 --- /dev/null +++ b/tests/ui/semicolon_outside_block.stderr @@ -0,0 +1,39 @@ +error: consider moving the `;` outside the block for consistent formatting + --> $DIR/semicolon_outside_block.rs:42:5 + | +LL | { unit_fn_block(); } + | ^^^^^^^^^^^^^^^^^^^^ help: put the `;` outside the block: `{ unit_fn_block() };` + | + = note: `-D clippy::semicolon-outside-block` implied by `-D warnings` + +error: consider moving the `;` outside the block for consistent formatting + --> $DIR/semicolon_outside_block.rs:43:5 + | +LL | unsafe { unit_fn_block(); } + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: put the `;` outside the block: `unsafe { unit_fn_block() };` + +error: consider moving the `;` outside the block for consistent formatting + --> $DIR/semicolon_outside_block.rs:52:5 + | +LL | / { +LL | | unit_fn_block(); +LL | | unit_fn_block(); +LL | | } + | |_____^ + | +help: put the `;` outside the block + | +LL ~ { +LL + unit_fn_block(); +LL + unit_fn_block() +LL + }; + | + +error: consider moving the `;` outside the block for consistent formatting + --> $DIR/semicolon_outside_block.rs:62:5 + | +LL | { m!(()); } + | ^^^^^^^^^^^ help: put the `;` outside the block: `{ m!(()) };` + +error: aborting due to 4 previous errors +