Skip to content

Configuration option to preserve blank lines at start of block #4303

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jul 14, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions Configurations.md
Original file line number Diff line number Diff line change
Expand Up @@ -1809,6 +1809,35 @@ pub enum Foo {}
pub enum Foo {}
```

## `preserve_block_start_blank_lines`

Preserves blanks lines at the start of the block. Note that this will preserve newlines, but strip
any other whitespace on those lines.

- **Default value**: `false`
- **Possible values**: `true`, `false`
- **Stable**: No

#### `false` (default):

```rust
fn say_hi() {
println!("hi");
}
```

#### `true`:

```rust
fn say_hi() {


println!("hi");
}
```

###

## `overflow_delimited_expr`

When structs, slices, arrays, and block/array-like macros are used as the last
Expand Down
3 changes: 3 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,8 @@ create_config! {
"Write an item and its attribute on the same line \
if their combined width is below a threshold";
format_generated_files: bool, false, false, "Format generated files";
preserve_block_start_blank_lines: bool, false, false, "Preserve blank lines at the start of \
blocks.";

// Options that can change the source code beyond whitespace/blocks (somewhat linty things)
merge_derives: bool, true, true, "Merge multiple `#[derive(...)]` into a single one";
Expand Down Expand Up @@ -617,6 +619,7 @@ blank_lines_lower_bound = 0
edition = "2018"
inline_attribute_width = 0
format_generated_files = false
preserve_block_start_blank_lines = false
merge_derives = true
use_try_shorthand = false
use_field_init_shorthand = false
Expand Down
27 changes: 27 additions & 0 deletions src/formatting/items.rs
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,14 @@ enum BodyElement<'a> {
ForeignItem(&'a ast::ForeignItem),
}

impl BodyElement<'_> {
pub(crate) fn span(&self) -> Span {
match self {
BodyElement::ForeignItem(fi) => fi.span(),
}
}
}

/// Represents a fn's signature.
pub(crate) struct FnSig<'a> {
decl: &'a ast::FnDecl,
Expand Down Expand Up @@ -325,6 +333,11 @@ impl<'a> FmtVisitor<'a> {
let indent_str = self.block_indent.to_string(self.config);
self.push_str(&indent_str);
} else {
let first_non_ws = item.body.first().map(|s| s.span().lo());
if let Some(opening_nls) = self.advance_to_first_block_item(first_non_ws) {
self.push_str(&opening_nls);
}

for item in &item.body {
self.format_body_element(item);
}
Expand Down Expand Up @@ -851,6 +864,14 @@ pub(crate) fn format_impl(
visitor.block_indent = item_indent;
visitor.last_pos = lo + BytePos(open_pos as u32);

let first_non_ws = inner_attributes(&item.attrs)
.first()
.map(|a| a.span.lo())
.or_else(|| items.first().map(|i| i.span().lo()));
if let Some(opening_nls) = visitor.advance_to_first_block_item(first_non_ws) {
result.push_str(&opening_nls);
}

visitor.visit_attrs(&item.attrs, ast::AttrStyle::Inner);
visitor.visit_impl_items(items);

Expand Down Expand Up @@ -1210,6 +1231,12 @@ pub(crate) fn format_trait(
visitor.block_indent = offset.block_only().block_indent(context.config);
visitor.last_pos = block_span.lo() + BytePos(open_pos as u32);

if let Some(opening_nls) =
visitor.advance_to_first_block_item(trait_items.first().map(|i| i.span().lo()))
{
result.push_str(&opening_nls);
}

for item in trait_items {
visitor.visit_trait_item(item);
}
Expand Down
100 changes: 77 additions & 23 deletions src/formatting/visitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,32 +182,68 @@ impl<'b, 'a: 'b> FmtVisitor<'a> {
}
}

/// Remove spaces between the opening brace and the first statement or the inner attribute
/// of the block.
fn trim_spaces_after_opening_brace(
/// Advances the position of the visitor to the first statement or the inner attribute of a
/// block.
///
/// If `preserve_block_start_blank_lines` is true, the return value contains the newlines that
/// should be preserved.
pub(crate) fn advance_to_first_block_item(
&mut self,
b: &ast::Block,
inner_attrs: Option<&[ast::Attribute]>,
) {
if let Some(first_stmt) = b.stmts.first() {
let hi = inner_attrs
.and_then(|attrs| inner_attributes(attrs).first().map(|attr| attr.span.lo()))
.unwrap_or_else(|| first_stmt.span().lo());
let missing_span = self.next_span(hi);
let snippet = self.snippet(missing_span);
let len = CommentCodeSlices::new(snippet)
.next()
.and_then(|(kind, _, s)| {
if kind == CodeCharKind::Normal {
s.rfind('\n')
} else {
None
first_item_pos: Option<BytePos>,
) -> Option<String> {
let missing_span = first_item_pos.map(|pos| self.next_span(pos))?;
let snippet = self.snippet(missing_span);

let len = CommentCodeSlices::new(snippet)
.next()
.and_then(|(kind, _, s)| {
if kind == CodeCharKind::Normal {
s.rfind('\n')
} else {
None
}
});
if let Some(len) = len {
self.last_pos = self.last_pos + BytePos::from_usize(len);
}

if self.config.preserve_block_start_blank_lines() {
// First we need to find the span to look for blank lines in. This is either the
// - span between the opening brace and first item, or
// - span between the opening brace and a comment before the first item
// We do this so that we get a span of contiguous whitespace, which makes processing the
// blank lines easier.
let blank_lines_snippet = if let Some(hi) = self
.snippet_provider
.span_to_snippet(missing_span)
.and_then(|s| s.find('/'))
{
self.snippet_provider.span_to_snippet(mk_sp(
missing_span.lo(),
missing_span.lo() + BytePos::from_usize(hi),
))
} else {
self.snippet_provider.span_to_snippet(missing_span)
};

if let Some(snippet) = blank_lines_snippet {
if snippet.find('\n') != snippet.rfind('\n') {
let mut lines = snippet.lines().map(&str::trim);
lines.next(); // Eat block-opening newline
lines.next_back(); // Eat newline before the first item
let mut result = String::new();
while let Some("") = lines.next() {
result.push('\n');
}
});
if let Some(len) = len {
self.last_pos = self.last_pos + BytePos::from_usize(len);
if !result.is_empty() {
return Some(result);
}
}
} else {
debug!("Failed to preserve blank lines for {:?}", missing_span);
}
}
None
}

pub(crate) fn visit_block(
Expand All @@ -227,7 +263,13 @@ impl<'b, 'a: 'b> FmtVisitor<'a> {
self.last_pos = self.last_pos + brace_compensation;
self.block_indent = self.block_indent.block_indent(self.config);
self.push_str("{");
self.trim_spaces_after_opening_brace(b, inner_attrs);

let first_non_ws = inner_attrs
.and_then(|attrs| attrs.first().map(|attr| attr.span.lo()))
.or_else(|| b.stmts.first().map(|s| s.span().lo()));
if let Some(opening_nls) = self.advance_to_first_block_item(first_non_ws) {
self.push_str(&opening_nls);
}

// Format inner attributes if available.
if let Some(attrs) = inner_attrs {
Expand Down Expand Up @@ -953,6 +995,18 @@ impl<'b, 'a: 'b> FmtVisitor<'a> {
} else {
self.last_pos = mod_lo;
self.block_indent = self.block_indent.block_indent(self.config);

let first_non_ws = inner_attributes(attrs)
.first()
.map(|attr| attr.span.lo())
.or_else(|| m.items.first().map(|s| s.span().lo()));
if let Some(opening_nls) = self.advance_to_first_block_item(first_non_ws) {
self.push_str(&opening_nls);
if attrs.is_empty() {
self.push_str("\n");
}
}

self.visit_attrs(attrs, ast::AttrStyle::Inner);
self.walk_mod_items(m);
let missing_span = self.next_span(m.inner.hi() - BytePos(1));
Expand Down
Loading