feat(rome_js_formatter): add format element label
MODIFY
crates/rome_formatter/src/buffer.rs
crates/rome_formatter/src/buffer.rs
use super::{write, Arguments, FormatElement};
-use crate::format_element::List;
+use crate::format_element::{LabelId, List};
use crate::{Format, FormatResult, FormatState};
use std::any::{Any, TypeId};
use std::fmt::Debug;
@@ -487,6 +487,60 @@ pub trait BufferExtensions: Buffer + Sized {
WillBreakBuffer::new(self)
}
+ /// It emits a custom buffer called [IsLabelledBuffer], 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).
+ ///
+ /// This functionality can be used only on one element and only after the element
+ /// is written in the buffer.
+ ///
+ /// ## Examples
+ ///
+ /// ```rust
+ /// use rome_formatter::prelude::*;
+ /// use rome_formatter::{format, write, LineWidth};
+ ///
+ /// enum SomeLabelId {}
+ ///
+ /// let context = SimpleFormatContext {
+ /// line_width: LineWidth::try_from(20).unwrap(),
+ /// ..SimpleFormatContext::default()
+ /// };
+ ///
+ /// let formatted = format!(
+ /// context,
+ /// [format_with(|f| {
+ /// let element = format_with(|f| {
+ /// let label_id = f.label_of::<SomeLabelId>();
+ ///
+ /// write!(f, [labelled(label_id, &token("labelled")),])
+ /// })
+ /// .memoized();
+ ///
+ /// 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()
+ /// };
+ ///
+ /// if is_labelled {
+ /// write!(f, [token("This is "), &element])
+ /// } else {
+ /// write!(f, [token("This is not "), &element])
+ /// }
+ /// })]
+ /// )
+ /// .unwrap();
+ ///
+ /// assert_eq!("This is labelled", 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.
///
@@ -542,11 +596,92 @@ pub trait BufferExtensions: Buffer + Sized {
fn inspect_null(&mut self) -> NullBuffer<Self::Context> {
NullBuffer::new(self)
}
+
+ #[must_use]
+ fn label_of<T: ?Sized + 'static>(&self) -> LabelId {
+ LabelId::of::<T>()
+ }
}
impl<T> BufferExtensions for T where T: Buffer {}
-#[must_use = "must eventually call `finish()` to retrieve the information"]
+#[must_use = "must eventually call `is_labelled()` to retrieve the information"]
+pub struct IsLabelledBuffer<'buffer, Context> {
+ inner: &'buffer mut dyn Buffer<Context = Context>,
+ label_id: LabelId,
+ is_labelled: bool,
+}
+
+impl<'buffer, Context> IsLabelledBuffer<'buffer, Context> {
+ pub fn new(buffer: &'buffer mut dyn Buffer<Context = Context>, label_id: LabelId) -> Self {
+ Self {
+ inner: buffer,
+ label_id,
+ is_labelled: false,
+ }
+ }
+
+ pub fn is_labelled(&self) -> bool {
+ self.is_labelled
+ }
+}
+
+impl<Context> Buffer for IsLabelledBuffer<'_, 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.inner.write_element(element)
+ }
+
+ 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::Any(Box::new(IsLabelledSnapshot {
+ inner: self.inner.snapshot(),
+ label_id: self.label_id,
+ is_labelled: self.is_labelled,
+ }))
+ }
+
+ fn restore_snapshot(&mut self, snapshot: BufferSnapshot) {
+ let snapshot = snapshot.unwrap_any::<IsLabelledSnapshot>();
+ self.inner.restore_snapshot(snapshot.inner);
+ self.is_labelled = snapshot.is_labelled;
+ self.label_id = snapshot.label_id;
+ }
+}
+
+struct IsLabelledSnapshot {
+ inner: BufferSnapshot,
+ label_id: LabelId,
+ is_labelled: bool,
+}
+
+#[must_use = "must eventually call `will_break()` to retrieve the information"]
pub struct WillBreakBuffer<'buffer, Context> {
breaks: bool,
inner: &'buffer mut dyn Buffer<Context = Context>,

MODIFY
crates/rome_formatter/src/builders.rs
crates/rome_formatter/src/builders.rs
@@ -523,6 +523,52 @@ impl<Context> std::fmt::Debug for FormatComment<'_, Context> {
}
}
+/// 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 `inspect_is_labelled` buffer extension and choose a different formatting layout.
+///
+/// See [inspect_is_labelled](BufferExtensions::inspect_is_labelled) for documentation.
+#[inline]
+pub fn labelled<Content, Context>(label_id: LabelId, content: &Content) -> FormatLabelled<Context>
+where
+ Content: Format<Context>,
+{
+ FormatLabelled {
+ label_id,
+ content: Argument::new(content),
+ }
+}
+
+#[derive(Copy, Clone)]
+pub struct FormatLabelled<'a, Context> {
+ label_id: LabelId,
+ content: Argument<'a, Context>,
+}
+
+impl<Context> Format<Context> for FormatLabelled<'_, Context> {
+ fn fmt(&self, f: &mut Formatter<Context>) -> 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))
+ }
+}
+
+impl<Context> std::fmt::Debug for FormatLabelled<'_, Context> {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ f.debug_tuple("Label")
+ .field(&self.label_id)
+ .field(&"{{content}}")
+ .finish()
+ }
+}
+
/// Inserts a single space. Allows to separate different tokens.
///
/// # Examples

MODIFY
crates/rome_formatter/src/format_element.rs
crates/rome_formatter/src/format_element.rs
@@ -2,6 +2,9 @@ use crate::{GroupId, TextSize};
#[cfg(target_pointer_width = "64")]
use rome_rowan::static_assert;
use rome_rowan::SyntaxTokenText;
+#[cfg(debug_assertions)]
+use std::any::type_name;
+use std::any::TypeId;
use std::borrow::Cow;
use std::fmt::{self, Debug, Formatter};
use std::ops::Deref;
@@ -70,6 +73,12 @@ pub enum FormatElement {
/// 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)]
@@ -150,6 +159,10 @@ impl Debug for FormatElement {
}
FormatElement::ExpandParent => write!(fmt, "ExpandParent"),
FormatElement::Interned(inner) => inner.fmt(fmt),
+ FormatElement::Label(label) => {
+ write!(fmt, "Label")?;
+ label.fmt(fmt)
+ }
}
}
}
@@ -352,6 +365,51 @@ impl Deref for Interned {
}
}
+#[derive(Eq, PartialEq, Copy, Clone, Debug)]
+pub struct LabelId {
+ id: TypeId,
+ #[cfg(debug_assertions)]
+ label: &'static str,
+}
+
+impl LabelId {
+ pub(crate) fn of<T: ?Sized + 'static>() -> Self {
+ Self {
+ id: TypeId::of::<T>(),
+ #[cfg(debug_assertions)]
+ label: type_name::<T>(),
+ }
+ }
+}
+
+#[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<FormatElement>) -> Self {
+ Self {
+ content: content.into_boxed_slice(),
+ label_id,
+ }
+ }
+
+ pub fn label_id(&self) -> LabelId {
+ self.label_id
+ }
+}
+
+impl Debug for Label {
+ fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
+ fmt.debug_struct("")
+ .field("label_id", &self.label_id)
+ .field("content", &self.content)
+ .finish()
+ }
+}
+
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct ConditionalGroupContent {
pub(crate) content: Content,
@@ -513,6 +571,7 @@ impl FormatElement {
| FormatElement::Comment(content)
| FormatElement::Fill(Fill { content, .. })
| FormatElement::Verbatim(Verbatim { content, .. })
+ | FormatElement::Label(Label { content, .. })
| FormatElement::Indent(content) => content.iter().any(FormatElement::will_break),
FormatElement::List(list) => list.content.iter().any(FormatElement::will_break),
FormatElement::Token(token) => token.contains('\n'),

MODIFY
crates/rome_formatter/src/lib.rs
crates/rome_formatter/src/lib.rs
@@ -47,12 +47,12 @@ use crate::printed_tokens::PrintedTokens;
use crate::printer::{Printer, PrinterOptions};
pub use arguments::{Argument, Arguments};
pub use buffer::{
- Buffer, BufferExtensions, BufferSnapshot, Inspect, NullBuffer, PreambleBuffer, VecBuffer,
- WillBreakBuffer,
+ Buffer, BufferExtensions, BufferSnapshot, Inspect, IsLabelledBuffer, NullBuffer,
+ PreambleBuffer, VecBuffer, WillBreakBuffer,
};
pub use builders::{
block_indent, comment, empty_line, get_lines_before, group_elements, hard_line_break,
- if_group_breaks, if_group_fits_on_line, indent, line_suffix, soft_block_indent,
+ if_group_breaks, if_group_fits_on_line, indent, labelled, line_suffix, soft_block_indent,
soft_line_break, soft_line_break_or_space, soft_line_indent_or_space, space_token, token,
BestFitting,
};

MODIFY
crates/rome_formatter/src/printer/mod.rs
crates/rome_formatter/src/printer/mod.rs
@@ -285,6 +285,12 @@ impl<'a> Printer<'a> {
}
}
FormatElement::Interned(content) => queue.enqueue(PrintElementCall::new(content, args)),
+ FormatElement::Label(label) => queue.extend(
+ label
+ .content
+ .iter()
+ .map(|element| PrintElementCall::new(element, args)),
+ ),
}
}
@@ -819,6 +825,7 @@ fn fits_element_on_line<'a, 'rest>(
}
}
FormatElement::Interned(content) => queue.enqueue(PrintElementCall::new(content, args)),
+ FormatElement::Label(label) => queue.extend(label.content.iter(), args),
}
Fits::Maybe

feat(rome_js_formatter): add format element label (#2783)

Co-authored-by: Emanuele Stoppa <my.burning@gmail.com>
Co-authored-by: Dominionys <6227442+Dominionys@users.noreply.github.com>