From 99ab34aad903be3b52c773e7163cf03b843ac5a6 Mon Sep 17 00:00:00 2001 From: Alexandra Parker Date: Wed, 12 Nov 2025 17:46:45 -0800 Subject: [PATCH] fix(jsx): avoid making type param lists ambiguous Neither function expressions nor function declarations are ambiguous: ```typescript // These parse correctly even in TSX function foo() { } const foo = function() {} ``` Only arrow functions are ambiguous. The presence of the `function` or `class` keywords is enough for the TypeScript parser to disambiguate. The guards against this were in place but deactivated by `trailingCommas: never`. Change it to always check before applying the preferred style. See: https://github.com/dprint/dprint-plugin-typescript/issues/636 Signed-off-by: Alexandra Parker --- src/generation/generate.rs | 31 +++++++++---------- tests/specs/general/Jsx_TrailingCommas.txt | 14 ++++----- .../general/Jsx_TrailingCommas_Never.txt | 23 ++++++++++++++ 3 files changed, 44 insertions(+), 24 deletions(-) create mode 100644 tests/specs/general/Jsx_TrailingCommas_Never.txt diff --git a/src/generation/generate.rs b/src/generation/generate.rs index d5090dc9..3883c67a 100644 --- a/src/generation/generate.rs +++ b/src/generation/generate.rs @@ -6363,25 +6363,20 @@ fn gen_type_parameters<'a>(node: TypeParamNode<'a>, context: &mut Context<'a>) - return items; - fn get_trailing_commas<'a>(node: TypeParamNode<'a>, context: &mut Context<'a>) -> TrailingCommas { - let trailing_commas = context.config.type_parameters_trailing_commas; - if trailing_commas == TrailingCommas::Never { - return trailing_commas; - } - + fn get_trailing_commas<'a>(node: TypeParamNode<'a>, context: &Context<'a>) -> TrailingCommas { // trailing commas should be allowed in type parameters only—not arguments - if let Some(type_params) = node.parent().get_type_parameters() { - if type_params.start() == node.start() { + match node.parent().get_type_parameters() { + Some(type_params) if type_params.start() == node.start() => { // Use trailing commas for function expressions in a JSX file // if the absence of one would lead to a parsing ambiguity. let parent = node.parent(); let is_ambiguous_jsx_fn_expr = context.is_jsx() - && (parent.kind() == NodeKind::ArrowExpr || parent.parent().unwrap().kind() == NodeKind::FnExpr) - // not ambiguous in a default export - && !matches!( - parent.parent().and_then(|p| p.parent()).map(|p| p.kind()), - Some(NodeKind::ExportDefaultExpr | NodeKind::ExportDefaultDecl) - ); + && (parent.kind() == NodeKind::ArrowExpr) + // not ambiguous in a default export + && !matches!( + parent.parent().and_then(|p| p.parent()).map(|p| p.kind()), + Some(NodeKind::ExportDefaultExpr | NodeKind::ExportDefaultDecl) + ); // Prevent "This syntax is reserved in files with the .mts or .cts extension." diagnostic. let is_cts_mts_arrow_fn = matches!(context.media_type, MediaType::Cts | MediaType::Mts) && parent.kind() == NodeKind::ArrowExpr; if is_ambiguous_jsx_fn_expr || is_cts_mts_arrow_fn { @@ -6396,11 +6391,13 @@ fn gen_type_parameters<'a>(node: TypeParamNode<'a>, context: &mut Context<'a>) - } } } - return trailing_commas; + + // Unambiguous. Use the preferred style. + context.config.type_parameters_trailing_commas } + // Type arguments. Trailing commas not allowed. + _ => TrailingCommas::Never, } - - TrailingCommas::Never } fn get_use_new_lines(node: &TypeParamNode, params: &[Node], context: &mut Context) -> bool { diff --git a/tests/specs/general/Jsx_TrailingCommas.txt b/tests/specs/general/Jsx_TrailingCommas.txt index d43ef5f3..703a0936 100644 --- a/tests/specs/general/Jsx_TrailingCommas.txt +++ b/tests/specs/general/Jsx_TrailingCommas.txt @@ -6,12 +6,12 @@ const Test2 = function() {}; const Test1 = () => false; const Test2 = function() {}; -== should keep trailing comma in expression type parameters in file parsed as jsx since there is no parsing ambiguity == +== should keep trailing comma in expression type parameters in file parsed as jsx since there is parsing ambiguity == const Test1 = () => false; -const Test2 = function() {}; -const Test3 =
; +const Test2 =
; -// not for declarations though +// not for function declarations or expressions though +const Test3 = function() {}; function test() { } @@ -27,10 +27,10 @@ const Test5 =

>() => {}; [expect] const Test1 = () => false; -const Test2 = function() {}; -const Test3 =

; +const Test2 =
; -// not for declarations though +// not for function declarations or expressions though +const Test3 = function() {}; function test() { } diff --git a/tests/specs/general/Jsx_TrailingCommas_Never.txt b/tests/specs/general/Jsx_TrailingCommas_Never.txt new file mode 100644 index 00000000..5d0dcaf5 --- /dev/null +++ b/tests/specs/general/Jsx_TrailingCommas_Never.txt @@ -0,0 +1,23 @@ +-- file.tsx -- +~~ trailingCommas: never ~~ +== should never trim trailing commas from single type parameters in expressions == +const test1: (t: T) => T = (t: T) => t; +export default (t: T) => t; +[].filter(() => false); +const test2 = function(t: T) { }; +const test3 = class { }; + +// an extends clause ought to be enough to resolve the ambiguity +const test4 = function(t: T) {}; +const test5 = class {}; + +[expect] +const test1: (t: T) => T = (t: T) => t; +export default (t: T) => t; +[].filter(() => false); +const test2 = function(t: T) {}; +const test3 = class {}; + +// an extends clause ought to be enough to resolve the ambiguity +const test4 = function(t: T) {}; +const test5 = class {};