Skip to content

Commit 8ab8b6c

Browse files
authored
perf: optimise embedded formatter (#7567)
1 parent 85d3a3a commit 8ab8b6c

File tree

3 files changed

+58
-94
lines changed

3 files changed

+58
-94
lines changed

crates/biome_formatter/src/format_element.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,10 @@ impl BestFittingElement {
317317
&self.variants
318318
}
319319

320+
pub(crate) fn variants_mut(&mut self) -> &mut [Box<[FormatElement]>] {
321+
&mut self.variants
322+
}
323+
320324
/// Returns the least expanded variant
321325
pub fn most_flat(&self) -> &[FormatElement] {
322326
self.variants.first().expect(

crates/biome_formatter/src/format_element/document.rs

Lines changed: 43 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -145,66 +145,52 @@ impl Document {
145145
pub fn as_elements(&self) -> &[FormatElement] {
146146
&self.elements
147147
}
148-
}
149-
150-
pub trait DocumentVisitor {
151-
/// Visit an element and optionally return a replacement
152-
fn visit_element(&mut self, element: &FormatElement) -> Option<FormatElement>;
153-
}
154148

155-
/// Applies a visitor to transform elements in the document
156-
pub struct ElementTransformer;
157-
158-
impl ElementTransformer {
159-
/// Visits a mutable [Document] and replaces its internal elements.
160-
pub fn transform_document<V: DocumentVisitor>(document: &mut Document, visitor: &mut V) {
161-
let elements = std::mem::take(&mut document.elements);
162-
document.elements = Self::transform_elements(elements, visitor);
149+
/// Transforms the document by visiting every element, optionally replacing
150+
/// them.
151+
///
152+
/// Accepts a `visitor` that will be called to visit each element, and which
153+
/// may optionally return a replacement.
154+
///
155+
/// Elements that contain nested elements, such as [FormatElement::Interned]
156+
/// and [FormatElement::BestFitting], have the visitor called on their
157+
/// nested elements, but not on the elements themselves.
158+
pub(crate) fn transform(
159+
&mut self,
160+
mut visitor: impl FnMut(&FormatElement) -> Option<FormatElement>,
161+
) {
162+
transform_elements(&mut self.elements, &mut visitor);
163163
}
164+
}
164165

165-
/// Iterates over each element of the document and map each element to a new element. The new element is
166-
/// optionally crated using [DocumentVisitor::visit_element]. If no element is returned, the original element is kept.
167-
///
168-
/// Nested data structures such as [FormatElement::Interned] and [FormatElement::BestFitting] use recursion and call
169-
/// [Self::transform_elements] again.
170-
fn transform_elements<V: DocumentVisitor>(
171-
elements: Vec<FormatElement>,
172-
visitor: &mut V,
173-
) -> Vec<FormatElement> {
174-
elements
175-
.into_iter()
176-
.map(|element| {
177-
// Transform nested elements first
178-
let transformed_element = match element {
179-
FormatElement::Interned(interned) => {
180-
let nested_elements = interned.deref().to_vec();
181-
let transformed_nested = Self::transform_elements(nested_elements, visitor);
182-
FormatElement::Interned(Interned::new(transformed_nested))
183-
}
184-
FormatElement::BestFitting(best_fitting) => {
185-
let variants: Vec<Box<[FormatElement]>> = best_fitting
186-
.variants()
187-
.iter()
188-
.map(|variant| {
189-
Self::transform_elements(variant.to_vec(), visitor)
190-
.into_boxed_slice()
191-
})
192-
.collect();
193-
// SAFETY: Safe because the number of variants is the same after the transformation
194-
unsafe {
195-
FormatElement::BestFitting(BestFittingElement::from_vec_unchecked(
196-
variants,
197-
))
198-
}
199-
}
200-
other => other,
201-
};
202-
// Then apply a visitor to the element itself
203-
visitor
204-
.visit_element(&transformed_element)
205-
.unwrap_or(transformed_element)
206-
})
207-
.collect()
166+
/// Iterates over each of the given `elements` and optionally replaces each
167+
/// element with a new one.
168+
///
169+
/// Nested data structures such as [FormatElement::Interned] and
170+
/// [FormatElement::BestFitting] use recursion and call [transform_elements()]
171+
/// again. The visitor is *not* invoked on these elements.
172+
fn transform_elements(
173+
elements: &mut [FormatElement],
174+
visitor: &mut impl FnMut(&FormatElement) -> Option<FormatElement>,
175+
) {
176+
for element in elements {
177+
match element {
178+
FormatElement::Interned(interned) => {
179+
let mut nested_elements = interned.deref().to_vec();
180+
transform_elements(&mut nested_elements, visitor);
181+
*element = FormatElement::Interned(Interned::new(nested_elements));
182+
}
183+
FormatElement::BestFitting(best_fitting) => {
184+
for variant in best_fitting.variants_mut() {
185+
transform_elements(variant, visitor);
186+
}
187+
}
188+
_ => {
189+
if let Some(replacement) = visitor(element) {
190+
*element = replacement;
191+
}
192+
}
193+
}
208194
}
209195
}
210196

crates/biome_formatter/src/lib.rs

Lines changed: 11 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,8 @@ use std::fmt::{Debug, Display};
5050
use crate::builders::syntax_token_cow_slice;
5151
use crate::comments::{CommentStyle, Comments, SourceComment};
5252
pub use crate::diagnostics::{ActualStart, FormatError, InvalidDocumentError, PrintError};
53-
use crate::format_element::document::{Document, ElementTransformer};
53+
use crate::format_element::document::Document;
5454
use crate::format_element::{Interned, LineMode};
55-
use crate::prelude::document::DocumentVisitor;
5655
#[cfg(debug_assertions)]
5756
use crate::printed_tokens::PrintedTokens;
5857
use crate::printer::{Printer, PrinterOptions};
@@ -892,41 +891,20 @@ impl<Context> Formatted<Context> {
892891

893892
/// Visits each embedded element and replaces it with elements contained inside the [Document]
894893
/// emitted by `fn_format_embedded`
895-
pub fn format_embedded<F>(&mut self, fn_format_embedded: F)
894+
pub fn format_embedded<F>(&mut self, mut fn_format_embedded: F)
896895
where
897896
F: FnMut(TextRange) -> Option<Document>,
898897
{
899-
let document = &mut self.document;
900-
901-
struct EmbeddedVisitor<F> {
902-
fn_format_embedded: F,
903-
}
904-
905-
impl<F> DocumentVisitor for EmbeddedVisitor<F>
906-
where
907-
F: FnMut(TextRange) -> Option<Document>,
908-
{
909-
fn visit_element(&mut self, element: &FormatElement) -> Option<FormatElement> {
910-
match element {
911-
FormatElement::Tag(Tag::StartEmbedded(range)) => {
912-
(self.fn_format_embedded)(*range).map(|document| {
913-
FormatElement::Interned(Interned::new(document.into_elements()))
914-
})
915-
}
916-
FormatElement::Tag(Tag::EndEmbedded) => {
917-
// FIXME: this might not play well for all cases, so we need to figure out
918-
// a nicer way to replace the tag
919-
Some(FormatElement::Line(LineMode::Hard))
920-
}
921-
_ => None,
922-
}
898+
self.document.transform(move |element| match element {
899+
FormatElement::Tag(Tag::StartEmbedded(range)) => fn_format_embedded(*range)
900+
.map(|document| FormatElement::Interned(Interned::new(document.into_elements()))),
901+
FormatElement::Tag(Tag::EndEmbedded) => {
902+
// FIXME: this might not play well for all cases, so we need to figure out
903+
// a nicer way to replace the tag
904+
Some(FormatElement::Line(LineMode::Hard))
923905
}
924-
}
925-
926-
ElementTransformer::transform_document(
927-
document,
928-
&mut EmbeddedVisitor { fn_format_embedded },
929-
);
906+
_ => None,
907+
});
930908
}
931909

932910
/// Returns the formatted document.
@@ -938,10 +916,6 @@ impl<Context> Formatted<Context> {
938916
pub fn into_document(self) -> Document {
939917
self.document
940918
}
941-
942-
pub fn swap_document(&mut self, document: Document) {
943-
self.document = document;
944-
}
945919
}
946920

947921
impl<Context> Formatted<Context>

0 commit comments

Comments
 (0)