diff --git a/crates/oxc_parser/src/cursor.rs b/crates/oxc_parser/src/cursor.rs index 921f4d1902..ae57c91fc8 100644 --- a/crates/oxc_parser/src/cursor.rs +++ b/crates/oxc_parser/src/cursor.rs @@ -408,7 +408,7 @@ impl<'a, C: Config> ParserImpl<'a, C> { #[expect(clippy::inline_always)] #[inline(always)] - fn parse_normal_list_into( + pub(crate) fn parse_normal_list_into( &mut self, list: &mut ArenaVec<'a, T>, close: Kind, diff --git a/crates/oxc_parser/src/ts/types.rs b/crates/oxc_parser/src/ts/types.rs index 092a725e94..db424a861f 100644 --- a/crates/oxc_parser/src/ts/types.rs +++ b/crates/oxc_parser/src/ts/types.rs @@ -1,6 +1,6 @@ use oxc_allocator::{ArenaBox, ArenaVec}; use oxc_ast::ast::*; -use oxc_span::{GetSpan, SPAN}; +use oxc_span::{GetSpan, SPAN, Span}; use oxc_str::Ident; use oxc_syntax::operator::UnaryOperator; @@ -498,10 +498,12 @@ impl<'a, C: Config> ParserImpl<'a, C> { self.parse_type_query() } Kind::LCurly => { - if self.lookahead(Self::is_start_of_mapped_type) { - self.parse_mapped_type() + let opening_span = self.cur_token().span(); + self.bump_any(); + if self.is_start_of_mapped_type() { + self.parse_mapped_type(opening_span.start) } else { - self.parse_type_literal() + self.parse_type_literal(opening_span) } } Kind::LBrack => self.parse_tuple_type(), @@ -622,7 +624,15 @@ impl<'a, C: Config> ParserImpl<'a, C> { } fn is_start_of_mapped_type(&mut self) -> bool { - self.bump_any(); + // The opening brace is shared with type literals. Ordinary named members cannot + // begin a mapped type, so only speculate for the remaining forms. + if self.cur_kind() != Kind::Readonly && self.cur_kind().is_identifier_name() { + return false; + } + self.lookahead(Self::is_start_of_mapped_type_worker) + } + + fn is_start_of_mapped_type_worker(&mut self) -> bool { let kind = self.cur_kind(); if kind == Kind::Plus || kind == Kind::Minus { self.bump_any(); @@ -655,10 +665,7 @@ impl<'a, C: Config> ParserImpl<'a, C> { || self.is_start_of_type(!is_js_doc_parameter) } - fn parse_mapped_type(&mut self) -> TSType<'a> { - debug_assert!(self.at(Kind::LCurly)); - let start = self.cur_start(); - self.advance(Kind::LCurly); + fn parse_mapped_type(&mut self, start: u32) -> TSType<'a> { let mut readonly = None; if self.eat(Kind::Readonly) { readonly = Some(TSMappedTypeModifierOperator::True); @@ -713,11 +720,11 @@ impl<'a, C: Config> ParserImpl<'a, C> { ) } - fn parse_type_literal(&mut self) -> TSType<'a> { - let start = self.cur_start(); - let member_list = - self.parse_normal_list(Kind::LCurly, Kind::RCurly, Self::parse_ts_type_signature); - TSType::new_ts_type_literal(self.end_span(start), member_list, self) + fn parse_type_literal(&mut self, opening_span: Span) -> TSType<'a> { + let mut member_list = ArenaVec::new_in(self); + self.parse_normal_list_into(&mut member_list, Kind::RCurly, Self::parse_ts_type_signature); + self.expect_closing(Kind::RCurly, opening_span); + TSType::new_ts_type_literal(self.end_span(opening_span.start), member_list, self) } fn parse_type_query(&mut self) -> TSType<'a> {