refactor(rome_formatter): Use `memoized.inspect` over `buffer.inspect`
MODIFY
crates/rome_formatter/src/buffer.rs
crates/rome_formatter/src/buffer.rs
@@ -483,14 +483,19 @@ pub trait BufferExtensions: Buffer + Sized {
/// formatted.print().as_code()
/// );
/// ```
+ ///
+ /// ## Alternatives
+ ///
+ /// Use `Memoized.inspect(f).will_break()` if you need to know if some content breaks that should
+ /// only be written later.
fn inspect_will_break(&mut self) -> WillBreakBuffer<Self::Context> {
WillBreakBuffer::new(self)
}
- /// It emits a custom buffer called [IsLabelledBuffer], which tracks
+ /// Wraps the current buffer in a [HasLabelBuffer], which tracks
/// labelled elements written in the main buffer, it does so by
- /// checking if [element](FormatElement) is [label](FormatElement::Label)
- /// with expected [label_id](LabelId).
+ /// checking if [element](FormatElement) is a [label](FormatElement::Label)
+ /// with the expected [label_id](LabelId).
///
/// This functionality can be used only on one element and only after the element
/// is written in the buffer.
@@ -511,142 +516,67 @@ pub trait BufferExtensions: Buffer + Sized {
/// let formatted = format!(
/// context,
/// [format_with(|f| {
- /// let element = format_with(|f| {
- /// let label_id = f.label_of::<SomeLabelId>();
+ /// let mut buffer = f.inspect_is_labelled::<SomeLabelId>();
///
- /// write!(f, [labelled(label_id, &token("labelled")),])
- /// })
- /// .memoized();
+ /// write!(buffer, [
+ /// labelled(
+ /// LabelId::of::<SomeLabelId>(),
+ /// &token("'I have a label'")
+ /// )
+ /// ])?;
///
- /// let is_labelled = {
- /// let mut null_buffer = f.inspect_null();
- /// let mut buffer = null_buffer.inspect_is_labelled::<SomeLabelId>();
- /// write!(buffer, [element])?;
- /// buffer.is_labelled()
- /// };
+ /// let is_labelled = buffer.has_label();
///
/// if is_labelled {
- /// write!(f, [token("This is "), &element])
+ /// write!(f, [token(" has label SomeLabelId")])
/// } else {
- /// write!(f, [token("This is not "), &element])
+ /// write!(f, [token(" doesn't have label SomeLabelId")])
/// }
/// })]
/// )
/// .unwrap();
///
- /// assert_eq!("This is labelled", formatted.print().as_code());
+ /// assert_eq!("'I have a label' has label SomeLabelId", formatted.print().as_code());
/// ```
- fn inspect_is_labelled<T: ?Sized + 'static>(&mut self) -> IsLabelledBuffer<Self::Context> {
- let label_id = self.label_of::<T>();
- IsLabelledBuffer::new(self, label_id)
- }
-
- /// It creates a buffer where all the elements are ignored, so the elements
- /// are not written anywhere at all.
- ///
- /// This can be useful when formatters are not yet written inside the main buffer
- /// and the consumer needs to inspect them, to decide the formatting layout in advance.
- ///
- /// ## Examples
- ///
- /// The following example shows how to use it with the `will_break` functionality
- ///
- /// ```
- /// use rome_formatter::{format, format_args, write, LineWidth};
- /// use rome_formatter::prelude::*;
- ///
- /// let context = SimpleFormatContext {
- /// line_width: LineWidth::try_from(20).unwrap(),
- /// ..SimpleFormatContext::default()
- /// };
///
+ /// /// ## Alternatives
///
- /// let formatted = format!(context, [format_with(|f| {
- ///
- /// let element = format_with(|f| {
- /// write!(f, [
- /// token("hello"),
- /// hard_line_break(),
- /// token("world!")
- /// ])
- /// }).memoized();
- ///
- /// let will_break = {
- /// let mut null_buffer = f.inspect_null();
- /// let mut buffer = null_buffer.inspect_will_break();
- /// write!(buffer, [element])?;
- /// buffer.will_break()
- /// };
- ///
- ///
- /// if will_break {
- /// write!(f, [token("break"), hard_line_break(), &element])
- /// } else {
- /// write!(f, [token("did not break")])
- /// }
- ///
- /// })]).unwrap();
- ///
- /// assert_eq!(
- /// "break\nhello\nworld!",
- /// formatted.print().as_code()
- /// );
- /// ```
- #[must_use]
- fn inspect_null(&mut self) -> NullBuffer<Self::Context> {
- NullBuffer::new(self)
- }
-
- #[must_use]
- fn label_of<T: ?Sized + 'static>(&self) -> LabelId {
- LabelId::of::<T>()
+ /// Use `Memoized.inspect(f).will_break()` if you need to know if some content breaks that should
+ /// only be written later.
+ fn inspect_is_labelled<T: ?Sized + 'static>(&mut self) -> HasLabelBuffer<Self::Context> {
+ let label_id = LabelId::of::<T>();
+ HasLabelBuffer::new(self, label_id)
}
}
impl<T> BufferExtensions for T where T: Buffer {}
#[must_use = "must eventually call `is_labelled()` to retrieve the information"]
-pub struct IsLabelledBuffer<'buffer, Context> {
+pub struct HasLabelBuffer<'buffer, Context> {
inner: &'buffer mut dyn Buffer<Context = Context>,
label_id: LabelId,
- is_labelled: bool,
+ has_label: bool,
}
-impl<'buffer, Context> IsLabelledBuffer<'buffer, Context> {
+impl<'buffer, Context> HasLabelBuffer<'buffer, Context> {
pub fn new(buffer: &'buffer mut dyn Buffer<Context = Context>, label_id: LabelId) -> Self {
Self {
inner: buffer,
label_id,
- is_labelled: false,
+ has_label: false,
}
}
- pub fn is_labelled(&self) -> bool {
- self.is_labelled
+ pub fn has_label(self) -> bool {
+ self.has_label
}
}
-impl<Context> Buffer for IsLabelledBuffer<'_, Context> {
+impl<Context> Buffer for HasLabelBuffer<'_, Context> {
type Context = Context;
fn write_element(&mut self, element: FormatElement) -> FormatResult<()> {
- if !self.is_labelled {
- let label = match &element {
- FormatElement::Label(label) => Some(label),
- FormatElement::Interned(inner) => {
- if let FormatElement::Label(label) = inner.deref() {
- Some(label)
- } else {
- None
- }
- }
- _ => None,
- };
-
- self.is_labelled = label
- .map(|label| label.label_id() == self.label_id)
- .unwrap_or(false);
- }
+ self.has_label |= element.has_label(self.label_id);
self.inner.write_element(element)
}
@@ -660,25 +590,22 @@ impl<Context> Buffer for IsLabelledBuffer<'_, Context> {
}
fn snapshot(&self) -> BufferSnapshot {
- BufferSnapshot::Any(Box::new(IsLabelledSnapshot {
+ BufferSnapshot::Any(Box::new(HasLabelledSnapshot {
inner: self.inner.snapshot(),
- label_id: self.label_id,
- is_labelled: self.is_labelled,
+ has_label: self.has_label,
}))
}
fn restore_snapshot(&mut self, snapshot: BufferSnapshot) {
- let snapshot = snapshot.unwrap_any::<IsLabelledSnapshot>();
+ let snapshot = snapshot.unwrap_any::<HasLabelledSnapshot>();
self.inner.restore_snapshot(snapshot.inner);
- self.is_labelled = snapshot.is_labelled;
- self.label_id = snapshot.label_id;
+ self.has_label = snapshot.has_label;
}
}
-struct IsLabelledSnapshot {
+struct HasLabelledSnapshot {
inner: BufferSnapshot,
- label_id: LabelId,
- is_labelled: bool,
+ has_label: bool,
}
#[must_use = "must eventually call `will_break()` to retrieve the information"]
@@ -734,38 +661,3 @@ struct WillBreakSnapshot {
inner: BufferSnapshot,
breaks: bool,
}
-
-pub struct NullBuffer<'buffer, Context> {
- inner: &'buffer mut dyn Buffer<Context = Context>,
-}
-
-impl<'buffer, Context> NullBuffer<'buffer, Context> {
- pub fn new(buffer: &'buffer mut dyn Buffer<Context = Context>) -> Self {
- Self { inner: buffer }
- }
-}
-
-impl<Context> Buffer for NullBuffer<'_, Context> {
- type Context = Context;
-
- fn write_element(&mut self, element: FormatElement) -> FormatResult<()> {
- drop(element);
- Ok(())
- }
-
- fn state(&self) -> &FormatState<Self::Context> {
- self.inner.state()
- }
-
- fn state_mut(&mut self) -> &mut FormatState<Self::Context> {
- self.inner.state_mut()
- }
-
- fn snapshot(&self) -> BufferSnapshot {
- BufferSnapshot::Position(0)
- }
-
- fn restore_snapshot(&mut self, snapshot: BufferSnapshot) {
- drop(snapshot);
- }
-}

MODIFY
crates/rome_formatter/src/format_element.rs
crates/rome_formatter/src/format_element.rs
@@ -373,7 +373,7 @@ pub struct LabelId {
}
impl LabelId {
- pub(crate) fn of<T: ?Sized + 'static>() -> Self {
+ pub fn of<T: ?Sized + 'static>() -> Self {
Self {
id: TypeId::of::<T>(),
#[cfg(debug_assertions)]
@@ -583,6 +583,15 @@ impl FormatElement {
}
}
+ /// Returns true if the element has the given label.
+ pub fn has_label(&self, label_id: LabelId) -> bool {
+ match self {
+ FormatElement::Label(label) => label.label_id == 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, verbatim or a comment.

MODIFY
crates/rome_formatter/src/lib.rs
crates/rome_formatter/src/lib.rs
@@ -47,8 +47,8 @@ use crate::printed_tokens::PrintedTokens;
use crate::printer::{Printer, PrinterOptions};
pub use arguments::{Argument, Arguments};
pub use buffer::{
- Buffer, BufferExtensions, BufferSnapshot, Inspect, IsLabelledBuffer, NullBuffer,
- PreambleBuffer, VecBuffer, WillBreakBuffer,
+ Buffer, BufferExtensions, BufferSnapshot, HasLabelBuffer, Inspect, PreambleBuffer, VecBuffer,
+ WillBreakBuffer,
};
pub use builders::{
block_indent, comment, empty_line, get_lines_before, group_elements, hard_line_break,
@@ -65,8 +65,6 @@ use rome_rowan::{
SyntaxToken, SyntaxTriviaPieceComments, TextRange, TextSize, TokenAtOffset,
};
use std::error::Error;
-use std::fmt;
-use std::fmt::Display;
use std::num::ParseIntError;
use std::str::FromStr;
@@ -173,8 +171,8 @@ impl TryFrom<u16> for LineWidth {
}
}
-impl Display for LineWidthFromIntError {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+impl std::fmt::Display for LineWidthFromIntError {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(
f,
"The line width exceeds the maximum value ({})",
@@ -344,7 +342,7 @@ pub enum FormatError {
SyntaxError,
}
-impl fmt::Display for FormatError {
+impl std::fmt::Display for FormatError {
fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
FormatError::SyntaxError => fmt.write_str("syntax error"),
@@ -1130,11 +1128,11 @@ pub struct FormatState<Context> {
pub printed_tokens: PrintedTokens,
}
-impl<Context> fmt::Debug for FormatState<Context>
+impl<Context> std::fmt::Debug for FormatState<Context>
where
- Context: fmt::Debug,
+ Context: std::fmt::Debug,
{
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
f.debug_struct("FormatState")
.field("context", &self.context)
.field(

MODIFY
crates/rome_js_formatter/src/js/expressions/call_arguments.rs
crates/rome_js_formatter/src/js/expressions/call_arguments.rs
@@ -98,12 +98,30 @@ impl FormatNodeRule<JsCallArguments> for FormatJsCallArguments {
// We now need to allocate a new vector with cached nodes, this is needed because
// we can't attempt to print the same node twice without incur in "printed token twice" errors.
// We also disallow the trailing separator, we are interested in doing it manually.
- let separated: Vec<_> = args
+ let mut separated: Vec<_> = args
.format_separated(JsSyntaxKind::COMMA)
.with_trailing_separator(TrailingSeparator::Omit)
.map(|e| e.memoized())
.collect();
+ let mut any_breaks = false;
+ let an_argument_breaks =
+ separated
+ .iter_mut()
+ .enumerate()
+ .any(|(index, element)| match element.inspect(f) {
+ Ok(element) => {
+ if element.will_break() {
+ any_breaks = true;
+ should_group_first_argument && index > 0
+ || (should_group_last_argument && index < args.len() - 1)
+ } else {
+ false
+ }
+ }
+ Err(_) => false,
+ });
+
// We now cache them the delimiters tokens. This is needed because `[rome_formatter::best_fitting]` will try to
// print each version first
// tokens on the left
@@ -116,39 +134,6 @@ impl FormatNodeRule<JsCallArguments> for FormatJsCallArguments {
let r_paren = r_paren.memoized();
let r_trailing_trivia = r_trailing_trivia.memoized();
- let edge_arguments_do_not_break = format_with(|f| {
- // `should_group_first_argument` and `should_group_last_argument` are mutually exclusive
- // which means that if one is `false`, then the other is `true`.
- // This means that in this branch we format the case where `should_group_first_argument`,
- // in the else branch we format the case where `should_group_last_argument` is `true`.
- write!(f, [l_leading_trivia, l_paren, l_trailing_trivia,])?;
- if should_group_first_argument {
- // special formatting of the first element
- let mut iter = separated.iter();
- // SAFETY: check on the existence of at least one argument are done before
- let first = iter.next().unwrap();
- f.join_with(&space_token())
- .entry(&format_with(|f| {
- write!(f, [&format_args![first, expand_parent()]])
- }))
- .entries(iter)
- .finish()?;
- } else {
- // special formatting of the last element
- let mut iter = separated.iter();
- // SAFETY: check on the existence of at least one argument are done before
- let last = iter.next_back().unwrap();
-
- f.join_with(&space_token())
- .entries(iter)
- .entry(&format_with(|f| {
- write!(f, [&format_args![last, expand_parent()]])
- }))
- .finish()?;
- }
- write!(f, [r_leading_trivia, r_paren, r_trailing_trivia])
- });
-
// This is the version of where all the arguments are broken out
let all_arguments_expanded = format_with(|f| {
// this formatting structure replicates what we have inside the `format_delimited`
@@ -179,24 +164,6 @@ impl FormatNodeRule<JsCallArguments> for FormatJsCallArguments {
)
});
- let mut null_buffer = f.inspect_null();
- let arguments_break = separated.iter().map(|element| {
- let mut will_break_buffer = null_buffer.inspect_will_break();
- write!(will_break_buffer, [element]).ok();
- will_break_buffer.will_break()
- });
- let mut any_breaks = false;
- let an_argument_breaks = arguments_break.enumerate().any(|(index, will_break)| {
- any_breaks |= will_break;
- if should_group_first_argument && index > 0
- || (should_group_last_argument && index < args.len() - 1)
- {
- will_break
- } else {
- false
- }
- });
-
if an_argument_breaks {
return write!(f, [all_arguments_expanded]);
}
@@ -205,6 +172,39 @@ impl FormatNodeRule<JsCallArguments> for FormatJsCallArguments {
write!(f, [expand_parent()])?;
}
+ let edge_arguments_do_not_break = format_with(|f| {
+ // `should_group_first_argument` and `should_group_last_argument` are mutually exclusive
+ // which means that if one is `false`, then the other is `true`.
+ // This means that in this branch we format the case where `should_group_first_argument`,
+ // in the else branch we format the case where `should_group_last_argument` is `true`.
+ write!(f, [l_leading_trivia, l_paren, l_trailing_trivia,])?;
+ if should_group_first_argument {
+ // special formatting of the first element
+ let mut iter = separated.iter();
+ // SAFETY: check on the existence of at least one argument are done before
+ let first = iter.next().unwrap();
+ f.join_with(&space_token())
+ .entry(&format_with(|f| {
+ write!(f, [&format_args![first, expand_parent()]])
+ }))
+ .entries(iter)
+ .finish()?;
+ } else {
+ // special formatting of the last element
+ let mut iter = separated.iter();
+ // SAFETY: check on the existence of at least one argument are done before
+ let last = iter.next_back().unwrap();
+
+ f.join_with(&space_token())
+ .entries(iter)
+ .entry(&format_with(|f| {
+ write!(f, [&format_args![last, expand_parent()]])
+ }))
+ .finish()?;
+ }
+ write!(f, [r_leading_trivia, r_paren, r_trailing_trivia])
+ });
+
write!(
f,
[best_fitting![

MODIFY
crates/rome_js_formatter/src/lib.rs
crates/rome_js_formatter/src/lib.rs
@@ -222,7 +222,7 @@ impl IntoFormat<JsFormatContext> for JsSyntaxToken {
/// Formats a range within a file, supported by Rome
///
/// This runs a simple heuristic to determine the initial indentation
-/// level of the node based on the provided [FormatContext], which
+/// level of the node based on the provided [JsFormatContext], which
/// must match currently the current initial of the file. Additionally,
/// because the reformatting happens only locally the resulting code
/// will be indented with the same level as the original selection,
@@ -269,7 +269,7 @@ pub fn format_node(context: JsFormatContext, root: &JsSyntaxNode) -> FormatResul
/// Formats a single node within a file, supported by Rome.
///
/// This runs a simple heuristic to determine the initial indentation
-/// level of the node based on the provided [FormatContext], which
+/// level of the node based on the provided [JsFormatContext], which
/// must match currently the current initial of the file. Additionally,
/// because the reformatting happens only locally the resulting code
/// will be indented with the same level as the original selection,

MODIFY
crates/rome_js_formatter/src/prelude.rs
crates/rome_js_formatter/src/prelude.rs
//! This module provides important and useful traits to help to format tokens and nodes
-//! when implementing the [crate::FormatNode] trait.
+//! when implementing the [crate::FormatNodeRule] trait.
pub(crate) use crate::{
AsFormat as _, FormatNodeRule, FormattedIterExt, JsFormatContext, JsFormatter,

MODIFY
crates/rome_js_formatter/src/utils/mod.rs
crates/rome_js_formatter/src/utils/mod.rs
@@ -154,10 +154,7 @@ pub(crate) fn node_has_leading_newline(node: &JsSyntaxNode) -> bool {
}
/// Format an element with a single line head and a body that might
-/// be either a block or a single statement
-///
-/// This will place the head element inside a [hard_group_elements], but
-/// the body will broken out of flat printing if its a single statement
+/// be either a block or a single statement.
pub struct FormatBodyStatement<'a> {
body: &'a JsAnyStatement,
}

refactor(rome_formatter): Use `memoized.inspect` over `buffer.inspect` (#2889)