about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md1
-rw-r--r--clippy_lints/src/declared_lints.rs1
-rw-r--r--clippy_lints/src/lib.rs2
-rw-r--r--clippy_lints/src/unnecessary_semicolon.rs63
-rw-r--r--tests/ui/unnecessary_semicolon.fixed32
-rw-r--r--tests/ui/unnecessary_semicolon.rs32
-rw-r--r--tests/ui/unnecessary_semicolon.stderr17
7 files changed, 148 insertions, 0 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ceb08eeac3f..99bac364c47 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6173,6 +6173,7 @@ Released 2018-09-13
 [`unnecessary_safety_comment`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_safety_comment
 [`unnecessary_safety_doc`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_safety_doc
 [`unnecessary_self_imports`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_self_imports
+[`unnecessary_semicolon`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_semicolon
 [`unnecessary_sort_by`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_sort_by
 [`unnecessary_struct_initialization`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_struct_initialization
 [`unnecessary_to_owned`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_to_owned
diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs
index 5e6cfd94283..251d3bce106 100644
--- a/clippy_lints/src/declared_lints.rs
+++ b/clippy_lints/src/declared_lints.rs
@@ -756,6 +756,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
     crate::unnecessary_map_on_constructor::UNNECESSARY_MAP_ON_CONSTRUCTOR_INFO,
     crate::unnecessary_owned_empty_strings::UNNECESSARY_OWNED_EMPTY_STRINGS_INFO,
     crate::unnecessary_self_imports::UNNECESSARY_SELF_IMPORTS_INFO,
+    crate::unnecessary_semicolon::UNNECESSARY_SEMICOLON_INFO,
     crate::unnecessary_struct_initialization::UNNECESSARY_STRUCT_INITIALIZATION_INFO,
     crate::unnecessary_wraps::UNNECESSARY_WRAPS_INFO,
     crate::unneeded_struct_pattern::UNNEEDED_STRUCT_PATTERN_INFO,
diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs
index a0eae8f6d1c..7888119567b 100644
--- a/clippy_lints/src/lib.rs
+++ b/clippy_lints/src/lib.rs
@@ -372,6 +372,7 @@ mod unnecessary_literal_bound;
 mod unnecessary_map_on_constructor;
 mod unnecessary_owned_empty_strings;
 mod unnecessary_self_imports;
+mod unnecessary_semicolon;
 mod unnecessary_struct_initialization;
 mod unnecessary_wraps;
 mod unneeded_struct_pattern;
@@ -972,5 +973,6 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
     store.register_late_pass(|_| Box::new(unnecessary_literal_bound::UnnecessaryLiteralBound));
     store.register_late_pass(move |_| Box::new(arbitrary_source_item_ordering::ArbitrarySourceItemOrdering::new(conf)));
     store.register_late_pass(|_| Box::new(unneeded_struct_pattern::UnneededStructPattern));
+    store.register_late_pass(|_| Box::new(unnecessary_semicolon::UnnecessarySemicolon));
     // add lints here, do not remove this comment, it's used in `new_lint`
 }
diff --git a/clippy_lints/src/unnecessary_semicolon.rs b/clippy_lints/src/unnecessary_semicolon.rs
new file mode 100644
index 00000000000..6bc56dffc57
--- /dev/null
+++ b/clippy_lints/src/unnecessary_semicolon.rs
@@ -0,0 +1,63 @@
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use rustc_errors::Applicability;
+use rustc_hir::{ExprKind, MatchSource, Stmt, StmtKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::declare_lint_pass;
+
+declare_clippy_lint! {
+    /// ### What it does
+    /// Checks for the presence of a semicolon at the end of
+    /// a `match` or `if` statement evaluating to `()`.
+    ///
+    /// ### Why is this bad?
+    /// The semicolon is not needed, and may be removed to
+    /// avoid confusion and visual clutter.
+    ///
+    /// ### Example
+    /// ```no_run
+    /// # let a: u32 = 42;
+    /// if a > 10 {
+    ///     println!("a is greater than 10");
+    /// };
+    /// ```
+    /// Use instead:
+    /// ```no_run
+    /// # let a: u32 = 42;
+    /// if a > 10 {
+    ///    println!("a is greater than 10");
+    /// }
+    /// ```
+    #[clippy::version = "1.86.0"]
+    pub UNNECESSARY_SEMICOLON,
+    pedantic,
+    "unnecessary semicolon after expression returning `()`"
+}
+
+declare_lint_pass!(UnnecessarySemicolon => [UNNECESSARY_SEMICOLON]);
+
+impl LateLintPass<'_> for UnnecessarySemicolon {
+    fn check_stmt(&mut self, cx: &LateContext<'_>, stmt: &Stmt<'_>) {
+        // rustfmt already takes care of removing semicolons at the end
+        // of loops.
+        if let StmtKind::Semi(expr) = stmt.kind
+            && !stmt.span.from_expansion()
+            && !expr.span.from_expansion()
+            && matches!(
+                expr.kind,
+                ExprKind::If(..) | ExprKind::Match(_, _, MatchSource::Normal | MatchSource::Postfix)
+            )
+            && cx.typeck_results().expr_ty(expr) == cx.tcx.types.unit
+        {
+            let semi_span = expr.span.shrink_to_hi().to(stmt.span.shrink_to_hi());
+            span_lint_and_sugg(
+                cx,
+                UNNECESSARY_SEMICOLON,
+                semi_span,
+                "unnecessary semicolon",
+                "remove",
+                String::new(),
+                Applicability::MachineApplicable,
+            );
+        }
+    }
+}
diff --git a/tests/ui/unnecessary_semicolon.fixed b/tests/ui/unnecessary_semicolon.fixed
new file mode 100644
index 00000000000..36d5c7806fe
--- /dev/null
+++ b/tests/ui/unnecessary_semicolon.fixed
@@ -0,0 +1,32 @@
+#![warn(clippy::unnecessary_semicolon)]
+#![feature(postfix_match)]
+
+fn no_lint(mut x: u32) -> Option<u32> {
+    Some(())?;
+
+    {
+        let y = 3;
+        dbg!(x + y)
+    };
+
+    {
+        let (mut a, mut b) = (10, 20);
+        (a, b) = (b + 1, a + 1);
+    }
+
+    Some(0)
+}
+
+fn main() {
+    let mut a = 3;
+    if a == 2 {
+        println!("This is weird");
+    }
+    //~^ ERROR: unnecessary semicolon
+
+    a.match {
+        3 => println!("three"),
+        _ => println!("not three"),
+    }
+    //~^ ERROR: unnecessary semicolon
+}
diff --git a/tests/ui/unnecessary_semicolon.rs b/tests/ui/unnecessary_semicolon.rs
new file mode 100644
index 00000000000..b6fa4f1c9ce
--- /dev/null
+++ b/tests/ui/unnecessary_semicolon.rs
@@ -0,0 +1,32 @@
+#![warn(clippy::unnecessary_semicolon)]
+#![feature(postfix_match)]
+
+fn no_lint(mut x: u32) -> Option<u32> {
+    Some(())?;
+
+    {
+        let y = 3;
+        dbg!(x + y)
+    };
+
+    {
+        let (mut a, mut b) = (10, 20);
+        (a, b) = (b + 1, a + 1);
+    }
+
+    Some(0)
+}
+
+fn main() {
+    let mut a = 3;
+    if a == 2 {
+        println!("This is weird");
+    };
+    //~^ ERROR: unnecessary semicolon
+
+    a.match {
+        3 => println!("three"),
+        _ => println!("not three"),
+    };
+    //~^ ERROR: unnecessary semicolon
+}
diff --git a/tests/ui/unnecessary_semicolon.stderr b/tests/ui/unnecessary_semicolon.stderr
new file mode 100644
index 00000000000..e6bf36e81e8
--- /dev/null
+++ b/tests/ui/unnecessary_semicolon.stderr
@@ -0,0 +1,17 @@
+error: unnecessary semicolon
+  --> tests/ui/unnecessary_semicolon.rs:24:6
+   |
+LL |     };
+   |      ^ help: remove
+   |
+   = note: `-D clippy::unnecessary-semicolon` implied by `-D warnings`
+   = help: to override `-D warnings` add `#[allow(clippy::unnecessary_semicolon)]`
+
+error: unnecessary semicolon
+  --> tests/ui/unnecessary_semicolon.rs:30:6
+   |
+LL |     };
+   |      ^ help: remove
+
+error: aborting due to 2 previous errors
+