Skip to content

Commit

Permalink
feat(parser): parse decorators properly (#2603)
Browse files Browse the repository at this point in the history
closes #2562
  • Loading branch information
Boshen committed Mar 4, 2024
1 parent 7cc9013 commit e2d2ce3
Show file tree
Hide file tree
Showing 9 changed files with 50 additions and 107 deletions.
17 changes: 15 additions & 2 deletions crates/oxc_parser/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ bitflags! {
/// i.e. the [Return] in Statement[Yield, Await, Return]
const Return = 1<< 3;

/// If node was parsed as part of a decorator
const Decorator = 1 << 4;

/// Typescript should parse extends clause as conditional type instead of type constrains.
/// Used in infer clause
///
Expand All @@ -37,13 +40,13 @@ bitflags! {
///
/// type X<U, T> = T extends (infer U extends number ? U : T) ? U : T;
/// The "(infer U extends number ? U : T)" is conditional type.
const DisallowConditionalTypes = 1 << 4;
const DisallowConditionalTypes = 1 << 5;

/// A declaration file, or inside something with the `declare` modifier.
/// Declarations that don't define an implementation is "ambient":
/// * ambient variable declaration => `declare var $: any`
/// * ambient class declaration => `declare class C { foo(); } , etc..`
const Ambient = 1 << 5;
const Ambient = 1 << 6;
}
}

Expand Down Expand Up @@ -74,6 +77,11 @@ impl Context {
self.contains(Self::Return)
}

#[inline]
pub(crate) fn has_decorator(self) -> bool {
self.contains(Self::Decorator)
}

#[inline]
pub(crate) fn has_disallow_conditional_types(self) -> bool {
self.contains(Self::DisallowConditionalTypes)
Expand Down Expand Up @@ -128,6 +136,11 @@ impl Context {
self.and(Self::Return, include)
}

#[inline]
pub(crate) fn and_decorator(self, include: bool) -> Self {
self.and(Self::Decorator, include)
}

#[inline]
pub(crate) fn and_ambient(self, include: bool) -> Self {
self.and(Self::Ambient, include)
Expand Down
87 changes: 20 additions & 67 deletions crates/oxc_parser/src/js/expression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,22 @@ impl<'a> ParserImpl<'a> {
pub(crate) fn parse_expression(&mut self) -> Result<Expression<'a>> {
let span = self.start_span();

let has_decorator = self.ctx.has_decorator();
if has_decorator {
self.ctx = self.ctx.and_decorator(false);
}

let lhs = self.parse_assignment_expression_base()?;
if !self.at(Kind::Comma) {
return Ok(lhs);
}

let expr = self.parse_sequence_expression(span, lhs)?;

if has_decorator {
self.ctx = self.ctx.and_decorator(true);
}

Ok(expr)
}

Expand Down Expand Up @@ -191,9 +200,10 @@ impl<'a> ParserImpl<'a> {

fn parse_parenthesized_expression(&mut self, span: Span) -> Result<Expression<'a>> {
let has_in = self.ctx.has_in();
self.ctx = self.ctx.and_in(true);
let has_decorator = self.ctx.has_decorator();
self.ctx = self.ctx.and_in(true).and_decorator(false);
let list = SequenceExpressionList::parse(self)?;
self.ctx = self.ctx.and_in(has_in);
self.ctx = self.ctx.and_in(has_in).and_decorator(has_decorator);

let mut expressions = list.elements;
let paren_span = self.end_span(span);
Expand Down Expand Up @@ -518,20 +528,6 @@ impl<'a> ParserImpl<'a> {
.and_then(|lhs| self.parse_member_expression_rhs(span, lhs, in_optional_chain))
}

/// `DecoratorMemberExpression`[Yield, Await]:
/// [ `IdentifierReference`[?Yield, ?Await] ]
/// [ `DecoratorMemberExpression`[?Yield, ?Await] . `IdentifierName` ]
/// [ `DecoratorMemberExpression`[?Yield, ?Await] . `PrivateIdentifier` ]
fn parse_decorator_member_expression(&mut self) -> Result<Expression<'a>> {
let lhs_span = self.start_span();
let lhs = self.parse_identifier_reference()?;
let mut lhs = self.ast.identifier_reference_expression(lhs);
while self.at(Kind::Dot) {
lhs = self.parse_static_member_expression(lhs_span, lhs, false)?;
}
Ok(lhs)
}

/// Section 13.3 Super Call
fn parse_super(&mut self) -> Expression<'a> {
let span = self.start_span();
Expand Down Expand Up @@ -561,12 +557,17 @@ impl<'a> ParserImpl<'a> {
let mut lhs = lhs;
loop {
lhs = match self.cur_kind() {
Kind::LBrack => self.parse_computed_member_expression(lhs_span, lhs, false)?,
// computed member expression is not allowed in decorator
// class C { @dec ["1"]() { } }
// ^
Kind::LBrack if !self.ctx.has_decorator() => {
self.parse_computed_member_expression(lhs_span, lhs, false)?
}
Kind::Dot => self.parse_static_member_expression(lhs_span, lhs, false)?,
Kind::QuestionDot => {
*in_optional_chain = true;
match self.peek_kind() {
Kind::LBrack => {
Kind::LBrack if !self.ctx.has_decorator() => {
self.bump_any(); // bump `?.`
self.parse_computed_member_expression(lhs_span, lhs, true)?
}
Expand Down Expand Up @@ -734,49 +735,6 @@ impl<'a> ParserImpl<'a> {
Ok(lhs)
}

/// `DecoratorCallExpression`[Yield, Await] :
/// `DecoratorMemberExpression`[?Yield, ?Await] `Arguments`[?Yield, ?Await]
/// This is different from `CallExpression` in that it only has one level. `@a()()` is not valid Decorator.
fn parse_decorator_call_expression(
&mut self,
lhs_span: Span,
lhs: Expression<'a>, /* `DecoratorMemberExpression` */
) -> Result<Expression<'a>> {
let mut type_arguments = None;
if matches!(self.cur_kind(), Kind::LAngle | Kind::ShiftLeft) && self.ts_enabled() {
let result = self.try_parse(|p| {
let arguments = p.parse_ts_type_arguments()?;
if p.at(Kind::RAngle) {
// a<b>>c is not (a<b>)>c, but a<(b>>c)
return Err(p.unexpected());
}

// a<b>c is (a<b)>c
if !p.at(Kind::LParen)
&& !p.at(Kind::NoSubstitutionTemplate)
&& !p.at(Kind::TemplateHead)
&& p.cur_kind().is_at_expression()
&& !p.cur_token().is_on_new_line
{
return Err(p.unexpected());
}

type_arguments = arguments;

Ok(())
});
if result.is_err() {
return Ok(lhs);
}
}

if self.at(Kind::LParen) {
self.parse_call_arguments(lhs_span, lhs, false, type_arguments.take())
} else {
Ok(lhs)
}
}

fn parse_call_arguments(
&mut self,
lhs_span: Span,
Expand Down Expand Up @@ -1093,12 +1051,7 @@ impl<'a> ParserImpl<'a> {
pub(crate) fn parse_decorator(&mut self) -> Result<Decorator<'a>> {
let span = self.start_span();
self.bump_any(); // bump @
let expr = if self.cur_kind() == Kind::LParen {
self.parse_paren_expression()?
} else {
let lhs = self.parse_decorator_member_expression()?;
self.parse_decorator_call_expression(span, lhs)?
};
let expr = self.with_context(Context::Decorator, Self::parse_lhs_expression)?;
Ok(self.ast.decorator(self.end_span(span), expr))
}

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 : 11/11 (100.00%)
Positive Passed: 11/11 (100.00%)
AST Parsed : 12/12 (100.00%)
Positive Passed: 12/12 (100.00%)
2 changes: 2 additions & 0 deletions tasks/coverage/misc/pass/oxc-2562.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
@http.controller('hooks').middleware(HooksMiddleware)
export class HooksController {}
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 : 11/11 (100.00%)
Positive Passed: 11/11 (100.00%)
AST Parsed : 12/12 (100.00%)
Positive Passed: 12/12 (100.00%)
Negative Passed: 8/8 (100.00%)

× Unexpected token
Expand Down
4 changes: 2 additions & 2 deletions tasks/coverage/parser_test262.snap
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
parser_test262 Summary:
AST Parsed : 45265/45265 (100.00%)
Positive Passed: 45265/45265 (100.00%)
AST Parsed : 45289/45289 (100.00%)
Positive Passed: 45289/45289 (100.00%)
Negative Passed: 3925/3929 (99.90%)
Expect Syntax Error: "language/import/import-assertions/json-invalid.js"
Expect Syntax Error: "language/import/import-assertions/json-named-bindings.js"
Expand Down
33 changes: 5 additions & 28 deletions tasks/coverage/parser_typescript.snap
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
parser_typescript Summary:
AST Parsed : 5239/5243 (99.92%)
Positive Passed: 5232/5243 (99.79%)
Negative Passed: 1063/4879 (21.79%)
AST Parsed : 5240/5243 (99.94%)
Positive Passed: 5233/5243 (99.81%)
Negative Passed: 1061/4879 (21.75%)
Expect Syntax Error: "compiler/ClassDeclaration10.ts"
Expect Syntax Error: "compiler/ClassDeclaration11.ts"
Expect Syntax Error: "compiler/ClassDeclaration13.ts"
Expand Down Expand Up @@ -2196,6 +2196,8 @@ Expect Syntax Error: "conformance/decorators/class/constructor/decoratorOnClassC
Expect Syntax Error: "conformance/decorators/class/decoratorChecksFunctionBodies.ts"
Expect Syntax Error: "conformance/decorators/class/decoratorOnClass8.ts"
Expect Syntax Error: "conformance/decorators/class/method/decoratorOnClassMethod10.ts"
Expect Syntax Error: "conformance/decorators/class/method/decoratorOnClassMethod11.ts"
Expect Syntax Error: "conformance/decorators/class/method/decoratorOnClassMethod12.ts"
Expect Syntax Error: "conformance/decorators/class/method/decoratorOnClassMethod6.ts"
Expect Syntax Error: "conformance/decorators/class/method/decoratorOnClassMethod8.ts"
Expect Syntax Error: "conformance/decorators/class/method/decoratorOnClassMethodOverload1.ts"
Expand Down Expand Up @@ -3918,15 +3920,6 @@ Expect to Parse: "conformance/es6/moduleExportsSystem/topLevelVarHoistingCommonJ
· ────
69 │ var y = _;
╰────
Expect to Parse: "conformance/esDecorators/esDecorators-preservesThis.ts"

× Unexpected token
╭─[conformance/esDecorators/esDecorators-preservesThis.ts:27:14]
26 │ class C {
27 │ @super.decorate
· ─────
28 │ method1() { }
╰────
Expect to Parse: "conformance/externalModules/topLevelAwait.2.ts"

× Cannot use `await` as an identifier in an async context
Expand Down Expand Up @@ -12037,22 +12030,6 @@ Expect to Parse: "conformance/salsa/plainJSRedeclare3.ts"
7 │ }
╰────

× Unexpected token
╭─[conformance/decorators/class/method/decoratorOnClassMethod11.ts:7:10]
6 │
7 │ @this.decorator
· ────
8 │ method() { }
╰────

× Unexpected token
╭─[conformance/decorators/class/method/decoratorOnClassMethod12.ts:8:10]
7 │ class C extends S {
8 │ @super.decorator
· ─────
9 │ method() { }
╰────

× Expected a semicolon or an implicit semicolon after a statement, but found none
╭─[conformance/decorators/class/method/decoratorOnClassMethod17.ts:7:17]
6 │ class Foo {
Expand Down
4 changes: 2 additions & 2 deletions tasks/coverage/prettier_misc.snap
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
prettier_misc Summary:
AST Parsed : 11/11 (100.00%)
Positive Passed: 6/11 (54.55%)
AST Parsed : 12/12 (100.00%)
Positive Passed: 7/12 (58.33%)
Expect to Parse: "pass/oxc-1740.tsx"
Expect to Parse: "pass/oxc-2087.ts"
Expect to Parse: "pass/oxc-2394.ts"
Expand Down
2 changes: 0 additions & 2 deletions tasks/coverage/src/test262.rs
Original file line number Diff line number Diff line change
Expand Up @@ -170,8 +170,6 @@ impl Case for Test262Case {
// Regex parser is required. See https://github.com/oxc-project/oxc/issues/385#issuecomment-1755566240
"regexp-v-flag",
"regexp-unicode-property-escapes",
// Stage 3 `https://github.com/tc39/proposal-decorators`
"decorators",
]
.iter()
.any(|feature| self.meta.features.iter().any(|f| **f == **feature))
Expand Down

0 comments on commit e2d2ce3

Please sign in to comment.