diff --git a/crates/oxc_parser/src/modifiers.rs b/crates/oxc_parser/src/modifiers.rs index 24f9ec6c044c8..89f088b12b7e0 100644 --- a/crates/oxc_parser/src/modifiers.rs +++ b/crates/oxc_parser/src/modifiers.rs @@ -2,6 +2,7 @@ use bitflags::bitflags; use oxc_allocator::Vec; use oxc_ast::ast::TSAccessibility; +use oxc_diagnostics::Result; use oxc_span::Span; use crate::{lexer::Kind, ParserImpl}; @@ -207,58 +208,28 @@ impl<'a> ParserImpl<'a> { Kind::Export => { self.bump_any(); match self.cur_kind() { - Kind::Default => { - self.bump_any(); - self.can_follow_default() - } + Kind::Default => self.next_token_can_follow_default_keyword(), Kind::Type => { self.bump_any(); - self.can_follow_export() + self.can_follow_export_modifier() } - _ => self.can_follow_export(), + _ => self.can_follow_modifier(), } } - Kind::Default => { - self.bump_any(); - self.can_follow_default() - } + Kind::Default => self.next_token_can_follow_default_keyword(), Kind::Accessor | Kind::Static | Kind::Get | Kind::Set => { // These modifiers can cross line. self.bump_any(); - Self::can_follow_modifier(self.cur_kind()) + self.can_follow_modifier() } // Rest modifiers cannot cross line _ => { self.bump_any(); - Self::can_follow_modifier(self.cur_kind()) && !self.cur_token().is_on_new_line + self.can_follow_modifier() && !self.cur_token().is_on_new_line } } } - fn can_follow_default(&mut self) -> bool { - let at_declaration = - matches!(self.cur_kind(), Kind::Class | Kind::Function | Kind::Interface); - let at_abstract_declaration = self.at(Kind::Abstract) - && self.peek_at(Kind::Class) - && !self.peek_token().is_on_new_line; - let at_async_function = self.at(Kind::Async) - && self.peek_at(Kind::Function) - && !self.peek_token().is_on_new_line; - at_declaration | at_abstract_declaration | at_async_function - } - - fn can_follow_export(&mut self) -> bool { - // Note that the `export` in export assignment is not a modifier - // and are handled explicitly in the parser. - !matches!(self.cur_kind(), Kind::Star | Kind::As | Kind::LCurly) - && Self::can_follow_modifier(self.cur_kind()) - } - - fn can_follow_modifier(kind: Kind) -> bool { - kind.is_literal_property_name() - || matches!(kind, Kind::LCurly | Kind::LBrack | Kind::Star | Kind::Dot3) - } - fn modifier(kind: Kind, span: Span) -> Modifier { let modifier_kind = match kind { Kind::Abstract => ModifierKind::Abstract, @@ -280,4 +251,165 @@ impl<'a> ParserImpl<'a> { }; Modifier { span, kind: modifier_kind } } + + pub(crate) fn parse_modifiers( + &mut self, + _allow_decorators: bool, + permit_const_as_modifier: bool, + stop_on_start_of_class_static_block: bool, + ) -> Modifiers<'a> { + let mut has_seen_static_modifier = false; + // let mut has_leading_modifier = false; + // let mut has_trailing_decorator = false; + let mut modifiers = self.ast.new_vec(); + + // parse leading decorators + // if (allowDecorators && token() === SyntaxKind.AtToken) { + // while (decorator = tryParseDecorator()) { + // list = append(list, decorator); + // } + // } + + // parse leading modifiers + while let Some(modifier) = self.try_parse_modifier( + has_seen_static_modifier, + permit_const_as_modifier, + stop_on_start_of_class_static_block, + ) { + if modifier.kind == ModifierKind::Static { + has_seen_static_modifier = true; + } + modifiers.push(modifier); + } + + // parse trailing decorators, but only if we parsed any leading modifiers + // if (hasLeadingModifier && allowDecorators && token() === SyntaxKind.AtToken) { + // while (decorator = tryParseDecorator()) { + // list = append(list, decorator); + // hasTrailingDecorator = true; + // } + // } + + // parse trailing modifiers, but only if we parsed any trailing decorators + // if (hasTrailingDecorator) { + // while (modifier = tryParseModifier(hasSeenStaticModifier, permitConstAsModifier, stopOnStartOfClassStaticBlock)) { + // if (modifier.kind === SyntaxKind.StaticKeyword) hasSeenStaticModifier = true; + // list = append(list, modifier); + // } + // } + + Modifiers::new(modifiers) + } + + fn try_parse_modifier( + &mut self, + _has_seen_static_modifier: bool, + _permit_const_as_modifier: bool, + _stop_on_start_of_class_static_block: bool, + ) -> Option { + let span = self.start_span(); + let kind = self.cur_kind(); + // if (token() === SyntaxKind.ConstKeyword && permitConstAsModifier) { + // We need to ensure that any subsequent modifiers appear on the same line + // so that when 'const' is a standalone declaration, we don't issue an error. + // if (!tryParse(nextTokenIsOnSameLineAndCanFollowModifier)) { + // return undefined; + // } + // } + // else if (stopOnStartOfClassStaticBlock && token() === SyntaxKind.StaticKeyword && lookAhead(nextTokenIsOpenBrace)) { + // return undefined; + // } + // else if (hasSeenStaticModifier && token() === SyntaxKind.StaticKeyword) { + // return undefined; + // } + // else { + if !self.parse_any_contextual_modifier() { + return None; + } + // } + Some(Self::modifier(kind, self.end_span(span))) + } + + fn parse_any_contextual_modifier(&mut self) -> bool { + self.cur_kind().is_modifier_kind() + && self.try_parse(Self::next_token_can_follow_modifier).is_some() + } + + fn next_token_can_follow_modifier(&mut self) -> Result<()> { + let b = match self.cur_kind() { + Kind::Const => self.peek_at(Kind::Enum), + Kind::Export => { + self.bump_any(); + match self.cur_kind() { + Kind::Default => self.lookahead(Self::next_token_can_follow_default_keyword), + Kind::Type => self.lookahead(Self::next_token_can_follow_export_modifier), + _ => self.can_follow_export_modifier(), + } + } + Kind::Default => self.next_token_can_follow_default_keyword(), + Kind::Static | Kind::Get | Kind::Set => { + self.bump_any(); + self.can_follow_modifier() + } + _ => self.next_token_is_on_same_line_and_can_follow_modifier(), + }; + if b { + Ok(()) + } else { + Err(self.unexpected()) + } + } + + fn next_token_is_on_same_line_and_can_follow_modifier(&mut self) -> bool { + self.bump_any(); + if self.cur_token().is_on_new_line { + return false; + } + self.can_follow_modifier() + } + + fn next_token_can_follow_default_keyword(&mut self) -> bool { + self.bump_any(); + match self.cur_kind() { + Kind::Class | Kind::Function | Kind::Interface | Kind::At => true, + Kind::Abstract if self.lookahead(Self::next_token_is_class_keyword_on_same_line) => { + true + } + Kind::Async if self.lookahead(Self::next_token_is_function_keyword_on_same_line) => { + true + } + _ => false, + } + } + + fn next_token_can_follow_export_modifier(&mut self) -> bool { + self.bump_any(); + self.can_follow_export_modifier() + } + + fn can_follow_export_modifier(&mut self) -> bool { + let kind = self.cur_kind(); + kind == Kind::At + && kind != Kind::Star + && kind != Kind::As + && kind != Kind::LCurly + && self.can_follow_modifier() + } + + fn can_follow_modifier(&mut self) -> bool { + match self.cur_kind() { + Kind::LBrack | Kind::LCurly | Kind::Star | Kind::Dot3 => true, + kind => kind.is_literal_property_name(), + } + } + + fn next_token_is_class_keyword_on_same_line(&mut self) -> bool { + self.bump_any(); + self.cur_kind() == Kind::Class && !self.cur_token().is_on_new_line + } + + fn next_token_is_function_keyword_on_same_line(&mut self) -> bool { + self.bump_any(); + self.cur_kind() == Kind::Function && !self.cur_token().is_on_new_line + } } diff --git a/crates/oxc_parser/src/ts/types.rs b/crates/oxc_parser/src/ts/types.rs index 348e332d3e5a5..82dcbbda3078c 100644 --- a/crates/oxc_parser/src/ts/types.rs +++ b/crates/oxc_parser/src/ts/types.rs @@ -118,12 +118,8 @@ impl<'a> ParserImpl<'a> { fn skip_parameter_start(&mut self) -> bool { // Skip modifiers - loop { - if self.cur_kind().is_modifier_kind() && !self.peek_at(Kind::Comma) { - self.bump_any(); - } else { - break; - } + if self.cur_kind().is_modifier_kind() { + self.parse_modifiers(false, false, false); } if self.cur_kind().is_identifier() || self.at(Kind::This) { self.bump_any(); diff --git a/tasks/coverage/codegen_misc.snap b/tasks/coverage/codegen_misc.snap index 5bb3a4fe6706a..282900bb7b2d9 100644 --- a/tasks/coverage/codegen_misc.snap +++ b/tasks/coverage/codegen_misc.snap @@ -1,3 +1,3 @@ codegen_misc Summary: -AST Parsed : 18/18 (100.00%) -Positive Passed: 18/18 (100.00%) +AST Parsed : 19/19 (100.00%) +Positive Passed: 19/19 (100.00%) diff --git a/tasks/coverage/misc/pass/oxc-3910.ts b/tasks/coverage/misc/pass/oxc-3910.ts new file mode 100644 index 0000000000000..62a2637e24f77 --- /dev/null +++ b/tasks/coverage/misc/pass/oxc-3910.ts @@ -0,0 +1 @@ +class Foo { private _(__: (accessor: ServicesAccessor) => unknown): void { } } diff --git a/tasks/coverage/parser_misc.snap b/tasks/coverage/parser_misc.snap index 01dbdf0611a4b..a4e5acc9846a0 100644 --- a/tasks/coverage/parser_misc.snap +++ b/tasks/coverage/parser_misc.snap @@ -1,6 +1,6 @@ parser_misc Summary: -AST Parsed : 18/18 (100.00%) -Positive Passed: 18/18 (100.00%) +AST Parsed : 19/19 (100.00%) +Positive Passed: 19/19 (100.00%) Negative Passed: 10/10 (100.00%) × Unexpected token diff --git a/tasks/coverage/prettier_misc.snap b/tasks/coverage/prettier_misc.snap index 724f118b07e6f..d67b7be37d324 100644 --- a/tasks/coverage/prettier_misc.snap +++ b/tasks/coverage/prettier_misc.snap @@ -1,10 +1,11 @@ prettier_misc Summary: -AST Parsed : 18/18 (100.00%) -Positive Passed: 11/18 (61.11%) +AST Parsed : 19/19 (100.00%) +Positive Passed: 11/19 (57.89%) Expect to Parse: "pass/oxc-1740.tsx" Expect to Parse: "pass/oxc-2087.ts" Expect to Parse: "pass/oxc-2394.ts" Expect to Parse: "pass/oxc-2674.tsx" Expect to Parse: "pass/oxc-2723.jsx" +Expect to Parse: "pass/oxc-3910.ts" Expect to Parse: "pass/swc-1627.js" Expect to Parse: "pass/swc-8243.tsx" diff --git a/tasks/coverage/transformer_misc.snap b/tasks/coverage/transformer_misc.snap index f8fa5e4263900..8cee345da8140 100644 --- a/tasks/coverage/transformer_misc.snap +++ b/tasks/coverage/transformer_misc.snap @@ -1,3 +1,3 @@ transformer_misc Summary: -AST Parsed : 18/18 (100.00%) -Positive Passed: 18/18 (100.00%) +AST Parsed : 19/19 (100.00%) +Positive Passed: 19/19 (100.00%)