From 10226631e066e2310977e6811aca96170bb641ca Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Fri, 23 Sep 2022 15:12:27 +0200 Subject: [PATCH] refactor(rome_formatter): Flat IR (#3160) --- crates/rome_formatter/src/arguments.rs | 25 +- crates/rome_formatter/src/buffer.rs | 91 +- crates/rome_formatter/src/builders.rs | 467 ++++--- crates/rome_formatter/src/format_element.rs | 896 ++---------- .../src/format_element/document.rs | 619 +++++++++ .../rome_formatter/src/format_element/tag.rs | 228 ++++ .../rome_formatter/src/format_extensions.rs | 28 +- crates/rome_formatter/src/formatter.rs | 55 +- crates/rome_formatter/src/intersperse.rs | 80 -- crates/rome_formatter/src/lib.rs | 213 ++- crates/rome_formatter/src/macros.rs | 86 +- crates/rome_formatter/src/prelude.rs | 3 + .../rome_formatter/src/printer/call_stack.rs | 235 ++++ .../src/printer/line_suffixes.rs | 42 + crates/rome_formatter/src/printer/mod.rs | 1199 ++++++++--------- crates/rome_formatter/src/printer/queue.rs | 469 +++++++ crates/rome_formatter/src/printer/stack.rs | 155 +++ crates/rome_formatter/src/trivia.rs | 43 +- crates/rome_formatter/src/verbatim.rs | 153 +-- crates/rome_js_formatter/src/builders.rs | 3 +- .../rome_js_formatter/src/check_reformat.rs | 6 +- .../src/js/expressions/template_element.rs | 26 +- .../src/js/lists/array_element_list.rs | 2 +- .../src/jsx/lists/child_list.rs | 89 +- crates/rome_js_formatter/src/lib.rs | 18 +- .../rome_js_formatter/src/syntax_rewriter.rs | 2 +- .../src/ts/lists/type_member_list.rs | 4 +- .../src/utils/assignment_like.rs | 4 +- crates/rome_js_formatter/src/utils/mod.rs | 17 +- .../rome_js_formatter/tests/check_reformat.rs | 6 +- .../rome_js_formatter/tests/prettier_tests.rs | 2 +- crates/rome_js_formatter/tests/spec_test.rs | 4 +- .../eval-arguments-binding.js.snap | 63 - .../src/file_handlers/javascript.rs | 9 +- crates/rome_wasm/Cargo.toml | 2 - crates/rome_wasm/build.rs | 2 +- npm/rome/tests/daemon/formatContent.test.mjs | 14 +- npm/rome/tests/wasm/formatContent.test.mjs | 14 +- xtask/bench/src/features/formatter.rs | 2 +- xtask/codegen/src/generate_bindings.rs | 2 +- xtask/contributors/src/main.rs | 2 +- 41 files changed, 3261 insertions(+), 2119 deletions(-) create mode 100644 crates/rome_formatter/src/format_element/document.rs create mode 100644 crates/rome_formatter/src/format_element/tag.rs delete mode 100644 crates/rome_formatter/src/intersperse.rs create mode 100644 crates/rome_formatter/src/printer/call_stack.rs create mode 100644 crates/rome_formatter/src/printer/line_suffixes.rs create mode 100644 crates/rome_formatter/src/printer/queue.rs create mode 100644 crates/rome_formatter/src/printer/stack.rs delete mode 100644 crates/rome_js_formatter/tests/specs/prettier/js/sloppy-mode/eval-arguments-binding.js.snap diff --git a/crates/rome_formatter/src/arguments.rs b/crates/rome_formatter/src/arguments.rs index 0df9a32403a..db7d2f46c91 100644 --- a/crates/rome_formatter/src/arguments.rs +++ b/crates/rome_formatter/src/arguments.rs @@ -67,11 +67,14 @@ impl<'fmt, Context> Argument<'fmt, Context> { /// use rome_formatter::prelude::*; /// use rome_formatter::{format, format_args}; /// +/// # fn main() -> FormatResult<()> { /// let formatted = format!(SimpleFormatContext::default(), [ /// format_args!(text("a"), space(), text("b")) -/// ]).unwrap(); +/// ])?; /// -/// assert_eq!("a b", formatted.print().as_code()); +/// assert_eq!("a b", formatted.print()?.as_code()); +/// # Ok(()) +/// # } /// ``` pub struct Arguments<'fmt, Context>(pub &'fmt [Argument<'fmt, Context>]); @@ -118,8 +121,9 @@ impl<'fmt, Context> From<&'fmt Argument<'fmt, Context>> for Arguments<'fmt, Cont #[cfg(test)] mod tests { + use crate::format_element::tag::Tag; use crate::prelude::*; - use crate::{format_args, format_element, write, FormatState, VecBuffer}; + use crate::{format_args, write, FormatState, VecBuffer}; #[test] fn test_nesting() { @@ -139,17 +143,18 @@ mod tests { .unwrap(); assert_eq!( - buffer.into_element(), - FormatElement::List(List::new(vec![ + buffer.into_vec(), + vec![ FormatElement::Text(Text::Static { text: "function" }), FormatElement::Space, FormatElement::Text(Text::Static { text: "a" }), FormatElement::Space, - FormatElement::Group(format_element::Group::new(vec![ - FormatElement::Text(Text::Static { text: "(" }), - FormatElement::Text(Text::Static { text: ")" }), - ])) - ])) + // Group + FormatElement::Tag(Tag::StartGroup(None)), + FormatElement::Text(Text::Static { text: "(" }), + FormatElement::Text(Text::Static { text: ")" }), + FormatElement::Tag(Tag::EndGroup) + ] ); } } diff --git a/crates/rome_formatter/src/buffer.rs b/crates/rome_formatter/src/buffer.rs index 7695daa0cb9..75742f1636a 100644 --- a/crates/rome_formatter/src/buffer.rs +++ b/crates/rome_formatter/src/buffer.rs @@ -1,5 +1,4 @@ use super::{write, Arguments, FormatElement}; -use crate::format_element::List; use crate::{Format, FormatResult, FormatState}; use std::any::{Any, TypeId}; use std::fmt::Debug; @@ -23,9 +22,9 @@ pub trait Buffer { /// let mut state = FormatState::new(SimpleFormatContext::default()); /// let mut buffer = VecBuffer::new(&mut state); /// - /// buffer.write_element(FormatElement::Text( Text::Static { text: "test"})).unwrap(); + /// buffer.write_element(FormatElement::Text(Text::Static { text: "test"})).unwrap(); /// - /// assert_eq!(buffer.into_element(), FormatElement::Text( Text::Static { text: "test"})); + /// assert_eq!(buffer.into_vec(), vec![FormatElement::Text(Text::Static { text: "test" })]); /// ``` /// fn write_element(&mut self, element: FormatElement) -> FormatResult<()>; @@ -51,7 +50,7 @@ pub trait Buffer { /// /// buffer.write_fmt(format_args!(text("Hello World"))).unwrap(); /// - /// assert_eq!(buffer.into_element(), FormatElement::Text( Text::Static { text: "Hello World"})); + /// assert_eq!(buffer.into_vec(), vec![FormatElement::Text(Text::Static { text: "Hello World" })]); /// ``` fn write_fmt(mut self: &mut Self, arguments: Arguments) -> FormatResult<()> { write(&mut self, arguments) @@ -182,31 +181,21 @@ impl<'a, Context> VecBuffer<'a, Context> { } /// Creates a buffer with the specified capacity - pub fn with_capacity(capacity: usize, context: &'a mut FormatState) -> Self { + pub fn with_capacity(capacity: usize, state: &'a mut FormatState) -> Self { Self { - state: context, + state, elements: Vec::with_capacity(capacity), } } - /// Consumes the buffer and returns its content as a [`FormatElement`] - pub fn into_element(mut self) -> FormatElement { - self.take_element() - } - /// Consumes the buffer and returns the written [`FormatElement]`s as a vector. pub fn into_vec(self) -> Vec { self.elements } /// Takes the elements without consuming self - pub fn take_element(&mut self) -> FormatElement { - if self.len() == 1 { - // Safety: Guaranteed by len check above - self.elements.pop().unwrap() - } else { - FormatElement::List(List::new(std::mem::take(&mut self.elements))) - } + pub fn take_vec(&mut self) -> Vec { + std::mem::take(&mut self.elements) } } @@ -228,10 +217,7 @@ impl Buffer for VecBuffer<'_, Context> { type Context = Context; fn write_element(&mut self, element: FormatElement) -> FormatResult<()> { - match element { - FormatElement::List(list) => self.elements.extend(list.into_vec()), - element => self.elements.push(element), - } + self.elements.push(element); Ok(()) } @@ -274,9 +260,6 @@ Make sure that you take and restore the snapshot in order and that this snapshot /// use rome_formatter::{FormatState, Formatted, PreambleBuffer, SimpleFormatContext, VecBuffer, write}; /// use rome_formatter::prelude::*; /// -/// let mut state = FormatState::new(SimpleFormatContext::default()); -/// let mut buffer = VecBuffer::new(&mut state); -/// /// struct Preamble; /// /// impl Format for Preamble { @@ -285,14 +268,21 @@ Make sure that you take and restore the snapshot in order and that this snapshot /// } /// } /// -/// let mut with_preamble = PreambleBuffer::new(&mut buffer, Preamble); +/// # fn main() -> FormatResult<()> { +/// let mut state = FormatState::new(SimpleFormatContext::default()); +/// let mut buffer = VecBuffer::new(&mut state); +/// +/// { +/// let mut with_preamble = PreambleBuffer::new(&mut buffer, Preamble); /// -/// write!(&mut with_preamble, [text("this text will be on a new line")]).unwrap(); +/// write!(&mut with_preamble, [text("this text will be on a new line")])?; +/// } /// -/// drop(with_preamble); +/// let formatted = Formatted::new(Document::from(buffer.into_vec()), SimpleFormatContext::default()); +/// assert_eq!("# heading\nthis text will be on a new line", formatted.print()?.as_code()); /// -/// let formatted = Formatted::new(buffer.into_element(), SimpleFormatContext::default()); -/// assert_eq!("# heading\nthis text will be on a new line", formatted.print().as_code()); +/// # Ok(()) +/// # } /// ``` /// /// The pre-amble does not get written if no content is written to the buffer. @@ -301,9 +291,6 @@ Make sure that you take and restore the snapshot in order and that this snapshot /// use rome_formatter::{FormatState, Formatted, PreambleBuffer, SimpleFormatContext, VecBuffer, write}; /// use rome_formatter::prelude::*; /// -/// let mut state = FormatState::new(SimpleFormatContext::default()); -/// let mut buffer = VecBuffer::new(&mut state); -/// /// struct Preamble; /// /// impl Format for Preamble { @@ -312,11 +299,17 @@ Make sure that you take and restore the snapshot in order and that this snapshot /// } /// } /// -/// let mut with_preamble = PreambleBuffer::new(&mut buffer, Preamble); -/// drop(with_preamble); +/// # fn main() -> FormatResult<()> { +/// let mut state = FormatState::new(SimpleFormatContext::default()); +/// let mut buffer = VecBuffer::new(&mut state); +/// { +/// let mut with_preamble = PreambleBuffer::new(&mut buffer, Preamble); +/// } /// -/// let formatted = Formatted::new(buffer.into_element(), SimpleFormatContext::default()); -/// assert_eq!("", formatted.print().as_code()); +/// let formatted = Formatted::new(Document::from(buffer.into_vec()), SimpleFormatContext::default()); +/// assert_eq!("", formatted.print()?.as_code()); +/// # Ok(()) +/// # } /// ``` pub struct PreambleBuffer<'buf, Preamble, Context> { /// The wrapped buffer @@ -351,16 +344,12 @@ where type Context = Context; fn write_element(&mut self, element: FormatElement) -> FormatResult<()> { - if element.is_empty() { - Ok(()) - } else { - if self.empty { - write!(self.inner, [&self.preamble])?; - self.empty = false; - } - - self.inner.write_element(element) + if self.empty { + write!(self.inner, [&self.preamble])?; + self.empty = false; } + + self.inner.write_element(element) } fn elements(&self) -> &[FormatElement] { @@ -459,6 +448,7 @@ pub trait BufferExtensions: Buffer + Sized { /// use rome_formatter::prelude::*; /// use rome_formatter::{write, format, SimpleFormatContext}; /// + /// # fn main() -> FormatResult<()> { /// let formatted = format!(SimpleFormatContext::default(), [format_with(|f| { /// let mut recording = f.start_recording(); /// @@ -479,10 +469,11 @@ pub trait BufferExtensions: Buffer + Sized { /// ); /// /// Ok(()) - /// })]).unwrap(); - /// - /// assert_eq!(formatted.print().as_code(), "ABCD"); + /// })])?; /// + /// assert_eq!(formatted.print()?.as_code(), "ABCD"); + /// # Ok(()) + /// # } /// ``` #[must_use] fn start_recording(&mut self) -> Recording { @@ -494,7 +485,7 @@ pub trait BufferExtensions: Buffer + Sized { where I: IntoIterator, { - for element in elements { + for element in elements.into_iter() { self.write_element(element)?; } diff --git a/crates/rome_formatter/src/builders.rs b/crates/rome_formatter/src/builders.rs index d4158fcca85..6ad608a97ae 100644 --- a/crates/rome_formatter/src/builders.rs +++ b/crates/rome_formatter/src/builders.rs @@ -1,3 +1,5 @@ +use crate::format_element::tag::{Condition, Tag}; +use crate::prelude::tag::{DedentMode, LabelId}; use crate::prelude::*; use crate::{format_element, write, Argument, Arguments, GroupId, TextRange, TextSize}; use crate::{Buffer, VecBuffer}; @@ -6,6 +8,7 @@ use std::borrow::Cow; use std::cell::Cell; use std::marker::PhantomData; use std::num::NonZeroU8; +use Tag::*; /// A line break that only gets printed if the enclosing `Group` doesn't fit on a single line. /// It's omitted if the enclosing `Group` fits on a single line. @@ -19,14 +22,17 @@ use std::num::NonZeroU8; /// use rome_formatter::{format, format_args}; /// use rome_formatter::prelude::*; /// +/// # fn main() -> FormatResult<()> { /// let elements = format!(SimpleFormatContext::default(), [ /// group(&format_args![text("a,"), soft_line_break(), text("b")]) -/// ]).unwrap(); +/// ])?; /// /// assert_eq!( /// "a,b", -/// elements.print().as_code() +/// elements.print()?.as_code() /// ); +/// # Ok(()) +/// # } /// ``` /// See [soft_line_break_or_space] if you want to insert a space between the elements if the enclosing /// `Group` fits on a single line. @@ -36,6 +42,7 @@ use std::num::NonZeroU8; /// use rome_formatter::{format, format_args, LineWidth, SimpleFormatOptions}; /// use rome_formatter::prelude::*; /// +/// # fn main() -> FormatResult<()> { /// let context = SimpleFormatContext::new(SimpleFormatOptions { /// line_width: LineWidth::try_from(10).unwrap(), /// ..SimpleFormatOptions::default() @@ -47,12 +54,14 @@ use std::num::NonZeroU8; /// soft_line_break(), /// text("so that the group doesn't fit on a single line"), /// ]) -/// ]).unwrap(); +/// ])?; /// /// assert_eq!( /// "a long word,\nso that the group doesn't fit on a single line", -/// elements.print().as_code() +/// elements.print()?.as_code() /// ); +/// # Ok(()) +/// # } /// ``` #[inline] pub const fn soft_line_break() -> Line { @@ -69,6 +78,7 @@ pub const fn soft_line_break() -> Line { /// use rome_formatter::{format, format_args}; /// use rome_formatter::prelude::*; /// +/// # fn main() -> FormatResult<()> { /// let elements = format!(SimpleFormatContext::default(), [ /// group(&format_args![ /// text("a,"), @@ -76,12 +86,14 @@ pub const fn soft_line_break() -> Line { /// text("b"), /// hard_line_break() /// ]) -/// ]).unwrap(); +/// ])?; /// /// assert_eq!( /// "a,\nb\n", -/// elements.print().as_code() +/// elements.print()?.as_code() /// ); +/// # Ok(()) +/// # } /// ``` #[inline] pub const fn hard_line_break() -> Line { @@ -97,6 +109,7 @@ pub const fn hard_line_break() -> Line { /// use rome_formatter::{format, format_args}; /// use rome_formatter::prelude::*; /// +/// fn main() -> FormatResult<()> { /// let elements = format!( /// SimpleFormatContext::default(), [ /// group(&format_args![ @@ -105,12 +118,14 @@ pub const fn hard_line_break() -> Line { /// text("b"), /// empty_line() /// ]) -/// ]).unwrap(); +/// ])?; /// /// assert_eq!( /// "a,\n\nb\n\n", -/// elements.print().as_code() +/// elements.print()?.as_code() /// ); +/// # Ok(()) +/// # } /// ``` #[inline] pub const fn empty_line() -> Line { @@ -126,18 +141,21 @@ pub const fn empty_line() -> Line { /// use rome_formatter::{format, format_args}; /// use rome_formatter::prelude::*; /// +/// # fn main() -> FormatResult<()> { /// let elements = format!(SimpleFormatContext::default(), [ /// group(&format_args![ /// text("a,"), /// soft_line_break_or_space(), /// text("b"), /// ]) -/// ]).unwrap(); +/// ])?; /// /// assert_eq!( /// "a, b", -/// elements.print().as_code() +/// elements.print()?.as_code() /// ); +/// # Ok(()) +/// # } /// ``` /// /// The printer breaks the lines if the enclosing `Group` doesn't fit on a single line: @@ -145,6 +163,7 @@ pub const fn empty_line() -> Line { /// use rome_formatter::{format_args, format, LineWidth, SimpleFormatOptions}; /// use rome_formatter::prelude::*; /// +/// # fn main() -> FormatResult<()> { /// let context = SimpleFormatContext::new(SimpleFormatOptions { /// line_width: LineWidth::try_from(10).unwrap(), /// ..SimpleFormatOptions::default() @@ -156,12 +175,14 @@ pub const fn empty_line() -> Line { /// soft_line_break_or_space(), /// text("so that the group doesn't fit on a single line"), /// ]) -/// ]).unwrap(); +/// ])?; /// /// assert_eq!( /// "a long word,\nso that the group doesn't fit on a single line", -/// elements.print().as_code() +/// elements.print()?.as_code() /// ); +/// # Ok(()) +/// # } /// ``` #[inline] pub const fn soft_line_break_or_space() -> Line { @@ -204,12 +225,15 @@ impl std::fmt::Debug for Line { /// use rome_formatter::format; /// use rome_formatter::prelude::*; /// -/// let elements = format!(SimpleFormatContext::default(), [text("Hello World")]).unwrap(); +/// # fn main() -> FormatResult<()> { +/// let elements = format!(SimpleFormatContext::default(), [text("Hello World")])?; /// /// assert_eq!( /// "Hello World", -/// elements.print().as_code() +/// elements.print()?.as_code() /// ); +/// # Ok(()) +/// # } /// ``` /// /// Printing a string literal as a literal requires that the string literal is properly escaped and @@ -219,10 +243,13 @@ impl std::fmt::Debug for Line { /// use rome_formatter::format; /// use rome_formatter::prelude::*; /// +/// # fn main() -> FormatResult<()> { /// // the tab must be encoded as \\t to not literally print a tab character ("Hello{tab}World" vs "Hello\tWorld") -/// let elements = format!(SimpleFormatContext::default(), [text("\"Hello\\tWorld\"")]).unwrap(); +/// let elements = format!(SimpleFormatContext::default(), [text("\"Hello\\tWorld\"")])?; /// -/// assert_eq!(r#""Hello\tWorld""#, elements.print().as_code()); +/// assert_eq!(r#""Hello\tWorld""#, elements.print()?.as_code()); +/// # Ok(()) +/// # } /// ``` #[inline] pub fn text(text: &'static str) -> StaticText { @@ -375,16 +402,19 @@ fn debug_assert_no_newlines(text: &str) { /// use rome_formatter::{format}; /// use rome_formatter::prelude::*; /// +/// fn main() -> FormatResult<()> { /// let elements = format!(SimpleFormatContext::default(), [ /// text("a"), /// line_suffix(&text("c")), /// text("b") -/// ]).unwrap(); +/// ])?; /// /// assert_eq!( /// "abc", -/// elements.print().as_code() +/// elements.print()?.as_code() /// ); +/// # Ok(()) +/// # } /// ``` #[inline] pub fn line_suffix(inner: &Content) -> LineSuffix @@ -403,11 +433,9 @@ pub struct LineSuffix<'a, Context> { impl Format for LineSuffix<'_, Context> { fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { - let mut buffer = VecBuffer::new(f.state_mut()); - buffer.write_fmt(Arguments::from(&self.content))?; - - let content = buffer.into_vec(); - f.write_element(FormatElement::LineSuffix(content.into_boxed_slice())) + f.write_element(FormatElement::Tag(StartLineSuffix))?; + Arguments::from(&self.content).fmt(f)?; + f.write_element(FormatElement::Tag(EndLineSuffix)) } } @@ -427,18 +455,21 @@ impl std::fmt::Debug for LineSuffix<'_, Context> { /// use rome_formatter::format; /// use rome_formatter::prelude::*; /// +/// # fn main() -> FormatResult<()> { /// let elements = format!(SimpleFormatContext::default(), [ /// text("a"), /// line_suffix(&text("c")), /// text("b"), /// line_suffix_boundary(), /// text("d") -/// ]).unwrap(); +/// ])?; /// /// assert_eq!( /// "abc\nd", -/// elements.print().as_code() +/// elements.print()?.as_code() /// ); +/// # Ok(()) +/// # } /// ``` pub const fn line_suffix_boundary() -> LineSuffixBoundary { LineSuffixBoundary @@ -456,8 +487,8 @@ impl Format for LineSuffixBoundary { /// Marks some content with a label. /// /// This does not directly influence how this content will be printed, but some -/// parts of the formatter may inspect the [labelled element](FormatElement::Label) -/// using [FormatElement::has_label]. +/// parts of the formatter may inspect the [labelled element](Tag::StartLabelled) +/// using [FormatElements::has_label]. /// /// ## Examples /// @@ -467,6 +498,7 @@ impl Format for LineSuffixBoundary { /// /// enum SomeLabelId {} /// +/// # fn main() -> FormatResult<()> { /// let formatted = format!( /// SimpleFormatContext::default(), /// [format_with(|f| { @@ -480,7 +512,7 @@ impl Format for LineSuffixBoundary { /// /// let recorded = recording.stop(); /// -/// let is_labelled = recorded.last().map_or(false, |element| element.has_label(LabelId::of::())); +/// let is_labelled = recorded.first().map_or(false, |element| element.has_label(LabelId::of::())); /// /// if is_labelled { /// write!(f, [text(" has label SomeLabelId")]) @@ -488,10 +520,11 @@ impl Format for LineSuffixBoundary { /// write!(f, [text(" doesn't have label SomeLabelId")]) /// } /// })] -/// ) -/// .unwrap(); +/// )?; /// -/// assert_eq!("'I have a label' has label SomeLabelId", formatted.print().as_code()); +/// assert_eq!("'I have a label' has label SomeLabelId", formatted.print()?.as_code()); +/// # Ok(()) +/// # } /// ``` /// /// ## Alternatives @@ -517,14 +550,9 @@ pub struct FormatLabelled<'a, Context> { impl Format for FormatLabelled<'_, Context> { fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { - let mut buffer = VecBuffer::new(f.state_mut()); - - buffer.write_fmt(Arguments::from(&self.content))?; - let content = buffer.into_vec(); - - let label = Label::new(self.label_id, content); - - f.write_element(FormatElement::Label(label)) + f.write_element(FormatElement::Tag(StartLabelled(self.label_id)))?; + Arguments::from(&self.content).fmt(f)?; + f.write_element(FormatElement::Tag(EndLabelled)) } } @@ -545,10 +573,13 @@ impl std::fmt::Debug for FormatLabelled<'_, Context> { /// use rome_formatter::format; /// use rome_formatter::prelude::*; /// +/// # fn main() -> FormatResult<()> { /// // the tab must be encoded as \\t to not literally print a tab character ("Hello{tab}World" vs "Hello\tWorld") -/// let elements = format!(SimpleFormatContext::default(), [text("a"), space(), text("b")]).unwrap(); +/// let elements = format!(SimpleFormatContext::default(), [text("a"), space(), text("b")])?; /// -/// assert_eq!("a b", elements.print().as_code()); +/// assert_eq!("a b", elements.print()?.as_code()); +/// # Ok(()) +/// # } /// ``` #[inline] pub const fn space() -> Space { @@ -578,6 +609,7 @@ impl Format for Space { /// use rome_formatter::{format, format_args}; /// use rome_formatter::prelude::*; /// +/// # fn main() -> FormatResult<()> { /// let block = format!(SimpleFormatContext::default(), [ /// text("switch {"), /// block_indent(&format_args![ @@ -589,12 +621,14 @@ impl Format for Space { /// ]) /// ]), /// text("}"), -/// ]).unwrap(); +/// ])?; /// /// assert_eq!( /// "switch {\n\tdefault:\n\t\tbreak;\n}", -/// block.print().as_code() +/// block.print()?.as_code() /// ); +/// # Ok(()) +/// # } /// ``` #[inline] pub fn indent(content: &Content) -> Indent @@ -613,16 +647,9 @@ pub struct Indent<'a, Context> { impl Format for Indent<'_, Context> { fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { - let mut buffer = VecBuffer::new(f.state_mut()); - - buffer.write_fmt(Arguments::from(&self.content))?; - - if buffer.is_empty() { - return Ok(()); - } - - let content = buffer.into_vec(); - f.write_element(FormatElement::Indent(content.into_boxed_slice())) + f.write_element(FormatElement::Tag(StartIndent))?; + Arguments::from(&self.content).fmt(f)?; + f.write_element(FormatElement::Tag(EndIndent)) } } @@ -644,6 +671,7 @@ impl std::fmt::Debug for Indent<'_, Context> { /// use rome_formatter::{format, format_args}; /// use rome_formatter::prelude::*; /// +/// # fn main() -> FormatResult<()> { /// let block = format!(SimpleFormatContext::default(), [ /// text("root"), /// align(2, &format_args![ @@ -662,12 +690,14 @@ impl std::fmt::Debug for Indent<'_, Context> { /// hard_line_break(), /// text("Dedent on root level is a no-op.") /// ]) -/// ]).unwrap(); +/// ])?; /// /// assert_eq!( /// "root\n aligned\nnot aligned\n\tIndented, not aligned\nDedent on root level is a no-op.", -/// block.print().as_code() +/// block.print()?.as_code() /// ); +/// # Ok(()) +/// # } /// ``` #[inline] pub fn dedent(content: &Content) -> Dedent @@ -688,19 +718,9 @@ pub struct Dedent<'a, Context> { impl Format for Dedent<'_, Context> { fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { - let mut buffer = VecBuffer::new(f.state_mut()); - - buffer.write_fmt(Arguments::from(&self.content))?; - - if buffer.is_empty() { - return Ok(()); - } - - let content = buffer.into_vec(); - f.write_element(FormatElement::Dedent { - content: content.into_boxed_slice(), - mode: self.mode, - }) + f.write_element(FormatElement::Tag(StartDedent(self.mode)))?; + Arguments::from(&self.content).fmt(f)?; + f.write_element(FormatElement::Tag(EndDedent)) } } @@ -718,6 +738,7 @@ impl std::fmt::Debug for Dedent<'_, Context> { /// use rome_formatter::{format, format_args}; /// use rome_formatter::prelude::*; /// +/// # fn main() -> FormatResult<()> { /// let block = format!(SimpleFormatContext::default(), [ /// text("root"), /// indent(&format_args![ @@ -738,12 +759,14 @@ impl std::fmt::Debug for Dedent<'_, Context> { /// text("end indent level 2"), /// ]) /// ]), -/// ]).unwrap(); +/// ])?; /// /// assert_eq!( /// "root\n\tindent level 1\n\t\tindent level 2\n\t\t two space align\nstarts at the beginning of the line\n\t\tend indent level 2", -/// block.print().as_code() +/// block.print()?.as_code() /// ); +/// # Ok(()) +/// # } /// ``` /// /// ## Prettier @@ -777,6 +800,7 @@ where /// use rome_formatter::{format, format_args}; /// use rome_formatter::prelude::*; /// +/// # fn main() -> FormatResult<()> { /// let block = format!(SimpleFormatContext::default(), [ /// text("a"), /// hard_line_break(), @@ -796,12 +820,14 @@ where /// text("}"), /// ]), /// text(";") -/// ]).unwrap(); +/// ])?; /// /// assert_eq!( /// "a\n? function () {\n }\n: function () {\n\t\tconsole.log('test');\n };", -/// block.print().as_code() +/// block.print()?.as_code() /// ); +/// # Ok(()) +/// # } /// ``` /// /// You can see that: @@ -819,6 +845,7 @@ where /// use rome_formatter::{format, format_args, IndentStyle, SimpleFormatOptions}; /// use rome_formatter::prelude::*; /// +/// # fn main() -> FormatResult<()> { /// let context = SimpleFormatContext::new(SimpleFormatOptions { /// indent_style: IndentStyle::Space(4), /// ..SimpleFormatOptions::default() @@ -843,12 +870,14 @@ where /// text("}"), /// ]), /// text(";") -/// ]).unwrap(); +/// ])?; /// /// assert_eq!( /// "a\n? function () {\n }\n: function () {\n console.log('test');\n };", -/// block.print().as_code() +/// block.print()?.as_code() /// ); +/// # Ok(()) +/// # } /// ``` /// /// The printing of `align` differs if using spaces as indention sequence *and* it contains an `indent`. @@ -874,19 +903,9 @@ pub struct Align<'a, Context> { impl Format for Align<'_, Context> { fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { - let mut buffer = VecBuffer::new(f.state_mut()); - - buffer.write_fmt(Arguments::from(&self.content))?; - - if buffer.is_empty() { - return Ok(()); - } - - let content = buffer.into_vec(); - f.write_element(FormatElement::Align(format_element::Align { - content: content.into_boxed_slice(), - count: self.count, - })) + f.write_element(FormatElement::Tag(StartAlign(tag::Align(self.count))))?; + Arguments::from(&self.content).fmt(f)?; + f.write_element(FormatElement::Tag(EndAlign)) } } @@ -912,6 +931,7 @@ impl std::fmt::Debug for Align<'_, Context> { /// use rome_formatter::{format, format_args}; /// use rome_formatter::prelude::*; /// +/// # fn main() -> FormatResult<()> { /// let block = format![ /// SimpleFormatContext::default(), /// [ @@ -923,12 +943,14 @@ impl std::fmt::Debug for Align<'_, Context> { /// ]), /// text("}"), /// ] -/// ].unwrap(); +/// ]?; /// /// assert_eq!( /// "{\n\tlet a = 10;\n\tlet c = a + 5;\n}", -/// block.print().as_code() +/// block.print()?.as_code() /// ); +/// # Ok(()) +/// # } /// ``` #[inline] pub fn block_indent(content: &impl Format) -> BlockIndent { @@ -950,6 +972,7 @@ pub fn block_indent(content: &impl Format) -> BlockIndent FormatResult<()> { /// let context = SimpleFormatContext::new(SimpleFormatOptions { /// line_width: LineWidth::try_from(10).unwrap(), /// ..SimpleFormatOptions::default() @@ -965,12 +988,14 @@ pub fn block_indent(content: &impl Format) -> BlockIndent(content: &impl Format) -> BlockIndent FormatResult<()> { /// let elements = format!(SimpleFormatContext::default(), [ /// group(&format_args![ /// text("["), @@ -988,12 +1014,14 @@ pub fn block_indent(content: &impl Format) -> BlockIndent(content: &impl Format) -> BlockIndent { @@ -1018,6 +1046,7 @@ pub fn soft_block_indent(content: &impl Format) -> BlockIndent /// use rome_formatter::{format, format_args, LineWidth, SimpleFormatOptions}; /// use rome_formatter::prelude::*; /// +/// # fn main() -> FormatResult<()> { /// let context = SimpleFormatContext::new(SimpleFormatOptions { /// line_width: LineWidth::try_from(10).unwrap(), /// ..SimpleFormatOptions::default() @@ -1036,12 +1065,14 @@ pub fn soft_block_indent(content: &impl Format) -> BlockIndent /// text("lastName"), /// ]), /// ]) -/// ]).unwrap(); +/// ])?; /// /// assert_eq!( /// "name =\n\tfirstName + lastName", -/// elements.print().as_code() +/// elements.print()?.as_code() /// ); +/// # Ok(()) +/// # } /// ``` /// /// Only adds a space if the enclosing `Group` fits on a single line @@ -1049,6 +1080,7 @@ pub fn soft_block_indent(content: &impl Format) -> BlockIndent /// use rome_formatter::{format, format_args}; /// use rome_formatter::prelude::*; /// +/// # fn main() -> FormatResult<()> { /// let elements = format!(SimpleFormatContext::default(), [ /// group(&format_args![ /// text("a"), @@ -1056,12 +1088,14 @@ pub fn soft_block_indent(content: &impl Format) -> BlockIndent /// text("="), /// soft_line_indent_or_space(&text("10")), /// ]) -/// ]).unwrap(); +/// ])?; /// /// assert_eq!( /// "a = 10", -/// elements.print().as_code() +/// elements.print()?.as_code() /// ); +/// # Ok(()) +/// # } /// ``` #[inline] pub fn soft_line_indent_or_space(content: &impl Format) -> BlockIndent { @@ -1087,35 +1121,37 @@ enum IndentMode { impl Format for BlockIndent<'_, Context> { fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { - let mut buffer = VecBuffer::new(f.state_mut()); + let snapshot = f.snapshot(); + + f.write_element(FormatElement::Tag(StartIndent))?; match self.mode { - IndentMode::Soft => write!(buffer, [soft_line_break()])?, - IndentMode::Block => write!(buffer, [hard_line_break()])?, + IndentMode::Soft => write!(f, [soft_line_break()])?, + IndentMode::Block => write!(f, [hard_line_break()])?, IndentMode::SoftLineOrSpace | IndentMode::SoftSpace => { - write!(buffer, [soft_line_break_or_space()])? + write!(f, [soft_line_break_or_space()])? } - }; + } - buffer.write_fmt(Arguments::from(&self.content))?; + let is_empty = { + let mut recording = f.start_recording(); + recording.write_fmt(Arguments::from(&self.content))?; + recording.stop().is_empty() + }; - // Don't create an indent if the content is empty - if buffer.len() == 1 { + if is_empty { + f.restore_snapshot(snapshot); return Ok(()); } - let content = buffer.into_vec(); - - f.write_element(FormatElement::Indent(content.into_boxed_slice()))?; + f.write_element(FormatElement::Tag(EndIndent))?; match self.mode { - IndentMode::Soft => write!(f, [soft_line_break()])?, - IndentMode::Block => write!(f, [hard_line_break()])?, - IndentMode::SoftSpace => write!(f, [soft_line_break_or_space()])?, - IndentMode::SoftLineOrSpace => {} + IndentMode::Soft => write!(f, [soft_line_break()]), + IndentMode::Block => write!(f, [hard_line_break()]), + IndentMode::SoftSpace => write!(f, [soft_line_break_or_space()]), + IndentMode::SoftLineOrSpace => Ok(()), } - - Ok(()) } } @@ -1142,6 +1178,7 @@ impl std::fmt::Debug for BlockIndent<'_, Context> { /// use rome_formatter::{format, format_args, LineWidth, SimpleFormatOptions}; /// use rome_formatter::prelude::*; /// +/// # fn main() -> FormatResult<()> { /// let context = SimpleFormatContext::new(SimpleFormatOptions { /// line_width: LineWidth::try_from(10).unwrap(), /// ..SimpleFormatOptions::default() @@ -1158,12 +1195,14 @@ impl std::fmt::Debug for BlockIndent<'_, Context> { /// ]), /// text("}") /// ]) -/// ]).unwrap(); +/// ])?; /// /// assert_eq!( /// "{\n\taPropertyThatExceeds: 'line width'\n}", -/// elements.print().as_code() +/// elements.print()?.as_code() /// ); +/// # Ok(()) +/// # } /// ``` /// /// Adds spaces around the content if the group fits on the line @@ -1171,6 +1210,7 @@ impl std::fmt::Debug for BlockIndent<'_, Context> { /// use rome_formatter::{format, format_args}; /// use rome_formatter::prelude::*; /// +/// # fn main() -> FormatResult<()> { /// let elements = format!(SimpleFormatContext::default(), [ /// group(&format_args![ /// text("{"), @@ -1182,12 +1222,14 @@ impl std::fmt::Debug for BlockIndent<'_, Context> { /// ]), /// text("}") /// ]) -/// ]).unwrap(); +/// ])?; /// /// assert_eq!( /// "{ a: 5 }", -/// elements.print().as_code() +/// elements.print()?.as_code() /// ); +/// # Ok(()) +/// # } /// ``` pub fn soft_space_or_block_indent(content: &impl Format) -> BlockIndent { BlockIndent { @@ -1213,6 +1255,7 @@ pub fn soft_space_or_block_indent(content: &impl Format) -> Bl /// use rome_formatter::{format, format_args}; /// use rome_formatter::prelude::*; /// +/// # fn main() -> FormatResult<()> { /// let elements = format!(SimpleFormatContext::default(), [ /// group(&format_args![ /// text("["), @@ -1225,12 +1268,14 @@ pub fn soft_space_or_block_indent(content: &impl Format) -> Bl /// ]), /// text("]"), /// ]) -/// ]).unwrap(); +/// ])?; /// /// assert_eq!( /// "[1, 2, 3]", -/// elements.print().as_code() +/// elements.print()?.as_code() /// ); +/// # Ok(()) +/// # } /// ``` /// /// The printer breaks the `Group` over multiple lines if its content doesn't fit on a single line @@ -1238,6 +1283,7 @@ pub fn soft_space_or_block_indent(content: &impl Format) -> Bl /// use rome_formatter::{format, format_args, LineWidth, SimpleFormatOptions}; /// use rome_formatter::prelude::*; /// +/// # fn main() -> FormatResult<()> { /// let context = SimpleFormatContext::new(SimpleFormatOptions { /// line_width: LineWidth::try_from(20).unwrap(), /// ..SimpleFormatOptions::default() @@ -1255,12 +1301,14 @@ pub fn soft_space_or_block_indent(content: &impl Format) -> Bl /// ]), /// text("]"), /// ]) -/// ]).unwrap(); +/// ])?; /// /// assert_eq!( /// "[\n\t'Good morning! How are you today?',\n\t2,\n\t3\n]", -/// elements.print().as_code() +/// elements.print()?.as_code() /// ); +/// # Ok(()) +/// # } /// ``` #[inline] pub fn group(content: &impl Format) -> Group { @@ -1288,8 +1336,8 @@ impl Group<'_, Context> { /// line or contains any hard line breaks. /// /// The formatter writes a [FormatElement::ExpandParent], forcing any enclosing group to expand, if `should_expand` is `true`. - /// It also omits the enclosing [FormatElement::Group] because the group would be forced to expand anyway. - /// The [FormatElement:Group] only gets written if the `group_id` specified with [Group::with_group_id] isn't [None] + /// It also omits the [`start`](Tag::StartGroup) and [`end`](Tag::EndGroup) tags because the group would be forced to expand anyway. + /// The [`start`](Tag::StartGroup) and [`end`](Tag::EndGroup) tags are only written if the `group_id` specified with [Group::with_group_id] isn't [None] /// because other IR elements may reference the group with that group id and the printer may panic /// if no group with the given id is present in the document. pub fn should_expand(mut self, should_expand: bool) -> Self { @@ -1305,22 +1353,23 @@ impl Format for Group<'_, Context> { return f.write_fmt(Arguments::from(&self.content)); } - let mut buffer = VecBuffer::new(f.state_mut()); + let write_group = !self.should_expand || self.group_id.is_some(); - buffer.write_fmt(Arguments::from(&self.content))?; + if write_group { + f.write_element(FormatElement::Tag(Tag::StartGroup(self.group_id)))?; + } if self.should_expand { - write!(buffer, [expand_parent()])?; + write!(f, [expand_parent()])?; } - let content = buffer.into_vec(); - if content.is_empty() && self.group_id.is_none() { - return Ok(()); - } + Arguments::from(&self.content).fmt(f)?; - let group = format_element::Group::new(content).with_id(self.group_id); + if write_group { + f.write_element(FormatElement::Tag(Tag::EndGroup))?; + } - f.write_element(FormatElement::Group(group)) + Ok(()) } } @@ -1344,6 +1393,7 @@ impl std::fmt::Debug for Group<'_, Context> { /// use rome_formatter::{format, format_args, LineWidth}; /// use rome_formatter::prelude::*; /// +/// # fn main() -> FormatResult<()> { /// let elements = format!(SimpleFormatContext::default(), [ /// group(&format_args![ /// text("["), @@ -1357,12 +1407,14 @@ impl std::fmt::Debug for Group<'_, Context> { /// ]), /// text("]"), /// ]) -/// ]).unwrap(); +/// ])?; /// /// assert_eq!( /// "[\n\t'Good morning! How are you today?',\n\t2,\n\t3\n]", -/// elements.print().as_code() +/// elements.print()?.as_code() /// ); +/// # Ok(()) +/// # } /// ``` /// /// # Prettier @@ -1395,6 +1447,7 @@ impl Format for ExpandParent { /// use rome_formatter::{format, format_args}; /// use rome_formatter::prelude::*; /// +/// # fn main() -> FormatResult<()> { /// let elements = format!(SimpleFormatContext::default(), [ /// group(&format_args![ /// text("["), @@ -1408,12 +1461,14 @@ impl Format for ExpandParent { /// ]), /// text("]"), /// ]) -/// ]).unwrap(); +/// ])?; /// /// assert_eq!( /// "[1, 2, 3]", -/// elements.print().as_code() +/// elements.print()?.as_code() /// ); +/// # Ok(()) +/// # } /// ``` /// /// Prints the trailing comma for the last array element if the `Group` doesn't fit on a single line @@ -1422,6 +1477,7 @@ impl Format for ExpandParent { /// use rome_formatter::prelude::*; /// use rome_formatter::printer::PrintWidth; /// +/// fn main() -> FormatResult<()> { /// let context = SimpleFormatContext::new(SimpleFormatOptions { /// line_width: LineWidth::try_from(20).unwrap(), /// ..SimpleFormatOptions::default() @@ -1440,12 +1496,14 @@ impl Format for ExpandParent { /// ]), /// text("]"), /// ]) -/// ]).unwrap(); +/// ])?; /// /// assert_eq!( /// "[\n\t'A somewhat longer string to force a line break',\n\t2,\n\t3,\n]", -/// elements.print().as_code() +/// elements.print()?.as_code() /// ); +/// # Ok(()) +/// # } /// ``` #[inline] pub fn if_group_breaks(content: &Content) -> IfGroupBreaks @@ -1471,6 +1529,7 @@ where /// use rome_formatter::{format, format_args}; /// use rome_formatter::prelude::*; /// +/// # fn main() -> FormatResult<()> { /// let formatted = format!(SimpleFormatContext::default(), [ /// group(&format_args![ /// text("["), @@ -1484,12 +1543,14 @@ where /// ]), /// text("]"), /// ]) -/// ]).unwrap(); +/// ])?; /// /// assert_eq!( /// "[1, 2, 3,]", -/// formatted.print().as_code() +/// formatted.print()?.as_code() /// ); +/// # Ok(()) +/// # } /// ``` /// /// Omits the trailing comma for the last array element if the `Group` doesn't fit on a single line @@ -1497,6 +1558,7 @@ where /// use rome_formatter::{format, format_args, LineWidth, SimpleFormatOptions}; /// use rome_formatter::prelude::*; /// +/// # fn main() -> FormatResult<()> { /// let context = SimpleFormatContext::new(SimpleFormatOptions { /// line_width: LineWidth::try_from(20).unwrap(), /// ..SimpleFormatOptions::default() @@ -1515,12 +1577,14 @@ where /// ]), /// text("]"), /// ]) -/// ]).unwrap(); +/// ])?; /// /// assert_eq!( /// "[\n\t'A somewhat longer string to force a line break',\n\t2,\n\t3\n]", -/// formatted.print().as_code() +/// formatted.print()?.as_code() /// ); +/// # Ok(()) +/// # } /// ``` #[inline] pub fn if_group_fits_on_line(flat_content: &Content) -> IfGroupBreaks @@ -1556,6 +1620,7 @@ impl IfGroupBreaks<'_, Context> { /// use rome_formatter::{format, format_args, write, LineWidth, SimpleFormatOptions}; /// use rome_formatter::prelude::*; /// + /// # fn main() -> FormatResult<()> { /// let context = SimpleFormatContext::new(SimpleFormatOptions { /// line_width: LineWidth::try_from(20).unwrap(), /// ..SimpleFormatOptions::default() @@ -1585,12 +1650,14 @@ impl IfGroupBreaks<'_, Context> { /// ], /// ).with_group_id(Some(group_id)) /// ]) - /// })]).unwrap(); + /// })])?; /// /// assert_eq!( /// "[\n\t1, 234568789,\n\t3456789, [4],\n]", - /// formatted.print().as_code() + /// formatted.print()?.as_code() /// ); + /// # Ok(()) + /// # } /// ``` pub fn with_group_id(mut self, group_id: Option) -> Self { self.group_id = group_id; @@ -1600,19 +1667,11 @@ impl IfGroupBreaks<'_, Context> { impl Format for IfGroupBreaks<'_, Context> { fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { - let mut buffer = VecBuffer::new(f.state_mut()); - - buffer.write_fmt(Arguments::from(&self.content))?; - - if buffer.is_empty() { - return Ok(()); - } - - let content = buffer.into_vec(); - f.write_element(FormatElement::ConditionalGroupContent( - ConditionalGroupContent::new(content.into_boxed_slice(), self.mode) - .with_group_id(self.group_id), - )) + f.write_element(FormatElement::Tag(StartConditionalContent( + Condition::new(self.mode).with_group_id(self.group_id), + )))?; + Arguments::from(&self.content).fmt(f)?; + f.write_element(FormatElement::Tag(EndConditionalContent)) } } @@ -1661,6 +1720,7 @@ impl std::fmt::Debug for IfGroupBreaks<'_, Context> { /// use rome_formatter::{format, format_args, LineWidth, SimpleFormatOptions, write}; /// use rome_formatter::prelude::*; /// +/// # fn main() -> FormatResult<()> { /// let content = format_with(|f| { /// let group_id = f.group_id("header"); /// @@ -1675,12 +1735,14 @@ impl std::fmt::Debug for IfGroupBreaks<'_, Context> { /// ..SimpleFormatOptions::default() /// }); /// -/// let formatted = format!(context, [content]).unwrap(); +/// let formatted = format!(context, [content])?; /// /// assert_eq!( /// "(aLongHeaderThatBreaksForSomeReason) =>\n\ta => b", -/// formatted.print().as_code() +/// formatted.print()?.as_code() /// ); +/// # Ok(()) +/// # } /// ``` /// /// It doesn't add an indent if the group wrapping the signature doesn't break: @@ -1688,6 +1750,7 @@ impl std::fmt::Debug for IfGroupBreaks<'_, Context> { /// use rome_formatter::{format, format_args, LineWidth, SimpleFormatOptions, write}; /// use rome_formatter::prelude::*; /// +/// # fn main() -> FormatResult<()> { /// let content = format_with(|f| { /// let group_id = f.group_id("header"); /// @@ -1697,12 +1760,14 @@ impl std::fmt::Debug for IfGroupBreaks<'_, Context> { /// ]) /// }); /// -/// let formatted = format!(SimpleFormatContext::default(), [content]).unwrap(); +/// let formatted = format!(SimpleFormatContext::default(), [content])?; /// /// assert_eq!( /// "(aLongHeaderThatBreaksForSomeReason) =>\na => b", -/// formatted.print().as_code() +/// formatted.print()?.as_code() /// ); +/// # Ok(()) +/// # } /// ``` #[inline] pub fn indent_if_group_breaks( @@ -1726,18 +1791,9 @@ pub struct IndentIfGroupBreaks<'a, Context> { impl Format for IndentIfGroupBreaks<'_, Context> { fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { - let mut buffer = VecBuffer::new(f.state_mut()); - - buffer.write_fmt(Arguments::from(&self.content))?; - - if buffer.is_empty() { - return Ok(()); - } - - let content = buffer.into_vec(); - f.write_element(FormatElement::IndentIfGroupBreaks( - format_element::IndentIfGroupBreaks::new(content.into_boxed_slice(), self.group_id), - )) + f.write_element(FormatElement::Tag(StartIndentIfGroupBreaks(self.group_id)))?; + Arguments::from(&self.content).fmt(f)?; + f.write_element(FormatElement::Tag(EndIndentIfGroupBreaks)) } } @@ -1804,9 +1860,12 @@ impl std::fmt::Debug for FormatWith { /// } /// } /// -/// let formatted = format!(SimpleFormatContext::default(), [MyFormat { items: vec!["a", "b", "c"]}]).unwrap(); +/// # fn main() -> FormatResult<()> { +/// let formatted = format!(SimpleFormatContext::default(), [MyFormat { items: vec!["a", "b", "c"]}])?; /// -/// assert_eq!("(\n\ta b c\n)", formatted.print().as_code()); +/// assert_eq!("(\n\ta b c\n)", formatted.print()?.as_code()); +/// # Ok(()) +/// # } /// ``` pub const fn format_with(formatter: T) -> FormatWith where @@ -1859,9 +1918,12 @@ where /// } /// } /// -/// let formatted = format!(SimpleFormatContext::default(), [MyFormat]).unwrap(); +/// # fn main() -> FormatResult<()> { +/// let formatted = format!(SimpleFormatContext::default(), [MyFormat])?; /// -/// assert_eq!("1\n\t2\n\t3\n\t4\n", formatted.print().as_code()); +/// assert_eq!("1\n\t2\n\t3\n\t4\n", formatted.print()?.as_code()); +/// # Ok(()) +/// # } /// ``` /// /// Formatting the same value twice results in a panic. @@ -2077,15 +2139,17 @@ pub fn get_lines_before(next_node: &SyntaxNode) -> usize { pub struct FillBuilder<'fmt, 'buf, Context> { result: FormatResult<()>, fmt: &'fmt mut Formatter<'buf, Context>, - items: Vec, + empty: bool, } impl<'a, 'buf, Context> FillBuilder<'a, 'buf, Context> { pub(crate) fn new(fmt: &'a mut Formatter<'buf, Context>) -> Self { + let result = fmt.write_element(FormatElement::Tag(StartFill)); + Self { - result: Ok(()), + result, fmt, - items: vec![], + empty: true, } } @@ -2109,17 +2173,17 @@ impl<'a, 'buf, Context> FillBuilder<'a, 'buf, Context> { entry: &dyn Format, ) -> &mut Self { self.result = self.result.and_then(|_| { - let mut buffer = VecBuffer::new(self.fmt.state_mut()); - - if !self.items.is_empty() { - write!(buffer, [separator])?; - self.items.push(buffer.take_element()); + if self.empty { + self.empty = false; + } else { + self.fmt.write_element(FormatElement::Tag(StartEntry))?; + separator.fmt(self.fmt)?; + self.fmt.write_element(FormatElement::Tag(EndEntry))?; } - write!(buffer, [entry])?; - self.items.push(buffer.into_element()); - - Ok(()) + self.fmt.write_element(FormatElement::Tag(StartEntry))?; + entry.fmt(self.fmt)?; + self.fmt.write_element(FormatElement::Tag(EndEntry)) }); self @@ -2127,17 +2191,8 @@ impl<'a, 'buf, Context> FillBuilder<'a, 'buf, Context> { /// Finishes the output and returns any error encountered pub fn finish(&mut self) -> FormatResult<()> { - self.result.and_then(|_| { - let mut items = std::mem::take(&mut self.items); - - match items.len() { - 0 => Ok(()), - 1 => self.fmt.write_element(items.pop().unwrap()), - _ => self - .fmt - .write_element(FormatElement::Fill(items.into_boxed_slice())), - } - }) + self.result + .and_then(|_| self.fmt.write_element(FormatElement::Tag(EndFill))) } } @@ -2158,7 +2213,7 @@ impl<'a, Context> BestFitting<'a, Context> { /// ## Safety /// The slice must contain at least two variants. pub unsafe fn from_arguments_unchecked(variants: Arguments<'a, Context>) -> Self { - debug_assert!( + assert!( variants.0.len() >= 2, "Requires at least the least expanded and most expanded variants" ); @@ -2175,9 +2230,11 @@ impl Format for BestFitting<'_, Context> { let mut formatted_variants = Vec::with_capacity(variants.len()); for variant in variants { + buffer.write_element(FormatElement::Tag(StartEntry))?; buffer.write_fmt(Arguments::from(variant))?; + buffer.write_element(FormatElement::Tag(EndEntry))?; - formatted_variants.push(buffer.take_element()); + formatted_variants.push(buffer.take_vec().into_boxed_slice()); } // SAFETY: The constructor guarantees that there are always at least two variants. It's, therefore, @@ -2188,8 +2245,6 @@ impl Format for BestFitting<'_, Context> { )) }; - f.write_element(element)?; - - Ok(()) + f.write_element(element) } } diff --git a/crates/rome_formatter/src/format_element.rs b/crates/rome_formatter/src/format_element.rs index 9ab1388e62f..ae2523c7bcb 100644 --- a/crates/rome_formatter/src/format_element.rs +++ b/crates/rome_formatter/src/format_element.rs @@ -1,26 +1,17 @@ -use crate::prelude::{dynamic_text, format_with}; -use crate::printer::LineEnding; -use crate::{ - format, format_args, group, soft_block_indent, soft_line_break_or_space, - soft_line_indent_or_space, space, text, write, Buffer, Format, FormatContext, FormatOptions, - FormatResult, Formatter, GroupId, IndentStyle, LineWidth, PrinterOptions, TextSize, - TransformSourceMap, -}; +pub mod document; +pub mod tag; + +use crate::format_element::tag::{LabelId, Tag}; + +use crate::{TagKind, TextSize}; #[cfg(target_pointer_width = "64")] use rome_rowan::static_assert; use rome_rowan::SyntaxTokenText; -use rustc_hash::FxHashMap; -#[cfg(debug_assertions)] -use std::any::type_name; -use std::any::TypeId; use std::borrow::Cow; use std::hash::{Hash, Hasher}; -use std::num::NonZeroU8; use std::ops::Deref; use std::rc::Rc; -type Content = Box<[FormatElement]>; - /// Language agnostic IR for formatting source code. /// /// Use the helper functions like [crate::space], [crate::soft_line_break] etc. defined in this file to create elements. @@ -32,175 +23,43 @@ pub enum FormatElement { /// A new line, see [crate::soft_line_break], [crate::hard_line_break], and [crate::soft_line_break_or_space] for documentation. Line(LineMode), - /// Indents the content one level deeper, see [crate::indent] for documentation and examples. - Indent(Content), - - /// Variant of [FormatElement::Indent] that indents content by a number of spaces. For example, `Align(2)` - /// indents any content following a line break by an additional two spaces. - /// - /// Nesting (Aligns)[FormatElement::Align] has the effect that all except the most inner align are handled as (Indent)[FormatElement::Indent]. - Align(Align), - - /// Reduces the indention of the specified content either by one level or to the root, depending on the mode. - /// Reverse operation of `Indent` and can be used to *undo* an `Align` for nested content. - Dedent { content: Content, mode: DedentMode }, - - /// Creates a logical group where its content is either consistently printed: - /// * on a single line: Omitting `LineMode::Soft` line breaks and printing spaces for `LineMode::SoftOrSpace` - /// * on multiple lines: Printing all line breaks - /// - /// See [crate::group] for documentation and examples. - Group(Group), - /// Forces the parent group to print in expanded mode. ExpandParent, - /// Allows to specify content that gets printed depending on whatever the enclosing group - /// is printed on a single line or multiple lines. See [crate::if_group_breaks] for examples. - ConditionalGroupContent(ConditionalGroupContent), - - /// Optimized version of [FormatElement::ConditionalGroupContent] for the case where some content - /// should be indented if the specified group breaks. - IndentIfGroupBreaks(IndentIfGroupBreaks), - - /// Concatenates multiple elements together. See [crate::Formatter::join_with] for examples. - List(List), - - /// Concatenates multiple elements together with a given separator printed in either - /// flat or expanded mode to fill the print width. Expect that the content is a list of alternating - /// [element, separator] See [crate::Formatter::fill]. - Fill(Content), - /// A text that should be printed as is, see [crate::text] for documentation and examples. Text(Text), - /// Delay the printing of its content until the next line break - LineSuffix(Content), - /// Prevents that line suffixes move past this boundary. Forces the printer to print any pending /// line suffixes, potentially by inserting a hard line break. LineSuffixBoundary, - /// A token that tracks tokens/nodes that are printed as verbatim. - Verbatim(Verbatim), - - /// A list of different variants representing the same content. The printer picks the best fitting content. - /// Line breaks inside of a best fitting don't propagate to parent groups. - BestFitting(BestFitting), - /// An interned format element. Useful when the same content must be emitted multiple times to avoid /// deep cloning the IR when using the `best_fitting!` macro or `if_group_fits_on_line` and `if_group_breaks`. Interned(Interned), - /// Special semantic element marking the content with a label. - /// This does not directly influence how the content will be printed. - /// - /// See [crate::labelled] for documentation. - Label(Label), -} - -#[derive(Clone, Copy, Eq, PartialEq, Debug)] -pub enum VerbatimKind { - Unknown, - Suppressed, - Verbatim { - /// the length of the formatted node - length: TextSize, - }, -} - -/// Information of the node/token formatted verbatim -#[derive(Clone, Eq, PartialEq)] -pub struct Verbatim { - /// The reason this range is using verbatim formatting - pub kind: VerbatimKind, - /// The [FormatElement] version of the node/token - pub content: Box<[FormatElement]>, -} - -impl Verbatim { - pub fn new_verbatim(content: Box<[FormatElement]>, length: TextSize) -> Self { - Self { - content, - kind: VerbatimKind::Verbatim { length }, - } - } - - pub fn new_unknown(content: Box<[FormatElement]>) -> Self { - Self { - content, - kind: VerbatimKind::Unknown, - } - } - - pub fn new_suppressed(content: Box<[FormatElement]>) -> Self { - Self { - content, - kind: VerbatimKind::Suppressed, - } - } - - pub fn is_unknown(&self) -> bool { - matches!(self.kind, VerbatimKind::Unknown) - } -} - -impl std::fmt::Display for FormatElement { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let formatted = format!(IrFormatContext::default(), [self]) - .expect("Formatting not to throw any FormatErrors"); + /// A list of different variants representing the same content. The printer picks the best fitting content. + /// Line breaks inside of a best fitting don't propagate to parent groups. + BestFitting(BestFitting), - f.write_str(formatted.print().as_code()) - } + /// A [Tag] that marks the start/end of some content to which some special formatting is applied. + Tag(Tag), } impl std::fmt::Debug for FormatElement { fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { - use std::write; match self { FormatElement::Space => write!(fmt, "Space"), - FormatElement::Line(content) => fmt.debug_tuple("Line").field(content).finish(), - FormatElement::Indent(content) => fmt.debug_tuple("Indent").field(content).finish(), - FormatElement::Dedent { content, mode } => fmt - .debug_struct("Dedent") - .field("content", content) - .field("mode", mode) - .finish(), - FormatElement::Align(Align { count, content }) => fmt - .debug_struct("Align") - .field("count", count) - .field("content", content) - .finish(), - FormatElement::Group(content) => { - write!(fmt, "Group")?; - content.fmt(fmt) - } - FormatElement::ConditionalGroupContent(content) => content.fmt(fmt), - FormatElement::IndentIfGroupBreaks(content) => content.fmt(fmt), - FormatElement::List(content) => { - write!(fmt, "List ")?; - content.fmt(fmt) - } - FormatElement::Fill(fill) => fill.fmt(fmt), - FormatElement::Text(content) => content.fmt(fmt), - FormatElement::LineSuffix(content) => { - fmt.debug_tuple("LineSuffix").field(content).finish() - } + FormatElement::Line(mode) => fmt.debug_tuple("Line").field(mode).finish(), + FormatElement::ExpandParent => write!(fmt, "ExpandParent"), + FormatElement::Text(text) => text.fmt(fmt), FormatElement::LineSuffixBoundary => write!(fmt, "LineSuffixBoundary"), - FormatElement::Verbatim(verbatim) => fmt - .debug_tuple("Verbatim") - .field(&verbatim.content) - .finish(), FormatElement::BestFitting(best_fitting) => { - write!(fmt, "BestFitting")?; - best_fitting.fmt(fmt) + fmt.debug_tuple("BestFitting").field(&best_fitting).finish() } - FormatElement::ExpandParent => write!(fmt, "ExpandParent"), - FormatElement::Interned(inner) => inner.fmt(fmt), - FormatElement::Label(label) => { - write!(fmt, "Label")?; - label.fmt(fmt) + FormatElement::Interned(interned) => { + fmt.debug_list().entries(interned.deref()).finish() } + FormatElement::Tag(tag) => fmt.debug_tuple("Tag").field(tag).finish(), } } } @@ -223,89 +82,6 @@ impl LineMode { } } -/// A token used to gather a list of elements; see [crate::Formatter::join_with]. -#[derive(Clone, Default, Eq, PartialEq)] -pub struct List { - content: Vec, -} - -impl std::fmt::Debug for List { - fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { - fmt.debug_list().entries(&self.content).finish() - } -} - -impl List { - pub(crate) fn new(content: Vec) -> Self { - Self { content } - } - - pub fn into_vec(self) -> Vec { - self.content - } -} - -impl Deref for List { - type Target = [FormatElement]; - - fn deref(&self) -> &Self::Target { - &self.content - } -} - -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct Align { - pub(super) content: Content, - pub(super) count: NonZeroU8, -} - -impl Align { - pub fn count(&self) -> NonZeroU8 { - self.count - } - - pub fn content(&self) -> &[FormatElement] { - &self.content - } -} - -/// Group is a special token that controls how the child tokens are printed. -/// -/// The printer first tries to print all tokens in the group onto a single line (ignoring soft line wraps) -/// but breaks the array cross multiple lines if it would exceed the specified `line_width`, if a child token is a hard line break or if a string contains a line break. -#[derive(Clone, PartialEq, Eq)] -pub struct Group { - pub(crate) content: Box<[FormatElement]>, - pub(crate) id: Option, -} - -impl std::fmt::Debug for Group { - fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { - if let Some(id) = &self.id { - fmt.debug_struct("") - .field("content", &self.content) - .field("id", id) - .finish() - } else { - fmt.debug_tuple("").field(&self.content).finish() - } - } -} - -impl Group { - pub fn new(content: Vec) -> Self { - Self { - content: content.into_boxed_slice(), - id: None, - } - } - - pub fn with_id(mut self, id: Option) -> Self { - self.id = id; - self - } -} - #[derive(Debug, Clone, Copy, Eq, PartialEq)] pub enum PrintMode { /// Omits any soft line breaks @@ -324,79 +100,12 @@ impl PrintMode { } } -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -pub enum DedentMode { - /// Reduces the indent by a level (if the current indent is > 0) - Level, - - /// Reduces the indent to the root - Root, -} - -/// Provides the printer with different representations for the same element so that the printer -/// can pick the best fitting variant. -/// -/// Best fitting is defined as the variant that takes the most horizontal space but fits on the line. -#[derive(Clone, Eq, PartialEq)] -pub struct BestFitting { - /// The different variants for this element. - /// The first element is the one that takes up the most space horizontally (the most flat), - /// The last element takes up the least space horizontally (but most horizontal space). - variants: Box<[FormatElement]>, -} - -impl BestFitting { - /// Creates a new best fitting IR with the given variants. The method itself isn't unsafe - /// but it is to discourage people from using it because the printer will panic if - /// the slice doesn't contain at least the least and most expanded variants. - /// - /// You're looking for a way to create a `BestFitting` object, use the `best_fitting![least_expanded, most_expanded]` macro. - /// - /// ## Safety - /// The slice must contain at least two variants. - #[doc(hidden)] - pub(crate) unsafe fn from_vec_unchecked(variants: Vec) -> Self { - debug_assert!( - variants.len() >= 2, - "Requires at least the least expanded and most expanded variants" - ); - - Self { - variants: variants.into_boxed_slice(), - } - } - - /// Returns the most expanded variant - pub fn most_expanded(&self) -> &FormatElement { - self.variants.last().expect( - "Most contain at least two elements, as guaranteed by the best fitting builder.", - ) - } - - pub fn variants(&self) -> &[FormatElement] { - &self.variants - } - - /// Returns the least expanded variant - pub fn most_flat(&self) -> &FormatElement { - self.variants.first().expect( - "Most contain at least two elements, as guaranteed by the best fitting builder.", - ) - } -} - -impl std::fmt::Debug for BestFitting { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_list().entries(&*self.variants).finish() - } -} - #[derive(Clone)] -pub struct Interned(Rc); +pub struct Interned(Rc<[FormatElement]>); impl Interned { - pub(crate) fn new(element: FormatElement) -> Self { - Self(Rc::new(element)) + pub(super) fn new(content: Vec) -> Self { + Self(content.into()) } } @@ -413,7 +122,7 @@ impl Hash for Interned { where H: Hasher, { - hasher.write_usize(Rc::as_ptr(&self.0) as usize); + Rc::as_ptr(&self.0).hash(hasher); } } @@ -424,114 +133,13 @@ impl std::fmt::Debug for Interned { } impl Deref for Interned { - type Target = FormatElement; + type Target = [FormatElement]; fn deref(&self) -> &Self::Target { self.0.deref() } } -#[derive(Eq, PartialEq, Copy, Clone)] -pub struct LabelId { - id: TypeId, - #[cfg(debug_assertions)] - label: &'static str, -} - -#[cfg(debug_assertions)] -impl std::fmt::Debug for LabelId { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(self.label) - } -} - -#[cfg(not(debug_assertions))] -impl std::fmt::Debug for LabelId { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - std::write!(f, "#{:?}", self.id) - } -} - -impl LabelId { - pub fn of() -> Self { - Self { - id: TypeId::of::(), - #[cfg(debug_assertions)] - label: type_name::(), - } - } -} - -#[derive(Clone, PartialEq, Eq)] -pub struct Label { - pub(crate) content: Box<[FormatElement]>, - label_id: LabelId, -} - -impl Label { - pub fn new(label_id: LabelId, content: Vec) -> Self { - Self { - content: content.into_boxed_slice(), - label_id, - } - } - - pub fn label_id(&self) -> LabelId { - self.label_id - } -} - -impl std::fmt::Debug for Label { - fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { - fmt.debug_struct("") - .field("label_id", &self.label_id) - .field("content", &self.content) - .finish() - } -} - -#[derive(Debug, Clone, Eq, PartialEq)] -pub struct IndentIfGroupBreaks { - pub(crate) content: Content, - - pub(crate) group_id: GroupId, -} - -impl IndentIfGroupBreaks { - pub fn new(content: Content, group_id: GroupId) -> Self { - Self { content, group_id } - } -} - -#[derive(Debug, Clone, Eq, PartialEq)] -pub struct ConditionalGroupContent { - pub(crate) content: Content, - - /// In what mode the content should be printed. - /// * Flat -> Omitted if the enclosing group is a multiline group, printed for groups fitting on a single line - /// * Multiline -> Omitted if the enclosing group fits on a single line, printed if the group breaks over multiple lines. - pub(crate) mode: PrintMode, - - /// The id of the group for which it should check if it breaks or not. The group must appear in the document - /// before the conditional group (but doesn't have to be in the ancestor chain). - pub(crate) group_id: Option, -} - -impl ConditionalGroupContent { - pub fn new(content: Box<[FormatElement]>, mode: PrintMode) -> Self { - Self { - content, - mode, - group_id: None, - } - } - - pub fn with_group_id(mut self, id: Option) -> Self { - self.group_id = id; - self - } -} - /// See [crate::text] for documentation #[derive(Eq, Clone)] pub enum Text { @@ -557,8 +165,6 @@ pub enum Text { impl std::fmt::Debug for Text { fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { - use std::write; - // This does not use debug_tuple so the tokens are // written on a single line even when pretty-printing match self { @@ -642,71 +248,58 @@ impl Deref for Text { } impl FormatElement { - /// Returns true if the element contains no content. - pub fn is_empty(&self) -> bool { + /// Returns `true` if self is a [FormatElement::Tag] + pub const fn is_tag(&self) -> bool { + matches!(self, FormatElement::Tag(_)) + } + + /// Returns `true` if self is a [FormatElement::Tag] and [Tag::is_start] is `true`. + pub const fn is_start_tag(&self) -> bool { match self { - FormatElement::List(list) => list.is_empty(), + FormatElement::Tag(tag) => tag.is_start(), _ => false, } } - /// Returns true if this [FormatElement] is guaranteed to break across multiple lines by the printer. - /// This is the case if this format element recursively contains a: - /// * [crate::empty_line] or [crate::hard_line_break] - /// * A token containing '\n' - /// - /// Use this with caution, this is only a heuristic and the printer may print the element over multiple - /// lines if this element is part of a group and the group doesn't fit on a single line. - pub fn will_break(&self) -> bool { + /// Returns `true` if self is a [FormatElement::Tag] and [Tag::is_end] is `true`. + pub const fn is_end_tag(&self) -> bool { + match self { + FormatElement::Tag(tag) => tag.is_end(), + _ => false, + } + } +} + +impl FormatElements for FormatElement { + fn will_break(&self) -> bool { match self { - FormatElement::Space => false, - FormatElement::Line(line_mode) => matches!(line_mode, LineMode::Hard | LineMode::Empty), - FormatElement::Group(Group { content, .. }) - | FormatElement::ConditionalGroupContent(ConditionalGroupContent { content, .. }) - | FormatElement::IndentIfGroupBreaks(IndentIfGroupBreaks { content, .. }) - | FormatElement::Fill(content) - | FormatElement::Verbatim(Verbatim { content, .. }) - | FormatElement::Label(Label { content, .. }) - | FormatElement::Indent(content) - | FormatElement::Dedent { content, .. } - | FormatElement::Align(Align { content, .. }) => { - content.iter().any(FormatElement::will_break) - } - FormatElement::List(list) => list.content.iter().any(FormatElement::will_break), - FormatElement::Text(token) => token.contains('\n'), - FormatElement::LineSuffix(_) => false, - FormatElement::BestFitting(_) => false, - FormatElement::LineSuffixBoundary => false, FormatElement::ExpandParent => true, - FormatElement::Interned(inner) => inner.0.will_break(), + FormatElement::Line(line_mode) => matches!(line_mode, LineMode::Hard | LineMode::Empty), + FormatElement::Text(text) => text.contains('\n'), + FormatElement::Interned(interned) => interned.will_break(), + FormatElement::LineSuffixBoundary + | FormatElement::Space + | FormatElement::Tag(_) + | FormatElement::BestFitting(_) => false, } } - /// Returns true if the element has the given label. - pub fn has_label(&self, label_id: LabelId) -> bool { + fn has_label(&self, label_id: LabelId) -> bool { match self { - FormatElement::Label(label) => label.label_id == label_id, + FormatElement::Tag(Tag::StartLabelled(actual)) => *actual == label_id, FormatElement::Interned(interned) => interned.deref().has_label(label_id), _ => false, } } - /// Utility function to get the "last element" of a [FormatElement], recursing - /// into lists and groups to find the last element that's not - /// a line break - pub fn last_element(&self) -> Option<&FormatElement> { - match self { - FormatElement::List(list) => { - list.iter().rev().find_map(|element| element.last_element()) - } - FormatElement::Line(_) => None, - - FormatElement::Group(Group { content, .. }) | FormatElement::Indent(content) => { - content.iter().rev().find_map(FormatElement::last_element) - } - FormatElement::Interned(Interned(inner)) => inner.last_element(), + fn start_tag(&self, _: TagKind) -> Option<&Tag> { + None + } - _ => Some(self), + fn end_tag(&self, kind: TagKind) -> Option<&Tag> { + match self { + FormatElement::Tag(tag) if tag.kind() == kind && tag.is_end() => Some(tag), + _ => None, } } } @@ -717,328 +310,85 @@ impl From for FormatElement { } } -impl From for FormatElement { - fn from(group: Group) -> Self { - FormatElement::Group(group) - } -} - -impl From for FormatElement { - fn from(token: List) -> Self { - FormatElement::List(token) - } +/// Provides the printer with different representations for the same element so that the printer +/// can pick the best fitting variant. +/// +/// Best fitting is defined as the variant that takes the most horizontal space but fits on the line. +#[derive(Clone, Eq, PartialEq)] +pub struct BestFitting { + /// The different variants for this element. + /// The first element is the one that takes up the most space horizontally (the most flat), + /// The last element takes up the least space horizontally (but most horizontal space). + variants: Box<[Box<[FormatElement]>]>, } -impl FromIterator for FormatElement { - fn from_iter>(iter: T) -> Self { - let iter = iter.into_iter(); - - let mut list = Vec::with_capacity(iter.size_hint().0); +impl BestFitting { + /// Creates a new best fitting IR with the given variants. The method itself isn't unsafe + /// but it is to discourage people from using it because the printer will panic if + /// the slice doesn't contain at least the least and most expanded variants. + /// + /// You're looking for a way to create a `BestFitting` object, use the `best_fitting![least_expanded, most_expanded]` macro. + /// + /// ## Safety + /// The slice must contain at least two variants. + #[doc(hidden)] + pub(crate) unsafe fn from_vec_unchecked(variants: Vec>) -> Self { + debug_assert!( + variants.len() >= 2, + "Requires at least the least expanded and most expanded variants" + ); - for element in iter { - match element { - FormatElement::List(append) => { - list.extend(append.content); - } - element => list.push(element), - } + Self { + variants: variants.into_boxed_slice(), } - - FormatElement::from(List::new(list)) } -} -impl From for FormatElement { - fn from(token: ConditionalGroupContent) -> Self { - FormatElement::ConditionalGroupContent(token) - } -} - -#[derive(Clone, Default, Debug)] -struct IrFormatContext { - /// The interned elements that have been printed to this point - printed_interned_elements: FxHashMap, -} - -impl FormatContext for IrFormatContext { - type Options = IrFormatOptions; - - fn options(&self) -> &Self::Options { - &IrFormatOptions - } - - fn source_map(&self) -> Option<&TransformSourceMap> { - None - } -} - -#[derive(Debug, Clone, Default)] -struct IrFormatOptions; - -impl FormatOptions for IrFormatOptions { - fn indent_style(&self) -> IndentStyle { - IndentStyle::Space(2) + /// Returns the most expanded variant + pub fn most_expanded(&self) -> &[FormatElement] { + self.variants.last().expect( + "Most contain at least two elements, as guaranteed by the best fitting builder.", + ) } - fn line_width(&self) -> LineWidth { - LineWidth(80) + pub fn variants(&self) -> &[Box<[FormatElement]>] { + &self.variants } - fn as_print_options(&self) -> PrinterOptions { - PrinterOptions { - tab_width: 2, - print_width: self.line_width().into(), - line_ending: LineEnding::LineFeed, - indent_style: IndentStyle::Space(2), - } + /// Returns the least expanded variant + pub fn most_flat(&self) -> &[FormatElement] { + self.variants.first().expect( + "Most contain at least two elements, as guaranteed by the best fitting builder.", + ) } } -impl Format for FormatElement { - fn fmt(&self, f: &mut crate::Formatter) -> FormatResult<()> { - match self { - FormatElement::Space => { - write!(f, [text(" ")]) - } - FormatElement::Line(mode) => match mode { - LineMode::SoftOrSpace => { - write!(f, [text("soft_line_break_or_space")]) - } - LineMode::Soft => { - write!(f, [text("soft_line_break")]) - } - LineMode::Hard => { - write!(f, [text("hard_line_break")]) - } - LineMode::Empty => { - write!(f, [text("empty_line")]) - } - }, - FormatElement::ExpandParent => { - write!(f, [text("expand_parent")]) - } - text @ FormatElement::Text(_) => f.write_element(text.clone()), - FormatElement::LineSuffixBoundary => { - write!(f, [text("line_suffix_boundary")]) - } - FormatElement::Indent(content) => { - write!(f, [text("indent("), content.as_ref(), text(")")]) - } - FormatElement::Dedent { content, mode } => { - let label = match mode { - DedentMode::Level => "dedent", - DedentMode::Root => "dedentRoot", - }; - - write!(f, [text(label), text("("), content.as_ref(), text(")")]) - } - FormatElement::Align(Align { content, count }) => { - write!( - f, - [ - text("align("), - dynamic_text(&count.to_string(), TextSize::default()), - text(","), - space(), - content.as_ref(), - text(")") - ] - ) - } - FormatElement::List(list) => { - write!(f, [list.as_ref()]) - } - FormatElement::LineSuffix(line_suffix) => { - write!(f, [text("line_suffix("), line_suffix.as_ref(), text(")")]) - } - FormatElement::Verbatim(verbatim) => { - write!(f, [text("verbatim("), verbatim.content.as_ref(), text(")")]) - } - FormatElement::Group(group_element) => { - write!(f, [text("group(")])?; - - if let Some(id) = group_element.id { - write!( - f, - [group(&soft_block_indent(&format_args![ - group_element.content.as_ref(), - text(","), - soft_line_break_or_space(), - text("{"), - group(&format_args![soft_line_indent_or_space(&format_args![ - text("id:"), - space(), - dynamic_text(&std::format!("{id:?}"), TextSize::default()), - soft_line_break_or_space() - ])]), - text("}") - ]))] - )?; - } else { - write!(f, [group_element.content.as_ref()])?; - } - - write!(f, [text(")")]) - } - FormatElement::IndentIfGroupBreaks(content) => { - write!( - f, - [ - text("indent_if_group_breaks("), - group(&soft_block_indent(&format_args![ - content.content.as_ref(), - text(","), - soft_line_break_or_space(), - text("{"), - group(&format_args![soft_line_indent_or_space(&format_args![ - text("group-id:"), - space(), - dynamic_text( - &std::format!("{:?}", content.group_id), - TextSize::default() - ), - soft_line_break_or_space() - ])]), - text("}") - ])) - ] - ) - } - FormatElement::ConditionalGroupContent(content) => { - match content.mode { - PrintMode::Flat => { - write!(f, [text("if_group_fits_on_line")])?; - } - PrintMode::Expanded => { - write!(f, [text("if_group_breaks")])?; - } - } - - write!(f, [text("(")])?; - - if let Some(id) = content.group_id { - write!( - f, - [group(&soft_block_indent(&format_args![ - content.content.as_ref(), - text(","), - soft_line_break_or_space(), - text("{"), - group(&format_args![soft_line_indent_or_space(&format_args![ - text("id:"), - space(), - dynamic_text(&std::format!("{id:?}"), TextSize::default()), - soft_line_break_or_space() - ])]), - text("}") - ]))] - )?; - } else { - write!(f, [content.content.as_ref()])?; - } - - write!(f, [text(")")]) - } - FormatElement::Label(labelled) => { - let label_id = labelled.label_id; - write!( - f, - [ - text("label(\""), - dynamic_text(&std::format!("{label_id:?}"), TextSize::default()), - text("\","), - space(), - labelled.content.as_ref(), - text(")") - ] - ) - } - FormatElement::Fill(content) => { - write!(f, [text("fill("), content.as_ref(), text(")")]) - } - - FormatElement::BestFitting(best_fitting) => { - write!( - f, - [text("best_fitting("), best_fitting.variants(), text(")")] - ) - } - FormatElement::Interned(interned) => { - let interned_elements = &mut f.context_mut().printed_interned_elements; - - let (index, inserted) = match interned_elements.get(interned) { - None => { - let index = interned_elements.len(); - interned_elements.insert(interned.clone(), index); - (index, true) - } - Some(index) => (*index, false), - }; - - if inserted { - write!( - f, - [ - dynamic_text(&std::format!(""), TextSize::default()), - space(), - ] - )?; - - match interned.0.as_ref() { - element @ FormatElement::Text(_) | element @ FormatElement::Space => { - write!(f, [text("\""), element, text("\"")]) - } - element => element.fmt(f), - } - } else { - write!( - f, - [dynamic_text( - &std::format!(""), - TextSize::default() - )] - ) - } - } - } +impl std::fmt::Debug for BestFitting { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_list().entries(&*self.variants).finish() } } -impl<'a> Format for &'a [FormatElement] { - fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { - write!( - f, - [ - text("["), - group(&soft_block_indent(&format_with(|f| { - let mut joiner = f.join_with(soft_line_break_or_space()); - let len = self.len(); - - for (index, element) in self.iter().enumerate() { - joiner.entry(&format_with(|f| { - let print_as_str = - matches!(element, FormatElement::Text(_) | FormatElement::Space); - - if print_as_str { - write!(f, [text("\""), &element, text("\"")])?; - } else { - write!(f, [group(&element)])?; - } +pub trait FormatElements { + /// Returns true if this [FormatElement] is guaranteed to break across multiple lines by the printer. + /// This is the case if this format element recursively contains a: + /// * [crate::empty_line] or [crate::hard_line_break] + /// * A token containing '\n' + /// + /// Use this with caution, this is only a heuristic and the printer may print the element over multiple + /// lines if this element is part of a group and the group doesn't fit on a single line. + fn will_break(&self) -> bool; - if index < len - 1 { - write!(f, [text(",")])?; - } + /// Returns true if the element has the given label. + fn has_label(&self, label: LabelId) -> bool; - Ok(()) - })); - } + /// Returns the start tag of `kind` if: + /// * the last element is an end tag of `kind`. + /// * there's a matching start tag in this document (may not be true if this slice is an interned element and the `start` is in the document storing the interned element). + fn start_tag(&self, kind: TagKind) -> Option<&Tag>; - joiner.finish() - }))), - text("]") - ] - ) - } + /// Returns the end tag if: + /// * the last element is an end tag of `kind` + fn end_tag(&self, kind: TagKind) -> Option<&Tag>; } #[cfg(test)] @@ -1060,20 +410,14 @@ mod tests { static_assert!(std::mem::size_of::() == 8usize); #[cfg(target_pointer_width = "64")] -static_assert!(std::mem::size_of::() == 8usize); - -#[cfg(target_pointer_width = "64")] -static_assert!(std::mem::size_of::() == 24usize); +static_assert!(std::mem::size_of::() == 8usize); #[cfg(target_pointer_width = "64")] static_assert!(std::mem::size_of::() == 24usize); #[cfg(not(debug_assertions))] #[cfg(target_pointer_width = "64")] -static_assert!(std::mem::size_of::() == 24usize); - -#[cfg(target_pointer_width = "64")] -static_assert!(std::mem::size_of::() == 24usize); +static_assert!(std::mem::size_of::() == 16usize); // Increasing the size of FormatElement has serious consequences on runtime performance and memory footprint. // Is there a more efficient way to encode the data to avoid increasing its size? Can the information diff --git a/crates/rome_formatter/src/format_element/document.rs b/crates/rome_formatter/src/format_element/document.rs new file mode 100644 index 00000000000..4fa41649ff7 --- /dev/null +++ b/crates/rome_formatter/src/format_element/document.rs @@ -0,0 +1,619 @@ +use super::tag::Tag; +use crate::format_element::tag::DedentMode; +use crate::prelude::*; +use crate::printer::LineEnding; +use crate::{format, write}; +use crate::{ + BufferExtensions, Format, FormatContext, FormatElement, FormatOptions, FormatResult, Formatter, + IndentStyle, LineWidth, PrinterOptions, TransformSourceMap, +}; +use rome_rowan::TextSize; +use std::collections::HashMap; +use std::ops::Deref; + +/// A formatted document. +#[derive(Debug, Clone, Eq, PartialEq, Default)] +pub struct Document { + elements: Vec, +} + +impl From> for Document { + fn from(elements: Vec) -> Self { + Self { elements } + } +} + +impl Deref for Document { + type Target = [FormatElement]; + + fn deref(&self) -> &Self::Target { + self.elements.as_slice() + } +} + +impl std::fmt::Display for Document { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let formatted = format!(IrFormatContext::default(), [self.elements.as_slice()]) + .expect("Formatting not to throw any FormatErrors"); + + f.write_str( + formatted + .print() + .expect("Expected a valid document") + .as_code(), + ) + } +} + +#[derive(Clone, Default, Debug)] +struct IrFormatContext { + /// The interned elements that have been printed to this point + printed_interned_elements: HashMap, +} + +impl FormatContext for IrFormatContext { + type Options = IrFormatOptions; + + fn options(&self) -> &Self::Options { + &IrFormatOptions + } + + fn source_map(&self) -> Option<&TransformSourceMap> { + None + } +} + +#[derive(Debug, Clone, Default)] +struct IrFormatOptions; + +impl FormatOptions for IrFormatOptions { + fn indent_style(&self) -> IndentStyle { + IndentStyle::Space(2) + } + + fn line_width(&self) -> LineWidth { + LineWidth(80) + } + + fn as_print_options(&self) -> PrinterOptions { + PrinterOptions { + tab_width: 2, + print_width: self.line_width().into(), + line_ending: LineEnding::LineFeed, + indent_style: IndentStyle::Space(2), + } + } +} + +impl Format for &[FormatElement] { + fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { + use Tag::*; + + write!(f, [ContentArrayStart])?; + + let mut tag_stack = Vec::new(); + let mut first_element = true; + let mut in_text = false; + + let mut iter = self.iter().peekable(); + + while let Some(element) = iter.next() { + if !first_element && !in_text && !element.is_end_tag() { + // Write a separator between every two elements + write!(f, [text(","), soft_line_break_or_space()])?; + } + + first_element = false; + + match element { + element @ FormatElement::Space | element @ FormatElement::Text(_) => { + if !in_text { + write!(f, [text("\"")])?; + } + + in_text = true; + + match element { + FormatElement::Text(_) => f.write_element(element.clone())?, + FormatElement::Space => { + write!(f, [text(" ")])?; + } + _ => unreachable!(), + } + + let is_next_text = matches!( + iter.peek(), + Some(FormatElement::Space | FormatElement::Text(_)) + ); + + if !is_next_text { + write!(f, [text("\"")])?; + in_text = false; + } + } + + FormatElement::Line(mode) => match mode { + LineMode::SoftOrSpace => { + write!(f, [text("soft_line_break_or_space")])?; + } + LineMode::Soft => { + write!(f, [text("soft_line_break")])?; + } + LineMode::Hard => { + write!(f, [text("hard_line_break")])?; + } + LineMode::Empty => { + write!(f, [text("empty_line")])?; + } + }, + FormatElement::ExpandParent => { + write!(f, [text("expand_parent")])?; + } + + FormatElement::LineSuffixBoundary => { + write!(f, [text("line_suffix_boundary")])?; + } + + FormatElement::BestFitting(best_fitting) => { + write!(f, [text("best_fitting([")])?; + f.write_elements([ + FormatElement::Tag(StartIndent), + FormatElement::Line(LineMode::Hard), + ])?; + + for variant in best_fitting.variants() { + write!(f, [variant.deref(), hard_line_break()])?; + } + + f.write_elements([ + FormatElement::Tag(EndIndent), + FormatElement::Line(LineMode::Hard), + ])?; + + write!(f, [text("])")])?; + } + + FormatElement::Interned(interned) => { + let interned_elements = &mut f.context_mut().printed_interned_elements; + + match interned_elements.get(interned).copied() { + None => { + let index = interned_elements.len(); + interned_elements.insert(interned.clone(), index); + + write!( + f, + [ + dynamic_text( + &std::format!(""), + TextSize::default() + ), + space(), + &interned.deref(), + ] + )?; + } + Some(reference) => { + write!( + f, + [dynamic_text( + &std::format!(""), + TextSize::default() + )] + )?; + } + } + } + + FormatElement::Tag(tag) => { + if tag.is_start() { + first_element = true; + tag_stack.push(tag.kind()); + } + // Handle documents with mismatching start/end or superfluous end tags + else { + match tag_stack.pop() { + None => { + // Only write the end tag without any indent to ensure the output document is valid. + write!( + f, + [ + text(">"), + ] + )?; + first_element = false; + continue; + } + Some(start_kind) if start_kind != tag.kind() => { + write!( + f, + [ + ContentArrayEnd, + text(")"), + soft_line_break_or_space(), + text("ERROR>") + ] + )?; + first_element = false; + continue; + } + _ => { + // all ok + } + } + } + + match tag { + StartIndent => { + write!(f, [text("indent(")])?; + } + + StartDedent(mode) => { + let label = match mode { + DedentMode::Level => "dedent", + DedentMode::Root => "dedentRoot", + }; + + write!(f, [text(label), text("(")])?; + } + + StartAlign(tag::Align(count)) => { + write!( + f, + [ + text("align("), + dynamic_text(&count.to_string(), TextSize::default()), + text(","), + space(), + ] + )?; + } + + StartLineSuffix => { + write!(f, [text("line_suffix(")])?; + } + + StartVerbatim(_) => { + write!(f, [text("verbatim(")])?; + } + + StartGroup(id) => { + write!(f, [text("group(")])?; + + if let Some(group_id) = id { + write!( + f, + [ + dynamic_text( + &std::format!("\"{group_id:?}\""), + TextSize::default() + ), + text(","), + space(), + ] + )?; + } + } + + StartIndentIfGroupBreaks(id) => { + write!( + f, + [ + text("indent_if_group_breaks("), + dynamic_text(&std::format!("\"{id:?}\""), TextSize::default()), + text(","), + space(), + ] + )?; + } + + StartConditionalContent(condition) => { + match condition.mode { + PrintMode::Flat => { + write!(f, [text("if_group_fits_on_line(")])?; + } + PrintMode::Expanded => { + write!(f, [text("if_group_breaks(")])?; + } + } + + if let Some(group_id) = condition.group_id { + write!( + f, + [ + dynamic_text( + &std::format!("\"{group_id:?}\""), + TextSize::default() + ), + text(","), + space(), + ] + )?; + } + } + + StartLabelled(label_id) => { + write!( + f, + [ + text("label(\""), + dynamic_text( + &std::format!("\"{label_id:?}\""), + TextSize::default() + ), + text("\","), + space(), + ] + )?; + } + + StartFill => { + write!(f, [text("fill(")])?; + } + + StartEntry => { + // handled after the match for all start tags + } + EndEntry => write!(f, [ContentArrayEnd])?, + + EndFill + | EndLabelled + | EndConditionalContent + | EndIndentIfGroupBreaks + | EndAlign + | EndIndent + | EndGroup + | EndLineSuffix + | EndDedent + | EndVerbatim => { + write!(f, [ContentArrayEnd, text(")")])?; + } + }; + + if tag.is_start() { + write!(f, [ContentArrayStart])?; + } + } + } + } + + while let Some(top) = tag_stack.pop() { + write!( + f, + [ + ContentArrayEnd, + text(")"), + soft_line_break_or_space(), + dynamic_text( + &std::format!(">"), + TextSize::default() + ), + ] + )?; + } + + write!(f, [ContentArrayEnd]) + } +} + +struct ContentArrayStart; + +impl Format for ContentArrayStart { + fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { + use Tag::*; + + write!(f, [text("[")])?; + + f.write_elements([ + FormatElement::Tag(StartGroup(None)), + FormatElement::Tag(StartIndent), + FormatElement::Line(LineMode::Soft), + ]) + } +} + +struct ContentArrayEnd; + +impl Format for ContentArrayEnd { + fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { + use Tag::*; + f.write_elements([ + FormatElement::Tag(EndIndent), + FormatElement::Line(LineMode::Soft), + FormatElement::Tag(EndGroup), + ])?; + + write!(f, [text("]")]) + } +} + +impl FormatElements for [FormatElement] { + fn will_break(&self) -> bool { + use Tag::*; + let mut ignore_depth = 0usize; + + for element in self { + match element { + // Line suffix + // Ignore if any of its content breaks + FormatElement::Tag(StartLineSuffix) => { + ignore_depth += 1; + } + FormatElement::Tag(EndLineSuffix) => { + ignore_depth -= 1; + } + FormatElement::Interned(interned) if ignore_depth == 0 => { + if interned.will_break() { + return true; + } + } + + element if ignore_depth == 0 && element.will_break() => { + return true; + } + _ => continue, + } + } + + debug_assert_eq!(ignore_depth, 0, "Unclosed start container"); + + false + } + + fn has_label(&self, expected: LabelId) -> bool { + self.first() + .map_or(false, |element| element.has_label(expected)) + } + + fn start_tag(&self, kind: TagKind) -> Option<&Tag> { + // Assert that the document ends at a tag with the specified kind; + let _ = self.end_tag(kind)?; + + fn traverse_slice<'a>( + slice: &'a [FormatElement], + kind: TagKind, + depth: &mut usize, + ) -> Option<&'a Tag> { + for element in slice.iter().rev() { + match element { + FormatElement::Tag(tag) if tag.kind() == kind => { + if tag.is_start() { + if *depth == 0 { + // Invalid document + return None; + } else if *depth == 1 { + return Some(tag); + } else { + *depth -= 1; + } + } else { + *depth += 1; + } + } + FormatElement::Interned(interned) => { + match traverse_slice(interned, kind, depth) { + Some(start) => { + return Some(start); + } + // Reached end or invalid document + None if *depth == 0 => { + return None; + } + _ => { + // continue with other elements + } + } + } + _ => {} + } + } + + None + } + + let mut depth = 0usize; + + traverse_slice(self, kind, &mut depth) + } + + fn end_tag(&self, kind: TagKind) -> Option<&Tag> { + self.last().and_then(|element| element.end_tag(kind)) + } +} + +#[cfg(test)] +mod tests { + use crate::prelude::*; + use crate::SimpleFormatContext; + use crate::{format, format_args, write}; + + #[test] + fn display_elements() { + let formatted = format!( + SimpleFormatContext::default(), + [format_with(|f| { + write!( + f, + [group(&format_args![ + text("("), + soft_block_indent(&format_args![ + text("Some longer content"), + space(), + text("That should ultimately break"), + ]) + ])] + ) + })] + ) + .unwrap(); + + let document = formatted.into_document(); + + assert_eq!( + &std::format!("{document}"), + r#"[ + group([ + "(", + indent([ + soft_line_break, + "Some longer content That should ultimately break" + ]), + soft_line_break + ]) +]"# + ); + } + + #[test] + fn display_invalid_document() { + use Tag::*; + + let document = Document::from(vec![ + FormatElement::Text(Text::Static { text: "[" }), + FormatElement::Tag(StartGroup(None)), + FormatElement::Tag(StartIndent), + FormatElement::Line(LineMode::Soft), + FormatElement::Text(Text::Static { text: "a" }), + // Close group instead of indent + FormatElement::Tag(EndGroup), + FormatElement::Line(LineMode::Soft), + FormatElement::Tag(EndIndent), + FormatElement::Text(Text::Static { text: "]" }), + // End tag without start + FormatElement::Tag(EndIndent), + // Start tag without an end + FormatElement::Tag(StartIndent), + ]); + + assert_eq!( + &std::format!("{document}"), + r#"[ + "[", + group([ + indent([soft_line_break, "a"]) + ERROR>, + soft_line_break + ]) + ERROR>, + "]">, + indent([]) + > +]"# + ); + } +} diff --git a/crates/rome_formatter/src/format_element/tag.rs b/crates/rome_formatter/src/format_element/tag.rs new file mode 100644 index 00000000000..c546152cc8e --- /dev/null +++ b/crates/rome_formatter/src/format_element/tag.rs @@ -0,0 +1,228 @@ +use crate::format_element::PrintMode; +use crate::{GroupId, TextSize}; +#[cfg(debug_assertions)] +use std::any::type_name; +use std::any::TypeId; +use std::num::NonZeroU8; + +/// A Tag marking the start and end of some content to which some special formatting should be applied. +/// +/// Tags always come in pairs of a start and an end tag and the styling defined by this tag +/// will be applied to all elements in between the start/end tags. +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum Tag { + /// Indents the content one level deeper, see [crate::indent] for documentation and examples. + StartIndent, + EndIndent, + + /// Variant of [TagKind::Indent] that indents content by a number of spaces. For example, `Align(2)` + /// indents any content following a line break by an additional two spaces. + /// + /// Nesting (Aligns)[TagKind::Align] has the effect that all except the most inner align are handled as (Indent)[TagKind::Indent]. + StartAlign(Align), + EndAlign, + + /// Reduces the indention of the specified content either by one level or to the root, depending on the mode. + /// Reverse operation of `Indent` and can be used to *undo* an `Align` for nested content. + StartDedent(DedentMode), + EndDedent, + + /// Creates a logical group where its content is either consistently printed: + /// * on a single line: Omitting `LineMode::Soft` line breaks and printing spaces for `LineMode::SoftOrSpace` + /// * on multiple lines: Printing all line breaks + /// + /// See [crate::group] for documentation and examples. + StartGroup(Option), + EndGroup, + + /// Allows to specify content that gets printed depending on whatever the enclosing group + /// is printed on a single line or multiple lines. See [crate::if_group_breaks] for examples. + StartConditionalContent(Condition), + EndConditionalContent, + + /// Optimized version of [Tag::StartConditionalContent] for the case where some content + /// should be indented if the specified group breaks. + StartIndentIfGroupBreaks(GroupId), + EndIndentIfGroupBreaks, + + /// Concatenates multiple elements together with a given separator printed in either + /// flat or expanded mode to fill the print width. Expect that the content is a list of alternating + /// [element, separator] See [crate::Formatter::fill]. + StartFill, + EndFill, + + /// Entry inside of a [Tag::StartFill] + StartEntry, + EndEntry, + + /// Delay the printing of its content until the next line break + StartLineSuffix, + EndLineSuffix, + + /// A token that tracks tokens/nodes that are printed as verbatim. + StartVerbatim(VerbatimKind), + EndVerbatim, + + /// Special semantic element marking the content with a label. + /// This does not directly influence how the content will be printed. + /// + /// See [crate::labelled] for documentation. + StartLabelled(LabelId), + EndLabelled, +} + +impl Tag { + /// Returns `true` if `self` is any start tag. + pub const fn is_start(&self) -> bool { + matches!( + self, + Tag::StartIndent + | Tag::StartAlign(_) + | Tag::StartDedent(_) + | Tag::StartGroup(_) + | Tag::StartConditionalContent(_) + | Tag::StartIndentIfGroupBreaks(_) + | Tag::StartFill + | Tag::StartEntry + | Tag::StartLineSuffix + | Tag::StartVerbatim(_) + | Tag::StartLabelled(_) + ) + } + + /// Returns `true` if `self` is any end tag. + pub const fn is_end(&self) -> bool { + !self.is_start() + } + + pub const fn kind(&self) -> TagKind { + use Tag::*; + + match self { + StartIndent | EndIndent => TagKind::Indent, + StartAlign(_) | EndAlign => TagKind::Align, + StartDedent(_) | EndDedent => TagKind::Dedent, + StartGroup(_) | EndGroup => TagKind::Group, + StartConditionalContent(_) | EndConditionalContent => TagKind::ConditionalContent, + StartIndentIfGroupBreaks(_) | EndIndentIfGroupBreaks => TagKind::IndentIfGroupBreaks, + StartFill | EndFill => TagKind::Fill, + StartEntry | EndEntry => TagKind::Entry, + StartLineSuffix | EndLineSuffix => TagKind::LineSuffix, + StartVerbatim(_) | EndVerbatim => TagKind::Verbatim, + StartLabelled(_) | EndLabelled => TagKind::Labelled, + } + } +} + +/// The kind of a [Tag]. +/// +/// Each start end tag pair has its own [tag kind](TagKind). +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum TagKind { + Indent, + Align, + Dedent, + Group, + ConditionalContent, + IndentIfGroupBreaks, + Fill, + Entry, + LineSuffix, + Verbatim, + Labelled, +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub enum DedentMode { + /// Reduces the indent by a level (if the current indent is > 0) + Level, + + /// Reduces the indent to the root + Root, +} + +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct Condition { + /// * Flat -> Omitted if the enclosing group is a multiline group, printed for groups fitting on a single line + /// * Multiline -> Omitted if the enclosing group fits on a single line, printed if the group breaks over multiple lines. + pub(crate) mode: PrintMode, + + /// The id of the group for which it should check if it breaks or not. The group must appear in the document + /// before the conditional group (but doesn't have to be in the ancestor chain). + pub(crate) group_id: Option, +} + +impl Condition { + pub fn new(mode: PrintMode) -> Self { + Self { + mode, + group_id: None, + } + } + + pub fn with_group_id(mut self, id: Option) -> Self { + self.group_id = id; + self + } + + pub fn mode(&self) -> PrintMode { + self.mode + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct Align(pub(crate) NonZeroU8); + +impl Align { + pub fn count(&self) -> NonZeroU8 { + self.0 + } +} + +#[derive(Eq, PartialEq, Copy, Clone)] +pub struct LabelId { + id: TypeId, + #[cfg(debug_assertions)] + label: &'static str, +} + +#[cfg(debug_assertions)] +impl std::fmt::Debug for LabelId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(self.label) + } +} + +#[cfg(not(debug_assertions))] +impl std::fmt::Debug for LabelId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::write!(f, "#{:?}", self.id) + } +} + +impl LabelId { + pub fn of() -> Self { + Self { + id: TypeId::of::(), + #[cfg(debug_assertions)] + label: type_name::(), + } + } +} + +#[derive(Clone, Copy, Eq, PartialEq, Debug)] +pub enum VerbatimKind { + Unknown, + Suppressed, + Verbatim { + /// the length of the formatted node + length: TextSize, + }, +} + +impl VerbatimKind { + pub const fn is_unknown(&self) -> bool { + matches!(self, VerbatimKind::Unknown) + } +} diff --git a/crates/rome_formatter/src/format_extensions.rs b/crates/rome_formatter/src/format_extensions.rs index 80ad873d24e..0dccc84ffb7 100644 --- a/crates/rome_formatter/src/format_extensions.rs +++ b/crates/rome_formatter/src/format_extensions.rs @@ -34,7 +34,7 @@ pub trait FormatOptional { /// none_token.with_or_empty(|token, f| write!(f, [token])) /// ]).unwrap(); /// - /// assert!(none_formatted.into_format_element().is_empty()); + /// assert!(none_formatted.into_document().is_empty()); /// /// let some_token = Some(MyFormat); /// assert_eq!( @@ -112,20 +112,23 @@ pub trait MemoizeFormat { /// } /// } /// + /// # fn main() -> FormatResult<()> { /// let normal = MyFormat::new(); /// /// // Calls `format` for everytime the object gets formatted /// assert_eq!( /// "Formatted 1 times. Formatted 2 times.", - /// format!(SimpleFormatContext::default(), [normal, space(), normal]).unwrap().print().as_code() + /// format!(SimpleFormatContext::default(), [normal, space(), normal])?.print()?.as_code() /// ); /// /// // Memoized memoizes the result and calls `format` only once. /// let memoized = normal.memoized(); /// assert_eq!( /// "Formatted 3 times. Formatted 3 times.", - /// format![SimpleFormatContext::default(), [memoized, space(), memoized]].unwrap().print().as_code() + /// format![SimpleFormatContext::default(), [memoized, space(), memoized]]?.print()?.as_code() /// ); + /// # Ok(()) + /// # } /// ``` /// fn memoized(self) -> Memoized @@ -142,7 +145,7 @@ impl MemoizeFormat for T where T: Format {} #[derive(Debug)] pub struct Memoized { inner: F, - memory: RefCell>>, + memory: RefCell>>>, options: PhantomData, } @@ -193,6 +196,7 @@ where /// } /// } /// + /// # fn main() -> FormatResult<()> { /// let content = format_with(|f| { /// let mut counter = Counter::default().memoized(); /// let counter_content = counter.inspect(f)?; @@ -207,19 +211,22 @@ where /// }); /// /// - /// let formatted = format!(SimpleFormatContext::default(), [content]).unwrap(); - /// assert_eq!("Counter:\n\tCount: 0\nCount: 0\n", formatted.print().as_code()) + /// let formatted = format!(SimpleFormatContext::default(), [content])?; + /// assert_eq!("Counter:\n\tCount: 0\nCount: 0\n", formatted.print()?.as_code()); + /// # Ok(()) + /// # } /// /// ``` - pub fn inspect(&mut self, f: &mut Formatter) -> FormatResult<&FormatElement> { + pub fn inspect(&mut self, f: &mut Formatter) -> FormatResult<&[FormatElement]> { let result = self .memory .get_mut() .get_or_insert_with(|| f.intern(&self.inner)); match result.as_ref() { - Ok(FormatElement::Interned(interned)) => Ok(interned.deref()), - Ok(other) => Ok(other), + Ok(Some(FormatElement::Interned(interned))) => Ok(interned.deref()), + Ok(Some(other)) => Ok(std::slice::from_ref(other)), + Ok(None) => Ok(&[]), Err(error) => Err(*error), } } @@ -234,11 +241,12 @@ where let result = memory.get_or_insert_with(|| f.intern(&self.inner)); match result { - Ok(elements) => { + Ok(Some(elements)) => { f.write_element(elements.clone())?; Ok(()) } + Ok(None) => Ok(()), Err(err) => Err(*err), } } diff --git a/crates/rome_formatter/src/formatter.rs b/crates/rome_formatter/src/formatter.rs index df248c3d029..ec6a8f1db3a 100644 --- a/crates/rome_formatter/src/formatter.rs +++ b/crates/rome_formatter/src/formatter.rs @@ -52,6 +52,7 @@ impl<'buf, Context> Formatter<'buf, Context> { /// use rome_formatter::format; /// use rome_formatter::prelude::*; /// + /// # fn main() -> FormatResult<()> { /// let formatted = format!(SimpleFormatContext::default(), [format_with(|f| { /// f.join() /// .entry(&text("a")) @@ -60,12 +61,14 @@ impl<'buf, Context> Formatter<'buf, Context> { /// .entry(&space()) /// .entry(&text("b")) /// .finish() - /// })]).unwrap(); + /// })])?; /// /// assert_eq!( /// "a + b", - /// formatted.print().as_code() - /// ) + /// formatted.print()?.as_code() + /// ); + /// # Ok(()) + /// # } /// ``` pub fn join<'a>(&'a mut self) -> JoinBuilder<'a, 'buf, (), Context> { JoinBuilder::new(self) @@ -81,6 +84,7 @@ impl<'buf, Context> Formatter<'buf, Context> { /// use rome_formatter::{format, format_args}; /// use rome_formatter::prelude::*; /// + /// # fn main() -> FormatResult<()> { /// let formatted = format!(SimpleFormatContext::default(), [format_with(|f| { /// f.join_with(&format_args!(text(","), space())) /// .entry(&text("1")) @@ -88,12 +92,14 @@ impl<'buf, Context> Formatter<'buf, Context> { /// .entry(&text("3")) /// .entry(&text("4")) /// .finish() - /// })]).unwrap(); + /// })])?; /// /// assert_eq!( /// "1, 2, 3, 4", - /// formatted.print().as_code() + /// formatted.print()?.as_code() /// ); + /// # Ok(()) + /// # } /// ``` pub fn join_with<'a, Joiner>( &'a mut self, @@ -137,6 +143,7 @@ impl<'buf, Context> Formatter<'buf, Context> { /// use rome_formatter::prelude::*; /// use rome_formatter::{format, format_args}; /// + /// # fn main() -> FormatResult<()> { /// let formatted = format!(SimpleFormatContext::default(), [format_with(|f| { /// f.fill() /// .entry(&soft_line_break_or_space(), &text("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")) @@ -144,18 +151,21 @@ impl<'buf, Context> Formatter<'buf, Context> { /// .entry(&soft_line_break_or_space(), &text("cccccccccccccccccccccccccccccc")) /// .entry(&soft_line_break_or_space(), &text("dddddddddddddddddddddddddddddd")) /// .finish() - /// })]).unwrap(); + /// })])?; /// /// assert_eq!( /// "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\ncccccccccccccccccccccccccccccc dddddddddddddddddddddddddddddd", - /// formatted.print().as_code() - /// ) + /// formatted.print()?.as_code() + /// ); + /// # Ok(()) + /// # } /// ``` /// /// ```rust /// use rome_formatter::prelude::*; /// use rome_formatter::{format, format_args}; /// + /// # fn main() -> FormatResult<()> { /// let entries = vec![ /// text("Important: "), /// text("Please do not commit memory bugs such as segfaults, buffer overflows, etc. otherwise you "), @@ -165,31 +175,36 @@ impl<'buf, Context> Formatter<'buf, Context> { /// /// let formatted = format!(SimpleFormatContext::default(), [format_with(|f| { /// f.fill().entries(&soft_line_break(), entries.iter()).finish() - /// })]).unwrap(); + /// })])?; /// /// assert_eq!( /// &std::format!("Important: \nPlease do not commit memory bugs such as segfaults, buffer overflows, etc. otherwise you \nwill be reprimanded"), - /// formatted.print().as_code() - /// ) + /// formatted.print()?.as_code() + /// ); + /// # Ok(()) + /// # } /// ``` pub fn fill<'a>(&'a mut self) -> FillBuilder<'a, 'buf, Context> { FillBuilder::new(self) } /// Formats `content` into an interned element without writing it to the formatter's buffer. - pub fn intern(&mut self, content: &dyn Format) -> FormatResult { + pub fn intern(&mut self, content: &dyn Format) -> FormatResult> { let mut buffer = VecBuffer::new(self.state_mut()); - crate::write!(&mut buffer, [content])?; + let elements = buffer.into_vec(); - let interned = match buffer.into_element() { - // No point in interning an empty list as that would only result in an unnecessary allocation. - FormatElement::List(list) if list.is_empty() => FormatElement::List(list), - interned @ FormatElement::Interned(_) => interned, - element => FormatElement::Interned(Interned::new(element)), - }; + Ok(self.intern_vec(elements)) + } - Ok(interned) + pub fn intern_vec(&mut self, mut elements: Vec) -> Option { + match elements.len() { + 0 => None, + // Doesn't get cheaper than calling clone, use the element directly + // SAFETY: Safe because of the `len == 1` check in the match arm. + 1 => Some(elements.pop().unwrap()), + _ => Some(FormatElement::Interned(Interned::new(elements))), + } } } diff --git a/crates/rome_formatter/src/intersperse.rs b/crates/rome_formatter/src/intersperse.rs deleted file mode 100644 index d385ee12bc1..00000000000 --- a/crates/rome_formatter/src/intersperse.rs +++ /dev/null @@ -1,80 +0,0 @@ -//! Copied from Rust's unstable iter.intersperse(). - -use std::{fmt::Debug, iter::Peekable}; - -/// An iterator adapter that places a separator between all elements. -#[derive(Debug, Clone)] -pub struct Intersperse -where - I::Item: Clone, -{ - separator: I::Item, - iter: Peekable, - needs_sep: bool, -} - -impl Intersperse -where - I::Item: Clone, -{ - pub fn new(iter: I, separator: I::Item) -> Self { - Self { - iter: iter.peekable(), - separator, - needs_sep: false, - } - } -} - -impl Iterator for Intersperse -where - I: Iterator, - I::Item: Clone, -{ - type Item = I::Item; - - #[inline] - fn next(&mut self) -> Option { - if self.needs_sep && self.iter.peek().is_some() { - self.needs_sep = false; - Some(self.separator.clone()) - } else { - self.needs_sep = true; - self.iter.next() - } - } - - fn size_hint(&self) -> (usize, Option) { - let (lo, hi) = self.iter.size_hint(); - let next_is_elem = !self.needs_sep; - let lo = lo.saturating_sub(next_is_elem as usize).saturating_add(lo); - let hi = match hi { - Some(hi) => hi.saturating_sub(next_is_elem as usize).checked_add(hi), - None => None, - }; - (lo, hi) - } - - fn fold(mut self, init: B, mut f: F) -> B - where - Self: Sized, - F: FnMut(B, Self::Item) -> B, - { - let mut accum = init; - - // Use `peek()` first to avoid calling `next()` on an empty iterator. - if !self.needs_sep || self.iter.peek().is_some() { - if let Some(x) = self.iter.next() { - accum = f(accum, x); - } - } - - let element = &self.separator; - - self.iter.fold(accum, |mut accum, x| { - accum = f(accum, element.clone()); - accum = f(accum, x); - accum - }) - } -} diff --git a/crates/rome_formatter/src/lib.rs b/crates/rome_formatter/src/lib.rs index 200a33ed670..cccb1794d2a 100644 --- a/crates/rome_formatter/src/lib.rs +++ b/crates/rome_formatter/src/lib.rs @@ -29,7 +29,6 @@ pub mod format_element; mod format_extensions; pub mod formatter; pub mod group_id; -pub mod intersperse; pub mod macros; pub mod prelude; #[cfg(debug_assertions)] @@ -41,8 +40,9 @@ mod verbatim; use crate::formatter::Formatter; use crate::group_id::UniqueGroupIdBuilder; -use crate::prelude::syntax_token_cow_slice; +use crate::prelude::{syntax_token_cow_slice, TagKind}; +use crate::format_element::document::Document; #[cfg(debug_assertions)] use crate::printed_tokens::PrintedTokens; use crate::printer::{Printer, PrinterOptions}; @@ -55,7 +55,7 @@ pub use builders::{ }; use crate::comments::{CommentStyle, Comments, SourceComment}; -pub use format_element::{normalize_newlines, FormatElement, Text, Verbatim, LINE_TERMINATORS}; +pub use format_element::{normalize_newlines, FormatElement, Text, LINE_TERMINATORS}; pub use group_id::GroupId; use rome_rowan::{ Language, SyntaxElement, SyntaxError, SyntaxNode, SyntaxResult, SyntaxToken, SyntaxTriviaPiece, @@ -301,21 +301,28 @@ pub struct SourceMarker { #[derive(Debug, Clone, Eq, PartialEq)] pub struct Formatted { - root: FormatElement, + document: Document, context: Context, } impl Formatted { - pub fn new(root: FormatElement, context: Context) -> Self { - Self { root, context } + pub fn new(document: Document, context: Context) -> Self { + Self { document, context } } + /// Returns the context used during formatting. pub fn context(&self) -> &Context { &self.context } - pub fn into_format_element(self) -> FormatElement { - self.root + /// Returns the formatted document. + pub fn document(&self) -> &Document { + &self.document + } + + /// Consumes `self` and returns the formatted document. + pub fn into_document(self) -> Document { + self.document } } @@ -323,28 +330,138 @@ impl Formatted where Context: FormatContext, { - pub fn print(&self) -> Printed { + pub fn print(&self) -> PrintResult { let print_options = self.context.options().as_print_options(); - let printed = Printer::new(print_options).print(&self.root); + let printed = Printer::new(print_options).print(&self.document)?; - match self.context.source_map() { + let printed = match self.context.source_map() { Some(source_map) => source_map.map_printed(printed), None => printed, - } + }; + + Ok(printed) } - pub fn print_with_indent(&self, indent: u16) -> Printed { + pub fn print_with_indent(&self, indent: u16) -> PrintResult { let print_options = self.context.options().as_print_options(); - let printed = Printer::new(print_options).print_with_indent(&self.root, indent); + let printed = Printer::new(print_options).print_with_indent(&self.document, indent)?; - match self.context.source_map() { + let printed = match self.context.source_map() { Some(source_map) => source_map.map_printed(printed), None => printed, + }; + + Ok(printed) + } +} + +#[derive(Debug, Clone, Eq, PartialEq)] +pub enum PrintError { + InvalidDocument(InvalidDocumentError), +} + +impl Error for PrintError {} + +impl std::fmt::Display for PrintError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + PrintError::InvalidDocument(inner) => { + std::write!(f, "Invalid document: {inner}") + } } } } +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum InvalidDocumentError { + /// Mismatching start/end kinds + /// + /// ```plain + /// StartIndent + /// ... + /// EndGroup + /// ``` + StartEndTagMismatch { + start_kind: TagKind, + end_kind: TagKind, + }, + + /// End tag without a corresponding start tag. + /// + /// ```plain + /// Text + /// EndGroup + /// ``` + StartTagMissing { kind: TagKind }, + + /// Expected a specific start tag but instead is: + /// * at the end of the document + /// * at another start tag + /// * at an end tag + ExpectedStart { + expected_start: TagKind, + actual: ActualStart, + }, +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum ActualStart { + /// The actual element is not a tag. + Content, + + /// The actual element was a start tag of another kind. + Start(TagKind), + + /// The actual element is an end tag instead of a start tag. + End(TagKind), + + /// Reached the end of the document + EndOfDocument, +} + +impl std::fmt::Display for InvalidDocumentError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + InvalidDocumentError::StartEndTagMismatch { + start_kind, + end_kind, + } => { + std::write!( + f, + "Expected end tag of kind {start_kind:?} but found {end_kind:?}." + ) + } + InvalidDocumentError::StartTagMissing { kind } => { + std::write!(f, "End tag of kind {kind:?} without matching start tag.") + } + InvalidDocumentError::ExpectedStart { + expected_start, + actual, + } => { + match actual { + ActualStart::EndOfDocument => { + std::write!(f, "Expected start tag of kind {expected_start:?} but at the end of document.") + } + ActualStart::Start(start) => { + std::write!(f, "Expected start tag of kind {expected_start:?} but found start tag of kind {start:?}.") + } + ActualStart::End(end) => { + std::write!(f, "Expected start tag of kind {expected_start:?} but found end tag of kind {end:?}.") + } + ActualStart::Content => { + std::write!(f, "Expected start tag of kind {expected_start:?} but found non-tag element.") + } + } + } + } + } +} + +pub type PrintResult = Result; + #[derive(Debug, Clone, Eq, PartialEq)] #[cfg_attr( feature = "serde", @@ -448,6 +565,9 @@ pub enum FormatError { /// In case range formatting failed because the provided range was larger /// than the formatted syntax tree RangeError { input: TextRange, tree: TextRange }, + + /// In case printing the document failed because it has an invalid structure. + InvalidDocument(InvalidDocumentError), } impl std::fmt::Display for FormatError { @@ -458,6 +578,7 @@ impl std::fmt::Display for FormatError { fmt, "formatting range {input:?} is larger than syntax tree {tree:?}" ), + FormatError::InvalidDocument(error) => std::write!(fmt, "Invalid document: {error}\n\n This is an internal Rome error. Please report if necessary."), } } } @@ -478,6 +599,20 @@ impl From<&SyntaxError> for FormatError { } } +impl From for FormatError { + fn from(error: PrintError) -> Self { + FormatError::from(&error) + } +} + +impl From<&PrintError> for FormatError { + fn from(error: &PrintError) -> Self { + match error { + PrintError::InvalidDocument(reason) => FormatError::InvalidDocument(*reason), + } + } +} + /// Formatting trait for types that can create a formatted representation. The `rome_formatter` equivalent /// to [std::fmt::Display]. /// @@ -501,10 +636,13 @@ impl From<&SyntaxError> for FormatError { /// } /// } /// +/// # fn main() -> FormatResult<()> { /// let paragraph = Paragraph(String::from("test")); -/// let formatted = format!(SimpleFormatContext::default(), [paragraph]).unwrap(); +/// let formatted = format!(SimpleFormatContext::default(), [paragraph])?; /// -/// assert_eq!("test\n", formatted.print().as_code()) +/// assert_eq!("test\n", formatted.print()?.as_code()); +/// # Ok(()) +/// # } /// ``` pub trait Format { /// Formats the object using the given formatter. @@ -737,14 +875,17 @@ where /// use rome_formatter::prelude::*; /// use rome_formatter::{VecBuffer, format_args, FormatState, write, Formatted}; /// +/// # fn main() -> FormatResult<()> { /// let mut state = FormatState::new(SimpleFormatContext::default()); /// let mut buffer = VecBuffer::new(&mut state); /// -/// write!(&mut buffer, [format_args!(text("Hello World"))]).unwrap(); +/// write!(&mut buffer, [format_args!(text("Hello World"))])?; /// -/// let formatted = Formatted::new(buffer.into_element(), SimpleFormatContext::default()); +/// let formatted = Formatted::new(Document::from(buffer.into_vec()), SimpleFormatContext::default()); /// -/// assert_eq!("Hello World", formatted.print().as_code()) +/// assert_eq!("Hello World", formatted.print()?.as_code()); +/// # Ok(()) +/// # } /// ``` /// /// Please note that using [`write!`] might be preferable. Example: @@ -753,14 +894,17 @@ where /// use rome_formatter::prelude::*; /// use rome_formatter::{VecBuffer, format_args, FormatState, write, Formatted}; /// +/// # fn main() -> FormatResult<()> { /// let mut state = FormatState::new(SimpleFormatContext::default()); /// let mut buffer = VecBuffer::new(&mut state); /// -/// write!(&mut buffer, [text("Hello World")]).unwrap(); +/// write!(&mut buffer, [text("Hello World")])?; /// -/// let formatted = Formatted::new(buffer.into_element(), SimpleFormatContext::default()); +/// let formatted = Formatted::new(Document::from(buffer.into_vec()), SimpleFormatContext::default()); /// -/// assert_eq!("Hello World", formatted.print().as_code()) +/// assert_eq!("Hello World", formatted.print()?.as_code()); +/// # Ok(()) +/// # } /// ``` /// #[inline(always)] @@ -785,8 +929,11 @@ pub fn write( /// use rome_formatter::prelude::*; /// use rome_formatter::{format, format_args}; /// -/// let formatted = format!(SimpleFormatContext::default(), [&format_args!(text("test"))]).unwrap(); -/// assert_eq!("test", formatted.print().as_code()); +/// # fn main() -> FormatResult<()> { +/// let formatted = format!(SimpleFormatContext::default(), [&format_args!(text("test"))])?; +/// assert_eq!("test", formatted.print()?.as_code()); +/// # Ok(()) +/// # } /// ``` /// /// Please note that using [`format!`] might be preferable. Example: @@ -795,8 +942,11 @@ pub fn write( /// use rome_formatter::prelude::*; /// use rome_formatter::{format}; /// -/// let formatted = format!(SimpleFormatContext::default(), [text("test")]).unwrap(); -/// assert_eq!("test", formatted.print().as_code()); +/// # fn main() -> FormatResult<()> { +/// let formatted = format!(SimpleFormatContext::default(), [text("test")])?; +/// assert_eq!("test", formatted.print()?.as_code()); +/// # Ok(()) +/// # } /// ``` pub fn format( context: Context, @@ -810,7 +960,10 @@ where buffer.write_fmt(arguments)?; - Ok(Formatted::new(buffer.into_element(), state.into_context())) + Ok(Formatted::new( + Document::from(buffer.into_vec()), + state.into_context(), + )) } /// Entry point for formatting a [SyntaxNode] for a specific language. @@ -879,7 +1032,7 @@ pub fn format_node( write!(buffer, [format_node])?; - let document = buffer.into_element(); + let document = Document::from(buffer.into_vec()); state.assert_formatted_all_tokens(&root); { @@ -1226,7 +1379,7 @@ pub fn format_sub_tree( }; let formatted = format_node(root, language)?; - let mut printed = formatted.print_with_indent(initial_indent); + let mut printed = formatted.print_with_indent(initial_indent)?; let sourcemap = printed.take_sourcemap(); let verbatim_ranges = printed.take_verbatim_ranges(); diff --git a/crates/rome_formatter/src/macros.rs b/crates/rome_formatter/src/macros.rs index fe4cb2ecbad..f374199b01a 100644 --- a/crates/rome_formatter/src/macros.rs +++ b/crates/rome_formatter/src/macros.rs @@ -14,11 +14,14 @@ /// use rome_formatter::{SimpleFormatContext, format, format_args}; /// use rome_formatter::prelude::*; /// +/// # fn main() -> FormatResult<()> { /// let formatted = format!(SimpleFormatContext::default(), [ /// format_args!(text("Hello World")) -/// ]).unwrap(); +/// ])?; /// -/// assert_eq!("Hello World", formatted.print().as_code()); +/// assert_eq!("Hello World", formatted.print()?.as_code()); +/// # Ok(()) +/// # } /// ``` /// /// [`Format`]: crate::Format @@ -46,23 +49,22 @@ macro_rules! format_args { /// use rome_formatter::prelude::*; /// use rome_formatter::{Buffer, FormatState, SimpleFormatContext, VecBuffer, write}; /// -/// fn main() -> FormatResult<()> { -/// let mut state = FormatState::new(SimpleFormatContext::default()); -/// let mut buffer = VecBuffer::new(&mut state); -/// write!(&mut buffer, [text("Hello"), space()])?; -/// write!(&mut buffer, [text("World")])?; -/// -/// assert_eq!( -/// buffer.into_element(), -/// FormatElement::from_iter([ -/// FormatElement::Text(Text::Static { text: "Hello" }), -/// FormatElement::Space, -/// FormatElement::Text(Text::Static { text: "World" }), -/// ]) -/// ); -/// -/// Ok(()) -/// } +/// # fn main() -> FormatResult<()> { +/// let mut state = FormatState::new(SimpleFormatContext::default()); +/// let mut buffer = VecBuffer::new(&mut state); +/// write!(&mut buffer, [text("Hello"), space()])?; +/// write!(&mut buffer, [text("World")])?; +/// +/// assert_eq!( +/// buffer.into_vec(), +/// vec![ +/// FormatElement::Text(Text::Static { text: "Hello" }), +/// FormatElement::Space, +/// FormatElement::Text(Text::Static { text: "World" }), +/// ] +/// ); +/// # Ok(()) +/// # } /// ``` #[macro_export] macro_rules! write { @@ -80,13 +82,16 @@ macro_rules! write { /// use rome_formatter::prelude::*; /// use rome_formatter::{FormatState, VecBuffer}; /// +/// # fn main() -> FormatResult<()> { /// let mut state = FormatState::new(SimpleFormatContext::default()); /// let mut buffer = VecBuffer::new(&mut state); /// -/// dbg_write!(buffer, [text("Hello")]).unwrap(); +/// dbg_write!(buffer, [text("Hello")])?; /// // ^-- prints: [src/main.rs:7][0] = StaticToken("Hello") /// -/// assert_eq!(buffer.into_element(), FormatElement::Text(Text::Static { text: "Hello" })); +/// assert_eq!(buffer.into_vec(), vec![FormatElement::Text(Text::Static { text: "Hello" })]); +/// # Ok(()) +/// # } /// ``` /// /// Note that the macro is intended as debugging tool and therefore you should avoid having @@ -124,8 +129,8 @@ macro_rules! dbg_write { /// let formatted = format!(SimpleFormatContext::default(), [text("("), text("a"), text(")")]).unwrap(); /// /// assert_eq!( -/// formatted.into_format_element(), -/// FormatElement::from_iter([ +/// formatted.into_document(), +/// Document::from(vec![ /// FormatElement::Text(Text::Static { text: "(" }), /// FormatElement::Text(Text::Static { text: "a" }), /// FormatElement::Text(Text::Static { text: ")" }), @@ -151,6 +156,7 @@ macro_rules! format { /// use rome_formatter::{Formatted, LineWidth, format, format_args, SimpleFormatOptions}; /// use rome_formatter::prelude::*; /// +/// # fn main() -> FormatResult<()> { /// let formatted = format!( /// SimpleFormatContext::default(), /// [ @@ -200,15 +206,15 @@ macro_rules! format { /// ) /// ) /// ] -/// ).unwrap(); +/// )?; /// -/// let elements = formatted.into_format_element(); +/// let document = formatted.into_document(); /// /// // Takes the first variant if everything fits on a single line /// assert_eq!( /// "aVeryLongIdentifier([1, 2, 3])", -/// Formatted::new(elements.clone(), SimpleFormatContext::default()) -/// .print() +/// Formatted::new(document.clone(), SimpleFormatContext::default()) +/// .print()? /// .as_code() /// ); /// @@ -216,18 +222,20 @@ macro_rules! format { /// // has some additional line breaks to make sure inner groups don't break /// assert_eq!( /// "aVeryLongIdentifier([\n\t1, 2, 3\n])", -/// Formatted::new(elements.clone(), SimpleFormatContext::new(SimpleFormatOptions { line_width: 21.try_into().unwrap(), ..SimpleFormatOptions::default() })) -/// .print() +/// Formatted::new(document.clone(), SimpleFormatContext::new(SimpleFormatOptions { line_width: 21.try_into().unwrap(), ..SimpleFormatOptions::default() })) +/// .print()? /// .as_code() /// ); /// /// // Prints the last option as last resort /// assert_eq!( /// "aVeryLongIdentifier(\n\t[\n\t\t1,\n\t\t2,\n\t\t3\n\t]\n)", -/// Formatted::new(elements.clone(), SimpleFormatContext::new(SimpleFormatOptions { line_width: 20.try_into().unwrap(), ..SimpleFormatOptions::default() })) -/// .print() +/// Formatted::new(document.clone(), SimpleFormatContext::new(SimpleFormatOptions { line_width: 20.try_into().unwrap(), ..SimpleFormatOptions::default() })) +/// .print()? /// .as_code() /// ); +/// # Ok(()) +/// # } /// ``` /// /// ## Complexity @@ -281,8 +289,8 @@ mod tests { write![&mut buffer, [TestFormat]].unwrap(); assert_eq!( - buffer.into_element(), - FormatElement::Text(Text::Static { text: "test" }) + buffer.into_vec(), + vec![FormatElement::Text(Text::Static { text: "test" })] ); } @@ -298,14 +306,14 @@ mod tests { .unwrap(); assert_eq!( - buffer.into_element(), - FormatElement::List(List::new(vec![ + buffer.into_vec(), + vec![ FormatElement::Text(Text::Static { text: "a" }), FormatElement::Space, FormatElement::Text(Text::Static { text: "simple" }), FormatElement::Space, FormatElement::Text(Text::Static { text: "test" }) - ])) + ] ); } @@ -396,24 +404,26 @@ mod tests { .unwrap(); let best_fitting_code = Formatted::new( - formatted_best_fitting.into_format_element(), + formatted_best_fitting.into_document(), SimpleFormatContext::new(SimpleFormatOptions { line_width: 30.try_into().unwrap(), ..SimpleFormatOptions::default() }), ) .print() + .expect("Document to be valid") .as_code() .to_string(); let normal_list_code = Formatted::new( - formatted_normal_list.into_format_element(), + formatted_normal_list.into_document(), SimpleFormatContext::new(SimpleFormatOptions { line_width: 30.try_into().unwrap(), ..SimpleFormatOptions::default() }), ) .print() + .expect("Document to be valid") .as_code() .to_string(); diff --git a/crates/rome_formatter/src/prelude.rs b/crates/rome_formatter/src/prelude.rs index cfaf2c02a3c..1a3a276a991 100644 --- a/crates/rome_formatter/src/prelude.rs +++ b/crates/rome_formatter/src/prelude.rs @@ -10,6 +10,9 @@ pub use crate::trivia::{ pub use crate::verbatim::{format_suppressed_node, format_unknown_node, format_verbatim_node}; +pub use crate::format_element::document::Document; +pub use crate::format_element::tag::{LabelId, Tag, TagKind}; + pub use crate::{ best_fitting, dbg_write, format, format_args, write, Buffer as _, BufferExtensions, Format, Format as _, FormatError, FormatResult, FormatRule, FormatWithRule as _, SimpleFormatContext, diff --git a/crates/rome_formatter/src/printer/call_stack.rs b/crates/rome_formatter/src/printer/call_stack.rs new file mode 100644 index 00000000000..243dbe6fa94 --- /dev/null +++ b/crates/rome_formatter/src/printer/call_stack.rs @@ -0,0 +1,235 @@ +use crate::format_element::tag::TagKind; +use crate::format_element::PrintMode; +use crate::printer::stack::{Stack, StackedStack}; +use crate::printer::Indention; +use crate::{IndentStyle, InvalidDocumentError, PrintError, PrintResult}; +use std::fmt::Debug; +use std::num::NonZeroU8; + +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +pub(super) enum StackFrameKind { + Root, + Tag(TagKind), +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +pub(super) struct StackFrame { + kind: StackFrameKind, + args: PrintElementArgs, +} + +/// Stores arguments passed to `print_element` call, holding the state specific to printing an element. +/// E.g. the `indent` depends on the token the Printer's currently processing. That's why +/// it must be stored outside of the [PrinterState] that stores the state common to all elements. +/// +/// The state is passed by value, which is why it's important that it isn't storing any heavy +/// data structures. Such structures should be stored on the [PrinterState] instead. +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub(super) struct PrintElementArgs { + indent: Indention, + mode: PrintMode, +} + +impl PrintElementArgs { + pub fn new(indent: Indention) -> Self { + Self { + indent, + ..Self::default() + } + } + + pub(super) fn mode(&self) -> PrintMode { + self.mode + } + + pub(super) fn indention(&self) -> Indention { + self.indent + } + + pub fn increment_indent_level(mut self, indent_style: IndentStyle) -> Self { + self.indent = self.indent.increment_level(indent_style); + self + } + + pub fn decrement_indent(mut self) -> Self { + self.indent = self.indent.decrement(); + self + } + + pub fn reset_indent(mut self) -> Self { + self.indent = Indention::default(); + self + } + + pub fn set_indent_align(mut self, count: NonZeroU8) -> Self { + self.indent = self.indent.set_align(count); + self + } + + pub fn with_print_mode(mut self, mode: PrintMode) -> Self { + self.mode = mode; + self + } +} + +impl Default for PrintElementArgs { + fn default() -> Self { + Self { + indent: Indention::Level(0), + mode: PrintMode::Expanded, + } + } +} + +/// Call stack that stores the [PrintElementCallArgs]. +/// +/// New [PrintElementCallArgs] are pushed onto the stack for every [`start`](Tag::is_start) [`Tag`](FormatElement::Tag) +/// and popped when reaching the corresponding [`end`](Tag::is_end) [`Tag`](FormatElement::Tag). +pub(super) trait CallStack { + type Stack: Stack + Debug; + + fn stack(&self) -> &Self::Stack; + + fn stack_mut(&mut self) -> &mut Self::Stack; + + /// Pops the call arguments at the top and asserts that they correspond to a start tag of `kind`. + /// + /// Returns `Ok` with the arguments if the kind of the top stack frame matches `kind`, otherwise + /// returns `Err`. + fn pop(&mut self, kind: TagKind) -> PrintResult { + let last = self.stack_mut().pop(); + + match last { + Some(StackFrame { + kind: StackFrameKind::Tag(actual_kind), + args, + }) if actual_kind == kind => Ok(args), + // Start / End kind don't match + Some(StackFrame { + kind: StackFrameKind::Tag(expected_kind), + .. + }) => Err(PrintError::InvalidDocument(Self::invalid_document_error( + kind, + Some(expected_kind), + ))), + // Tried to pop the outer most stack frame, which is not valid + Some( + frame @ StackFrame { + kind: StackFrameKind::Root, + .. + }, + ) => { + // Put it back in to guarantee that the stack is never empty + self.stack_mut().push(frame); + Err(PrintError::InvalidDocument(Self::invalid_document_error( + kind, None, + ))) + } + + // This should be unreachable but having it for completeness. Happens if the stack is empty. + None => Err(PrintError::InvalidDocument(Self::invalid_document_error( + kind, None, + ))), + } + } + + #[cold] + fn invalid_document_error( + end_kind: TagKind, + start_kind: Option, + ) -> InvalidDocumentError { + match start_kind { + None => InvalidDocumentError::StartTagMissing { kind: end_kind }, + Some(start_kind) => InvalidDocumentError::StartEndTagMismatch { + start_kind, + end_kind, + }, + } + } + + /// Returns the [PrintElementArgs] for the current stack frame. + fn top(&self) -> PrintElementArgs { + self.stack() + .top() + .expect("Expected `stack` to never be empty.") + .args + } + + /// Returns the [TagKind] of the current stack frame or [None] if this is the root stack frame. + fn top_kind(&self) -> Option { + match self + .stack() + .top() + .expect("Expected `stack` to never be empty.") + .kind + { + StackFrameKind::Root => None, + StackFrameKind::Tag(kind) => Some(kind), + } + } + + /// Creates a new stack frame for a [FormatElement::Tag] of `kind` with `args` as the call arguments. + fn push(&mut self, kind: TagKind, args: PrintElementArgs) { + self.stack_mut().push(StackFrame { + kind: StackFrameKind::Tag(kind), + args, + }) + } +} + +/// Call stack used for printing the [FormatElement]s +#[derive(Debug, Clone)] +pub(super) struct PrintCallStack(Vec); + +impl PrintCallStack { + pub(super) fn new(args: PrintElementArgs) -> Self { + Self(vec![StackFrame { + kind: StackFrameKind::Root, + args, + }]) + } +} + +impl CallStack for PrintCallStack { + type Stack = Vec; + + fn stack(&self) -> &Self::Stack { + &self.0 + } + + fn stack_mut(&mut self) -> &mut Self::Stack { + &mut self.0 + } +} + +/// Call stack used for measuring if some content fits on the line. +/// +/// The stack is a view on top of the [PrintCallStack] because the stack frames are still necessary for printing. +#[must_use] +pub(super) struct FitsCallStack<'print> { + stack: StackedStack<'print, StackFrame>, +} + +impl<'print> FitsCallStack<'print> { + pub(super) fn new(print: &'print PrintCallStack, saved: Vec) -> Self { + let stack = StackedStack::with_vec(&print.0, saved); + + Self { stack } + } + + pub(super) fn finish(self) -> Vec { + self.stack.into_vec() + } +} + +impl<'a> CallStack for FitsCallStack<'a> { + type Stack = StackedStack<'a, StackFrame>; + + fn stack(&self) -> &Self::Stack { + &self.stack + } + + fn stack_mut(&mut self) -> &mut Self::Stack { + &mut self.stack + } +} diff --git a/crates/rome_formatter/src/printer/line_suffixes.rs b/crates/rome_formatter/src/printer/line_suffixes.rs new file mode 100644 index 00000000000..a17857cd47d --- /dev/null +++ b/crates/rome_formatter/src/printer/line_suffixes.rs @@ -0,0 +1,42 @@ +use crate::printer::call_stack::PrintElementArgs; +use crate::FormatElement; + +/// Stores the queued line suffixes. +#[derive(Debug, Default)] +pub(super) struct LineSuffixes<'a> { + suffixes: Vec>, +} + +impl<'a> LineSuffixes<'a> { + /// Extends the line suffixes with `elements`, storing their call stack arguments with them. + pub(super) fn extend(&mut self, args: PrintElementArgs, elements: I) + where + I: IntoIterator, + { + self.suffixes + .extend(elements.into_iter().map(LineSuffixEntry::Suffix)); + self.suffixes.push(LineSuffixEntry::Args(args)); + } + + /// Takes all the pending line suffixes. + pub(super) fn take_pending<'l>( + &'l mut self, + ) -> impl Iterator> + DoubleEndedIterator + 'l + ExactSizeIterator + { + self.suffixes.drain(..) + } + + /// Returns `true` if there are any line suffixes and `false` otherwise. + pub(super) fn has_pending(&self) -> bool { + !self.suffixes.is_empty() + } +} + +#[derive(Debug, Copy, Clone)] +pub(super) enum LineSuffixEntry<'a> { + /// A line suffix to print + Suffix(&'a FormatElement), + + /// Potentially changed call arguments that should be used to format any following items. + Args(PrintElementArgs), +} diff --git a/crates/rome_formatter/src/printer/mod.rs b/crates/rome_formatter/src/printer/mod.rs index 45a612c102f..db94982a4fc 100644 --- a/crates/rome_formatter/src/printer/mod.rs +++ b/crates/rome_formatter/src/printer/mod.rs @@ -1,15 +1,30 @@ +mod call_stack; +mod line_suffixes; mod printer_options; +mod queue; +mod stack; pub use printer_options::*; -use crate::format_element::{ - Align, ConditionalGroupContent, DedentMode, Group, IndentIfGroupBreaks, LineMode, PrintMode, - VerbatimKind, +use crate::format_element::{BestFitting, LineMode, PrintMode}; +use crate::{ + ActualStart, FormatElement, GroupId, IndentStyle, InvalidDocumentError, PrintError, + PrintResult, Printed, SourceMarker, TextRange, }; -use crate::{FormatElement, GroupId, IndentStyle, Printed, SourceMarker, TextRange}; +use crate::format_element::document::Document; +use crate::format_element::tag::Condition; +use crate::prelude::tag::{DedentMode, Tag, TagKind, VerbatimKind}; +use crate::prelude::Tag::EndFill; +use crate::printer::call_stack::{ + CallStack, FitsCallStack, PrintCallStack, PrintElementArgs, StackFrame, +}; +use crate::printer::line_suffixes::{LineSuffixEntry, LineSuffixes}; +use crate::printer::queue::{ + AllPredicate, FitsPredicate, FitsQueue, PrintQueue, Queue, SeparatorItemPairPredicate, + SingleEntryPredicate, +}; use rome_rowan::{TextLen, TextSize}; -use std::iter::{once, Rev}; use std::num::NonZeroU8; /// Prints the format elements into a string @@ -28,55 +43,56 @@ impl<'a> Printer<'a> { } /// Prints the passed in element as well as all its content - pub fn print(self, element: &'a FormatElement) -> Printed { - self.print_with_indent(element, 0) + pub fn print(self, document: &'a Document) -> PrintResult { + self.print_with_indent(document, 0) } /// Prints the passed in element as well as all its content, /// starting at the specified indentation level - pub fn print_with_indent(mut self, element: &'a FormatElement, indent: u16) -> Printed { + pub fn print_with_indent( + mut self, + document: &'a Document, + indent: u16, + ) -> PrintResult { tracing::debug_span!("Printer::print").in_scope(move || { - let mut queue = ElementCallQueue::default(); - - queue.enqueue(PrintElementCall::new( - element, - PrintElementArgs::new(Indention::Level(indent)), - )); - - while let Some(print_element_call) = queue.dequeue() { - self.print_element( - &mut queue, - print_element_call.element, - print_element_call.args, - ); + let mut stack = PrintCallStack::new(PrintElementArgs::new(Indention::Level(indent))); + let mut queue: PrintQueue<'a> = PrintQueue::new(document.as_ref()); - if queue.is_empty() && !self.state.line_suffixes.is_empty() { - queue.extend(self.state.line_suffixes.drain(..)); + while let Some(element) = queue.pop() { + self.print_element(&mut stack, &mut queue, element)?; + + if queue.is_empty() { + self.flush_line_suffixes(&mut queue, &mut stack, None); } } - Printed::new( + Ok(Printed::new( self.state.buffer, None, self.state.source_markers, self.state.verbatim_markers, - ) + )) }) } /// Prints a single element and push the following elements to queue fn print_element( &mut self, - queue: &mut ElementCallQueue<'a>, + stack: &mut PrintCallStack, + queue: &mut PrintQueue<'a>, element: &'a FormatElement, - args: PrintElementArgs, - ) { + ) -> PrintResult<()> { + use Tag::*; + + let args = stack.top(); + match element { FormatElement::Space => { if self.state.line_width > 0 { self.state.pending_space = true; } } + FormatElement::Text(token) => { if !self.state.pending_indent.is_empty() { let (indent_char, repeat_count) = match self.options.indent_style() { @@ -136,12 +152,63 @@ impl<'a> Printer<'a> { }); } - FormatElement::Group(Group { content, id }) => { - let group_mode = match args.mode { + FormatElement::Line(line_mode) => { + if args.mode().is_flat() + && matches!(line_mode, LineMode::Soft | LineMode::SoftOrSpace) + { + if line_mode == &LineMode::SoftOrSpace && self.state.line_width > 0 { + self.state.pending_space = true; + } + } else if self.state.line_suffixes.has_pending() { + self.flush_line_suffixes(queue, stack, Some(element)); + } else { + // Only print a newline if the current line isn't already empty + if self.state.line_width > 0 { + self.print_str("\n"); + } + + // Print a second line break if this is an empty line + if line_mode == &LineMode::Empty && !self.state.has_empty_line { + self.print_str("\n"); + self.state.has_empty_line = true; + } + + self.state.pending_space = false; + self.state.pending_indent = args.indention(); + + // Fit's only tests if groups up to the first line break fit. + // The next group must re-measure if it still fits. + self.state.measured_group_fits = false; + } + } + + FormatElement::ExpandParent => { + // No-op, only has an effect on `fits` + debug_assert!( + !args.mode().is_flat(), + "Fits should always return false for `ExpandParent`" + ); + } + + FormatElement::LineSuffixBoundary => { + const HARD_BREAK: &FormatElement = &FormatElement::Line(LineMode::Hard); + self.flush_line_suffixes(queue, stack, Some(HARD_BREAK)); + } + + FormatElement::BestFitting(best_fitting) => { + self.print_best_fitting(best_fitting, queue, stack)?; + } + + FormatElement::Interned(content) => { + queue.extend_back(content); + } + + FormatElement::Tag(StartGroup(id)) => { + let group_mode = match args.mode() { PrintMode::Flat if self.state.measured_group_fits => { // A parent group has already verified that this group fits on a single line // Thus, just continue in flat mode - queue.extend_with_args(content.iter(), args); + stack.push(TagKind::Group, args); PrintMode::Flat } // The printer is either in expanded mode or it's necessary to re-measure if the group fits @@ -149,19 +216,20 @@ impl<'a> Printer<'a> { _ => { // Measure to see if the group fits up on a single line. If that's the case, // print the group in "flat" mode, otherwise continue in expanded mode + stack.push(TagKind::Group, args.with_print_mode(PrintMode::Flat)); + let fits = fits_on_line(AllPredicate, queue, stack, self)?; + stack.pop(TagKind::Group)?; - let flat_args = args.with_print_mode(PrintMode::Flat); - if fits_on_line(content.iter(), flat_args, queue, self) { - queue.extend_with_args(content.iter(), flat_args); + let mode = if fits { self.state.measured_group_fits = true; PrintMode::Flat } else { - queue.extend_with_args( - content.iter(), - args.with_print_mode(PrintMode::Expanded), - ); PrintMode::Expanded - } + }; + + stack.push(TagKind::Group, args.with_print_mode(mode)); + + mode } }; @@ -170,165 +238,92 @@ impl<'a> Printer<'a> { } } - FormatElement::Fill(content) => { - self.print_fill(queue, content, args); - } - - FormatElement::List(list) => { - queue.extend_with_args(list.iter(), args); + FormatElement::Tag(StartFill) => { + stack.push(TagKind::Fill, args); + self.print_fill_entries(queue, stack)?; } - FormatElement::Indent(content) => { - queue.extend_with_args( - content.iter(), + FormatElement::Tag(StartIndent) => { + stack.push( + TagKind::Indent, args.increment_indent_level(self.options.indent_style()), ); } - FormatElement::Dedent { content, mode } => { + FormatElement::Tag(StartDedent(mode)) => { let args = match mode { DedentMode::Level => args.decrement_indent(), DedentMode::Root => args.reset_indent(), }; - queue.extend_with_args(content.iter(), args); + stack.push(TagKind::Dedent, args); } - FormatElement::Align(Align { content, count }) => { - queue.extend_with_args(content.iter(), args.set_indent_align(*count)) + FormatElement::Tag(StartAlign(align)) => { + stack.push(TagKind::Align, args.set_indent_align(align.count())); } - FormatElement::ConditionalGroupContent(ConditionalGroupContent { - mode, - content, - group_id, - }) => { + FormatElement::Tag(StartConditionalContent(Condition { mode, group_id })) => { let group_mode = match group_id { - None => args.mode, + None => args.mode(), Some(id) => self.state.group_modes.unwrap_print_mode(*id, element), }; - if &group_mode == mode { - queue.extend_with_args(content.iter(), args); + if group_mode != *mode { + queue.skip_content(TagKind::ConditionalContent); + } else { + stack.push(TagKind::ConditionalContent, args); } } - FormatElement::IndentIfGroupBreaks(IndentIfGroupBreaks { content, group_id }) => { + FormatElement::Tag(StartIndentIfGroupBreaks(group_id)) => { let group_mode = self.state.group_modes.unwrap_print_mode(*group_id, element); - match group_mode { - PrintMode::Flat => queue.extend_with_args(content.iter(), args), - PrintMode::Expanded => queue.extend_with_args( - content.iter(), - args.increment_indent_level(self.options.indent_style), - ), - } - } - - FormatElement::Line(line_mode) => { - if args.mode.is_flat() - && matches!(line_mode, LineMode::Soft | LineMode::SoftOrSpace) - { - if line_mode == &LineMode::SoftOrSpace && self.state.line_width > 0 { - self.state.pending_space = true; - } - } else if !self.state.line_suffixes.is_empty() { - self.queue_line_suffixes(element, args, queue); - } else { - // Only print a newline if the current line isn't already empty - if self.state.line_width > 0 { - self.print_str("\n"); - } - - // Print a second line break if this is an empty line - if line_mode == &LineMode::Empty && !self.state.has_empty_line { - self.print_str("\n"); - self.state.has_empty_line = true; - } - - self.state.pending_space = false; - self.state.pending_indent = args.indent; + let args = match group_mode { + PrintMode::Flat => args, + PrintMode::Expanded => args.increment_indent_level(self.options.indent_style), + }; - // Fit's only tests if groups up to the first line break fit. - // The next group must re-measure if it still fits. - self.state.measured_group_fits = false; - } + stack.push(TagKind::IndentIfGroupBreaks, args); } - FormatElement::LineSuffix(suffix) => { + FormatElement::Tag(StartLineSuffix) => { self.state .line_suffixes - .extend(suffix.iter().map(|e| PrintElementCall::new(e, args))); - } - FormatElement::LineSuffixBoundary => { - const HARD_BREAK: &FormatElement = &FormatElement::Line(LineMode::Hard); - self.queue_line_suffixes(HARD_BREAK, args, queue); + .extend(args, queue.iter_content(TagKind::LineSuffix)); } - FormatElement::Verbatim(verbatim) => { - if let VerbatimKind::Verbatim { length } = &verbatim.kind { + FormatElement::Tag(StartVerbatim(kind)) => { + if let VerbatimKind::Verbatim { length } = kind { self.state.verbatim_markers.push(TextRange::at( TextSize::from(self.state.buffer.len() as u32), *length, )); } - queue.extend_with_args(verbatim.content.iter(), args); + stack.push(TagKind::Verbatim, args); } - FormatElement::ExpandParent => { - // No-op, only has an effect on `fits` - debug_assert!( - !args.mode.is_flat(), - "Fits should always return false for `ExpandParent`" - ); + + FormatElement::Tag(tag @ (StartLabelled(_) | StartEntry)) => { + stack.push(tag.kind(), args); } - FormatElement::BestFitting(best_fitting) => { - match args.mode { - PrintMode::Flat if self.state.measured_group_fits => { - queue.enqueue(PrintElementCall::new(best_fitting.most_flat(), args)) - } - _ => { - let last_index = best_fitting.variants().len() - 1; - for (index, variant) in best_fitting.variants().iter().enumerate() { - if index == last_index { - // No variant fits, take the last (most expanded) as fallback - queue.enqueue(PrintElementCall::new( - variant, - args.with_print_mode(PrintMode::Expanded), - )); - break; - } else { - // Test if this variant fits and if so, use it. Otherwise try the next - // variant. - - // Try to fit only the first variant on a single line - let mode = if index == 0 { - PrintMode::Flat - } else { - PrintMode::Expanded - }; - - if fits_on_line([variant], args.with_print_mode(mode), queue, self) - { - self.state.measured_group_fits = true; - queue.enqueue(PrintElementCall::new( - variant, - args.with_print_mode(mode), - )); - return; - } - } - } - } - } + FormatElement::Tag( + tag @ (EndLabelled + | EndEntry + | EndGroup + | EndIndent + | EndDedent + | EndAlign + | EndConditionalContent + | EndIndentIfGroupBreaks + | EndVerbatim + | EndLineSuffix + | EndFill), + ) => { + stack.pop(tag.kind())?; } - FormatElement::Interned(content) => queue.enqueue(PrintElementCall::new(content, args)), - FormatElement::Label(label) => queue.extend( - label - .content - .iter() - .map(|element| PrintElementCall::new(element, args)), - ), - } + }; + + Ok(()) } fn push_marker(&mut self, marker: SourceMarker) { @@ -341,20 +336,92 @@ impl<'a> Printer<'a> { } } - fn queue_line_suffixes( + fn flush_line_suffixes( &mut self, - line_break: &'a FormatElement, - args: PrintElementArgs, - queue: &mut ElementCallQueue<'a>, + queue: &mut PrintQueue<'a>, + stack: &mut PrintCallStack, + line_break: Option<&'a FormatElement>, ) { - if self.state.line_suffixes.is_empty() { - return; + let suffixes = self.state.line_suffixes.take_pending(); + + if suffixes.len() > 0 { + // Print this line break element again once all the line suffixes have been flushed + if let Some(line_break) = line_break { + queue.push(line_break); + } + + for entry in suffixes.rev() { + match entry { + LineSuffixEntry::Suffix(suffix) => { + queue.push(suffix); + } + LineSuffixEntry::Args(args) => { + stack.push(TagKind::LineSuffix, args); + const LINE_SUFFIX_END: &FormatElement = + &FormatElement::Tag(Tag::EndLineSuffix); + + queue.push(LINE_SUFFIX_END); + } + } + } } + } + + fn print_best_fitting( + &mut self, + best_fitting: &'a BestFitting, + queue: &mut PrintQueue<'a>, + stack: &mut PrintCallStack, + ) -> PrintResult<()> { + let args = stack.top(); + + if args.mode().is_flat() { + queue.extend_back(best_fitting.most_flat()); + self.print_entry(queue, stack, args) + } else { + let normal_variants = &best_fitting.variants()[..best_fitting.variants().len() - 1]; + + for (index, variant) in normal_variants.iter().enumerate() { + // Test if this variant fits and if so, use it. Otherwise try the next + // variant. + + // Try to fit only the first variant on a single line + let mode = if index == 0 { + PrintMode::Flat + } else { + PrintMode::Expanded + }; + + if !matches!(variant.first(), Some(&FormatElement::Tag(Tag::StartEntry))) { + return invalid_start_tag(TagKind::Entry, variant.first()); + } + + // Skip the first element because we want to override the args for the entry and the + // args must be popped from the stack as soon as it sees the matching end entry. + let content = &variant[1..]; + + queue.extend_back(content); + stack.push(TagKind::Entry, args.with_print_mode(mode)); + let variant_fits = fits_on_line(AllPredicate, queue, stack, self)?; + stack.pop(TagKind::Entry)?; - // Print this line break element again once all the line suffixes have been flushed - let call_self = PrintElementCall::new(line_break, args); + // Remove the content slice because printing needs the variant WITH the start entry + let popped_slice = queue.pop_slice(); + debug_assert_eq!(popped_slice, Some(content)); - queue.extend(self.state.line_suffixes.drain(..).chain(once(call_self))); + if variant_fits { + self.state.measured_group_fits = true; + + queue.extend_back(variant); + return self.print_entry(queue, stack, args.with_print_mode(mode)); + } + } + + // No variant fits, take the last (most expanded) as fallback + let most_expanded = best_fitting.most_expanded(); + queue.extend_back(most_expanded); + self.print_entry(queue, stack, args.with_print_mode(PrintMode::Expanded)) + } } /// Tries to fit as much content as possible on a single line. @@ -364,146 +431,169 @@ impl<'a> Printer<'a> { /// * The first and second content fit on a single line. It prints the content and separator in flat mode. /// * The first content fits on a single line, but the second doesn't. It prints the content in flat and the separator in expanded mode. /// * Neither the first nor the second content fit on the line. It brings the first content and the separator in expanded mode. - fn print_fill( + fn print_fill_entries( &mut self, - queue: &mut ElementCallQueue<'a>, - content: &'a [FormatElement], - args: PrintElementArgs, - ) { - let empty_rest = ElementCallQueue::default(); - - let mut items = content.iter(); - - let current_content = match items.next() { - None => { - // Empty list - return; - } - Some(item) => item, - }; + queue: &mut PrintQueue<'a>, + stack: &mut PrintCallStack, + ) -> PrintResult<()> { + let args = stack.top(); + + if matches!(queue.top(), Some(FormatElement::Tag(Tag::EndFill))) { + // Empty fill + return Ok(()); + } - let mut current_fits = fits_on_line( - [current_content], - args.with_print_mode(PrintMode::Flat), - &empty_rest, - self, - ); + // Print the first item + let mut current_fits = self.fits_fill_entry( + SingleEntryPredicate::default(), + queue, + stack, + PrintMode::Flat, + )?; - self.print_all( + self.print_entry( queue, - &[current_content], + stack, args.with_print_mode(if current_fits { PrintMode::Flat } else { PrintMode::Expanded }), - ); + )?; + + // Process remaining items, it's a sequence of separator, item, separator, item... + while matches!(queue.top(), Some(FormatElement::Tag(Tag::StartEntry))) { + // A line break in expanded mode is always necessary if the current item didn't fit. + // otherwise see if both contents fit on the line. + let all_fits = if current_fits { + self.fits_fill_entry( + SeparatorItemPairPredicate::default(), + queue, + stack, + PrintMode::Flat, + )? + } else { + false + }; - // Process remaining items - loop { - match (items.next(), items.next()) { - (Some(separator), Some(next_item)) => { - // A line break in expanded mode is always necessary if the current item didn't fit. - // otherwise see if both contents fit on the line. - let current_and_next_fit = current_fits - && fits_on_line( - [separator, next_item], - args.with_print_mode(PrintMode::Flat), - &empty_rest, - self, - ); - - if current_and_next_fit { - // Print Space and next item on the same line - self.print_all( - queue, - &[separator, next_item], - args.with_print_mode(PrintMode::Flat), - ); - } else { - // Print the separator and then check again if the next item fits on the line now - self.print_all( - queue, - &[separator], - args.with_print_mode(PrintMode::Expanded), - ); - - let next_fits = fits_on_line( - [next_item], - args.with_print_mode(PrintMode::Flat), - &empty_rest, - self, - ); - - if next_fits { - self.print_all( - queue, - &[next_item], - args.with_print_mode(PrintMode::Flat), - ); - } else { - self.print_all( - queue, - &[next_item], - args.with_print_mode(PrintMode::Expanded), - ); - } - - current_fits = next_fits; - } - } - // Trailing separator - (Some(separator), _) => { - let print_mode = if current_fits - && fits_on_line( - [separator], - args.with_print_mode(PrintMode::Flat), - &empty_rest, - self, - ) { + let separator_mode = if all_fits { + PrintMode::Flat + } else { + PrintMode::Expanded + }; + + // Separator + self.print_entry(queue, stack, args.with_print_mode(separator_mode))?; + + // If this was a trailing separator, exit + if !matches!(queue.top(), Some(FormatElement::Tag(Tag::StartEntry))) { + break; + } + + if all_fits { + // Item + self.print_entry(queue, stack, args.with_print_mode(PrintMode::Flat))?; + } else { + // Test if item fits now + let next_fits = self.fits_fill_entry( + SingleEntryPredicate::default(), + queue, + stack, + PrintMode::Flat, + )?; + + self.print_entry( + queue, + stack, + args.with_print_mode(if next_fits { PrintMode::Flat } else { PrintMode::Expanded - }; + }), + )?; - self.print_all(queue, &[separator], args.with_print_mode(print_mode)); - } - (None, None) => { - break; - } - (None, Some(_)) => { - // Unreachable for iterators implementing [FusedIterator] which slice.iter implements. - // Reaching this means that the first `iter.next()` returned `None` but calling `iter.next()` - // again returns `Some` - unreachable!() - } + current_fits = next_fits; } } + + if queue.top() == Some(&FormatElement::Tag(EndFill)) { + Ok(()) + } else { + invalid_end_tag(TagKind::Fill, stack.top_kind()) + } + } + + fn fits_fill_entry

( + &mut self, + predicate: P, + queue: &mut PrintQueue<'a>, + stack: &mut PrintCallStack, + mode: PrintMode, + ) -> PrintResult + where + P: FitsPredicate, + { + let start_entry = queue.top(); + + if !matches!(start_entry, Some(&FormatElement::Tag(Tag::StartEntry))) { + return invalid_start_tag(TagKind::Entry, start_entry); + } + + // Create a virtual group around each fill entry + stack.push(TagKind::Group, stack.top().with_print_mode(mode)); + let fits = fits_on_line(predicate, queue, stack, self)?; + stack.pop(TagKind::Group)?; + + Ok(fits) } /// Fully print an element (print the element itself and all its descendants) /// /// Unlike [print_element], this function ensures the entire element has /// been printed when it returns and the queue is back to its original state - fn print_all( + fn print_entry( &mut self, - queue: &mut ElementCallQueue<'a>, - elements: &[&'a FormatElement], + queue: &mut PrintQueue<'a>, + stack: &mut PrintCallStack, args: PrintElementArgs, - ) { - let min_queue_length = queue.0.len(); + ) -> PrintResult<()> { + let start_entry = queue.top(); + + if !matches!(start_entry, Some(&FormatElement::Tag(Tag::StartEntry))) { + return invalid_start_tag(TagKind::Entry, start_entry); + } - queue.extend(elements.iter().map(|e| PrintElementCall::new(e, args))); + let mut depth = 0; - while let Some(call) = queue.dequeue() { - self.print_element(queue, call.element, call.args); + while let Some(element) = queue.pop() { + match element { + FormatElement::Tag(Tag::StartEntry) => { + // Handle the start of the first element by pushing the args on the stack. + if depth == 0 { + depth = 1; + stack.push(TagKind::Entry, args); + continue; + } - if queue.0.len() == min_queue_length { - return; + depth += 1; + } + FormatElement::Tag(Tag::EndEntry) => { + depth -= 1; + // Reached the end entry, pop the entry from the stack and return. + if depth == 0 { + stack.pop(TagKind::Entry)?; + return Ok(()); + } + } + _ => { + // Fall through + } } - debug_assert!(queue.0.len() > min_queue_length); + self.print_element(stack, queue, element)?; } + + invalid_end_tag(TagKind::Entry, stack.top_kind()) } fn print_str(&mut self, content: &str) { @@ -552,12 +642,13 @@ struct PrinterState<'a> { generated_column: usize, line_width: usize, has_empty_line: bool, - line_suffixes: Vec>, + line_suffixes: LineSuffixes<'a>, verbatim_markers: Vec, group_modes: GroupModes, // Re-used queue to measure if a group fits. Optimisation to avoid re-allocating a new // vec everytime a group gets measured - measure_queue: Vec>, + fits_stack: Vec, + fits_queue: Vec<&'a [FormatElement]>, } /// Tracks the mode in which groups with ids are printed. Stores the groups at `group.id()` index. @@ -590,61 +681,6 @@ impl GroupModes { } } -/// Stores arguments passed to `print_element` call, holding the state specific to printing an element. -/// E.g. the `indent` depends on the token the Printer's currently processing. That's why -/// it must be stored outside of the [PrinterState] that stores the state common to all elements. -/// -/// The state is passed by value, which is why it's important that it isn't storing any heavy -/// data structures. Such structures should be stored on the [PrinterState] instead. -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -struct PrintElementArgs { - indent: Indention, - mode: PrintMode, -} - -impl PrintElementArgs { - pub fn new(indent: Indention) -> Self { - Self { - indent, - ..Self::default() - } - } - - pub fn increment_indent_level(mut self, indent_style: IndentStyle) -> Self { - self.indent = self.indent.increment_level(indent_style); - self - } - - pub fn decrement_indent(mut self) -> Self { - self.indent = self.indent.decrement(); - self - } - - pub fn reset_indent(mut self) -> Self { - self.indent = Indention::default(); - self - } - - pub fn set_indent_align(mut self, count: NonZeroU8) -> Self { - self.indent = self.indent.set_align(count); - self - } - - pub fn with_print_mode(mut self, mode: PrintMode) -> Self { - self.mode = mode; - self - } -} - -impl Default for PrintElementArgs { - fn default() -> Self { - Self { - indent: Indention::Level(0), - mode: PrintMode::Expanded, - } - } -} - #[derive(Copy, Clone, Eq, PartialEq, Debug)] enum Indention { /// Indent the content by `count` levels by using the indention sequence specified by the printer options. @@ -738,149 +774,98 @@ impl Default for Indention { } } -/// The Printer uses a stack that emulates recursion. E.g. recursively processing the elements: -/// `indent(&concat(string, string))` would result in the following call stack: -/// -/// ```plain -/// print_element(indent, indent = 0); -/// print_element(concat, indent = 1); -/// print_element(string, indent = 1); -/// print_element(string, indent = 1); -/// ``` -/// The `PrintElementCall` stores the data for a single `print_element` call consisting of the element -/// and the `args` that's passed to `print_element`. -#[derive(Debug, Eq, PartialEq, Clone)] -struct PrintElementCall<'element> { - element: &'element FormatElement, - args: PrintElementArgs, -} - -impl<'element> PrintElementCall<'element> { - pub fn new(element: &'element FormatElement, args: PrintElementArgs) -> Self { - Self { element, args } - } -} - -/// Small helper that manages the order in which the elements should be visited. -#[derive(Debug, Default)] -struct ElementCallQueue<'a>(Vec>); - -impl<'a> ElementCallQueue<'a> { - pub fn new(elements: Vec>) -> Self { - Self(elements) - } - - fn extend(&mut self, calls: T) - where - T: IntoIterator>, - T::IntoIter: DoubleEndedIterator, - { - // Reverse the calls because elements are removed from the back of the vec - // in reversed insertion order - self.0.extend(calls.into_iter().rev()); - } - - fn extend_with_args(&mut self, elements: I, args: PrintElementArgs) - where - I: IntoIterator, - I::IntoIter: DoubleEndedIterator, - { - self.extend( - elements - .into_iter() - .map(|element| PrintElementCall::new(element, args)), - ) - } - - pub fn enqueue(&mut self, call: PrintElementCall<'a>) { - self.0.push(call); - } - - pub fn dequeue(&mut self) -> Option> { - self.0.pop() - } - - fn is_empty(&self) -> bool { - self.0.is_empty() - } - - fn into_vec(self) -> Vec> { - self.0 - } -} - /// Tests if it's possible to print the content of the queue up to the first hard line break /// or the end of the document on a single line without exceeding the line width. -#[must_use = "Only determines if content fits on a single line but doesn't print it"] -fn fits_on_line<'a, I>( - elements: I, - args: PrintElementArgs, - queue: &ElementCallQueue<'a>, +fn fits_on_line<'a, 'print, P>( + predicate: P, + print_queue: &'print PrintQueue<'a>, + stack: &'print PrintCallStack, printer: &mut Printer<'a>, -) -> bool +) -> PrintResult where - I: IntoIterator, - I::IntoIter: DoubleEndedIterator, + P: FitsPredicate, { - let shared_buffer = std::mem::take(&mut printer.state.measure_queue); - debug_assert!(shared_buffer.is_empty()); - - let mut measure_queue = MeasureQueue::new() - .with_rest(queue) - .with_queue(ElementCallQueue::new(shared_buffer)); + let saved_stack = std::mem::take(&mut printer.state.fits_stack); + let saved_queue = std::mem::take(&mut printer.state.fits_queue); + debug_assert!(saved_stack.is_empty()); + debug_assert!(saved_queue.is_empty()); - measure_queue.extend(elements.into_iter(), args); + let mut fits_queue = FitsQueue::new(print_queue, saved_queue); + let mut fits_stack = FitsCallStack::new(stack, saved_stack); - let mut measure_state = MeasureState { + let mut fits_state = FitsState { pending_indent: printer.state.pending_indent, pending_space: printer.state.pending_space, line_width: printer.state.line_width, - has_line_suffix: !printer.state.line_suffixes.is_empty(), + has_line_suffix: printer.state.line_suffixes.has_pending(), group_modes: &mut printer.state.group_modes, }; - let result = loop { - match measure_queue.dequeue() { - None => { - break true; - } + let result = all_fit( + predicate, + &mut fits_state, + &mut fits_queue, + &mut fits_stack, + &printer.options, + ); - Some((element, args)) => match fits_element_on_line( - element, - args, - &mut measure_state, - &mut measure_queue, - &printer.options, - ) { - Fits::Yes => { - break true; - } - Fits::No => { - break false; - } - Fits::Maybe => { - continue; - } - }, + printer.state.fits_stack = fits_stack.finish(); + printer.state.fits_queue = fits_queue.finish(); + + printer.state.fits_stack.clear(); + printer.state.fits_queue.clear(); + + result.map(|fits| match fits { + Fits::Maybe | Fits::Yes => true, + Fits::No => false, + }) +} + +/// Tests if it's possible to print the content of the queue up to the first hard line break +/// or the end of the document on a single line without exceeding the line width. +fn all_fit<'a, 'print, P>( + mut predicate: P, + fits_state: &mut FitsState, + queue: &mut FitsQueue<'a, 'print>, + stack: &mut FitsCallStack<'print>, + options: &PrinterOptions, +) -> PrintResult +where + P: FitsPredicate, +{ + while let Some(element) = queue.pop() { + if !predicate.apply(element)? { + break; } - }; - let mut shared_buffer = measure_queue.into_vec(); - // Clear out remaining items - shared_buffer.clear(); - printer.state.measure_queue = shared_buffer; + match fits_element_on_line(element, fits_state, queue, stack, options)? { + Fits::Yes => { + return Ok(Fits::Yes); + } + Fits::No => { + return Ok(Fits::No); + } + Fits::Maybe => { + continue; + } + } + } - result + Ok(Fits::Maybe) } /// Tests if the passed element fits on the current line or not. fn fits_element_on_line<'a, 'rest>( element: &'a FormatElement, - args: PrintElementArgs, - state: &mut MeasureState, - queue: &mut MeasureQueue<'a, 'rest>, + state: &mut FitsState, + queue: &mut FitsQueue<'a, 'rest>, + stack: &mut FitsCallStack<'rest>, options: &PrinterOptions, -) -> Fits { +) -> PrintResult { + use Tag::*; + + let args = stack.top(); + match element { FormatElement::Space => { if state.line_width > 0 { @@ -889,14 +874,14 @@ fn fits_element_on_line<'a, 'rest>( } FormatElement::Line(line_mode) => { - if args.mode.is_flat() { + if args.mode().is_flat() { match line_mode { LineMode::SoftOrSpace => { state.pending_space = true; } LineMode::Soft => {} LineMode::Hard | LineMode::Empty => { - return Fits::No; + return Ok(Fits::No); } } } else { @@ -904,71 +889,10 @@ fn fits_element_on_line<'a, 'rest>( // is what the mode's initialized to by default // This means, the printer is outside of the current element at this point and any // line break should be printed as regular line break -> Fits - return Fits::Yes; - } - } - - FormatElement::Indent(content) => queue.extend( - content.iter(), - args.increment_indent_level(options.indent_style()), - ), - - FormatElement::Dedent { content, mode } => { - let args = match mode { - DedentMode::Level => args.decrement_indent(), - DedentMode::Root => args.reset_indent(), - }; - queue.extend(content.iter(), args) - } - - FormatElement::Align(Align { content, count }) => { - queue.extend(content.iter(), args.set_indent_align(*count)) - } - - FormatElement::Group(group) => { - queue.extend(group.content.iter(), args); - - if let Some(id) = group.id { - state.group_modes.insert_print_mode(id, args.mode); - } - } - - FormatElement::ConditionalGroupContent(conditional) => { - let group_mode = match conditional.group_id { - None => args.mode, - Some(group_id) => state - .group_modes - .get_print_mode(group_id) - .unwrap_or(args.mode), - }; - - if group_mode == conditional.mode { - queue.extend(conditional.content.iter(), args); + return Ok(Fits::Yes); } } - FormatElement::IndentIfGroupBreaks(indent) => { - let group_mode = state - .group_modes - .get_print_mode(indent.group_id) - .unwrap_or(args.mode); - - match group_mode { - PrintMode::Flat => queue.extend(indent.content.iter(), args), - PrintMode::Expanded => queue.extend( - indent.content.iter(), - args.increment_indent_level(options.indent_style()), - ), - } - } - - FormatElement::List(list) => queue.extend(list.iter(), args), - - FormatElement::Fill(content) => queue - .queue - .0 - .extend(content.iter().rev().map(|t| PrintElementCall::new(t, args))), - FormatElement::Text(token) => { let indent = std::mem::take(&mut state.pending_indent); state.line_width += @@ -982,10 +906,10 @@ fn fits_element_on_line<'a, 'rest>( let char_width = match c { '\t' => options.tab_width, '\n' => { - return match args.mode { + return Ok(match args.mode() { PrintMode::Flat => Fits::No, PrintMode::Expanded => Fits::Yes, - } + }) } _ => 1, }; @@ -993,41 +917,159 @@ fn fits_element_on_line<'a, 'rest>( } if state.line_width > options.print_width.into() { - return Fits::No; + return Ok(Fits::No); } state.pending_space = false; } - FormatElement::LineSuffix(_) => { - state.has_line_suffix = true; - } - FormatElement::LineSuffixBoundary => { if state.has_line_suffix { - return Fits::No; + return Ok(Fits::No); + } + } + + FormatElement::ExpandParent => { + if args.mode().is_flat() { + return Ok(Fits::No); + } + } + + FormatElement::BestFitting(best_fitting) => match args.mode() { + PrintMode::Flat => { + queue.extend_back(best_fitting.most_flat()); + } + PrintMode::Expanded => queue.extend_back(best_fitting.most_expanded()), + }, + + FormatElement::Interned(content) => queue.extend_back(content), + + FormatElement::Tag(StartIndent) => { + stack.push( + TagKind::Indent, + args.increment_indent_level(options.indent_style()), + ); + } + + FormatElement::Tag(StartDedent(mode)) => { + let args = match mode { + DedentMode::Level => args.decrement_indent(), + DedentMode::Root => args.reset_indent(), + }; + stack.push(TagKind::Dedent, args); + } + + FormatElement::Tag(StartAlign(align)) => { + stack.push(TagKind::Align, args.set_indent_align(align.count())); + } + + FormatElement::Tag(StartGroup(id)) => { + stack.push(TagKind::Group, args); + + if let Some(id) = id { + state.group_modes.insert_print_mode(*id, args.mode()); } } - FormatElement::Verbatim(verbatim) => queue.extend(verbatim.content.iter(), args), - FormatElement::BestFitting(best_fitting) => { - let content = match args.mode { - PrintMode::Flat => best_fitting.most_flat(), - PrintMode::Expanded => best_fitting.most_expanded(), + FormatElement::Tag(StartConditionalContent(condition)) => { + let group_mode = match condition.group_id { + None => args.mode(), + Some(group_id) => state + .group_modes + .get_print_mode(group_id) + .unwrap_or_else(|| args.mode()), }; - queue.enqueue(PrintElementCall::new(content, args)) + if group_mode != condition.mode { + queue.skip_content(TagKind::ConditionalContent); + } else { + stack.push(TagKind::ConditionalContent, args); + } } - FormatElement::ExpandParent => { - if args.mode.is_flat() { - return Fits::No; + + FormatElement::Tag(StartIndentIfGroupBreaks(id)) => { + let group_mode = state + .group_modes + .get_print_mode(*id) + .unwrap_or_else(|| args.mode()); + + match group_mode { + PrintMode::Flat => { + stack.push(TagKind::IndentIfGroupBreaks, args); + } + PrintMode::Expanded => { + stack.push( + TagKind::IndentIfGroupBreaks, + args.increment_indent_level(options.indent_style()), + ); + } } } - FormatElement::Interned(content) => queue.enqueue(PrintElementCall::new(content, args)), - FormatElement::Label(label) => queue.extend(label.content.iter(), args), + + FormatElement::Tag(StartLineSuffix) => { + queue.skip_content(TagKind::LineSuffix); + state.has_line_suffix = true; + } + + FormatElement::Tag(EndLineSuffix) => { + return invalid_end_tag(TagKind::LineSuffix, stack.top_kind()); + } + + FormatElement::Tag( + tag @ (StartFill | StartVerbatim(_) | StartLabelled(_) | StartEntry), + ) => { + stack.push(tag.kind(), args); + } + FormatElement::Tag( + tag @ (EndFill + | EndVerbatim + | EndLabelled + | EndEntry + | EndGroup + | EndIndentIfGroupBreaks + | EndConditionalContent + | EndAlign + | EndDedent + | EndIndent), + ) => { + stack.pop(tag.kind())?; + } } - Fits::Maybe + Ok(Fits::Maybe) +} + +#[cold] +fn invalid_end_tag(end_tag: TagKind, start_tag: Option) -> PrintResult { + Err(PrintError::InvalidDocument(match start_tag { + None => InvalidDocumentError::StartTagMissing { kind: end_tag }, + Some(kind) => InvalidDocumentError::StartEndTagMismatch { + start_kind: end_tag, + end_kind: kind, + }, + })) +} + +#[cold] +fn invalid_start_tag(expected: TagKind, actual: Option<&FormatElement>) -> PrintResult { + let start = match actual { + None => ActualStart::EndOfDocument, + Some(FormatElement::Tag(tag)) => { + if tag.is_start() { + ActualStart::Start(tag.kind()) + } else { + ActualStart::End(tag.kind()) + } + } + Some(_) => ActualStart::Content, + }; + + Err(PrintError::InvalidDocument( + InvalidDocumentError::ExpectedStart { + actual: start, + expected_start: expected, + }, + )) } #[derive(Copy, Clone, Debug, Eq, PartialEq)] @@ -1051,7 +1093,7 @@ impl From for Fits { /// State used when measuring if a group fits on a single line #[derive(Debug)] -struct MeasureState<'group> { +struct FitsState<'group> { pending_indent: Indention, pending_space: bool, has_line_suffix: bool, @@ -1059,77 +1101,11 @@ struct MeasureState<'group> { group_modes: &'group mut GroupModes, } -#[derive(Debug)] -struct MeasureQueue<'a, 'rest> { - /// Queue that holds the elements that the `fits` operation inspects. - /// Normally, these all the elements belonging to the group that is tested if it fits - queue: ElementCallQueue<'a>, - /// Queue that contains the remaining elements in the documents. - rest_queue: Rev>>, -} - -impl<'a, 'rest> MeasureQueue<'a, 'rest> { - fn new() -> Self { - Self { - rest_queue: [].iter().rev(), - queue: ElementCallQueue::default(), - } - } - - fn with_rest(mut self, rest_queue: &'rest ElementCallQueue<'a>) -> Self { - // Last element in the vector is the first element in the queue - self.rest_queue = rest_queue.0.as_slice().iter().rev(); - self - } - - fn with_queue(mut self, queue: ElementCallQueue<'a>) -> Self { - debug_assert!(queue.is_empty()); - self.queue = queue; - self - } - - fn enqueue(&mut self, call: PrintElementCall<'a>) { - self.queue.enqueue(call); - } - - fn extend(&mut self, elements: T, args: PrintElementArgs) - where - T: IntoIterator, - T::IntoIter: DoubleEndedIterator, - { - // Reverse the calls because elements are removed from the back of the vec - // in reversed insertion order - self.queue.0.extend( - elements - .into_iter() - .rev() - .map(|element| PrintElementCall::new(element, args)), - ); - } - - fn dequeue(&mut self) -> Option<(&'a FormatElement, PrintElementArgs)> { - let next = match self.queue.dequeue() { - Some(call) => (call.element, call.args), - None => { - let rest_item = self.rest_queue.next()?; - - (rest_item.element, rest_item.args) - } - }; - - Some(next) - } - - fn into_vec(self) -> Vec> { - self.queue.into_vec() - } -} - #[cfg(test)] mod tests { use crate::prelude::*; use crate::printer::{LineEnding, PrintWidth, Printer, PrinterOptions}; - use crate::{format_args, write, FormatState, IndentStyle, Printed, VecBuffer}; + use crate::{format_args, write, Document, FormatState, IndentStyle, Printed, VecBuffer}; fn format(root: &dyn Format<()>) -> Printed { format_with_options( @@ -1147,7 +1123,9 @@ mod tests { write!(&mut buffer, [root]).unwrap(); - Printer::new(options).print(&buffer.into_element()) + Printer::new(options) + .print(&Document::from(buffer.into_vec())) + .expect("Document to be valid") } #[test] @@ -1379,10 +1357,11 @@ two lines`, .finish() .unwrap(); - let document = buffer.into_element(); + let document = Document::from(buffer.into_vec()); let printed = Printer::new(PrinterOptions::default().with_print_width(PrintWidth::new(10))) - .print(&document); + .print(&document) + .unwrap(); assert_eq!( printed.as_code(), diff --git a/crates/rome_formatter/src/printer/queue.rs b/crates/rome_formatter/src/printer/queue.rs new file mode 100644 index 00000000000..15c0a79765c --- /dev/null +++ b/crates/rome_formatter/src/printer/queue.rs @@ -0,0 +1,469 @@ +use crate::format_element::tag::TagKind; +use crate::prelude::Tag; +use crate::printer::stack::{Stack, StackedStack}; +use crate::printer::{invalid_end_tag, invalid_start_tag}; +use crate::{FormatElement, PrintResult}; +use std::fmt::Debug; +use std::iter::FusedIterator; +use std::marker::PhantomData; + +/// Queue of [FormatElement]s. +pub(super) trait Queue<'a> { + type Stack: Stack<&'a [FormatElement]>; + + fn stack(&self) -> &Self::Stack; + + fn stack_mut(&mut self) -> &mut Self::Stack; + + fn next_index(&self) -> usize; + + fn set_next_index(&mut self, index: usize); + + /// Pops the element at the end of the queue. + fn pop(&mut self) -> Option<&'a FormatElement> { + match self.stack().top() { + Some(top_slice) => { + // SAFETY: Safe because queue ensures that slices inside `slices` are never empty. + let next_index = self.next_index(); + let element = &top_slice[next_index]; + + if next_index + 1 == top_slice.len() { + self.stack_mut().pop().unwrap(); + self.set_next_index(0); + } else { + self.set_next_index(next_index + 1); + } + + Some(element) + } + None => None, + } + } + + /// Returns the next element, not traversing into [FormatElement::Interned]. + fn top_with_interned(&self) -> Option<&'a FormatElement> { + self.stack() + .top() + .map(|top_slice| &top_slice[self.next_index()]) + } + + /// Returns the next element, recursively resolving the first element of [FormatElement::Interned]. + fn top(&self) -> Option<&'a FormatElement> { + let mut top = self.top_with_interned(); + + while let Some(FormatElement::Interned(interned)) = top { + top = interned.first() + } + + top + } + + /// Queues a single element to process before the other elements in this queue. + fn push(&mut self, element: &'a FormatElement) { + self.extend_back(std::slice::from_ref(element)) + } + + /// Queues a slice of elements to process before the other elements in this queue. + fn extend_back(&mut self, elements: &'a [FormatElement]) { + match elements { + [] => { + // Don't push empty slices + } + slice => { + let next_index = self.next_index(); + let stack = self.stack_mut(); + if let Some(top) = stack.pop() { + stack.push(&top[next_index..]) + } + + stack.push(slice); + self.set_next_index(0); + } + } + } + + /// Removes top slice. + fn pop_slice(&mut self) -> Option<&'a [FormatElement]> { + self.set_next_index(0); + self.stack_mut().pop() + } + + /// Skips all content until it finds the corresponding end tag with the given kind. + fn skip_content(&mut self, kind: TagKind) + where + Self: Sized, + { + let iter = self.iter_content(kind); + + for _ in iter { + // consume whole iterator until end + } + } + + /// Iterates over all elements until it finds the matching end tag of the specified kind. + fn iter_content<'q>(&'q mut self, kind: TagKind) -> QueueContentIterator<'a, 'q, Self> + where + Self: Sized, + { + QueueContentIterator::new(self, kind) + } +} + +/// Queue with the elements to print. +#[derive(Debug, Default, Clone)] +pub(super) struct PrintQueue<'a> { + slices: Vec<&'a [FormatElement]>, + next_index: usize, +} + +impl<'a> PrintQueue<'a> { + pub(super) fn new(slice: &'a [FormatElement]) -> Self { + let slices = match slice { + [] => Vec::default(), + slice => vec![slice], + }; + + Self { + slices, + next_index: 0, + } + } + + pub(super) fn is_empty(&self) -> bool { + self.slices.is_empty() + } +} + +impl<'a> Queue<'a> for PrintQueue<'a> { + type Stack = Vec<&'a [FormatElement]>; + + fn stack(&self) -> &Self::Stack { + &self.slices + } + + fn stack_mut(&mut self) -> &mut Self::Stack { + &mut self.slices + } + + fn next_index(&self) -> usize { + self.next_index + } + + fn set_next_index(&mut self, index: usize) { + self.next_index = index + } +} + +/// Queue for measuring if an element fits on the line. +/// +/// The queue is a view on top of the [PrintQueue] because no elements should be removed +/// from the [PrintQueue] while measuring. +#[must_use] +#[derive(Debug)] +pub(super) struct FitsQueue<'a, 'print> { + stack: StackedStack<'print, &'a [FormatElement]>, + next_index: usize, +} + +impl<'a, 'print> FitsQueue<'a, 'print> { + pub(super) fn new( + print_queue: &'print PrintQueue<'a>, + saved: Vec<&'a [FormatElement]>, + ) -> Self { + let stack = StackedStack::with_vec(&print_queue.slices, saved); + + Self { + stack, + next_index: print_queue.next_index, + } + } + + pub(super) fn finish(self) -> Vec<&'a [FormatElement]> { + self.stack.into_vec() + } +} + +impl<'a, 'print> Queue<'a> for FitsQueue<'a, 'print> { + type Stack = StackedStack<'print, &'a [FormatElement]>; + + fn stack(&self) -> &Self::Stack { + &self.stack + } + + fn stack_mut(&mut self) -> &mut Self::Stack { + &mut self.stack + } + + fn next_index(&self) -> usize { + self.next_index + } + + fn set_next_index(&mut self, index: usize) { + self.next_index = index; + } +} + +/// Iterator that calls [Queue::pop] until it reaches the end of the document. +/// +/// The iterator traverses into the content of any [FormatElement::Interned]. +pub(super) struct QueueIterator<'a, 'q, Q: Queue<'a>> { + queue: &'q mut Q, + lifetime: PhantomData<&'a ()>, +} + +impl<'a, Q> Iterator for QueueIterator<'a, '_, Q> +where + Q: Queue<'a>, +{ + type Item = &'a FormatElement; + + fn next(&mut self) -> Option { + self.queue.pop() + } +} + +impl<'a, Q> FusedIterator for QueueIterator<'a, '_, Q> where Q: Queue<'a> {} + +pub(super) struct QueueContentIterator<'a, 'q, Q: Queue<'a>> { + queue: &'q mut Q, + kind: TagKind, + depth: usize, + lifetime: PhantomData<&'a ()>, +} + +impl<'a, 'q, Q> QueueContentIterator<'a, 'q, Q> +where + Q: Queue<'a>, +{ + fn new(queue: &'q mut Q, kind: TagKind) -> Self { + Self { + queue, + kind, + depth: 1, + lifetime: PhantomData, + } + } +} + +impl<'a, Q> Iterator for QueueContentIterator<'a, '_, Q> +where + Q: Queue<'a>, +{ + type Item = &'a FormatElement; + + fn next(&mut self) -> Option { + match self.depth { + 0 => None, + _ => { + let mut top = self.queue.pop(); + + while let Some(FormatElement::Interned(interned)) = top { + self.queue.extend_back(interned); + top = self.queue.pop(); + } + + match top.expect("Missing end signal.") { + element @ FormatElement::Tag(tag) if tag.kind() == self.kind => { + if tag.is_start() { + self.depth += 1; + } else { + self.depth -= 1; + + if self.depth == 0 { + return None; + } + } + + Some(element) + } + element => Some(element), + } + } + } + } +} + +impl<'a, Q> FusedIterator for QueueContentIterator<'a, '_, Q> where Q: Queue<'a> {} + +/// A predicate determining when to end measuring if some content fits on the line. +/// +/// Called for every [`element`](FormatElement) in the [FitsQueue] when measuring if a content +/// fits on the line. The measuring of the content ends for the first [`element`](FormatElement) that this +/// predicate returns `false`. +pub(super) trait FitsPredicate { + fn apply(&mut self, element: &FormatElement) -> PrintResult; +} + +/// Filter that includes all elements until it reaches the end of the document. +pub(super) struct AllPredicate; + +impl FitsPredicate for AllPredicate { + fn apply(&mut self, _element: &FormatElement) -> PrintResult { + Ok(true) + } +} + +/// Filter that takes all elements between two matching [Tag::StartEntry] and [Tag::EndEntry] tags. +#[derive(Debug)] +pub(super) enum SingleEntryPredicate { + Entry { depth: usize }, + Done, +} + +impl Default for SingleEntryPredicate { + fn default() -> Self { + SingleEntryPredicate::Entry { depth: 0 } + } +} + +impl FitsPredicate for SingleEntryPredicate { + fn apply(&mut self, element: &FormatElement) -> PrintResult { + let result = match self { + SingleEntryPredicate::Done => false, + SingleEntryPredicate::Entry { depth } => match element { + FormatElement::Tag(Tag::StartEntry) => { + *depth += 1; + + true + } + FormatElement::Tag(Tag::EndEntry) => { + if *depth == 0 { + return invalid_end_tag(TagKind::Entry, None); + } + + *depth -= 1; + + if *depth == 0 { + *self = SingleEntryPredicate::Done; + } + + true + } + FormatElement::Interned(_) => true, + element if *depth == 0 => { + return invalid_start_tag(TagKind::Entry, Some(element)); + } + _ => true, + }, + }; + + Ok(result) + } +} + +/// Queue filter that returns all elements belonging to the separator and the following item of a fill pair. +/// +/// The fill element consists of entries where each entry is separated by [Tag::StartEntry] and [Tag::EndEntry]. +/// This filter takes up to two [Tag::StartEntry]/[Tag::StopEntry] but may end after one if +/// it reaches the [Tag::EndFill] element (last item without a separator). +#[derive(Debug)] +pub(super) enum SeparatorItemPairPredicate { + Separator { depth: usize }, + Item { depth: usize }, + Done, +} + +impl SeparatorItemPairPredicate { + const fn is_item(&self) -> bool { + matches!(self, SeparatorItemPairPredicate::Item { .. }) + } +} + +impl Default for SeparatorItemPairPredicate { + fn default() -> Self { + Self::Separator { depth: 0 } + } +} + +impl FitsPredicate for SeparatorItemPairPredicate { + fn apply(&mut self, element: &FormatElement) -> PrintResult { + let is_item = self.is_item(); + + let result = match self { + SeparatorItemPairPredicate::Item { depth } + | SeparatorItemPairPredicate::Separator { depth } => { + match element { + FormatElement::Tag(Tag::StartEntry) => { + *depth += 1; + + true + } + FormatElement::Tag(Tag::EndEntry) => { + if *depth == 0 { + return invalid_end_tag(TagKind::Entry, None); + } + + *depth -= 1; + + if *depth == 0 { + if is_item { + *self = SeparatorItemPairPredicate::Done; + } else { + *self = SeparatorItemPairPredicate::Item { depth: 0 } + } + } + + true + } + // No item, trailing separator only + FormatElement::Tag(Tag::EndFill) if *depth == 0 && is_item => { + *self = SeparatorItemPairPredicate::Done; + false + } + FormatElement::Interned(_) => true, + element if *depth == 0 => { + return invalid_start_tag(TagKind::Entry, Some(element)); + } + _ => true, + } + } + SeparatorItemPairPredicate::Done => false, + }; + + Ok(result) + } +} + +#[cfg(test)] +mod tests { + use crate::format_element::LineMode; + use crate::prelude::Tag; + use crate::printer::queue::{PrintQueue, Queue}; + use crate::FormatElement; + + #[test] + fn extend_back_pop_last() { + let mut queue = + PrintQueue::new(&[FormatElement::Tag(Tag::StartEntry), FormatElement::Space]); + + assert_eq!(queue.pop(), Some(&FormatElement::Tag(Tag::StartEntry))); + + queue.extend_back(&[FormatElement::Line(LineMode::SoftOrSpace)]); + + assert_eq!( + queue.pop(), + Some(&FormatElement::Line(LineMode::SoftOrSpace)) + ); + assert_eq!(queue.pop(), Some(&FormatElement::Space)); + + assert_eq!(queue.pop(), None); + } + + #[test] + fn extend_back_empty_queue() { + let mut queue = + PrintQueue::new(&[FormatElement::Tag(Tag::StartEntry), FormatElement::Space]); + + assert_eq!(queue.pop(), Some(&FormatElement::Tag(Tag::StartEntry))); + assert_eq!(queue.pop(), Some(&FormatElement::Space)); + + queue.extend_back(&[FormatElement::Line(LineMode::SoftOrSpace)]); + + assert_eq!( + queue.pop(), + Some(&FormatElement::Line(LineMode::SoftOrSpace)) + ); + + assert_eq!(queue.pop(), None); + } +} diff --git a/crates/rome_formatter/src/printer/stack.rs b/crates/rome_formatter/src/printer/stack.rs new file mode 100644 index 00000000000..fa25e6c5d60 --- /dev/null +++ b/crates/rome_formatter/src/printer/stack.rs @@ -0,0 +1,155 @@ +/// A school book stack. Allows adding, removing, and inspecting elements at the back. +pub(super) trait Stack { + /// Removes the last element if any and returns it + fn pop(&mut self) -> Option; + + /// Pushes a new element at the back + fn push(&mut self, value: T); + + /// Returns the last element if any + fn top(&self) -> Option<&T>; + + /// Returns `true` if the stack is empty + fn is_empty(&self) -> bool; +} + +impl Stack for Vec { + fn pop(&mut self) -> Option { + self.pop() + } + + fn push(&mut self, value: T) { + self.push(value) + } + + fn top(&self) -> Option<&T> { + self.last() + } + + fn is_empty(&self) -> bool { + self.is_empty() + } +} + +/// A Stack that is stacked on top of another stack. Guarantees that the underlying stack remains unchanged. +#[derive(Debug, Clone)] +pub(super) struct StackedStack<'a, T> { + /// The content of the original stack. + original: &'a [T], + + /// The index of the "top" element in the original stack. + original_top: usize, + + /// Items that have been pushed since the creation of this stack and aren't part of the `original` stack. + stack: Vec, +} + +impl<'a, T> StackedStack<'a, T> { + #[cfg(test)] + pub(super) fn new(original: &'a [T]) -> Self { + Self::with_vec(original, Vec::new()) + } + + /// Creates a new stack that uses `stack` for storing its elements. + pub(super) fn with_vec(original: &'a [T], stack: Vec) -> Self { + Self { + original_top: original.len(), + original, + stack, + } + } + + /// Returns the underlying `stack` vector. + pub(super) fn into_vec(self) -> Vec { + self.stack + } +} + +impl Stack for StackedStack<'_, T> +where + T: Copy, +{ + fn pop(&mut self) -> Option { + self.stack.pop().or_else(|| { + if self.original_top == 0 { + None + } else { + self.original_top -= 1; + Some(self.original[self.original_top]) + } + }) + } + + fn push(&mut self, value: T) { + self.stack.push(value); + } + + fn top(&self) -> Option<&T> { + self.stack.last().or_else(|| { + if self.original_top == 0 { + None + } else { + Some(&self.original[self.original_top - 1]) + } + }) + } + + fn is_empty(&self) -> bool { + self.original_top == 0 && self.stack.is_empty() + } +} + +#[cfg(test)] +mod tests { + use crate::printer::stack::{Stack, StackedStack}; + + #[test] + fn restore_consumed_stack() { + let original = vec![1, 2, 3]; + let mut restorable = StackedStack::new(&original); + + restorable.push(4); + + assert_eq!(restorable.pop(), Some(4)); + assert_eq!(restorable.pop(), Some(3)); + assert_eq!(restorable.pop(), Some(2)); + assert_eq!(restorable.pop(), Some(1)); + assert_eq!(restorable.pop(), None); + + assert_eq!(original, vec![1, 2, 3]); + } + + #[test] + fn restore_partially_consumed_stack() { + let original = vec![1, 2, 3]; + let mut restorable = StackedStack::new(&original); + + restorable.push(4); + + assert_eq!(restorable.pop(), Some(4)); + assert_eq!(restorable.pop(), Some(3)); + assert_eq!(restorable.pop(), Some(2)); + restorable.push(5); + restorable.push(6); + restorable.push(7); + + assert_eq!(original, vec![1, 2, 3]); + } + + #[test] + fn restore_stack() { + let original = vec![1, 2, 3]; + let mut restorable = StackedStack::new(&original); + + restorable.push(4); + restorable.push(5); + restorable.push(6); + restorable.push(7); + + assert_eq!(restorable.pop(), Some(7)); + assert_eq!(restorable.pop(), Some(6)); + assert_eq!(restorable.pop(), Some(5)); + + assert_eq!(original, vec![1, 2, 3]); + } +} diff --git a/crates/rome_formatter/src/trivia.rs b/crates/rome_formatter/src/trivia.rs index b437f8421ca..48ca05bd3db 100644 --- a/crates/rome_formatter/src/trivia.rs +++ b/crates/rome_formatter/src/trivia.rs @@ -1,10 +1,11 @@ //! Provides builders for comments and skipped token trivia. +use crate::format_element::tag::VerbatimKind; use crate::prelude::*; use crate::{ comments::{CommentKind, CommentStyle}, write, Argument, Arguments, CstFormatContext, FormatRefWithRule, GroupId, SourceComment, - TextRange, VecBuffer, + TextRange, }; use rome_rowan::{Language, SyntaxNode, SyntaxToken}; #[cfg(debug_assertions)] @@ -422,13 +423,21 @@ where fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { write!( f, - [ - if_group_breaks(&Arguments::from(&self.content)).with_group_id(self.group_id), - // Print the trivia otherwise - if_group_fits_on_line(&format_skipped_token_trivia(self.token)) - .with_group_id(self.group_id), - ] - ) + [if_group_breaks(&Arguments::from(&self.content)).with_group_id(self.group_id),] + )?; + + if f.comments().has_skipped(self.token) { + // Print the trivia otherwise + write!( + f, + [ + if_group_fits_on_line(&format_skipped_token_trivia(self.token)) + .with_group_id(self.group_id) + ] + )?; + } + + Ok(()) } } @@ -533,17 +542,13 @@ impl FormatSkippedTokenTrivia<'_, L> { let skipped_range = skipped_range.unwrap_or_else(|| TextRange::empty(self.token.text_range().start())); - let verbatim = { - let mut buffer = VecBuffer::new(f.state_mut()); - write!(buffer, [syntax_token_text_slice(self.token, skipped_range)])?; - - FormatElement::Verbatim(Verbatim::new_verbatim( - buffer.into_vec().into_boxed_slice(), - skipped_range.len(), - )) - }; - - f.write_element(verbatim)?; + f.write_element(FormatElement::Tag(Tag::StartVerbatim( + VerbatimKind::Verbatim { + length: skipped_range.len(), + }, + )))?; + write!(f, [syntax_token_text_slice(self.token, skipped_range)])?; + f.write_element(FormatElement::Tag(Tag::EndVerbatim))?; // Write whitespace separator between skipped/last comment and token if dangling_comments.is_empty() { diff --git a/crates/rome_formatter/src/verbatim.rs b/crates/rome_formatter/src/verbatim.rs index 89e93cc4180..60da1b60c43 100644 --- a/crates/rome_formatter/src/verbatim.rs +++ b/crates/rome_formatter/src/verbatim.rs @@ -1,6 +1,6 @@ +use crate::format_element::tag::VerbatimKind; use crate::prelude::*; use crate::trivia::{FormatLeadingComments, FormatTrailingComments}; -use crate::VecBuffer; use crate::{write, CstFormatContext}; use rome_rowan::{Direction, Language, SyntaxElement, SyntaxNode, TextRange}; @@ -12,7 +12,7 @@ use rome_rowan::{Direction, Language, SyntaxElement, SyntaxNode, TextRange}; /// You may be inclined to call `node.text` directly. However, using `text` doesn't track the nodes /// nor its children source mapping information, resulting in incorrect source maps for this subtree. /// -/// These nodes and tokens get tracked as [FormatElement::Verbatim], useful to understand +/// These nodes and tokens get tracked as [VerbatimKind::Verbatim], useful to understand /// if these nodes still need to have their own implementation. pub fn format_verbatim_node(node: &SyntaxNode) -> FormatVerbatimNode { FormatVerbatimNode { @@ -58,104 +58,87 @@ where |source_map| source_map.trimmed_source_range(self.node), ); - let mut buffer = VecBuffer::new(f.state_mut()); - - write!( - buffer, - [format_with(|f: &mut Formatter| { - fn source_range(f: &Formatter, range: TextRange) -> TextRange - where - Context: CstFormatContext, - { - f.context() - .source_map() - .map_or_else(|| range, |source_map| source_map.source_range(range)) - } + f.write_element(FormatElement::Tag(Tag::StartVerbatim(self.kind)))?; - // Format all leading comments that are outside of the node's source range. - if self.format_comments { - let comments = f.context().comments().clone(); - let leading_comments = comments.leading_comments(self.node); + fn source_range(f: &Formatter, range: TextRange) -> TextRange + where + Context: CstFormatContext, + { + f.context() + .source_map() + .map_or_else(|| range, |source_map| source_map.source_range(range)) + } - let outside_trimmed_range = leading_comments.partition_point(|comment| { - comment.piece().text_range().end() <= trimmed_source_range.start() - }); + // Format all leading comments that are outside of the node's source range. + if self.format_comments { + let comments = f.context().comments().clone(); + let leading_comments = comments.leading_comments(self.node); - let (outside_trimmed_range, in_trimmed_range) = - leading_comments.split_at(outside_trimmed_range); + let outside_trimmed_range = leading_comments.partition_point(|comment| { + comment.piece().text_range().end() <= trimmed_source_range.start() + }); - write!(f, [FormatLeadingComments::Comments(outside_trimmed_range)])?; + let (outside_trimmed_range, in_trimmed_range) = + leading_comments.split_at(outside_trimmed_range); - for comment in in_trimmed_range { - comment.mark_formatted(); - } - } + write!(f, [FormatLeadingComments::Comments(outside_trimmed_range)])?; - // Find the first skipped token trivia, if any, and include it in the verbatim range because - // the comments only format **up to** but not including skipped token trivia. - let start_source = self - .node - .first_leading_trivia() - .into_iter() - .flat_map(|trivia| trivia.pieces()) - .filter(|trivia| trivia.is_skipped()) - .map(|trivia| source_range(f, trivia.text_range()).start()) - .take_while(|start| *start < trimmed_source_range.start()) - .next() - .unwrap_or_else(|| trimmed_source_range.start()); - - let original_source = f.context().source_map().map_or_else( - || self.node.text_trimmed().to_string(), - |source_map| { - source_map.text()[trimmed_source_range.cover_offset(start_source)] - .to_string() - }, - ); - - dynamic_text( - &normalize_newlines(&original_source, LINE_TERMINATORS), - self.node.text_trimmed_range().start(), - ) - .fmt(f)?; - - for comment in f.context().comments().dangling_comments(self.node) { - comment.mark_formatted(); - } + for comment in in_trimmed_range { + comment.mark_formatted(); + } + } - // Format all trailing comments that are outside of the trimmed range. - if self.format_comments { - let comments = f.context().comments().clone(); + // Find the first skipped token trivia, if any, and include it in the verbatim range because + // the comments only format **up to** but not including skipped token trivia. + let start_source = self + .node + .first_leading_trivia() + .into_iter() + .flat_map(|trivia| trivia.pieces()) + .filter(|trivia| trivia.is_skipped()) + .map(|trivia| source_range(f, trivia.text_range()).start()) + .take_while(|start| *start < trimmed_source_range.start()) + .next() + .unwrap_or_else(|| trimmed_source_range.start()); + + let original_source = f.context().source_map().map_or_else( + || self.node.text_trimmed().to_string(), + |source_map| { + source_map.text()[trimmed_source_range.cover_offset(start_source)].to_string() + }, + ); - let trailing_comments = comments.trailing_comments(self.node); + dynamic_text( + &normalize_newlines(&original_source, LINE_TERMINATORS), + self.node.text_trimmed_range().start(), + ) + .fmt(f)?; - let outside_trimmed_range_start = - trailing_comments.partition_point(|comment| { - source_range(f, comment.piece().text_range()).end() - <= trimmed_source_range.end() - }); + for comment in f.context().comments().dangling_comments(self.node) { + comment.mark_formatted(); + } - let (in_trimmed_range, outside_trimmed_range) = - trailing_comments.split_at(outside_trimmed_range_start); + // Format all trailing comments that are outside of the trimmed range. + if self.format_comments { + let comments = f.context().comments().clone(); - for comment in in_trimmed_range { - comment.mark_formatted(); - } + let trailing_comments = comments.trailing_comments(self.node); - write!(f, [FormatTrailingComments::Comments(outside_trimmed_range)])?; - } + let outside_trimmed_range_start = trailing_comments.partition_point(|comment| { + source_range(f, comment.piece().text_range()).end() <= trimmed_source_range.end() + }); - Ok(()) - })] - )?; + let (in_trimmed_range, outside_trimmed_range) = + trailing_comments.split_at(outside_trimmed_range_start); - let content = buffer.into_vec(); + for comment in in_trimmed_range { + comment.mark_formatted(); + } - let verbatim = Verbatim { - content: content.into_boxed_slice(), - kind: self.kind, - }; + write!(f, [FormatTrailingComments::Comments(outside_trimmed_range)])?; + } - f.write_element(FormatElement::Verbatim(verbatim)) + f.write_element(FormatElement::Tag(Tag::EndVerbatim)) } } @@ -167,7 +150,7 @@ impl FormatVerbatimNode<'_, L> { } /// Formats unknown nodes. The difference between this method and `format_verbatim` is that this method -/// doesn't track nodes/tokens as [FormatElement::Verbatim]. They are just printed as they are. +/// doesn't track nodes/tokens as [VerbatimKind::Verbatim]. They are just printed as they are. pub fn format_unknown_node(node: &SyntaxNode) -> FormatVerbatimNode { FormatVerbatimNode { node, diff --git a/crates/rome_js_formatter/src/builders.rs b/crates/rome_js_formatter/src/builders.rs index 8b3d5a05f91..9cbd27abe37 100644 --- a/crates/rome_js_formatter/src/builders.rs +++ b/crates/rome_js_formatter/src/builders.rs @@ -28,13 +28,14 @@ where match self.node.format().fmt(f) { Ok(result) => Ok(result), - Err(_) => { + Err(FormatError::SyntaxError) => { f.restore_state_snapshot(snapshot); // Lists that yield errors are formatted as they were suppressed nodes. // Doing so, the formatter formats the nodes/tokens as is. format_suppressed_node(self.node.syntax()).fmt(f) } + Err(err) => Err(err), } } } diff --git a/crates/rome_js_formatter/src/check_reformat.rs b/crates/rome_js_formatter/src/check_reformat.rs index ec3a280ad4e..69a8107a718 100644 --- a/crates/rome_js_formatter/src/check_reformat.rs +++ b/crates/rome_js_formatter/src/check_reformat.rs @@ -45,13 +45,13 @@ pub fn check_reformat(params: CheckReformatParams) { } let formatted = format_node(options.clone(), &re_parse.syntax()).unwrap(); - let printed = formatted.print(); + let printed = formatted.print().unwrap(); if text != printed.as_code() { - let input_formatted = format_node(options, root).unwrap().into_format_element(); + let input_formatted = format_node(options, root).unwrap().into_document(); let pretty_input_ir = format!("{input_formatted}"); - let pretty_reformat_ir = format!("{}", formatted.into_format_element()); + let pretty_reformat_ir = format!("{}", formatted.into_document()); // Print a diff of the Formatter IR emitted for the input and the output let diff = similar_asserts::SimpleDiff::from_str( diff --git a/crates/rome_js_formatter/src/js/expressions/template_element.rs b/crates/rome_js_formatter/src/js/expressions/template_element.rs index 6b413391bd7..8c2979f4d92 100644 --- a/crates/rome_js_formatter/src/js/expressions/template_element.rs +++ b/crates/rome_js_formatter/src/js/expressions/template_element.rs @@ -1,4 +1,6 @@ use crate::prelude::*; +use rome_formatter::format_element::document::Document; +use rome_formatter::prelude::tag::Tag; use rome_formatter::printer::{PrintWidth, Printer}; use rome_formatter::{ format_args, write, CstFormatContext, FormatOptions, FormatRuleWithOptions, VecBuffer, @@ -87,13 +89,13 @@ impl Format for FormatTemplateElement { // the print width. let mut buffer = VecBuffer::new(f.state_mut()); write!(buffer, [format_expression])?; - let root = buffer.into_element(); + let root = Document::from(buffer.into_vec()); let print_options = f .options() .as_print_options() .with_print_width(PrintWidth::infinite()); - let printed = Printer::new(print_options).print(&root); + let printed = Printer::new(print_options).print(&root)?; write!( f, @@ -215,21 +217,17 @@ where // Adds as many nested `indent` elements until it reaches the desired indention level. let format_indented = format_with(|f| { - if level == 0 { - write!(f, [content]) - } else { - let mut buffer = VecBuffer::new(f.state_mut()); - - write!(buffer, [content])?; - - let mut indented = buffer.into_element(); + for _ in 0..level { + f.write_element(FormatElement::Tag(Tag::StartIndent))?; + } - for _ in 0..level { - indented = FormatElement::Indent(vec![indented].into_boxed_slice()); - } + write!(f, [content])?; - f.write_element(indented) + for _ in 0..level { + f.write_element(FormatElement::Tag(Tag::EndIndent))?; } + + Ok(()) }); // Adds any necessary `align` for spaces not covered by indent level. diff --git a/crates/rome_js_formatter/src/js/lists/array_element_list.rs b/crates/rome_js_formatter/src/js/lists/array_element_list.rs index c864ceb4b95..799d705118a 100644 --- a/crates/rome_js_formatter/src/js/lists/array_element_list.rs +++ b/crates/rome_js_formatter/src/js/lists/array_element_list.rs @@ -58,7 +58,7 @@ impl FormatRule for FormatJsArrayElementList { } } -#[derive(Copy, Clone)] +#[derive(Copy, Clone, Debug)] enum ArrayLayout { /// Tries to fit as many array elements on a single line as possible. /// diff --git a/crates/rome_js_formatter/src/jsx/lists/child_list.rs b/crates/rome_js_formatter/src/jsx/lists/child_list.rs index 171c9e0f80c..ee9b9027b10 100644 --- a/crates/rome_js_formatter/src/jsx/lists/child_list.rs +++ b/crates/rome_js_formatter/src/jsx/lists/child_list.rs @@ -4,6 +4,7 @@ use crate::utils::jsx::{ JsxRawSpace, JsxSpace, }; use crate::JsFormatter; +use rome_formatter::format_element::tag::Tag; use rome_formatter::{format_args, write, CstFormatContext, FormatRuleWithOptions, VecBuffer}; use rome_js_syntax::{JsxAnyChild, JsxChildList}; use std::cell::RefCell; @@ -480,32 +481,7 @@ impl MultilineBuilder { content: &dyn Format, f: &mut JsFormatter, ) { - let result = std::mem::replace(&mut self.result, Ok(Vec::new())); - - self.result = result.and_then(|mut elements| { - let elements = match self.layout { - MultilineLayout::Fill => { - // Make sure that the separator and content only ever write a single element - let mut buffer = VecBuffer::new(f.state_mut()); - write!(buffer, [content])?; - - elements.push(buffer.into_element()); - - // Fill requires a sequence of [element, separator, element, separator] - // Push an empty list as separator - elements.push(FormatElement::List(List::default())); - elements - } - MultilineLayout::NoFill => { - let mut buffer = VecBuffer::new_with_vec(f.state_mut(), elements); - write!(buffer, [content])?; - - buffer.into_vec() - } - }; - - Ok(elements) - }) + self.write(content, &format_with(|_| Ok(())), f) } fn write( @@ -516,25 +492,25 @@ impl MultilineBuilder { ) { let result = std::mem::replace(&mut self.result, Ok(Vec::new())); - self.result = result.and_then(|mut elements| { - let elements = match self.layout { - MultilineLayout::Fill => { - // Make sure that the separator and content only ever write a single element - let mut buffer = VecBuffer::new(f.state_mut()); - write!(buffer, [content])?; - - elements.push(buffer.into_element()); - - let mut buffer = VecBuffer::new_with_vec(f.state_mut(), elements); - write!(buffer, [separator])?; - buffer.into_vec() - } - MultilineLayout::NoFill => { - let mut buffer = VecBuffer::new_with_vec(f.state_mut(), elements); - write!(buffer, [content, separator])?; - - buffer.into_vec() - } + self.result = result.and_then(|elements| { + let elements = { + let mut buffer = VecBuffer::new_with_vec(f.state_mut(), elements); + match self.layout { + MultilineLayout::Fill => { + // Make sure that the separator and content only ever write a single element + buffer.write_element(FormatElement::Tag(Tag::StartEntry))?; + write!(buffer, [content])?; + buffer.write_element(FormatElement::Tag(Tag::EndEntry))?; + + buffer.write_element(FormatElement::Tag(Tag::StartEntry))?; + write!(buffer, [separator])?; + buffer.write_element(FormatElement::Tag(Tag::EndEntry))?; + } + MultilineLayout::NoFill => { + write!(buffer, [content, separator])?; + } + }; + buffer.into_vec() }; Ok(elements) }) @@ -555,12 +531,19 @@ pub(crate) struct FormatMultilineChildren { impl Format for FormatMultilineChildren { fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { - let elements = self.elements.take(); - let format_inner = format_once(|f| match self.layout { - MultilineLayout::Fill => { - f.write_element(FormatElement::Fill(elements.into_boxed_slice())) + let format_inner = format_once(|f| { + if let Some(elements) = f.intern_vec(self.elements.take()) { + match self.layout { + MultilineLayout::Fill => f.write_elements([ + FormatElement::Tag(Tag::StartFill), + elements, + FormatElement::Tag(Tag::EndFill), + ])?, + MultilineLayout::NoFill => f.write_element(elements)?, + }; } - MultilineLayout::NoFill => f.write_elements(elements), + + Ok(()) }); write!(f, [block_indent(&format_inner)]) @@ -616,7 +599,9 @@ pub(crate) struct FormatFlatChildren { impl Format for FormatFlatChildren { fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { - let elements = self.elements.take(); - f.write_elements(elements) + if let Some(elements) = f.intern_vec(self.elements.take()) { + f.write_element(elements)?; + } + Ok(()) } } diff --git a/crates/rome_js_formatter/src/lib.rs b/crates/rome_js_formatter/src/lib.rs index 04b4b4141cb..562643ff6f7 100644 --- a/crates/rome_js_formatter/src/lib.rs +++ b/crates/rome_js_formatter/src/lib.rs @@ -860,9 +860,11 @@ function() { #[test] // use this test check if your snippet prints as you wish, without using a snapshot fn quick_test() { - let src = r#"long_obj = -

hello world
- + let src = r#" +new Test() + .test() + .test([, 0]) + .test(); "#; let syntax = SourceType::jsx(); @@ -871,7 +873,8 @@ function() { let result = format_node(options.clone(), &tree.syntax()) .unwrap() - .print(); + .print() + .unwrap(); check_reformat(CheckReformatParams { root: &tree.syntax(), text: result.as_code(), @@ -881,7 +884,12 @@ function() { }); assert_eq!( result.as_code(), - "type Example = {\n\t[A in B]: T;\n} & {\n\t[A in B]: T;\n};\n" + r#"[ + 5, + 7234932436, + // comment 3 +]; +"# ); } diff --git a/crates/rome_js_formatter/src/syntax_rewriter.rs b/crates/rome_js_formatter/src/syntax_rewriter.rs index d6323f6e694..c547eca32aa 100644 --- a/crates/rome_js_formatter/src/syntax_rewriter.rs +++ b/crates/rome_js_formatter/src/syntax_rewriter.rs @@ -911,7 +911,7 @@ mod tests { let formatted = format_node(JsFormatOptions::new(SourceType::default()), &transformed).unwrap(); - let printed = formatted.print(); + let printed = formatted.print().unwrap(); assert_eq!(printed.as_code(), "(a * b * c) / 3;\n"); diff --git a/crates/rome_js_formatter/src/ts/lists/type_member_list.rs b/crates/rome_js_formatter/src/ts/lists/type_member_list.rs index 912efe6fa91..0519a2584f6 100644 --- a/crates/rome_js_formatter/src/ts/lists/type_member_list.rs +++ b/crates/rome_js_formatter/src/ts/lists/type_member_list.rs @@ -38,9 +38,7 @@ impl Format for TsTypeMemberItem { let mut recording = f.start_recording(); write!(recording, [self.member.format()])?; - is_verbatim = recording.stop().last().map_or(false, |last| { - matches!(last.last_element(), Some(FormatElement::Verbatim(_))) - }); + is_verbatim = recording.stop().end_tag(TagKind::Verbatim).is_some(); Ok(()) }))] diff --git a/crates/rome_js_formatter/src/utils/assignment_like.rs b/crates/rome_js_formatter/src/utils/assignment_like.rs index 2d265ddc2d8..50e50699c99 100644 --- a/crates/rome_js_formatter/src/utils/assignment_like.rs +++ b/crates/rome_js_formatter/src/utils/assignment_like.rs @@ -893,14 +893,14 @@ impl Format for JsAnyAssignmentLike { // 4. we write the left node inside the main buffer based on the layout let mut buffer = VecBuffer::new(f.state_mut()); let is_left_short = self.write_left(&mut Formatter::new(&mut buffer))?; - let formatted_left = buffer.into_element(); + let formatted_left = buffer.into_vec(); // Compare name only if we are in a position of computing it. // If not (for example, left is not an identifier), then let's fallback to false, // so we can continue the chain of checks let layout = self.layout(is_left_short, f)?; - let left = format_once(|f| f.write_element(formatted_left)); + let left = format_once(|f| f.write_elements(formatted_left)); let right = format_with(|f| self.write_right(f, layout)); let inner_content = format_with(|f| { diff --git a/crates/rome_js_formatter/src/utils/mod.rs b/crates/rome_js_formatter/src/utils/mod.rs index 95f4e1048a8..480e32d912d 100644 --- a/crates/rome_js_formatter/src/utils/mod.rs +++ b/crates/rome_js_formatter/src/utils/mod.rs @@ -196,19 +196,22 @@ impl Format for FormatWithSemicolon<'_> { let mut recording = f.start_recording(); write!(recording, [self.content])?; - let is_unknown = recording - .stop() - .last() - .map_or(false, |last| match last.last_element() { - Some(FormatElement::Verbatim(elem)) => elem.is_unknown(), - _ => false, - }); + let written = recording.stop(); + + let is_unknown = + written + .start_tag(TagKind::Verbatim) + .map_or(false, |signal| match signal { + Tag::StartVerbatim(kind) => kind.is_unknown(), + _ => unreachable!(), + }); if let Some(semicolon) = self.semicolon { write!(f, [semicolon.format()])?; } else if !is_unknown { text(";").fmt(f)?; } + Ok(()) } } diff --git a/crates/rome_js_formatter/tests/check_reformat.rs b/crates/rome_js_formatter/tests/check_reformat.rs index bb6f97b14c4..2c68e180f39 100644 --- a/crates/rome_js_formatter/tests/check_reformat.rs +++ b/crates/rome_js_formatter/tests/check_reformat.rs @@ -46,12 +46,12 @@ pub fn check_reformat(params: CheckReformatParams) { } let formatted = format_node(options.clone(), &re_parse.syntax()).unwrap(); - let printed = formatted.print(); + let printed = formatted.print().unwrap(); if text != printed.as_code() { let input_format_element = format_node(options, root).unwrap(); - let pretty_input_ir = format!("{}", formatted.into_format_element()); - let pretty_reformat_ir = format!("{}", input_format_element.into_format_element()); + let pretty_input_ir = format!("{}", formatted.into_document()); + let pretty_reformat_ir = format!("{}", input_format_element.into_document()); // Print a diff of the Formatter IR emitted for the input and the output let diff = similar_asserts::SimpleDiff::from_str( diff --git a/crates/rome_js_formatter/tests/prettier_tests.rs b/crates/rome_js_formatter/tests/prettier_tests.rs index 503668524ee..d3d0865aa5c 100644 --- a/crates/rome_js_formatter/tests/prettier_tests.rs +++ b/crates/rome_js_formatter/tests/prettier_tests.rs @@ -87,7 +87,7 @@ fn test_snapshot(input: &'static str, _: &str, _: &str, _: &str) { ) } _ => rome_js_formatter::format_node(options.clone(), &syntax) - .map(|formatted| formatted.print()), + .map(|formatted| formatted.print().unwrap()), }; let formatted = result.expect("formatting failed"); diff --git a/crates/rome_js_formatter/tests/spec_test.rs b/crates/rome_js_formatter/tests/spec_test.rs index a10825b984f..16d9b1057ea 100644 --- a/crates/rome_js_formatter/tests/spec_test.rs +++ b/crates/rome_js_formatter/tests/spec_test.rs @@ -232,7 +232,7 @@ pub fn run(spec_input_file: &str, _expected_file: &str, test_directory: &str, fi // we ignore the error for now let options = JsFormatOptions::new(source_type); let formatted = format_node(options.clone(), &root).unwrap(); - let printed = formatted.print(); + let printed = formatted.print().unwrap(); let file_name = spec_input_file.file_name().unwrap().to_str().unwrap(); if !has_errors { @@ -261,7 +261,7 @@ pub fn run(spec_input_file: &str, _expected_file: &str, test_directory: &str, fi // we don't track the source type inside the serializable structs, so we // inject it here let formatted = format_node(format_options.clone(), &root).unwrap(); - let printed = formatted.print(); + let printed = formatted.print().unwrap(); if !has_errors { check_reformat::check_reformat(check_reformat::CheckReformatParams { diff --git a/crates/rome_js_formatter/tests/specs/prettier/js/sloppy-mode/eval-arguments-binding.js.snap b/crates/rome_js_formatter/tests/specs/prettier/js/sloppy-mode/eval-arguments-binding.js.snap deleted file mode 100644 index 5dccdd28f55..00000000000 --- a/crates/rome_js_formatter/tests/specs/prettier/js/sloppy-mode/eval-arguments-binding.js.snap +++ /dev/null @@ -1,63 +0,0 @@ ---- -source: crates/rome_js_formatter/tests/prettier_tests.rs -info: - test_file: "js\\sloppy-mode\\eval-arguments-binding.js" ---- - -# Input - -```js -function myfunc() { - var eval - var arguments; -} -``` - - -# Prettier differences - -```diff ---- Prettier -+++ Rome -@@ -1,4 +1,4 @@ - function myfunc() { -- var eval; -+ var eval - var arguments; - } -``` - -# Output - -```js -function myfunc() { - var eval - var arguments; -} -``` - - -# Errors -``` -eval-arguments-binding.js:2:7 SyntaxError ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - × Illegal use of `eval` as an identifier in strict mode - - ┌─ eval-arguments-binding.js:2:7 - │ - 2 │ var eval - │ ^^^^ - -eval-arguments-binding.js:3:7 SyntaxError ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - × Illegal use of `arguments` as an identifier in strict mode - - ┌─ eval-arguments-binding.js:3:7 - │ - 3 │ var arguments; - │ ^^^^^^^^^ - - -``` - - diff --git a/crates/rome_service/src/file_handlers/javascript.rs b/crates/rome_service/src/file_handlers/javascript.rs index 8c7700ba411..efbb0677618 100644 --- a/crates/rome_service/src/file_handlers/javascript.rs +++ b/crates/rome_service/src/file_handlers/javascript.rs @@ -197,7 +197,7 @@ fn debug_formatter_ir( let tree = parse.syntax(); let formatted = format_node(options, &tree)?; - let root_element = formatted.into_format_element(); + let root_element = formatted.into_document(); Ok(root_element.to_string()) } @@ -383,8 +383,11 @@ fn format( let tree = parse.syntax(); let formatted = format_node(options, &tree)?; - let printed = formatted.print(); - Ok(printed) + + match formatted.print() { + Ok(printed) => Ok(printed), + Err(error) => Err(RomeError::FormatError(error.into())), + } } fn format_range( diff --git a/crates/rome_wasm/Cargo.toml b/crates/rome_wasm/Cargo.toml index f19d45012a7..787bf07c728 100644 --- a/crates/rome_wasm/Cargo.toml +++ b/crates/rome_wasm/Cargo.toml @@ -7,8 +7,6 @@ repository = "https://github.com/rome/tools" description = "WebAssembly bindings to the Rome Workspace API" license = "MIT" -[package.metadata.wasm-pack.profile.dev] -wasm-opt = ['-O1'] [lib] crate-type = ["cdylib", "rlib"] diff --git a/crates/rome_wasm/build.rs b/crates/rome_wasm/build.rs index 8dbcbfdcc79..5102b0e5e6e 100644 --- a/crates/rome_wasm/build.rs +++ b/crates/rome_wasm/build.rs @@ -68,7 +68,7 @@ fn main() -> io::Result<()> { // Wasm-bindgen will paste the generated TS code as-is into the final .d.ts file, // ensure it looks good by running it through the formatter let formatted = format_node(JsFormatOptions::new(SourceType::ts()), module.syntax()).unwrap(); - let printed = formatted.print(); + let printed = formatted.print().unwrap(); let definitions = printed.into_code(); // Generate wasm-bindgen extern type imports for all the types defined in the TS code diff --git a/npm/rome/tests/daemon/formatContent.test.mjs b/npm/rome/tests/daemon/formatContent.test.mjs index 1574614051d..629bbadd4cd 100644 --- a/npm/rome/tests/daemon/formatContent.test.mjs +++ b/npm/rome/tests/daemon/formatContent.test.mjs @@ -49,7 +49,7 @@ describe("Rome Deamon formatter", async () => { expect(result.content).toEqual("function f() {}\n"); expect(result.diagnostics).toEqual([]); expect(result.ir).toMatchInlineSnapshot( - '"[\\"function\\", \\" \\", \\"f\\", group([\\"(\\", \\")\\"]), \\" \\", \\"{\\", \\"}\\", hard_line_break]"', + '"[\\"function f\\", group([\\"()\\"]), \\" {}\\", hard_line_break]"', ); }); @@ -75,16 +75,12 @@ describe("Rome Deamon formatter", async () => { expect(result.ir).toMatchInlineSnapshot( ` "[ - group([\\"let\\", \\" \\", \\"a\\"]), + group([\\"let a\\"]), \\";\\", hard_line_break, - \\"function\\", - \\" \\", - \\"g\\", - group([\\"(\\", \\")\\"]), - \\" \\", - \\"{\\", - \\"}\\", + \\"function g\\", + group([\\"()\\"]), + \\" {}\\", hard_line_break ]" `, diff --git a/npm/rome/tests/wasm/formatContent.test.mjs b/npm/rome/tests/wasm/formatContent.test.mjs index b266bf8f753..ce2ffc09afd 100644 --- a/npm/rome/tests/wasm/formatContent.test.mjs +++ b/npm/rome/tests/wasm/formatContent.test.mjs @@ -46,7 +46,7 @@ describe("Rome WebAssembly formatContent", () => { expect(result.content).toEqual("function f() {}\n"); expect(result.diagnostics).toEqual([]); expect(result.ir).toMatchInlineSnapshot( - '"[\\"function\\", \\" \\", \\"f\\", group([\\"(\\", \\")\\"]), \\" \\", \\"{\\", \\"}\\", hard_line_break]"', + '"[\\"function f\\", group([\\"()\\"]), \\" {}\\", hard_line_break]"', ); }); @@ -80,16 +80,12 @@ describe("Rome WebAssembly formatContent", () => { expect(result.ir).toMatchInlineSnapshot( ` "[ - group([\\"let\\", \\" \\", \\"a\\"]), + group([\\"let a\\"]), \\";\\", hard_line_break, - \\"function\\", - \\" \\", - \\"g\\", - group([\\"(\\", \\")\\"]), - \\" \\", - \\"{\\", - \\"}\\", + \\"function g\\", + group([\\"()\\"]), + \\" {}\\", hard_line_break ]" `, diff --git a/xtask/bench/src/features/formatter.rs b/xtask/bench/src/features/formatter.rs index c7c9d1e617a..9838131b07e 100644 --- a/xtask/bench/src/features/formatter.rs +++ b/xtask/bench/src/features/formatter.rs @@ -52,7 +52,7 @@ pub fn run_format(root: &JsSyntaxNode, source_type: SourceType) -> Printed { print_diff(stats, dhat::HeapStats::get()); } - printed + printed.expect("Document to be valid") } impl FormatterMeasurement { diff --git a/xtask/codegen/src/generate_bindings.rs b/xtask/codegen/src/generate_bindings.rs index 1c9dcd6790d..2d39e8e1ad0 100644 --- a/xtask/codegen/src/generate_bindings.rs +++ b/xtask/codegen/src/generate_bindings.rs @@ -396,7 +396,7 @@ pub(crate) fn generate_workspace_bindings(mode: Mode) -> Result<()> { .build(); let formatted = format_node(JsFormatOptions::new(SourceType::ts()), module.syntax()).unwrap(); - let printed = formatted.print(); + let printed = formatted.print().unwrap(); let code = printed.into_code(); update(&bindings_path, &code, &mode)?; diff --git a/xtask/contributors/src/main.rs b/xtask/contributors/src/main.rs index 998693c740e..aa342035979 100644 --- a/xtask/contributors/src/main.rs +++ b/xtask/contributors/src/main.rs @@ -4,7 +4,7 @@ use std::fmt::Write; use xtask::glue::fs2; use xtask::*; -/// A token is needed to run this script. To create a token, go to https://github.com/settings/tokens +/// A token is needed to run this script. To create a token, go to /// and give it read access to the repository. /// /// Only users that have read rights can run this script