diff --git a/crates/oxc_formatter/src/formatter/format_element/mod.rs b/crates/oxc_formatter/src/formatter/format_element/mod.rs index b7799b1918e11..bd58c5b49bb46 100644 --- a/crates/oxc_formatter/src/formatter/format_element/mod.rs +++ b/crates/oxc_formatter/src/formatter/format_element/mod.rs @@ -412,8 +412,10 @@ impl Width { /// that it is a multiline text if it contains a line feed. #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum TextWidth { + /// Single-line text width Width(Width), - Multiline, + /// Multi-line text whose `width` is from the start of the text to the first line break + Multiline(Width), } impl TextWidth { @@ -424,7 +426,7 @@ impl TextWidth { for c in text.chars() { let char_width = match c { '\t' => indent_width.value(), - '\n' => return TextWidth::Multiline, + '\n' => return Self::Multiline(Width::new(width)), #[expect(clippy::cast_possible_truncation)] c => c.width().unwrap_or(0) as u8, }; @@ -444,14 +446,7 @@ impl TextWidth { TextWidth::Width(Width::new(len as u32)) } - pub const fn width(self) -> Option { - match self { - TextWidth::Width(width) => Some(width), - TextWidth::Multiline => None, - } - } - pub(crate) const fn is_multiline(self) -> bool { - matches!(self, TextWidth::Multiline) + matches!(self, TextWidth::Multiline(_)) } } diff --git a/crates/oxc_formatter/src/formatter/printer/mod.rs b/crates/oxc_formatter/src/formatter/printer/mod.rs index 70b27c93b4ae1..18d9dcfe9e276 100644 --- a/crates/oxc_formatter/src/formatter/printer/mod.rs +++ b/crates/oxc_formatter/src/formatter/printer/mod.rs @@ -626,16 +626,22 @@ impl<'a> Printer<'a> { } self.state.line_width += text.len(); } - Text::Text { text, width } => { - if let Some(width) = width.width() { + Text::Text { text, width } => match width { + TextWidth::Width(width) => { self.state.buffer.print_str(text); self.state.line_width += width.value() as usize; - } else { - for char in text.chars() { + } + TextWidth::Multiline(width) => { + let line_break_position = text.find('\n').unwrap_or(text.len()); + let (first_line, remaining) = text.split_at(line_break_position); + self.state.buffer.print_str(first_line); + self.state.line_width += width.value() as usize; + // Print the remaining lines + for char in remaining.chars() { self.print_char(char); } } - } + }, } self.state.has_empty_line = false; @@ -1171,19 +1177,21 @@ impl<'a, 'print> FitsMeasurer<'a, 'print> { Text::Token(text) => { self.state.line_width += text.len(); } - Text::Text { text, width } => { - if let Some(width) = width.width() { + Text::Text { text, width } => match width { + TextWidth::Width(width) => { self.state.line_width += width.value() as usize; - } else { + } + TextWidth::Multiline(width) => { return if self.must_be_flat - || self.state.line_width > usize::from(self.options().print_width) + || self.state.line_width + width.value() as usize + > usize::from(self.options().print_width) { Fits::No } else { Fits::Yes }; } - } + }, } if self.state.line_width > usize::from(self.options().print_width) { diff --git a/crates/oxc_formatter/tests/fixtures/js/calls/test.js b/crates/oxc_formatter/tests/fixtures/js/calls/test.js index 287e44828a1b6..a29a66bcfafba 100644 --- a/crates/oxc_formatter/tests/fixtures/js/calls/test.js +++ b/crates/oxc_formatter/tests/fixtures/js/calls/test.js @@ -22,4 +22,8 @@ test.only("Test only", () => {}); describe.only("Describe only", () => {}); it.only("It only", () => {}); -test(code.replace((c) => ""), () => {}); \ No newline at end of file +test(code.replace((c) => ""), () => {}); + +expect(content) + .toMatch(`props: /*@__PURE__*/_mergeDefaults(['foo', 'bar', 'baz'], { +})`) diff --git a/crates/oxc_formatter/tests/fixtures/js/calls/test.js.snap b/crates/oxc_formatter/tests/fixtures/js/calls/test.js.snap index d1e39e96f9c86..3250a3cabd774 100644 --- a/crates/oxc_formatter/tests/fixtures/js/calls/test.js.snap +++ b/crates/oxc_formatter/tests/fixtures/js/calls/test.js.snap @@ -27,6 +27,11 @@ describe.only("Describe only", () => {}); it.only("It only", () => {}); test(code.replace((c) => ""), () => {}); + +expect(content) + .toMatch(`props: /*@__PURE__*/_mergeDefaults(['foo', 'bar', 'baz'], { +})`) + ==================== Output ==================== // Test patterns with multiple levels should not break incorrectly test.describe.serial("My test suite", () => { @@ -57,4 +62,8 @@ test( () => {}, ); +expect(content) + .toMatch(`props: /*@__PURE__*/_mergeDefaults(['foo', 'bar', 'baz'], { +})`); + ===================== End =====================