Skip to content

Commit 75ce598

Browse files
committed
feat: keep trailing commas in TSX arrow and function expression type parameters in order to help prevent parsing ambiguities
Closes #274
1 parent 9e06a98 commit 75ce598

File tree

5 files changed

+46
-3
lines changed

5 files changed

+46
-3
lines changed

src/format_text.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ pub fn format_text(file_path: &Path, file_text: &str, config: &Configuration) ->
4545
let parsed_source_file = parse_swc_ast(file_path, file_text)?;
4646
Ok(dprint_core::formatting::format(|| {
4747
let print_items = parse(&SourceFileInfo {
48+
is_jsx: parsed_source_file.is_jsx,
4849
module: &parsed_source_file.module,
4950
info: &parsed_source_file.info,
5051
tokens: &parsed_source_file.tokens,
@@ -58,6 +59,7 @@ pub fn format_text(file_path: &Path, file_text: &str, config: &Configuration) ->
5859

5960
#[derive(Clone)]
6061
pub struct SourceFileInfo<'a> {
62+
pub is_jsx: bool,
6163
pub module: &'a swc_ecmascript::ast::Module,
6264
pub info: &'a dyn swc_ast_view::SourceFile,
6365
pub tokens: &'a [TokenAndSpan],

src/parsing/context.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use crate::configuration::*;
77
use crate::utils::Stack;
88

99
pub struct Context<'a> {
10+
pub is_jsx: bool,
1011
pub module: &'a Module<'a>,
1112
pub config: &'a Configuration,
1213
pub comments: CommentTracker<'a>,
@@ -27,12 +28,14 @@ pub struct Context<'a> {
2728

2829
impl<'a> Context<'a> {
2930
pub fn new(
30-
config: &'a Configuration,
31+
is_jsx: bool,
3132
tokens: &'a [TokenAndSpan],
3233
current_node: Node<'a>,
3334
module: &'a Module,
35+
config: &'a Configuration,
3436
) -> Context<'a> {
3537
Context {
38+
is_jsx,
3639
module,
3740
config,
3841
comments: CommentTracker::new(module, tokens),

src/parsing/parser.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,11 @@ pub fn parse(info: &SourceFileInfo<'_>, config: &Configuration) -> PrintItems {
2424
swc_ast_view::with_ast_view_for_module(source_file_info, |module| {
2525
let module_node = Node::Module(module);
2626
let mut context = Context::new(
27-
config,
27+
info.is_jsx,
2828
info.tokens,
2929
module_node,
3030
module,
31+
config,
3132
);
3233
let mut items = parse_node(module_node, &mut context);
3334
items.push_condition(if_true(
@@ -4566,6 +4567,15 @@ fn parse_type_parameters<'a>(node: TypeParamNode<'a>, context: &mut Context<'a>)
45664567
// trailing commas should be allowed in type parameters only—not arguments
45674568
if let Some(type_params) = node.parent().get_type_parameters() {
45684569
if type_params.lo() == node.lo() {
4570+
// Always use trailing commas for arrow function expressions in a JSX file
4571+
// if one exists as it may be used to assist with parsing ambiguity.
4572+
if context.is_jsx && (node.parent().kind() == NodeKind::ArrowExpr || node.parent().parent().unwrap().kind() == NodeKind::FnExpr) {
4573+
let comma_count = type_params.tokens_fast(context.module).iter().filter(|t| t.token == Token::Comma).count();
4574+
let has_trailing_comma = comma_count >= type_params.params.len();
4575+
if has_trailing_comma {
4576+
return TrailingCommas::Always;
4577+
}
4578+
}
45694579
return trailing_commas;
45704580
}
45714581
}

src/swc/parse_swc_ast.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use dprint_core::types::{ErrBox, Error};
66
use swc_ast_view::SourceFileTextInfo;
77

88
pub struct ParsedSourceFile {
9+
pub is_jsx: bool,
910
pub module: swc_ecmascript::ast::Module,
1011
pub info: SourceFileTextInfo,
1112
pub tokens: Vec<TokenAndSpan>,
@@ -34,11 +35,12 @@ pub fn parse_swc_ast(file_path: &Path, file_text: &str) -> Result<ParsedSourceFi
3435
fn parse_inner(file_path: &Path, file_text: &str) -> Result<ParsedSourceFile, ErrBox> {
3536
let string_input = StringInput::new(file_text, BytePos(0), BytePos(file_text.len() as u32));
3637
let source_file_info = SourceFileTextInfo::new(BytePos(0), file_text.to_string());
38+
let is_jsx = should_parse_as_jsx(file_path);
3739

3840
let comments: SingleThreadedComments = Default::default();
3941
let (module, tokens) = {
4042
let ts_config = swc_ecmascript::parser::TsConfig {
41-
tsx: should_parse_as_jsx(file_path),
43+
tsx: is_jsx,
4244
dynamic_import: true,
4345
decorators: true,
4446
import_assertions: true,
@@ -73,6 +75,7 @@ fn parse_inner(file_path: &Path, file_text: &str) -> Result<ParsedSourceFile, Er
7375
// }
7476

7577
return Ok(ParsedSourceFile {
78+
is_jsx,
7679
leading_comments,
7780
trailing_comments,
7881
module,
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
== should not keep trailing comma in expression type parameters in non-jsx file since there is no parsing ambiguity ==
2+
const Test1 = <T,>() => false;
3+
const Test2 = function<T,>() {};
4+
5+
[expect]
6+
const Test1 = <T>() => false;
7+
const Test2 = function<T>() {};
8+
9+
== should keep trailing comma in expression type parameters in file parsed as jsx since there is no parsing ambiguity ==
10+
const Test1 = <T,>() => false;
11+
const Test2 = function<T,>() {};
12+
const Test3 = <div></div>;
13+
14+
// not for declarations though
15+
function test<T,>() {
16+
}
17+
18+
[expect]
19+
const Test1 = <T,>() => false;
20+
const Test2 = function<T,>() {};
21+
const Test3 = <div></div>;
22+
23+
// not for declarations though
24+
function test<T>() {
25+
}

0 commit comments

Comments
 (0)