Skip to content

Commit 5d688a0

Browse files
committed
fix(formatter): measuring multiline text in fits_text is incorrect (#15762)
Regressed by #15372. The changes are made to match previous behavior. In `fits_text`, once the text width is multiple lines, we should check whether the current line width plus the width of the text to the first line break is greater than the allowed line width to take action, but previously, we didn't include `plus the width of the text to the first line break`.
1 parent e306958 commit 5d688a0

File tree

4 files changed

+37
-21
lines changed

4 files changed

+37
-21
lines changed

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

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -412,8 +412,10 @@ impl Width {
412412
/// that it is a multiline text if it contains a line feed.
413413
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
414414
pub enum TextWidth {
415+
/// Single-line text width
415416
Width(Width),
416-
Multiline,
417+
/// Multi-line text whose `width` is from the start of the text to the first line break
418+
Multiline(Width),
417419
}
418420

419421
impl TextWidth {
@@ -424,7 +426,7 @@ impl TextWidth {
424426
for c in text.chars() {
425427
let char_width = match c {
426428
'\t' => indent_width.value(),
427-
'\n' => return TextWidth::Multiline,
429+
'\n' => return Self::Multiline(Width::new(width)),
428430
#[expect(clippy::cast_possible_truncation)]
429431
c => c.width().unwrap_or(0) as u8,
430432
};
@@ -444,14 +446,7 @@ impl TextWidth {
444446
TextWidth::Width(Width::new(len as u32))
445447
}
446448

447-
pub const fn width(self) -> Option<Width> {
448-
match self {
449-
TextWidth::Width(width) => Some(width),
450-
TextWidth::Multiline => None,
451-
}
452-
}
453-
454449
pub(crate) const fn is_multiline(self) -> bool {
455-
matches!(self, TextWidth::Multiline)
450+
matches!(self, TextWidth::Multiline(_))
456451
}
457452
}

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

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -626,16 +626,22 @@ impl<'a> Printer<'a> {
626626
}
627627
self.state.line_width += text.len();
628628
}
629-
Text::Text { text, width } => {
630-
if let Some(width) = width.width() {
629+
Text::Text { text, width } => match width {
630+
TextWidth::Width(width) => {
631631
self.state.buffer.print_str(text);
632632
self.state.line_width += width.value() as usize;
633-
} else {
634-
for char in text.chars() {
633+
}
634+
TextWidth::Multiline(width) => {
635+
let line_break_position = text.find('\n').unwrap_or(text.len());
636+
let (first_line, remaining) = text.split_at(line_break_position);
637+
self.state.buffer.print_str(first_line);
638+
self.state.line_width += width.value() as usize;
639+
// Print the remaining lines
640+
for char in remaining.chars() {
635641
self.print_char(char);
636642
}
637643
}
638-
}
644+
},
639645
}
640646

641647
self.state.has_empty_line = false;
@@ -1171,19 +1177,21 @@ impl<'a, 'print> FitsMeasurer<'a, 'print> {
11711177
Text::Token(text) => {
11721178
self.state.line_width += text.len();
11731179
}
1174-
Text::Text { text, width } => {
1175-
if let Some(width) = width.width() {
1180+
Text::Text { text, width } => match width {
1181+
TextWidth::Width(width) => {
11761182
self.state.line_width += width.value() as usize;
1177-
} else {
1183+
}
1184+
TextWidth::Multiline(width) => {
11781185
return if self.must_be_flat
1179-
|| self.state.line_width > usize::from(self.options().print_width)
1186+
|| self.state.line_width + width.value() as usize
1187+
> usize::from(self.options().print_width)
11801188
{
11811189
Fits::No
11821190
} else {
11831191
Fits::Yes
11841192
};
11851193
}
1186-
}
1194+
},
11871195
}
11881196

11891197
if self.state.line_width > usize::from(self.options().print_width) {

crates/oxc_formatter/tests/fixtures/js/calls/test.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,8 @@ test.only("Test only", () => {});
2222
describe.only("Describe only", () => {});
2323
it.only("It only", () => {});
2424

25-
test(code.replace((c) => ""), () => {});
25+
test(code.replace((c) => ""), () => {});
26+
27+
expect(content)
28+
.toMatch(`props: /*@__PURE__*/_mergeDefaults(['foo', 'bar', 'baz'], {
29+
})`)

crates/oxc_formatter/tests/fixtures/js/calls/test.js.snap

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@ describe.only("Describe only", () => {});
2727
it.only("It only", () => {});
2828

2929
test(code.replace((c) => ""), () => {});
30+
31+
expect(content)
32+
.toMatch(`props: /*@__PURE__*/_mergeDefaults(['foo', 'bar', 'baz'], {
33+
})`)
34+
3035
==================== Output ====================
3136
// Test patterns with multiple levels should not break incorrectly
3237
test.describe.serial("My test suite", () => {
@@ -57,4 +62,8 @@ test(
5762
() => {},
5863
);
5964

65+
expect(content)
66+
.toMatch(`props: /*@__PURE__*/_mergeDefaults(['foo', 'bar', 'baz'], {
67+
})`);
68+
6069
===================== End =====================

0 commit comments

Comments
 (0)