Skip to content

Commit

Permalink
fix(parser): fix enum member parsing (#4543)
Browse files Browse the repository at this point in the history
Closes #4449
  • Loading branch information
DonIsaac committed Jul 30, 2024
1 parent d384f60 commit d5c4b19
Show file tree
Hide file tree
Showing 19 changed files with 292 additions and 23 deletions.
3 changes: 2 additions & 1 deletion crates/oxc_ast/src/ast/ts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,9 @@ inherit_variants! {
pub enum TSEnumMemberName<'a> {
StaticIdentifier(Box<'a, IdentifierName<'a>>) = 64,
StaticStringLiteral(Box<'a, StringLiteral<'a>>) = 65,
StaticTemplateLiteral(Box<'a, TemplateLiteral<'a>>) = 66,
// Invalid Grammar `enum E { 1 }`
StaticNumericLiteral(Box<'a, NumericLiteral<'a>>) = 66,
StaticNumericLiteral(Box<'a, NumericLiteral<'a>>) = 67,
// Invalid Grammar `enum E { [computed] }`
// `Expression` variants added here by `inherit_variants!` macro
@inherit Expression
Expand Down
31 changes: 31 additions & 0 deletions crates/oxc_ast/src/generated/ast_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8116,6 +8116,37 @@ impl<'a> AstBuilder<'a> {
TSEnumMemberName::StaticStringLiteral(inner.into_in(self.allocator))
}

/// Build a [`TSEnumMemberName::StaticTemplateLiteral`]
///
/// This node contains a [`TemplateLiteral`] that will be stored in the memory arena.
///
/// ## Parameters
/// - span: The [`Span`] covering this node
/// - quasis
/// - expressions
#[inline]
pub fn ts_enum_member_name_template_literal(
self,
span: Span,
quasis: Vec<'a, TemplateElement<'a>>,
expressions: Vec<'a, Expression<'a>>,
) -> TSEnumMemberName<'a> {
TSEnumMemberName::StaticTemplateLiteral(self.alloc(self.template_literal(
span,
quasis,
expressions,
)))
}

/// Convert a [`TemplateLiteral`] into a [`TSEnumMemberName::StaticTemplateLiteral`]
#[inline]
pub fn ts_enum_member_name_from_template_literal<T>(self, inner: T) -> TSEnumMemberName<'a>
where
T: IntoIn<'a, Box<'a, TemplateLiteral<'a>>>,
{
TSEnumMemberName::StaticTemplateLiteral(inner.into_in(self.allocator))
}

/// Build a [`TSEnumMemberName::StaticNumericLiteral`]
///
/// This node contains a [`NumericLiteral`] that will be stored in the memory arena.
Expand Down
1 change: 1 addition & 0 deletions crates/oxc_ast/src/generated/span.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1278,6 +1278,7 @@ impl<'a> GetSpan for TSEnumMemberName<'a> {
match self {
Self::StaticIdentifier(it) => it.span(),
Self::StaticStringLiteral(it) => it.span(),
Self::StaticTemplateLiteral(it) => it.span(),
Self::StaticNumericLiteral(it) => it.span(),
Self::BooleanLiteral(it) => it.span(),
Self::NullLiteral(it) => it.span(),
Expand Down
1 change: 1 addition & 0 deletions crates/oxc_ast/src/generated/visit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3878,6 +3878,7 @@ pub mod walk {
match it {
TSEnumMemberName::StaticIdentifier(it) => visitor.visit_identifier_name(it),
TSEnumMemberName::StaticStringLiteral(it) => visitor.visit_string_literal(it),
TSEnumMemberName::StaticTemplateLiteral(it) => visitor.visit_template_literal(it),
TSEnumMemberName::StaticNumericLiteral(it) => visitor.visit_numeric_literal(it),
match_expression!(TSEnumMemberName) => visitor.visit_expression(it.to_expression()),
}
Expand Down
1 change: 1 addition & 0 deletions crates/oxc_ast/src/generated/visit_mut.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4102,6 +4102,7 @@ pub mod walk_mut {
match it {
TSEnumMemberName::StaticIdentifier(it) => visitor.visit_identifier_name(it),
TSEnumMemberName::StaticStringLiteral(it) => visitor.visit_string_literal(it),
TSEnumMemberName::StaticTemplateLiteral(it) => visitor.visit_template_literal(it),
TSEnumMemberName::StaticNumericLiteral(it) => visitor.visit_numeric_literal(it),
match_expression!(TSEnumMemberName) => visitor.visit_expression(it.to_expression_mut()),
}
Expand Down
1 change: 1 addition & 0 deletions crates/oxc_codegen/src/gen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3531,6 +3531,7 @@ impl<'a, const MINIFY: bool> Gen<MINIFY> for TSEnumMember<'a> {
match &self.id {
TSEnumMemberName::StaticIdentifier(decl) => decl.gen(p, ctx),
TSEnumMemberName::StaticStringLiteral(decl) => decl.gen(p, ctx),
TSEnumMemberName::StaticTemplateLiteral(decl) => decl.gen(p, ctx),
TSEnumMemberName::StaticNumericLiteral(decl) => decl.gen(p, ctx),
decl @ match_expression!(TSEnumMemberName) => {
p.print_str("[");
Expand Down
5 changes: 5 additions & 0 deletions crates/oxc_isolated_declarations/src/enum.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ enum ConstantValue {
}

impl<'a> IsolatedDeclarations<'a> {
/// # Panics
/// if the enum member is a template literal with substitutions.
pub fn transform_ts_enum_declaration(
&mut self,
decl: &TSEnumDeclaration<'a>,
Expand Down Expand Up @@ -45,6 +47,9 @@ impl<'a> IsolatedDeclarations<'a> {
let member_name = match &member.id {
TSEnumMemberName::StaticIdentifier(id) => &id.name,
TSEnumMemberName::StaticStringLiteral(str) => &str.value,
TSEnumMemberName::StaticTemplateLiteral(template) => {
&template.quasi().expect("Template enum members cannot have substitutions.")
}
#[allow(clippy::unnested_or_patterns)] // Clippy is wrong
TSEnumMemberName::StaticNumericLiteral(_)
| match_expression!(TSEnumMemberName) => {
Expand Down
13 changes: 13 additions & 0 deletions crates/oxc_parser/src/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -449,3 +449,16 @@ pub fn accessibility_modifier_on_private_property(modifier: &Modifier) -> OxcDia
ts_error("18010", "An accessibility modifier cannot be used with a private identifier.")
.with_label(modifier.span)
}

// ================================== TS ENUMS =================================

/// Computed property names are not allowed in enums.ts(1164)
#[cold]
pub fn computed_property_names_not_allowed_in_enums(span: Span) -> OxcDiagnostic {
ts_error("1164", "Computed property names are not allowed in enums.").with_label(span)
}
/// An enum member cannot have a numeric name.ts(2452)
#[cold]
pub fn enum_member_cannot_have_numeric_name(span: Span) -> OxcDiagnostic {
ts_error("2452", "An enum member cannot have a numeric name.").with_label(span)
}
2 changes: 1 addition & 1 deletion crates/oxc_parser/src/js/expression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -407,7 +407,7 @@ impl<'a> ParserImpl<'a> {
/// `TemplateLiteral`[Yield, Await, Tagged] :
/// `NoSubstitutionTemplate`
/// `SubstitutionTemplate`[?Yield, ?Await, ?Tagged]
fn parse_template_literal(&mut self, tagged: bool) -> Result<TemplateLiteral<'a>> {
pub(crate) fn parse_template_literal(&mut self, tagged: bool) -> Result<TemplateLiteral<'a>> {
let span = self.start_span();
let mut expressions = self.ast.vec();
let mut quasis = self.ast.vec();
Expand Down
31 changes: 29 additions & 2 deletions crates/oxc_parser/src/ts/statement.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use oxc_allocator::Box;
use oxc_ast::ast::*;
use oxc_diagnostics::Result;
use oxc_span::Span;
use oxc_span::{GetSpan, Span};

use crate::{
diagnostics,
Expand Down Expand Up @@ -59,21 +59,37 @@ impl<'a> ParserImpl<'a> {
None
};

Ok(TSEnumMember { span: self.end_span(span), id, initializer })
let span = self.end_span(span);
if initializer.is_some() && matches!(id, TSEnumMemberName::StaticTemplateLiteral(_)) {
self.error(diagnostics::invalid_assignment(span));
}

Ok(TSEnumMember { span, id, initializer })
}

fn parse_ts_enum_member_name(&mut self) -> Result<TSEnumMemberName<'a>> {
match self.cur_kind() {
Kind::LBrack => {
let node = self.parse_computed_property_name()?;
self.check_invalid_ts_enum_computed_property(&node);
Ok(self.ast.ts_enum_member_name_expression(node))
}
Kind::Str => {
let node = self.parse_literal_string()?;
Ok(self.ast.ts_enum_member_name_from_string_literal(node))
}
Kind::NoSubstitutionTemplate | Kind::TemplateHead => {
let node = self.parse_template_literal(false)?;
if !node.expressions.is_empty() {
self.error(diagnostics::computed_property_names_not_allowed_in_enums(
node.span(),
));
}
Ok(self.ast.ts_enum_member_name_from_template_literal(node))
}
kind if kind.is_number() => {
let node = self.parse_literal_number()?;
self.error(diagnostics::enum_member_cannot_have_numeric_name(node.span()));
Ok(self.ast.ts_enum_member_name_from_numeric_literal(node))
}
_ => {
Expand All @@ -82,6 +98,17 @@ impl<'a> ParserImpl<'a> {
}
}
}
fn check_invalid_ts_enum_computed_property(&mut self, property: &Expression<'a>) {
match property {
Expression::StringLiteral(_) => {}
Expression::TemplateLiteral(template) if template.expressions.is_empty() => {}
Expression::NumericLiteral(_) => {
self.error(diagnostics::enum_member_cannot_have_numeric_name(property.span()));
}
_ => self
.error(diagnostics::computed_property_names_not_allowed_in_enums(property.span())),
}
}

/** ------------------- Annotation ----------------- */

Expand Down
3 changes: 3 additions & 0 deletions crates/oxc_semantic/src/binder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,9 @@ impl<'a> Binder<'a> for TSEnumMember<'a> {
let name = match &self.id {
TSEnumMemberName::StaticIdentifier(id) => Cow::Borrowed(id.name.as_str()),
TSEnumMemberName::StaticStringLiteral(s) => Cow::Borrowed(s.value.as_str()),
TSEnumMemberName::StaticTemplateLiteral(s) => Cow::Borrowed(
s.quasi().expect("Template enum members must have no substitutions.").as_str(),
),
TSEnumMemberName::StaticNumericLiteral(n) => Cow::Owned(n.value.to_string()),
match_expression!(TSEnumMemberName) => panic!("TODO: implement"),
};
Expand Down
14 changes: 10 additions & 4 deletions crates/oxc_transformer/src/typescript/enum.rs
Original file line number Diff line number Diff line change
Expand Up @@ -203,11 +203,17 @@ impl<'a> TypeScriptEnum<'a> {
let mut prev_member_name: Option<Atom<'a>> = None;

for member in members {
let member_name = match &member.id {
let member_name: &Atom<'_> = match &member.id {
TSEnumMemberName::StaticIdentifier(id) => &id.name,
TSEnumMemberName::StaticStringLiteral(str) => &str.value,
#[allow(clippy::unnested_or_patterns)] // Clippy is wrong
TSEnumMemberName::StaticNumericLiteral(_) | match_expression!(TSEnumMemberName) => {
TSEnumMemberName::StaticStringLiteral(str)
| TSEnumMemberName::StringLiteral(str) => &str.value,
TSEnumMemberName::StaticTemplateLiteral(template)
| TSEnumMemberName::TemplateLiteral(template) => {
&template.quasi().expect("Template enum members cannot have substitutions.")
}
// parse error, but better than a panic
TSEnumMemberName::StaticNumericLiteral(n) => &Atom::from(n.raw),
match_expression!(TSEnumMemberName) => {
unreachable!()
}
};
Expand Down
3 changes: 3 additions & 0 deletions crates/oxc_traverse/src/walk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3695,6 +3695,9 @@ pub(crate) unsafe fn walk_ts_enum_member_name<'a, Tr: Traverse<'a>>(
TSEnumMemberName::StaticStringLiteral(node) => {
walk_string_literal(traverser, (&mut **node) as *mut _, ctx)
}
TSEnumMemberName::StaticTemplateLiteral(node) => {
walk_template_literal(traverser, (&mut **node) as *mut _, ctx)
}
TSEnumMemberName::StaticNumericLiteral(node) => {
walk_numeric_literal(traverser, (&mut **node) as *mut _, ctx)
}
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 : 24/24 (100.00%)
Positive Passed: 24/24 (100.00%)
AST Parsed : 25/25 (100.00%)
Positive Passed: 25/25 (100.00%)
7 changes: 7 additions & 0 deletions tasks/coverage/misc/fail/oxc-4449.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// should fail
enum A { [foo] } // Computed property names are not allowed in enums
enum B { [1] } // An enum member cannot have a numeric name.
enum C { 1 } // An enum member cannot have a numeric name.
enum D { [`test${foo}`] } // Computed property names are not allowed in enums.
enum E { `baz` = 2 } // Enum member expected.
enum F { ['baz' + 'baz'] } // Computed property names are not allowed in enums.
9 changes: 9 additions & 0 deletions tasks/coverage/misc/pass/oxc-4449.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// should work
enum A { ['baz'] } // ❌ currently fails
enum B { [`baz`] } // ❌ currently fails
enum C { ['baz'] = 2 } // ❌ currently fails
enum D { [`baz`] = 2 } // ❌ currently fails
enum E { 'baz' } // 👍 work fine
enum F { baz } // 👍 work fine
enum G { 'baz' = 2 } // 👍 work fine
enum H { baz = 2 } // 👍 work fine
53 changes: 50 additions & 3 deletions tasks/coverage/parser_misc.snap
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
parser_misc Summary:
AST Parsed : 24/24 (100.00%)
Positive Passed: 24/24 (100.00%)
Negative Passed: 13/13 (100.00%)
AST Parsed : 25/25 (100.00%)
Positive Passed: 25/25 (100.00%)
Negative Passed: 14/14 (100.00%)

× Unexpected token
╭─[fail/oxc-169.js:2:1]
Expand Down Expand Up @@ -173,6 +173,53 @@ Negative Passed: 13/13 (100.00%)
╰────
help: Try insert a semicolon here

× TS(1164): Computed property names are not allowed in enums.
╭─[fail/oxc-4449.ts:2:11]
1// should fail
2enum A { [foo] } // Computed property names are not allowed in enums
· ───
3enum B { [1] } // An enum member cannot have a numeric name.
╰────

× TS(2452): An enum member cannot have a numeric name.
╭─[fail/oxc-4449.ts:3:11]
2 │ enum A { [foo] } // Computed property names are not allowed in enums
3enum B { [1] } // An enum member cannot have a numeric name.
· ─
4enum C { 1 } // An enum member cannot have a numeric name.
╰────

× TS(2452): An enum member cannot have a numeric name.
╭─[fail/oxc-4449.ts:4:10]
3 │ enum B { [1] } // An enum member cannot have a numeric name.
4enum C { 1 } // An enum member cannot have a numeric name.
· ─
5enum D { [`test${foo}`] } // Computed property names are not allowed in enums.
╰────

× TS(1164): Computed property names are not allowed in enums.
╭─[fail/oxc-4449.ts:5:11]
4enum C { 1 } // An enum member cannot have a numeric name.
5enum D { [`test${foo}`] } // Computed property names are not allowed in enums.
· ────────────
6enum E { `baz` = 2 } // Enum member expected.
╰────

× Cannot assign to this expression
╭─[fail/oxc-4449.ts:6:10]
5enum D { [`test${foo}`] } // Computed property names are not allowed in enums.
6enum E { `baz` = 2 } // Enum member expected.
· ─────────
7enum F { ['baz' + 'baz'] } // Computed property names are not allowed in enums.
╰────

× TS(1164): Computed property names are not allowed in enums.
╭─[fail/oxc-4449.ts:7:11]
6enum E { `baz` = 2 } // Enum member expected.
7enum F { ['baz' + 'baz'] } // Computed property names are not allowed in enums.
· ─────────────
╰────

× The keyword 'let' is reserved
╭─[fail/oxc.js:1:1]
1let.a = 1;
Expand Down
Loading

0 comments on commit d5c4b19

Please sign in to comment.