From 1e023e14e551361d13f5d6fd7cdbbfa5398ea5e2 Mon Sep 17 00:00:00 2001 From: Dunqing <29533304+Dunqing@users.noreply.github.com> Date: Wed, 4 Feb 2026 09:11:02 +0000 Subject: [PATCH] fix(formatter): preserve trailing comma in mts/cts arrow generics (#18928) close: #18924 --- .../src/print/type_parameters.rs | 38 +++++++++++++++---- .../cts-arrow-trailing-comma.cts | 2 + .../cts-arrow-trailing-comma.cts.snap | 21 ++++++++++ .../mts-arrow-trailing-comma.mts | 2 + .../mts-arrow-trailing-comma.mts.snap | 21 ++++++++++ 5 files changed, 76 insertions(+), 8 deletions(-) create mode 100644 crates/oxc_formatter/tests/fixtures/ts/type-parameters/cts-arrow-trailing-comma.cts create mode 100644 crates/oxc_formatter/tests/fixtures/ts/type-parameters/cts-arrow-trailing-comma.cts.snap create mode 100644 crates/oxc_formatter/tests/fixtures/ts/type-parameters/mts-arrow-trailing-comma.mts create mode 100644 crates/oxc_formatter/tests/fixtures/ts/type-parameters/mts-arrow-trailing-comma.mts.snap diff --git a/crates/oxc_formatter/src/print/type_parameters.rs b/crates/oxc_formatter/src/print/type_parameters.rs index 85e39ff63a32c..a8d6738fce308 100644 --- a/crates/oxc_formatter/src/print/type_parameters.rs +++ b/crates/oxc_formatter/src/print/type_parameters.rs @@ -1,5 +1,6 @@ use oxc_allocator::Vec; use oxc_ast::ast::*; +use oxc_span::FileExtension; use crate::{ ast_nodes::{AstNode, AstNodes}, @@ -68,17 +69,11 @@ impl<'a> FormatWrite<'a> for AstNode<'a, TSTypeParameter<'a>> { impl<'a> Format<'a> for AstNode<'a, Vec<'a, TSTypeParameter<'a>>> { fn fmt(&self, f: &mut Formatter<'_, 'a>) { // Type parameter lists of arrow function expressions have to include at least one comma - // to avoid any ambiguity with JSX elements. + // to avoid any ambiguity with JSX elements, and in `.mts`/`.cts` sources. // Thus, we have to add a trailing comma when there is a single type parameter. // The comma can be omitted in the case where the single parameter has a constraint, // i.i. an `extends` clause. - let trailing_separator = if self.len() == 1 - // This only concern sources that allow JSX or a restricted standard variant. - && f.context().source_type().is_jsx() - && matches!(self.grand_parent(), AstNodes::ArrowFunctionExpression(_)) - // Ignore Type parameter with an `extends` clause or a default type. - && !self.first().is_some_and(|t| t.constraint().is_some() || t.default().is_some()) - { + let trailing_separator = if should_force_trailing_comma_for_arrow_function(self, f) { TrailingSeparator::Mandatory } else { FormatTrailingCommas::ES5.trailing_separator(f.options()) @@ -92,6 +87,33 @@ impl<'a> Format<'a> for AstNode<'a, Vec<'a, TSTypeParameter<'a>>> { } } +/// Matches Prettier's `shouldForceTrailingComma` behavior for arrow functions. +/// +/// +fn should_force_trailing_comma_for_arrow_function( + params: &AstNode<'_, Vec<'_, TSTypeParameter<'_>>>, + f: &Formatter<'_, '_>, +) -> bool { + if params.len() != 1 { + return false; + } + + if !matches!(params.grand_parent(), AstNodes::ArrowFunctionExpression(_)) { + return false; + } + + // Ignore type parameters with a constraint or default type. + if params.first().is_some_and(|t| t.constraint().is_some() || t.default().is_some()) { + return false; + } + + let source_type = f.context().source_type(); + let is_ts_extension = matches!(source_type.extension(), Some(FileExtension::Ts)); + + // Force trailing comma for non-.ts sources (e.g. .tsx, .mts, .cts) or when extension is unknown. + !is_ts_extension +} + #[derive(Default)] pub struct FormatTSTypeParametersOptions { pub group_id: Option, diff --git a/crates/oxc_formatter/tests/fixtures/ts/type-parameters/cts-arrow-trailing-comma.cts b/crates/oxc_formatter/tests/fixtures/ts/type-parameters/cts-arrow-trailing-comma.cts new file mode 100644 index 0000000000000..b37856a750a68 --- /dev/null +++ b/crates/oxc_formatter/tests/fixtures/ts/type-parameters/cts-arrow-trailing-comma.cts @@ -0,0 +1,2 @@ +// index.cts +const fn = () => {} diff --git a/crates/oxc_formatter/tests/fixtures/ts/type-parameters/cts-arrow-trailing-comma.cts.snap b/crates/oxc_formatter/tests/fixtures/ts/type-parameters/cts-arrow-trailing-comma.cts.snap new file mode 100644 index 0000000000000..fcc5755f28bef --- /dev/null +++ b/crates/oxc_formatter/tests/fixtures/ts/type-parameters/cts-arrow-trailing-comma.cts.snap @@ -0,0 +1,21 @@ +--- +source: crates/oxc_formatter/tests/fixtures/mod.rs +--- +==================== Input ==================== +// index.cts +const fn = () => {} + +==================== Output ==================== +------------------ +{ printWidth: 80 } +------------------ +// index.cts +const fn = () => {}; + +------------------- +{ printWidth: 100 } +------------------- +// index.cts +const fn = () => {}; + +===================== End ===================== diff --git a/crates/oxc_formatter/tests/fixtures/ts/type-parameters/mts-arrow-trailing-comma.mts b/crates/oxc_formatter/tests/fixtures/ts/type-parameters/mts-arrow-trailing-comma.mts new file mode 100644 index 0000000000000..40c907c50f5f4 --- /dev/null +++ b/crates/oxc_formatter/tests/fixtures/ts/type-parameters/mts-arrow-trailing-comma.mts @@ -0,0 +1,2 @@ +// index.mts +const fn = () => {} diff --git a/crates/oxc_formatter/tests/fixtures/ts/type-parameters/mts-arrow-trailing-comma.mts.snap b/crates/oxc_formatter/tests/fixtures/ts/type-parameters/mts-arrow-trailing-comma.mts.snap new file mode 100644 index 0000000000000..3de73aa01eae7 --- /dev/null +++ b/crates/oxc_formatter/tests/fixtures/ts/type-parameters/mts-arrow-trailing-comma.mts.snap @@ -0,0 +1,21 @@ +--- +source: crates/oxc_formatter/tests/fixtures/mod.rs +--- +==================== Input ==================== +// index.mts +const fn = () => {} + +==================== Output ==================== +------------------ +{ printWidth: 80 } +------------------ +// index.mts +const fn = () => {}; + +------------------- +{ printWidth: 100 } +------------------- +// index.mts +const fn = () => {}; + +===================== End =====================