diff --git a/crates/oxc_parser/src/cursor.rs b/crates/oxc_parser/src/cursor.rs index 57e38900ad9eb..30246fee999c4 100644 --- a/crates/oxc_parser/src/cursor.rs +++ b/crates/oxc_parser/src/cursor.rs @@ -76,6 +76,7 @@ impl<'a> ParserImpl<'a> { /// Peek at kind #[inline] + #[expect(dead_code)] pub(crate) fn peek_at(&mut self, kind: Kind) -> bool { self.peek_token().kind() == kind } @@ -369,14 +370,14 @@ impl<'a> ParserImpl<'a> { &mut self, close: Kind, separator: Kind, - trailing_separator: bool, f: F, - ) -> Vec<'a, T> + ) -> (Vec<'a, T>, Option) where F: Fn(&mut Self) -> T, { let mut list = self.ast.vec(); let mut first = true; + let mut trailing_separator = None; loop { if self.cur_kind() == close || self.has_fatal_error() { break; @@ -384,17 +385,16 @@ impl<'a> ParserImpl<'a> { if first { first = false; } else { - if !trailing_separator && self.at(separator) && self.peek_at(close) { - break; - } + let separator_span = self.start_span(); self.expect(separator); if self.at(close) { + trailing_separator = Some(separator_span); break; } } list.push(f(self)); } - list + (list, trailing_separator) } pub(crate) fn parse_delimited_list_with_rest( diff --git a/crates/oxc_parser/src/js/expression.rs b/crates/oxc_parser/src/js/expression.rs index 81c47ae89dc04..783c9a52df62c 100644 --- a/crates/oxc_parser/src/js/expression.rs +++ b/crates/oxc_parser/src/js/expression.rs @@ -201,15 +201,19 @@ impl<'a> ParserImpl<'a> { fn parse_parenthesized_expression(&mut self, span: u32) -> Expression<'a> { self.expect(Kind::LParen); let expr_span = self.start_span(); - let mut expressions = self.context(Context::In, Context::Decorator, |p| { + let (mut expressions, comma_span) = self.context(Context::In, Context::Decorator, |p| { p.parse_delimited_list( Kind::RParen, Kind::Comma, - /* trailing_separator */ false, Self::parse_assignment_expression_or_higher, ) }); + if let Some(comma_span) = comma_span { + let error = diagnostics::expect_token(")", ",", self.end_span(comma_span)); + return self.fatal_error(error); + } + if expressions.is_empty() { self.expect(Kind::RParen); let error = diagnostics::empty_parenthesized_expression(self.end_span(span)); @@ -416,17 +420,10 @@ impl<'a> ParserImpl<'a> { pub(crate) fn parse_array_expression(&mut self) -> Expression<'a> { let span = self.start_span(); self.expect(Kind::LBrack); - let elements = self.context(Context::In, Context::empty(), |p| { - p.parse_delimited_list( - Kind::RBrack, - Kind::Comma, - /* trailing_separator */ false, - Self::parse_array_expression_element, - ) + let (elements, comma_span) = self.context(Context::In, Context::empty(), |p| { + p.parse_delimited_list(Kind::RBrack, Kind::Comma, Self::parse_array_expression_element) }); - if self.at(Kind::Comma) { - let comma_span = self.start_span(); - self.bump_any(); + if let Some(comma_span) = comma_span { self.state.trailing_commas.insert(span, self.end_span(comma_span)); } self.expect(Kind::RBrack); @@ -616,13 +613,8 @@ impl<'a> ParserImpl<'a> { let name = self.parse_identifier_name(); self.expect(Kind::LParen); - let arguments = self.context(Context::In, Context::Decorator, |p| { - p.parse_delimited_list( - Kind::RParen, - Kind::Comma, - /* trailing_separator */ true, - Self::parse_v8_intrinsic_argument, - ) + let (arguments, _) = self.context(Context::In, Context::Decorator, |p| { + p.parse_delimited_list(Kind::RParen, Kind::Comma, Self::parse_v8_intrinsic_argument) }); self.expect(Kind::RParen); self.ast.expression_v_8_intrinsic(self.end_span(span), name, arguments) @@ -871,13 +863,8 @@ impl<'a> ParserImpl<'a> { let arguments = if self.eat(Kind::LParen) { // ArgumentList[Yield, Await] : // AssignmentExpression[+In, ?Yield, ?Await] - let call_arguments = self.context(Context::In, Context::empty(), |p| { - p.parse_delimited_list( - Kind::RParen, - Kind::Comma, - /* trailing_separator */ true, - Self::parse_call_argument, - ) + let (call_arguments, _) = self.context(Context::In, Context::empty(), |p| { + p.parse_delimited_list(Kind::RParen, Kind::Comma, Self::parse_call_argument) }); self.expect(Kind::RParen); call_arguments @@ -966,13 +953,8 @@ impl<'a> ParserImpl<'a> { // ArgumentList[Yield, Await] : // AssignmentExpression[+In, ?Yield, ?Await] self.expect(Kind::LParen); - let call_arguments = self.context(Context::In, Context::Decorator, |p| { - p.parse_delimited_list( - Kind::RParen, - Kind::Comma, - /* trailing_separator */ true, - Self::parse_call_argument, - ) + let (call_arguments, _) = self.context(Context::In, Context::Decorator, |p| { + p.parse_delimited_list(Kind::RParen, Kind::Comma, Self::parse_call_argument) }); self.expect(Kind::RParen); self.ast.expression_call( diff --git a/crates/oxc_parser/src/js/module.rs b/crates/oxc_parser/src/js/module.rs index 01facfa073fb7..a85f21c6a6340 100644 --- a/crates/oxc_parser/src/js/module.rs +++ b/crates/oxc_parser/src/js/module.rs @@ -180,13 +180,10 @@ impl<'a> ParserImpl<'a> { import_kind: ImportOrExportKind, ) -> Vec<'a, ImportDeclarationSpecifier<'a>> { self.expect(Kind::LCurly); - let list = self.context(Context::empty(), self.ctx, |p| { - p.parse_delimited_list( - Kind::RCurly, - Kind::Comma, - /* trailing_separator */ true, - |parser| parser.parse_import_specifier(import_kind), - ) + let (list, _) = self.context(Context::empty(), self.ctx, |p| { + p.parse_delimited_list(Kind::RCurly, Kind::Comma, |parser| { + parser.parse_import_specifier(import_kind) + }) }); self.expect(Kind::RCurly); list @@ -203,13 +200,8 @@ impl<'a> ParserImpl<'a> { }; let span = self.start_span(); self.expect(Kind::LCurly); - let with_entries = self.context(Context::empty(), self.ctx, |p| { - p.parse_delimited_list( - Kind::RCurly, - Kind::Comma, - /*trailing_separator*/ true, - Self::parse_import_attribute, - ) + let (with_entries, _) = self.context(Context::empty(), self.ctx, |p| { + p.parse_delimited_list(Kind::RCurly, Kind::Comma, Self::parse_import_attribute) }); self.expect(Kind::RCurly); @@ -327,13 +319,10 @@ impl<'a> ParserImpl<'a> { fn parse_export_named_specifiers(&mut self, span: u32) -> Box<'a, ExportNamedDeclaration<'a>> { let export_kind = self.parse_import_or_export_kind(); self.expect(Kind::LCurly); - let mut specifiers = self.context(Context::empty(), self.ctx, |p| { - p.parse_delimited_list( - Kind::RCurly, - Kind::Comma, - /* trailing_separator */ true, - |parser| parser.parse_export_named_specifier(export_kind), - ) + let (mut specifiers, _) = self.context(Context::empty(), self.ctx, |p| { + p.parse_delimited_list(Kind::RCurly, Kind::Comma, |parser| { + parser.parse_export_named_specifier(export_kind) + }) }); self.expect(Kind::RCurly); let (source, with_clause) = if self.eat(Kind::From) && self.cur_kind().is_literal() { diff --git a/crates/oxc_parser/src/js/object.rs b/crates/oxc_parser/src/js/object.rs index af28660e171fa..9debbeea64985 100644 --- a/crates/oxc_parser/src/js/object.rs +++ b/crates/oxc_parser/src/js/object.rs @@ -13,11 +13,10 @@ impl<'a> ParserImpl<'a> { pub(crate) fn parse_object_expression(&mut self) -> Box<'a, ObjectExpression<'a>> { let span = self.start_span(); self.expect(Kind::LCurly); - let object_expression_properties = self.context(Context::In, Context::empty(), |p| { + let (object_expression_properties, _) = self.context(Context::In, Context::empty(), |p| { p.parse_delimited_list( Kind::RCurly, Kind::Comma, - /* trailing_separator */ false, Self::parse_object_expression_property, ) }); diff --git a/crates/oxc_parser/src/ts/statement.rs b/crates/oxc_parser/src/ts/statement.rs index 51cb2a15fe7f5..113f324a4aae9 100644 --- a/crates/oxc_parser/src/ts/statement.rs +++ b/crates/oxc_parser/src/ts/statement.rs @@ -38,12 +38,8 @@ impl<'a> ParserImpl<'a> { pub(crate) fn parse_ts_enum_body(&mut self) -> TSEnumBody<'a> { let span = self.start_span(); self.expect(Kind::LCurly); - let members = self.parse_delimited_list( - Kind::RCurly, - Kind::Comma, - /* trailing_separator */ true, - Self::parse_ts_enum_member, - ); + let (members, _) = + self.parse_delimited_list(Kind::RCurly, Kind::Comma, Self::parse_ts_enum_member); self.expect(Kind::RCurly); self.ast.ts_enum_body(self.end_span(span), members) } diff --git a/crates/oxc_parser/src/ts/types.rs b/crates/oxc_parser/src/ts/types.rs index cedab87d3a40f..25bd58d9b5ecf 100644 --- a/crates/oxc_parser/src/ts/types.rs +++ b/crates/oxc_parser/src/ts/types.rs @@ -149,12 +149,8 @@ impl<'a> ParserImpl<'a> { } let span = self.start_span(); self.expect(Kind::LAngle); - let params = self.parse_delimited_list( - Kind::RAngle, - Kind::Comma, - /* trailing_separator */ true, - Self::parse_ts_type_parameter, - ); + let (params, _) = + self.parse_delimited_list(Kind::RAngle, Kind::Comma, Self::parse_ts_type_parameter); self.expect(Kind::RAngle); Some(self.ast.alloc_ts_type_parameter_declaration(self.end_span(span), params)) } @@ -788,12 +784,8 @@ impl<'a> ParserImpl<'a> { if self.at(Kind::LAngle) { let span = self.start_span(); self.expect(Kind::LAngle); - let params = self.parse_delimited_list( - Kind::RAngle, - Kind::Comma, - /* trailing_separator */ true, - Self::parse_ts_type, - ); + let (params, _) = + self.parse_delimited_list(Kind::RAngle, Kind::Comma, Self::parse_ts_type); self.expect(Kind::RAngle); return Some( self.ast.alloc_ts_type_parameter_instantiation(self.end_span(span), params), @@ -808,12 +800,8 @@ impl<'a> ParserImpl<'a> { if !self.cur_token().is_on_new_line() && self.re_lex_l_angle() == Kind::LAngle { let span = self.start_span(); self.expect(Kind::LAngle); - let params = self.parse_delimited_list( - Kind::RAngle, - Kind::Comma, - /* trailing_separator */ true, - Self::parse_ts_type, - ); + let (params, _) = + self.parse_delimited_list(Kind::RAngle, Kind::Comma, Self::parse_ts_type); self.expect(Kind::RAngle); return Some( self.ast.alloc_ts_type_parameter_instantiation(self.end_span(span), params), @@ -833,12 +821,7 @@ impl<'a> ParserImpl<'a> { return None; } self.expect(Kind::LAngle); - let params = self.parse_delimited_list( - Kind::RAngle, - Kind::Comma, - /* trailing_separator */ true, - Self::parse_ts_type, - ); + let (params, _) = self.parse_delimited_list(Kind::RAngle, Kind::Comma, Self::parse_ts_type); // `a < b> = c`` is valid but `a < b >= c` is BinaryExpression if matches!(self.re_lex_right_angle(), Kind::GtEq) { return self.unexpected(); @@ -867,10 +850,9 @@ impl<'a> ParserImpl<'a> { fn parse_tuple_type(&mut self) -> TSType<'a> { let span = self.start_span(); self.expect(Kind::LBrack); - let elements = self.parse_delimited_list( + let (elements, _) = self.parse_delimited_list( Kind::RBrack, Kind::Comma, - /* trailing_separator */ true, Self::parse_tuple_element_name_or_tuple_element_type, ); self.expect(Kind::RBrack); @@ -1243,13 +1225,14 @@ impl<'a> ParserImpl<'a> { diagnostics::cannot_appear_on_an_index_signature, ); self.expect(Kind::LBrack); - let params = self.parse_delimited_list( + let (params, comma_span) = self.parse_delimited_list( Kind::RBrack, Kind::Comma, - /* trailing_separator */ - false, // An index signature cannot have a trailing comma. Self::parse_ts_index_signature_name, ); + if let Some(comma_span) = comma_span { + self.error(diagnostics::expect_token("]", ",", self.end_span(comma_span))); + } self.expect(Kind::RBrack); if params.len() != 1 { self.error(diagnostics::index_signature_one_parameter(self.end_span(span))); diff --git a/tasks/coverage/snapshots/parser_typescript.snap b/tasks/coverage/snapshots/parser_typescript.snap index df99b290139f6..049e0e96ce989 100644 --- a/tasks/coverage/snapshots/parser_typescript.snap +++ b/tasks/coverage/snapshots/parser_typescript.snap @@ -13042,6 +13042,24 @@ Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/salsa/private 3 │ }; ╰──── + × Expected `]` but found `,` + ╭─[typescript/tests/cases/compiler/indexSignatureWithTrailingComma.ts:6:17] + 5 │ interface B { + 6 │ [key: string,]: string; + · ┬ + · ╰── `]` expected + 7 │ } + ╰──── + + × Expected `]` but found `,` + ╭─[typescript/tests/cases/compiler/indexSignatureWithTrailingComma.ts:10:17] + 9 │ class C { + 10 │ [key: string,]: null; + · ┬ + · ╰── `]` expected + 11 │ } + ╰──── + × Unexpected token ╭─[typescript/tests/cases/compiler/indexSignatureWithoutTypeAnnotation1.ts:2:14] 1 │ class C {