Skip to content

Commit a726941

Browse files
committed
Configuration option to preserve blank lines at start of block
Adds the option for - Functions - Control flow blocks - Expression statements (i.e. `let x = { ... };`) - Traits - Trait Impls - Mods - Foreign Mods If I am missing a kind of item, let me know :) Closes #2868
1 parent 144c9ec commit a726941

File tree

8 files changed

+932
-13
lines changed

8 files changed

+932
-13
lines changed

Configurations.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1804,6 +1804,35 @@ pub enum Foo {}
18041804
pub enum Foo {}
18051805
```
18061806

1807+
## `preserve_block_start_blank_lines`
1808+
1809+
Preserves blanks lines at the start of the block. Note that this will preserve newlines, but strip
1810+
any other whitespace on those lines.
1811+
1812+
- **Default value**: `false`
1813+
- **Possible values**: `true`, `false`
1814+
- **Stable**: No
1815+
1816+
#### `false` (default):
1817+
1818+
```rust
1819+
fn say_hi() {
1820+
println!("hi");
1821+
}
1822+
```
1823+
1824+
#### `true`:
1825+
1826+
```rust
1827+
fn say_hi() {
1828+
1829+
1830+
println!("hi");
1831+
}
1832+
```
1833+
1834+
###
1835+
18071836
## `overflow_delimited_expr`
18081837

18091838
When structs, slices, arrays, and block/array-like macros are used as the last

src/config.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,8 @@ create_config! {
129129
inline_attribute_width: usize, 0, false,
130130
"Write an item and its attribute on the same line \
131131
if their combined width is below a threshold";
132+
preserve_block_start_blank_lines: bool, false, false, "Preserve blank lines at the start of \
133+
blocks.";
132134

133135
// Options that can change the source code beyond whitespace/blocks (somewhat linty things)
134136
merge_derives: bool, true, true, "Merge multiple `#[derive(...)]` into a single one";
@@ -615,6 +617,7 @@ blank_lines_upper_bound = 1
615617
blank_lines_lower_bound = 0
616618
edition = "2018"
617619
inline_attribute_width = 0
620+
preserve_block_start_blank_lines = false
618621
merge_derives = true
619622
use_try_shorthand = false
620623
use_field_init_shorthand = false

src/formatting/items.rs

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,14 @@ enum BodyElement<'a> {
221221
ForeignItem(&'a ast::ForeignItem),
222222
}
223223

224+
impl BodyElement<'_> {
225+
pub(crate) fn span(&self) -> Span {
226+
match self {
227+
BodyElement::ForeignItem(fi) => fi.span(),
228+
}
229+
}
230+
}
231+
224232
/// Represents a fn's signature.
225233
pub(crate) struct FnSig<'a> {
226234
decl: &'a ast::FnDecl,
@@ -333,6 +341,10 @@ impl<'a> FmtVisitor<'a> {
333341
let indent_str = self.block_indent.to_string(self.config);
334342
self.push_str(&indent_str);
335343
} else {
344+
let first_non_ws = item.body.first().map(|s| s.span().lo());
345+
let opening_nls = &self.trim_spaces_after_opening_brace(first_non_ws);
346+
self.push_str(&opening_nls);
347+
336348
for item in &item.body {
337349
self.format_body_element(item);
338350
}
@@ -854,12 +866,25 @@ pub(crate) fn format_impl(
854866
visitor.block_indent = item_indent;
855867
visitor.last_pos = lo + BytePos(open_pos as u32);
856868

869+
let open_nls = &visitor.trim_spaces_after_opening_brace(
870+
inner_attributes(&item.attrs)
871+
.first()
872+
.map(|a| a.span.lo())
873+
.or(items.first().map(|i| i.span().lo())),
874+
);
875+
857876
visitor.visit_attrs(&item.attrs, ast::AttrStyle::Inner);
858877
visitor.visit_impl_items(items);
859878

860879
visitor.format_missing(item.span.hi() - BytePos(1));
861880

862-
let inner_indent_str = visitor.block_indent.to_string_with_newline(context.config);
881+
let inner_indent_str = if !open_nls.is_empty() {
882+
result.push_str(open_nls);
883+
visitor.block_indent.to_string(context.config)
884+
} else {
885+
visitor.block_indent.to_string_with_newline(context.config)
886+
};
887+
863888
let outer_indent_str = offset.block_only().to_string_with_newline(context.config);
864889

865890
result.push_str(&inner_indent_str);
@@ -1209,13 +1234,21 @@ pub(crate) fn format_trait(
12091234
visitor.block_indent = offset.block_only().block_indent(context.config);
12101235
visitor.last_pos = block_span.lo() + BytePos(open_pos as u32);
12111236

1237+
let open_nls = &visitor
1238+
.trim_spaces_after_opening_brace(trait_items.first().map(|i| i.span().lo()));
1239+
12121240
for item in trait_items {
12131241
visitor.visit_trait_item(item);
12141242
}
12151243

12161244
visitor.format_missing(item.span.hi() - BytePos(1));
12171245

1218-
let inner_indent_str = visitor.block_indent.to_string_with_newline(context.config);
1246+
let inner_indent_str = if !open_nls.is_empty() {
1247+
result.push_str(open_nls);
1248+
visitor.block_indent.to_string(context.config)
1249+
} else {
1250+
visitor.block_indent.to_string_with_newline(context.config)
1251+
};
12191252

12201253
result.push_str(&inner_indent_str);
12211254
result.push_str(visitor.buffer.trim());

src/formatting/visitor.rs

Lines changed: 63 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -181,18 +181,56 @@ impl<'b, 'a: 'b> FmtVisitor<'a> {
181181
}
182182

183183
/// Remove spaces between the opening brace and the first statement or the inner attribute
184-
/// of the block.
185-
fn trim_spaces_after_opening_brace(
184+
/// of the block, fast-forwarding this position of the visitor.
185+
/// If `preserve_block_start_blank_lines` is true, the return value contains the newlines that
186+
/// should be preserved.
187+
pub(crate) fn trim_spaces_after_opening_brace(
186188
&mut self,
187-
b: &ast::Block,
188-
inner_attrs: Option<&[ast::Attribute]>,
189-
) {
190-
if let Some(first_stmt) = b.stmts.first() {
191-
let hi = inner_attrs
192-
.and_then(|attrs| inner_attributes(attrs).first().map(|attr| attr.span.lo()))
193-
.unwrap_or_else(|| first_stmt.span().lo());
194-
let missing_span = self.next_span(hi);
189+
first_item_pos: Option<BytePos>,
190+
) -> String {
191+
let mut result = String::new();
192+
if let Some(first_item_pos) = first_item_pos {
193+
let missing_span = self.next_span(first_item_pos);
195194
let snippet = self.snippet(missing_span);
195+
196+
if self.config.preserve_block_start_blank_lines() {
197+
// First we need to find the span to look for blank lines in. This is either the
198+
// - span between the opening brace and first item, or
199+
// - span between the opening brace and a comment before the first item
200+
// We do this so that we get a span of contiguous whitespace, which makes
201+
// processing the blank lines easier.
202+
let blank_lines_snippet = if let Some(hi) = self
203+
.snippet_provider
204+
.span_to_snippet(missing_span)
205+
.and_then(|s| s.find('/'))
206+
{
207+
self.snippet_provider.span_to_snippet(mk_sp(
208+
missing_span.lo(),
209+
missing_span.lo() + BytePos::from_usize(hi),
210+
))
211+
} else {
212+
self.snippet_provider.span_to_snippet(missing_span)
213+
};
214+
215+
if let Some(snippet) = blank_lines_snippet {
216+
let has_multiple_blank_lines =
217+
if let (Some(l), Some(r)) = (snippet.find('\n'), snippet.rfind('\n')) {
218+
l != r
219+
} else {
220+
false
221+
};
222+
if has_multiple_blank_lines {
223+
let mut lines = snippet.lines().map(&str::trim);
224+
lines.next(); // Eat block-opening newline
225+
while let Some("") = lines.next() {
226+
result.push('\n');
227+
}
228+
}
229+
} else {
230+
debug!("Failed to preserve blank lines for {:?}", missing_span);
231+
}
232+
}
233+
196234
let len = CommentCodeSlices::new(snippet)
197235
.next()
198236
.and_then(|(kind, _, s)| {
@@ -206,6 +244,7 @@ impl<'b, 'a: 'b> FmtVisitor<'a> {
206244
self.last_pos = self.last_pos + BytePos::from_usize(len);
207245
}
208246
}
247+
result
209248
}
210249

211250
pub(crate) fn visit_block(
@@ -225,7 +264,12 @@ impl<'b, 'a: 'b> FmtVisitor<'a> {
225264
self.last_pos = self.last_pos + brace_compensation;
226265
self.block_indent = self.block_indent.block_indent(self.config);
227266
self.push_str("{");
228-
self.trim_spaces_after_opening_brace(b, inner_attrs);
267+
268+
let first_non_ws = inner_attrs
269+
.and_then(|attrs| inner_attributes(attrs).first().map(|attr| attr.span.lo()))
270+
.or(b.stmts.first().map(|s| s.span().lo()));
271+
let opening_nls = &self.trim_spaces_after_opening_brace(first_non_ws);
272+
self.push_str(&opening_nls);
229273

230274
// Format inner attributes if available.
231275
if let Some(attrs) = inner_attrs {
@@ -945,6 +989,14 @@ impl<'b, 'a: 'b> FmtVisitor<'a> {
945989
} else {
946990
self.last_pos = mod_lo;
947991
self.block_indent = self.block_indent.block_indent(self.config);
992+
993+
let first_non_ws = inner_attributes(attrs)
994+
.first()
995+
.map(|attr| attr.span.lo())
996+
.or(m.items.first().map(|s| s.span().lo()));
997+
let opening_nls = &self.trim_spaces_after_opening_brace(first_non_ws);
998+
self.push_str(&opening_nls);
999+
9481000
self.visit_attrs(attrs, ast::AttrStyle::Inner);
9491001
self.walk_mod_items(m);
9501002
let missing_span = self.next_span(m.inner.hi() - BytePos(1));

0 commit comments

Comments
 (0)