diff --git a/Configurations.md b/Configurations.md index a89fbe863e6..38e306ed02c 100644 --- a/Configurations.md +++ b/Configurations.md @@ -1877,6 +1877,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 diff --git a/src/config/mod.rs b/src/config/mod.rs index 5dbe532ac38..a5f0fda0943 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -139,6 +139,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"; @@ -607,6 +609,7 @@ edition = "2015" version = "One" 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 diff --git a/src/items.rs b/src/items.rs index babc56f86ed..bf3edf40bda 100644 --- a/src/items.rs +++ b/src/items.rs @@ -172,6 +172,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, @@ -263,6 +271,11 @@ impl<'a> FmtVisitor<'a> { self.block_indent = self.block_indent.block_indent(self.config); if !item.body.is_empty() { + 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); } @@ -766,6 +779,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); @@ -1142,6 +1163,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(items.first().map(|i| i.span().lo())) + { + result.push_str(&opening_nls); + } + for item in items { visitor.visit_trait_item(item); } diff --git a/src/visitor.rs b/src/visitor.rs index 0177689958a..c092c3090e8 100644 --- a/src/visitor.rs +++ b/src/visitor.rs @@ -183,32 +183,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, + ) -> Option { + 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( @@ -228,7 +264,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 { @@ -934,6 +976,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(items); let missing_span = self.next_span(inner_span.hi() - BytePos(1)); diff --git a/tests/source/configs/preserve_block_start_blank_lines/false.rs b/tests/source/configs/preserve_block_start_blank_lines/false.rs new file mode 100644 index 00000000000..c48cefe4a27 --- /dev/null +++ b/tests/source/configs/preserve_block_start_blank_lines/false.rs @@ -0,0 +1,208 @@ +// rustfmt-preserve_block_start_blank_lines: false + +fn noop() { + println!("hi"); +} + +fn say_hi() { + + + println!("hi"); +} + +fn say_hi() { + + + #![attr] + println!("hi"); +} + +fn say_hi() { + + + //! Abcdef + + println!("hi"); +} + +fn say_hi() { + + + // Abcdef + + println!("hi"); +} + +fn say_hi() { + // Abcdef + + println!("hi"); +} + +fn container() { + if true { + + + // comment + do_work(); + } else { + + + // comment + other_work(); + } + + let val = { + + + // comment + get_val(); + }; +} + +trait Hello { + + + // comment + + fn say_hi() -> &str; +} + +trait Hello { + // comment + + fn say_hi() -> &str; +} + +trait Hello { + + + + fn say_hi() -> &str; +} + +trait Hello { + fn say_hi() -> &str; +} + +impl Hello for &str { + + + // comment + + fn say_hi() -> &str { "hi" } +} + +impl Hello for &str { + // comment + + fn say_hi() -> &str { "hi" } +} + +impl Hello for &str { + + + + fn say_hi() -> &str { "hi" } +} + +impl Hello for &str { + fn say_hi() -> &str { "hi" } +} + +mod Hi { + + + // comment + + fn say_hi() -> &str; +} + +mod Hi { + + + /// comment + + fn say_hi() -> &str; +} + +mod Hi { + + + // comment + + /// comment + + fn say_hi() -> &str; +} + +mod Hi { + + + // comment + + #![attr] + + fn say_hi() -> &str; +} + +mod Hi { + + + #![attr] + + fn say_hi() -> &str; +} + +mod Hi { + // comment + fn say_hi() -> &str; +} + +mod Hi { + fn say_hi() -> &str; +} + +extern "C" { + + + // comment + + fn say_hi() -> &str; +} + +extern "C" { + + + /// comment + + fn say_hi() -> &str; +} + +extern "C" { + + + // comment + + /// comment + + fn say_hi() -> &str; +} + +extern "C" { + + + // comment + + #[attr] + fn say_hi() -> &str; +} + +extern "C" { + // comment + fn say_hi() -> &str; +} + +extern "C" { + fn say_hi() -> &str; +} diff --git a/tests/source/configs/preserve_block_start_blank_lines/true.rs b/tests/source/configs/preserve_block_start_blank_lines/true.rs new file mode 100644 index 00000000000..4ad48781fde --- /dev/null +++ b/tests/source/configs/preserve_block_start_blank_lines/true.rs @@ -0,0 +1,208 @@ +// rustfmt-preserve_block_start_blank_lines: true + +fn noop() { + println!("hi"); +} + +fn say_hi() { + + + println!("hi"); +} + +fn say_hi() { + + + #![attr] + println!("hi"); +} + +fn say_hi() { + + + //! Abcdef + + println!("hi"); +} + +fn say_hi() { + + + // Abcdef + + println!("hi"); +} + +fn say_hi() { + // Abcdef + + println!("hi"); +} + +fn container() { + if true { + + + // comment + do_work(); + } else { + + + // comment + other_work(); + } + + let val = { + + + // comment + get_val(); + }; +} + +trait Hello { + + + // comment + + fn say_hi() -> &str; +} + +trait Hello { + // comment + + fn say_hi() -> &str; +} + +trait Hello { + + + + fn say_hi() -> &str; +} + +trait Hello { + fn say_hi() -> &str; +} + +impl Hello for &str { + + + // comment + + fn say_hi() -> &str { "hi" } +} + +impl Hello for &str { + // comment + + fn say_hi() -> &str { "hi" } +} + +impl Hello for &str { + + + + fn say_hi() -> &str { "hi" } +} + +impl Hello for &str { + fn say_hi() -> &str { "hi" } +} + +mod Hi { + + + // comment + + fn say_hi() -> &str; +} + +mod Hi { + + + /// comment + + fn say_hi() -> &str; +} + +mod Hi { + + + // comment + + /// comment + + fn say_hi() -> &str; +} + +mod Hi { + + + // comment + + #![attr] + + fn say_hi() -> &str; +} + +mod Hi { + + + #![attr] + + fn say_hi() -> &str; +} + +mod Hi { + // comment + fn say_hi() -> &str; +} + +mod Hi { + fn say_hi() -> &str; +} + +extern "C" { + + + // comment + + fn say_hi() -> &str; +} + +extern "C" { + + + /// comment + + fn say_hi() -> &str; +} + +extern "C" { + + + // comment + + /// comment + + fn say_hi() -> &str; +} + +extern "C" { + + + // comment + + #[attr] + fn say_hi() -> &str; +} + +extern "C" { + // comment + fn say_hi() -> &str; +} + +extern "C" { + fn say_hi() -> &str; +} diff --git a/tests/target/configs/preserve_block_start_blank_lines/false.rs b/tests/target/configs/preserve_block_start_blank_lines/false.rs new file mode 100644 index 00000000000..fa7cf974b01 --- /dev/null +++ b/tests/target/configs/preserve_block_start_blank_lines/false.rs @@ -0,0 +1,172 @@ +// rustfmt-preserve_block_start_blank_lines: false + +fn noop() { + println!("hi"); +} + +fn say_hi() { + println!("hi"); +} + +fn say_hi() { + #![attr] + println!("hi"); +} + +fn say_hi() { + //! Abcdef + + println!("hi"); +} + +fn say_hi() { + // Abcdef + + println!("hi"); +} + +fn say_hi() { + // Abcdef + + println!("hi"); +} + +fn container() { + if true { + // comment + do_work(); + } else { + // comment + other_work(); + } + + let val = { + // comment + get_val(); + }; +} + +trait Hello { + // comment + + fn say_hi() -> &str; +} + +trait Hello { + // comment + + fn say_hi() -> &str; +} + +trait Hello { + fn say_hi() -> &str; +} + +trait Hello { + fn say_hi() -> &str; +} + +impl Hello for &str { + // comment + + fn say_hi() -> &str { + "hi" + } +} + +impl Hello for &str { + // comment + + fn say_hi() -> &str { + "hi" + } +} + +impl Hello for &str { + fn say_hi() -> &str { + "hi" + } +} + +impl Hello for &str { + fn say_hi() -> &str { + "hi" + } +} + +mod Hi { + // comment + + fn say_hi() -> &str; +} + +mod Hi { + /// comment + + fn say_hi() -> &str; +} + +mod Hi { + // comment + + /// comment + + fn say_hi() -> &str; +} + +mod Hi { + // comment + + #![attr] + + fn say_hi() -> &str; +} + +mod Hi { + #![attr] + + fn say_hi() -> &str; +} + +mod Hi { + // comment + fn say_hi() -> &str; +} + +mod Hi { + fn say_hi() -> &str; +} + +extern "C" { + // comment + + fn say_hi() -> &str; +} + +extern "C" { + /// comment + fn say_hi() -> &str; +} + +extern "C" { + // comment + + /// comment + fn say_hi() -> &str; +} + +extern "C" { + // comment + + #[attr] + fn say_hi() -> &str; +} + +extern "C" { + // comment + fn say_hi() -> &str; +} + +extern "C" { + fn say_hi() -> &str; +} diff --git a/tests/target/configs/preserve_block_start_blank_lines/true.rs b/tests/target/configs/preserve_block_start_blank_lines/true.rs new file mode 100644 index 00000000000..b475797e80f --- /dev/null +++ b/tests/target/configs/preserve_block_start_blank_lines/true.rs @@ -0,0 +1,214 @@ +// rustfmt-preserve_block_start_blank_lines: true + +fn noop() { + println!("hi"); +} + +fn say_hi() { + + + println!("hi"); +} + +fn say_hi() { + + + #![attr] + println!("hi"); +} + +fn say_hi() { + + + //! Abcdef + + println!("hi"); +} + +fn say_hi() { + + + // Abcdef + + println!("hi"); +} + +fn say_hi() { + // Abcdef + + println!("hi"); +} + +fn container() { + if true { + + + // comment + do_work(); + } else { + + + // comment + other_work(); + } + + let val = { + + + // comment + get_val(); + }; +} + +trait Hello { + + + // comment + + fn say_hi() -> &str; +} + +trait Hello { + // comment + + fn say_hi() -> &str; +} + +trait Hello { + + + + fn say_hi() -> &str; +} + +trait Hello { + fn say_hi() -> &str; +} + +impl Hello for &str { + + + // comment + + fn say_hi() -> &str { + "hi" + } +} + +impl Hello for &str { + // comment + + fn say_hi() -> &str { + "hi" + } +} + +impl Hello for &str { + + + + fn say_hi() -> &str { + "hi" + } +} + +impl Hello for &str { + fn say_hi() -> &str { + "hi" + } +} + +mod Hi { + + + // comment + + fn say_hi() -> &str; +} + +mod Hi { + + + /// comment + + fn say_hi() -> &str; +} + +mod Hi { + + + // comment + + /// comment + + fn say_hi() -> &str; +} + +mod Hi { + + + // comment + + #![attr] + + fn say_hi() -> &str; +} + +mod Hi { + + + #![attr] + + fn say_hi() -> &str; +} + +mod Hi { + // comment + fn say_hi() -> &str; +} + +mod Hi { + fn say_hi() -> &str; +} + +extern "C" { + + + // comment + + fn say_hi() -> &str; +} + +extern "C" { + + + /// comment + fn say_hi() -> &str; +} + +extern "C" { + + + // comment + + /// comment + fn say_hi() -> &str; +} + +extern "C" { + + + // comment + + #[attr] + fn say_hi() -> &str; +} + +extern "C" { + // comment + fn say_hi() -> &str; +} + +extern "C" { + fn say_hi() -> &str; +}