@@ -64,7 +64,48 @@ declare_clippy_lint! {
6464 restriction,
6565 "add a semicolon outside the block"
6666}
67- declare_lint_pass ! ( SemicolonBlock => [ SEMICOLON_INSIDE_BLOCK , SEMICOLON_OUTSIDE_BLOCK ] ) ;
67+ declare_clippy_lint ! {
68+ /// ### What it does
69+ ///
70+ /// Suggests moving the semicolon from a block's final expression outside of
71+ /// the block if it's singleline, and inside the block if it's multiline.
72+ ///
73+ /// ### Why is this bad?
74+ ///
75+ /// Some may prefer if the semicolon is outside if a block is only one
76+ /// expression, as this allows rustfmt to make it singleline. In the case that
77+ /// it isn't, it should be inside.
78+ /// Take a look at both `semicolon_inside_block` and `semicolon_outside_block` for alternatives.
79+ ///
80+ /// ### Example
81+ ///
82+ /// ```rust
83+ /// # fn f(_: u32) {}
84+ /// # let x = 0;
85+ /// unsafe { f(x); }
86+ ///
87+ /// unsafe {
88+ /// let x = 1;
89+ /// f(x)
90+ /// };
91+ /// ```
92+ /// Use instead:
93+ /// ```rust
94+ /// # fn f(_: u32) {}
95+ /// # let x = 0;
96+ /// unsafe { f(x) };
97+ ///
98+ /// unsafe {
99+ /// let x = 1;
100+ /// f(x);
101+ /// }
102+ /// ```
103+ #[ clippy:: version = "1.68.0" ]
104+ pub SEMICOLON_OUTSIDE_BLOCK_IF_SINGLELINE ,
105+ restriction,
106+ "add a semicolon inside the block if it's singleline, otherwise outside"
107+ }
108+ declare_lint_pass ! ( SemicolonBlock => [ SEMICOLON_INSIDE_BLOCK , SEMICOLON_OUTSIDE_BLOCK , SEMICOLON_OUTSIDE_BLOCK_IF_SINGLELINE ] ) ;
68109
69110impl LateLintPass < ' _ > for SemicolonBlock {
70111 fn check_stmt ( & mut self , cx : & LateContext < ' _ > , stmt : & Stmt < ' _ > ) {
@@ -98,6 +139,8 @@ fn semicolon_inside_block(cx: &LateContext<'_>, block: &Block<'_>, tail: &Expr<'
98139 let insert_span = tail. span . source_callsite ( ) . shrink_to_hi ( ) ;
99140 let remove_span = semi_span. with_lo ( block. span . hi ( ) ) ;
100141
142+ check_semicolon_outside_block_if_singleline ( cx, block, remove_span, insert_span, true , "inside" ) ;
143+
101144 span_lint_and_then (
102145 cx,
103146 SEMICOLON_INSIDE_BLOCK ,
@@ -120,6 +163,8 @@ fn semicolon_outside_block(cx: &LateContext<'_>, block: &Block<'_>, tail_stmt_ex
120163 let semi_span = cx. sess ( ) . source_map ( ) . stmt_span ( semi_span, block. span ) ;
121164 let remove_span = semi_span. with_lo ( tail_stmt_expr. span . source_callsite ( ) . hi ( ) ) ;
122165
166+ check_semicolon_outside_block_if_singleline ( cx, block, remove_span, insert_span, false , "outside" ) ;
167+
123168 span_lint_and_then (
124169 cx,
125170 SEMICOLON_OUTSIDE_BLOCK ,
@@ -135,3 +180,48 @@ fn semicolon_outside_block(cx: &LateContext<'_>, block: &Block<'_>, tail_stmt_ex
135180 } ,
136181 ) ;
137182}
183+
184+ fn check_semicolon_outside_block_if_singleline (
185+ cx : & LateContext < ' _ > ,
186+ block : & Block < ' _ > ,
187+ remove_span : Span ,
188+ insert_span : Span ,
189+ inequality : bool ,
190+ ty : & str ,
191+ ) {
192+ let remove_line = cx
193+ . sess ( )
194+ . source_map ( )
195+ . lookup_line ( remove_span. lo ( ) )
196+ . expect ( "failed to get `remove_span`'s line" )
197+ . line ;
198+ let insert_line = cx
199+ . sess ( )
200+ . source_map ( )
201+ . lookup_line ( insert_span. lo ( ) )
202+ . expect ( "failed to get `insert_span`'s line" )
203+ . line ;
204+
205+ let eq = if inequality {
206+ remove_line != insert_line
207+ } else {
208+ remove_line == insert_line
209+ } ;
210+
211+ if eq {
212+ span_lint_and_then (
213+ cx,
214+ SEMICOLON_OUTSIDE_BLOCK_IF_SINGLELINE ,
215+ block. span ,
216+ & format ! ( "consider moving the `;` {ty} the block for consistent formatting" ) ,
217+ |diag| {
218+ multispan_sugg_with_applicability (
219+ diag,
220+ "put the `;` here" ,
221+ Applicability :: MachineApplicable ,
222+ [ ( remove_span, String :: new ( ) ) , ( insert_span, ";" . to_owned ( ) ) ] ,
223+ ) ;
224+ } ,
225+ ) ;
226+ }
227+ }
0 commit comments