Skip to content
Merged
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
204 changes: 168 additions & 36 deletions crates/oxc_parser/src/modifiers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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,
Expand All @@ -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<Modifier> {
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
}
}
8 changes: 2 additions & 6 deletions crates/oxc_parser/src/ts/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
4 changes: 2 additions & 2 deletions tasks/coverage/codegen_misc.snap
Original file line number Diff line number Diff line change
@@ -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%)
1 change: 1 addition & 0 deletions tasks/coverage/misc/pass/oxc-3910.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
class Foo { private _(__: (accessor: ServicesAccessor) => unknown): void { } }
4 changes: 2 additions & 2 deletions tasks/coverage/parser_misc.snap
Original file line number Diff line number Diff line change
@@ -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
Expand Down
5 changes: 3 additions & 2 deletions tasks/coverage/prettier_misc.snap
Original file line number Diff line number Diff line change
@@ -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"
4 changes: 2 additions & 2 deletions tasks/coverage/transformer_misc.snap
Original file line number Diff line number Diff line change
@@ -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%)