Skip to content

Commit 98c9234

Browse files
committed
perf(formatter): optimize FormatElement::Token printing (#15365)
`FormatElement::Token` is ASCII-only and does not contain line breaks or tab characters, so we can skip the `\n` check and simplify the logic of getting `FormatElement::Token` length
1 parent f90e515 commit 98c9234

File tree

3 files changed

+98
-63
lines changed

3 files changed

+98
-63
lines changed

crates/oxc_formatter/src/formatter/format_element/document.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -118,9 +118,8 @@ impl Document<'_> {
118118
// propagate their expansion.
119119
false
120120
}
121-
FormatElement::Token { text } | FormatElement::Text { text, .. } => {
122-
text.contains('\n')
123-
}
121+
// `FormatElement::Token` cannot contain line breaks
122+
FormatElement::Text { text, .. } => text.contains('\n'),
124123
FormatElement::LocatedTokenText { slice, .. } => slice.contains('\n'),
125124
FormatElement::ExpandParent
126125
| FormatElement::Line(LineMode::Hard | LineMode::Empty) => true,

crates/oxc_formatter/src/formatter/format_element/mod.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -240,13 +240,15 @@ impl FormatElements for FormatElement<'_> {
240240
FormatElement::ExpandParent => true,
241241
FormatElement::Tag(Tag::StartGroup(group)) => !group.mode().is_flat(),
242242
FormatElement::Line(line_mode) => line_mode.will_break(),
243-
FormatElement::Token { text } | FormatElement::Text { text } => text.contains('\n'),
243+
FormatElement::Text { text } => text.contains('\n'),
244244
FormatElement::LocatedTokenText { slice, .. } => slice.contains('\n'),
245245
FormatElement::Interned(interned) => interned.will_break(),
246246
// Traverse into the most flat version because the content is guaranteed to expand when even
247247
// the most flat version contains some content that forces a break.
248248
FormatElement::BestFitting(best_fitting) => best_fitting.most_flat().will_break(),
249-
FormatElement::LineSuffixBoundary
249+
// `FormatElement::Token` cannot contain line breaks
250+
FormatElement::Token { .. }
251+
| FormatElement::LineSuffixBoundary
250252
| FormatElement::Space
251253
| FormatElement::Tag(_)
252254
| FormatElement::HardSpace => false,

crates/oxc_formatter/src/formatter/printer/mod.rs

Lines changed: 92 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -93,12 +93,12 @@ impl<'a> Printer<'a> {
9393
}
9494
}
9595

96-
FormatElement::Token { text } => self.print_text(text),
96+
FormatElement::Token { text } => self.print_text(Text::Token(text)),
9797
FormatElement::Text { text } => {
98-
self.print_text(text);
98+
self.print_text(Text::Text(text));
9999
}
100100
FormatElement::LocatedTokenText { slice, source_position } => {
101-
self.print_text(slice);
101+
self.print_text(Text::Text(slice));
102102
}
103103
FormatElement::Line(line_mode) => {
104104
if args.mode().is_flat() {
@@ -122,12 +122,13 @@ impl<'a> Printer<'a> {
122122

123123
// Only print a newline if the current line isn't already empty
124124
if self.state.line_width > 0 {
125-
self.print_str("\n");
125+
self.print_char('\n');
126+
self.state.has_empty_line = false;
126127
}
127128

128129
// Print a second line break if this is an empty line
129130
if line_mode == &LineMode::Empty && !self.state.has_empty_line {
130-
self.print_str("\n");
131+
self.print_char('\n');
131132
self.state.has_empty_line = true;
132133
}
133134

@@ -300,38 +301,6 @@ impl<'a> Printer<'a> {
300301
result
301302
}
302303

303-
fn print_text(&mut self, text: &str) {
304-
if !self.state.pending_indent.is_empty() {
305-
let (indent_char, repeat_count) = match self.options.indent_style() {
306-
IndentStyle::Tab => ('\t', 1),
307-
IndentStyle::Space => (' ', self.options.indent_width().value()),
308-
};
309-
310-
let indent = std::mem::take(&mut self.state.pending_indent);
311-
let total_indent_char_count = indent.level() as usize * repeat_count as usize;
312-
313-
self.state
314-
.buffer
315-
.reserve(total_indent_char_count + indent.align() as usize + text.len());
316-
317-
for _ in 0..total_indent_char_count {
318-
self.print_char(indent_char);
319-
}
320-
321-
for _ in 0..indent.align() {
322-
self.print_char(' ');
323-
}
324-
}
325-
326-
// Print pending spaces
327-
if self.state.pending_space {
328-
self.print_str(" ");
329-
self.state.pending_space = false;
330-
}
331-
332-
self.print_str(text);
333-
}
334-
335304
fn flush_line_suffixes(
336305
&mut self,
337306
queue: &mut PrintQueue<'a>,
@@ -633,12 +602,49 @@ impl<'a> Printer<'a> {
633602
invalid_end_tag(TagKind::Entry, stack.top_kind())
634603
}
635604

636-
fn print_str(&mut self, content: &str) {
637-
for char in content.chars() {
638-
self.print_char(char);
605+
fn print_text(&mut self, text: Text) {
606+
if !self.state.pending_indent.is_empty() {
607+
let (indent_char, repeat_count) = match self.options.indent_style() {
608+
IndentStyle::Tab => ('\t', 1),
609+
IndentStyle::Space => (' ', self.options.indent_width().value()),
610+
};
611+
612+
let indent = std::mem::take(&mut self.state.pending_indent);
613+
let total_indent_char_count = indent.level() as usize * repeat_count as usize;
614+
615+
self.state
616+
.buffer
617+
.reserve(total_indent_char_count + indent.align() as usize + text.len());
618+
619+
for _ in 0..total_indent_char_count {
620+
self.print_char(indent_char);
621+
}
639622

640-
self.state.has_empty_line = false;
623+
for _ in 0..indent.align() {
624+
self.print_char(' ');
625+
}
626+
}
627+
628+
// Print pending spaces
629+
if self.state.pending_space {
630+
self.state.buffer.push(' ');
631+
self.state.pending_space = false;
632+
self.state.line_width += 1;
633+
}
634+
635+
match text {
636+
Text::Token(text) => {
637+
self.state.buffer.push_str(text);
638+
self.state.line_width += text.len();
639+
}
640+
Text::Text(text) => {
641+
for char in text.chars() {
642+
self.print_char(char);
643+
}
644+
}
641645
}
646+
647+
self.state.has_empty_line = false;
642648
}
643649

644650
fn print_char(&mut self, char: char) {
@@ -1015,10 +1021,15 @@ impl<'a, 'print> FitsMeasurer<'a, 'print> {
10151021
}
10161022
}
10171023

1018-
FormatElement::Token { text } | FormatElement::Text { text, .. } => {
1019-
return Ok(self.fits_text(text));
1024+
FormatElement::Token { text } => {
1025+
return Ok(self.fits_text(Text::Token(text)));
1026+
}
1027+
FormatElement::Text { text, .. } => {
1028+
return Ok(self.fits_text(Text::Text(text)));
1029+
}
1030+
FormatElement::LocatedTokenText { slice, .. } => {
1031+
return Ok(self.fits_text(Text::Text(slice)));
10201032
}
1021-
FormatElement::LocatedTokenText { slice, .. } => return Ok(self.fits_text(slice)),
10221033

10231034
FormatElement::LineSuffixBoundary => {
10241035
if self.state.has_line_suffix {
@@ -1157,7 +1168,7 @@ impl<'a, 'print> FitsMeasurer<'a, 'print> {
11571168
Ok(Fits::Maybe)
11581169
}
11591170

1160-
fn fits_text(&mut self, text: &str) -> Fits {
1171+
fn fits_text(&mut self, text: Text) -> Fits {
11611172
let indent = std::mem::take(&mut self.state.pending_indent);
11621173
self.state.line_width += indent.level() as usize
11631174
* self.options().indent_width().value() as usize
@@ -1167,21 +1178,28 @@ impl<'a, 'print> FitsMeasurer<'a, 'print> {
11671178
self.state.line_width += 1;
11681179
}
11691180

1170-
for c in text.chars() {
1171-
let char_width = match c {
1172-
'\t' => self.options().indent_width.value() as usize,
1173-
'\n' => {
1174-
return if self.must_be_flat
1175-
|| self.state.line_width > usize::from(self.options().print_width)
1176-
{
1177-
Fits::No
1178-
} else {
1179-
Fits::Yes
1181+
match text {
1182+
Text::Token(text) => {
1183+
self.state.line_width += text.len();
1184+
}
1185+
Text::Text(text) => {
1186+
for c in text.chars() {
1187+
let char_width = match c {
1188+
'\t' => self.options().indent_width.value() as usize,
1189+
'\n' => {
1190+
return if self.must_be_flat
1191+
|| self.state.line_width > usize::from(self.options().print_width)
1192+
{
1193+
Fits::No
1194+
} else {
1195+
Fits::Yes
1196+
};
1197+
}
1198+
c => c.width().unwrap_or(0),
11801199
};
1200+
self.state.line_width += char_width;
11811201
}
1182-
c => c.width().unwrap_or(0),
1183-
};
1184-
self.state.line_width += char_width;
1202+
}
11851203
}
11861204

11871205
if self.state.line_width > usize::from(self.options().print_width) {
@@ -1271,6 +1289,22 @@ struct FitsState {
12711289
line_width: usize,
12721290
}
12731291

1292+
#[derive(Copy, Clone, Debug)]
1293+
enum Text<'a> {
1294+
/// ASCII only text that contains no line breaks or tab characters.
1295+
Token(&'a str),
1296+
/// Arbitrary text. May contain `\n` line breaks, tab characters, or unicode characters.
1297+
Text(&'a str),
1298+
}
1299+
1300+
impl Text<'_> {
1301+
fn len(&self) -> usize {
1302+
match self {
1303+
Text::Token(text) | Text::Text(text) => text.len(),
1304+
}
1305+
}
1306+
}
1307+
12741308
#[cfg(test)]
12751309
mod tests {
12761310
use oxc_allocator::Allocator;

0 commit comments

Comments
 (0)