From 93bb861dc3522e54574b7f1a6f5efd60225a08c0 Mon Sep 17 00:00:00 2001 From: leaysgur <6259812+leaysgur@users.noreply.github.com> Date: Thu, 26 Feb 2026 15:24:02 +0000 Subject: [PATCH] fix(formatter): Trim trailing whitespace before breaking line (#19740) This doesn't seem to be an issue with native js/ts format paths, but it appears to cause problems when converting Prettier Docs, such as with gql-in-js. Without this change, ```gql type Mutation { create__TYPE_NAME__(input: Create__TYPE_NAME__Input!): __TYPE_NAME__! @skipAuth ``` will be formatted: ```gql type Mutation { create__TYPE_NAME__(input: Create__TYPE_NAME__Input!): __TYPE_NAME__! @skipAuth ``` See trailing ` ` after `__TYPE_NAME__!`. --- .../test/api/xxx-in-js-conformance.test.ts | 36 +++++++------------ crates/oxc_data_structures/src/code_buffer.rs | 11 ++++++ .../src/formatter/printer/mod.rs | 1 + 3 files changed, 24 insertions(+), 24 deletions(-) diff --git a/apps/oxfmt/test/api/xxx-in-js-conformance.test.ts b/apps/oxfmt/test/api/xxx-in-js-conformance.test.ts index 96c38a74d1821..92aa1c7e66a62 100644 --- a/apps/oxfmt/test/api/xxx-in-js-conformance.test.ts +++ b/apps/oxfmt/test/api/xxx-in-js-conformance.test.ts @@ -12,33 +12,21 @@ describe("Prettier conformance for graphql-in-js", () => { "format.test.js", "comment-tag.js", // /* GraphQL */ ]); - // TODO: Fix in next PR - // graphqlFixtures.push( - // { - // name: "inline/gql-trailing-space.js", - // content: `export const schema = gql\` - // type Mutation { - // create__TYPE_NAME__(input: Create__TYPE_NAME__Input!): __TYPE_NAME__! - // @skipAuth - // update__TYPE_NAME__( - // id: Int! - // input: Update__TYPE_NAME__Input! - // ): __TYPE_NAME__! @skipAuth - // delete__TYPE_NAME__(id: Int!): __TYPE_NAME__! @skipAuth - // } - // \` - // `, - // }, - // ); + graphqlFixtures.push({ - name: "inline/gql-dummy.js", + name: "inline/gql-trailing-space.js", content: `export const schema = gql\` - type Relation { - id: Int! - name: String! + type Mutation { + create__TYPE_NAME__(input: Create__TYPE_NAME__Input!): __TYPE_NAME__! + @skipAuth + update__TYPE_NAME__( + id: Int! + input: Update__TYPE_NAME__Input! + ): __TYPE_NAME__! @skipAuth + delete__TYPE_NAME__(id: Int!): __TYPE_NAME__! @skipAuth } - \` - `, +\` +`, }); describe.concurrent.each(graphqlFixtures)("$name", ({ name, content }) => { diff --git a/crates/oxc_data_structures/src/code_buffer.rs b/crates/oxc_data_structures/src/code_buffer.rs index d57691574aff6..51e36db8a3104 100644 --- a/crates/oxc_data_structures/src/code_buffer.rs +++ b/crates/oxc_data_structures/src/code_buffer.rs @@ -538,6 +538,17 @@ impl CodeBuffer { unsafe { self.buf.set_len(len + bytes) }; } + /// Remove trailing whitespace (spaces and tabs) from the buffer. + /// + /// This trims trailing whitespace before line breaks, matching Prettier's `trimEnd(out)` behavior. + /// + #[inline] + pub fn trim_trailing_ascii_whitespace(&mut self) { + while self.buf.last().is_some_and(|&b| b == b' ' || b == b'\t') { + self.buf.pop(); + } + } + /// Get contents of buffer as a byte slice. /// /// # Example diff --git a/crates/oxc_formatter/src/formatter/printer/mod.rs b/crates/oxc_formatter/src/formatter/printer/mod.rs index a5dee9acad8d8..a6563cabd5da0 100644 --- a/crates/oxc_formatter/src/formatter/printer/mod.rs +++ b/crates/oxc_formatter/src/formatter/printer/mod.rs @@ -127,6 +127,7 @@ impl<'a> Printer<'a> { // Only print a newline if the current line isn't already empty if self.state.line_width > 0 { + self.state.buffer.trim_trailing_ascii_whitespace(); self.print_char('\n'); self.state.has_empty_line = false; }