Skip to content

Commit

Permalink
fix(parser): parse function type parameter name accessor (#3926)
Browse files Browse the repository at this point in the history
fixes #3910
  • Loading branch information
Boshen committed Jun 26, 2024
1 parent 63b98bd commit 275349a
Show file tree
Hide file tree
Showing 7 changed files with 180 additions and 50 deletions.
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%)

0 comments on commit 275349a

Please sign in to comment.