Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 14 additions & 17 deletions src/generation/generate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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 {
Expand Down
14 changes: 7 additions & 7 deletions tests/specs/general/Jsx_TrailingCommas.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ const Test2 = function<T,>() {};
const Test1 = <T>() => false;
const Test2 = function<T>() {};

== 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 = <T,>() => false;
const Test2 = function<T,>() {};
const Test3 = <div></div>;
const Test2 = <div></div>;

// not for declarations though
// not for function declarations or expressions though
const Test3 = function<T,>() {};
function test<T,>() {
}

Expand All @@ -27,10 +27,10 @@ const Test5 = <P extends R<S, T>>() => {};

[expect]
const Test1 = <T,>() => false;
const Test2 = function<T,>() {};
const Test3 = <div></div>;
const Test2 = <div></div>;

// not for declarations though
// not for function declarations or expressions though
const Test3 = function<T>() {};
function test<T>() {
}

Expand Down
23 changes: 23 additions & 0 deletions tests/specs/general/Jsx_TrailingCommas_Never.txt
Original file line number Diff line number Diff line change
@@ -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: T) => t;
export default <T,>(t: T) => t;
[].filter(<T,>() => false);
const test2 = function<T,>(t: T) { };
const test3 = class<T,> { };

// an extends clause ought to be enough to resolve the ambiguity
const test4 = function<T extends unknown>(t: T) {};
const test5 = class<T extends unknown> {};

[expect]
const test1: <T>(t: T) => T = <T,>(t: T) => t;
export default <T,>(t: T) => t;
[].filter(<T,>() => false);
const test2 = function<T>(t: T) {};
const test3 = class<T> {};

// an extends clause ought to be enough to resolve the ambiguity
const test4 = function<T extends unknown>(t: T) {};
const test5 = class<T extends unknown> {};