]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/semicolon_block.rs
Add test case for blocks with semicolon inside and outside a block
[rust.git] / clippy_lints / src / semicolon_block.rs
1 use clippy_utils::diagnostics::{multispan_sugg_with_applicability, span_lint_and_then};
2 use rustc_errors::Applicability;
3 use rustc_hir::{Block, Expr, ExprKind, Stmt, StmtKind};
4 use rustc_lint::{LateContext, LateLintPass, LintContext};
5 use rustc_session::{declare_lint_pass, declare_tool_lint};
6 use rustc_span::Span;
7
8 declare_clippy_lint! {
9     /// ### What it does
10     ///
11     /// Suggests moving the semicolon after a block to the inside of the block, after its last
12     /// expression.
13     ///
14     /// ### Why is this bad?
15     ///
16     /// For consistency it's best to have the semicolon inside/outside the block. Either way is fine
17     /// and this lint suggests inside the block.
18     /// Take a look at `semicolon_outside_block` for the other alternative.
19     ///
20     /// ### Example
21     ///
22     /// ```rust
23     /// # fn f(_: u32) {}
24     /// # let x = 0;
25     /// unsafe { f(x) };
26     /// ```
27     /// Use instead:
28     /// ```rust
29     /// # fn f(_: u32) {}
30     /// # let x = 0;
31     /// unsafe { f(x); }
32     /// ```
33     #[clippy::version = "1.66.0"]
34     pub SEMICOLON_INSIDE_BLOCK,
35     restriction,
36     "add a semicolon inside the block"
37 }
38 declare_clippy_lint! {
39     /// ### What it does
40     ///
41     /// Suggests moving the semicolon from a block's final expression outside of the block.
42     ///
43     /// ### Why is this bad?
44     ///
45     /// For consistency it's best to have the semicolon inside/outside the block. Either way is fine
46     /// and this lint suggests outside the block.
47     /// Take a look at `semicolon_inside_block` for the other alternative.
48     ///
49     /// ### Example
50     ///
51     /// ```rust
52     /// # fn f(_: u32) {}
53     /// # let x = 0;
54     /// unsafe { f(x); }
55     /// ```
56     /// Use instead:
57     /// ```rust
58     /// # fn f(_: u32) {}
59     /// # let x = 0;
60     /// unsafe { f(x) };
61     /// ```
62     #[clippy::version = "1.66.0"]
63     pub SEMICOLON_OUTSIDE_BLOCK,
64     restriction,
65     "add a semicolon outside the block"
66 }
67 declare_lint_pass!(SemicolonBlock => [SEMICOLON_INSIDE_BLOCK, SEMICOLON_OUTSIDE_BLOCK]);
68
69 impl LateLintPass<'_> for SemicolonBlock {
70     fn check_stmt(&mut self, cx: &LateContext<'_>, stmt: &Stmt<'_>) {
71         match stmt.kind {
72             StmtKind::Expr(Expr {
73                 kind: ExprKind::Block(block, _),
74                 ..
75             }) if !block.span.from_expansion() => {
76                 let Block {
77                     expr: None,
78                     stmts: [.., stmt],
79                     ..
80                 } = block else { return };
81                 let &Stmt {
82                     kind: StmtKind::Semi(expr),
83                     span,
84                     ..
85                 } = stmt else { return };
86                 semicolon_outside_block(cx, block, expr, span);
87             },
88             StmtKind::Semi(Expr {
89                 kind: ExprKind::Block(block @ Block { expr: Some(tail), .. }, _),
90                 ..
91             }) if !block.span.from_expansion() => semicolon_inside_block(cx, block, tail, stmt.span),
92             _ => (),
93         }
94     }
95 }
96
97 fn semicolon_inside_block(cx: &LateContext<'_>, block: &Block<'_>, tail: &Expr<'_>, semi_span: Span) {
98     let insert_span = tail.span.source_callsite().shrink_to_hi();
99     let remove_span = semi_span.with_lo(block.span.hi());
100
101     span_lint_and_then(
102         cx,
103         SEMICOLON_INSIDE_BLOCK,
104         semi_span,
105         "consider moving the `;` inside the block for consistent formatting",
106         |diag| {
107             multispan_sugg_with_applicability(
108                 diag,
109                 "put the `;` here",
110                 Applicability::MachineApplicable,
111                 [(remove_span, String::new()), (insert_span, ";".to_owned())],
112             );
113         },
114     );
115 }
116
117 fn semicolon_outside_block(cx: &LateContext<'_>, block: &Block<'_>, tail_stmt_expr: &Expr<'_>, semi_span: Span) {
118     let insert_span = block.span.with_lo(block.span.hi());
119     // account for macro calls
120     let semi_span = cx.sess().source_map().stmt_span(semi_span, block.span);
121     let remove_span = semi_span.with_lo(tail_stmt_expr.span.source_callsite().hi());
122
123     span_lint_and_then(
124         cx,
125         SEMICOLON_OUTSIDE_BLOCK,
126         block.span,
127         "consider moving the `;` outside the block for consistent formatting",
128         |diag| {
129             multispan_sugg_with_applicability(
130                 diag,
131                 "put the `;` here",
132                 Applicability::MachineApplicable,
133                 [(remove_span, String::new()), (insert_span, ";".to_owned())],
134             );
135         },
136     );
137 }