Skip to content

Commit

Permalink
Implement empty lines surrounding blocks
Browse files Browse the repository at this point in the history
  • Loading branch information
richardpringle committed Jul 3, 2024
1 parent 533b760 commit fd7b6e0
Show file tree
Hide file tree
Showing 8 changed files with 2,348 additions and 7 deletions.
13 changes: 13 additions & 0 deletions Configurations.md
Original file line number Diff line number Diff line change
Expand Up @@ -1105,6 +1105,19 @@ macro_rules! foo {

See also [`format_macro_bodies`](#format_macro_bodies).

## `surround_blocks_with_empty_lines`

#### `false` (default):

```rust
// todo
```

#### `true`:

```rust
// todo
```

## `format_macro_bodies`

Expand Down
4 changes: 4 additions & 0 deletions src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,9 @@ create_config! {
format_generated_files: bool, true, false, "Format generated files";
generated_marker_line_search_limit: usize, 5, false, "Number of lines to check for a \
`@generated` marker when `format_generated_files` is enabled";
surround_blocks_with_empty_lines: bool, false, false,
"Surround all block expressions with \
empty lines. This option is not stable and could be removed at any time.";

// 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 @@ -683,6 +686,7 @@ version = "One"
inline_attribute_width = 0
format_generated_files = true
generated_marker_line_search_limit = 5
surround_blocks_with_empty_lines = false
merge_derives = true
use_try_shorthand = false
use_field_init_shorthand = false
Expand Down
58 changes: 58 additions & 0 deletions src/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1203,6 +1203,64 @@ pub(crate) fn is_simple_block(
&& attrs.map_or(true, |a| a.is_empty())
}

pub(crate) fn contains_curly_block(expr: &ast::Expr) -> bool {
match expr.kind {
ast::ExprKind::If(..)
| ast::ExprKind::While(..)
| ast::ExprKind::ForLoop { .. }
| ast::ExprKind::Loop(..)
| ast::ExprKind::Match(..)
| ast::ExprKind::Let(..)
| ast::ExprKind::Block(..)
| ast::ExprKind::TryBlock(..)
| ast::ExprKind::Try(..)
| ast::ExprKind::Gen(..) => true,
ast::ExprKind::Unary(_, ref expr)
| ast::ExprKind::Cast(ref expr, _)
| ast::ExprKind::Type(ref expr, _)
| ast::ExprKind::Await(ref expr, _)
| ast::ExprKind::Field(ref expr, _)
| ast::ExprKind::Become(ref expr)
| ast::ExprKind::Repeat(ref expr, _)
| ast::ExprKind::Paren(ref expr)
| ast::ExprKind::AddrOf(_, _, ref expr)
| ast::ExprKind::AssignOp(_, ref expr, ..) => contains_curly_block(expr),
ast::ExprKind::Closure(ref closure) => contains_curly_block(&closure.body),
ast::ExprKind::Binary(_, ref a, ref b)
| ast::ExprKind::Assign(ref a, ref b, _)
| ast::ExprKind::Index(ref a, ref b, _) => {
contains_curly_block(a) || contains_curly_block(b)
}
ast::ExprKind::Break(_, ref maybe_expr)
| ast::ExprKind::Ret(ref maybe_expr)
| ast::ExprKind::Yield(ref maybe_expr)
| ast::ExprKind::Yeet(ref maybe_expr) => {
maybe_expr.as_deref().map_or(false, contains_curly_block)
}
ast::ExprKind::Range(ref maybe_a, ref maybe_b, _) => maybe_a
.as_deref()
.or(maybe_b.as_deref())
.map_or(false, contains_curly_block),
ast::ExprKind::InlineAsm(..)
| ast::ExprKind::OffsetOf(..)
| ast::ExprKind::MacCall(..)
| ast::ExprKind::Struct(..)
| ast::ExprKind::Continue(..)
| ast::ExprKind::IncludedBytes(..)
| ast::ExprKind::FormatArgs(..)
| ast::ExprKind::Path(..)
| ast::ExprKind::Array(..)
| ast::ExprKind::ConstBlock(..)
| ast::ExprKind::Call(..)
| ast::ExprKind::MethodCall(..)
| ast::ExprKind::Tup(..)
| ast::ExprKind::Lit(..)
| ast::ExprKind::Underscore
| ast::ExprKind::Err(_)
| ast::ExprKind::Dummy => false,
}
}

/// Checks whether a block contains at most one statement or expression, and no
/// comments or attributes.
pub(crate) fn is_simple_block_stmt(
Expand Down
2 changes: 1 addition & 1 deletion src/items.rs
Original file line number Diff line number Diff line change
Expand Up @@ -489,7 +489,7 @@ impl<'a> FmtVisitor<'a> {
return None;
}

let res = Stmt::from_ast_node(block.stmts.first()?, true)
let res = Stmt::from_ast_node(block.stmts.first()?, true, true)
.rewrite(&self.get_context(), self.shape())?;

let width = self.block_indent.width() + fn_str.len() + res.len() + 5;
Expand Down
54 changes: 48 additions & 6 deletions src/stmt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use rustc_span::Span;

use crate::comment::recover_comment_removed;
use crate::config::Version;
use crate::expr::{format_expr, is_simple_block, ExprType};
use crate::expr::{contains_curly_block, format_expr, is_simple_block, ExprType};
use crate::rewrite::{Rewrite, RewriteContext};
use crate::shape::Shape;
use crate::source_map::LineRangeUtils;
Expand All @@ -12,6 +12,7 @@ use crate::utils::semicolon_for_stmt;

pub(crate) struct Stmt<'a> {
inner: &'a ast::Stmt,
is_first: bool,
is_last: bool,
}

Expand Down Expand Up @@ -41,15 +42,24 @@ impl<'a> Stmt<'a> {
if is_simple_block(context, block, attrs) {
let inner = &block.stmts[0];
// Simple blocks only contain one expr and no stmts
let is_first = true;
let is_last = true;
Some(Stmt { inner, is_last })
Some(Stmt {
inner,
is_first,
is_last,
})
} else {
None
}
}

pub(crate) fn from_ast_node(inner: &'a ast::Stmt, is_last: bool) -> Self {
Stmt { inner, is_last }
pub(crate) fn from_ast_node(inner: &'a ast::Stmt, is_first: bool, is_last: bool) -> Self {
Stmt {
inner,
is_first,
is_last,
}
}

pub(crate) fn from_ast_nodes<I>(iter: I) -> Vec<Self>
Expand All @@ -58,9 +68,19 @@ impl<'a> Stmt<'a> {
{
let mut result = vec![];
let mut iter = iter.peekable();
while iter.peek().is_some() {

if let Some(inner) = iter.next() {
result.push(Stmt {
inner: iter.next().unwrap(),
inner,
is_first: true,
is_last: iter.peek().is_none(),
})
}

while let Some(inner) = iter.next() {
result.push(Stmt {
inner,
is_first: false,
is_last: iter.peek().is_none(),
})
}
Expand All @@ -71,6 +91,14 @@ impl<'a> Stmt<'a> {
matches!(self.inner.kind, ast::StmtKind::Empty)
}

pub(crate) fn is_first(&self) -> bool {
self.is_first
}

pub(crate) fn is_last(&self) -> bool {
self.is_last
}

fn is_last_expr(&self) -> bool {
if !self.is_last {
return false;
Expand All @@ -86,6 +114,20 @@ impl<'a> Stmt<'a> {
_ => false,
}
}

pub(crate) fn is_block_with_curly_braces(&self) -> bool {
match self.as_ast_node().kind {
ast::StmtKind::Let(ref local) => match local.kind {
ast::LocalKind::Decl => false,
ast::LocalKind::Init(ref expr) => contains_curly_block(expr),
ast::LocalKind::InitElse(..) => true,
},
ast::StmtKind::Expr(ref expr) | ast::StmtKind::Semi(ref expr) => {
contains_curly_block(expr)
}
_ => false,
}
}
}

impl<'a> Rewrite for Stmt<'a> {
Expand Down
19 changes: 19 additions & 0 deletions src/visitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -876,8 +876,27 @@ impl<'b, 'a: 'b> FmtVisitor<'a> {
.collect();

if items.is_empty() {
let stmt = &stmts[0];

let should_preced_with_empty_line = self.config.surround_blocks_with_empty_lines()
&& !self.buffer.ends_with('\n')
&& !stmt.is_first()
&& stmt.is_block_with_curly_braces();

if should_preced_with_empty_line {
self.push_str("\n");
}

self.visit_stmt(&stmts[0], include_current_empty_semi);

let should_follow_with_empty_line = self.config.surround_blocks_with_empty_lines()
&& !stmt.is_last()
&& stmt.is_block_with_curly_braces();

if should_follow_with_empty_line {
self.push_str("\n");
}

// FIXME(calebcartwright 2021-01-03) - This exists strictly to maintain legacy
// formatting where rustfmt would preserve redundant semicolons on Items in a
// statement position.
Expand Down
Loading

0 comments on commit fd7b6e0

Please sign in to comment.