Skip to content
Open
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
22 changes: 20 additions & 2 deletions src/js_parser/parse/parse_typescript.rs
Original file line number Diff line number Diff line change
Expand Up @@ -587,7 +587,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O
name: js_ast::StoreStr::new(b"" as &[u8]),
value: None,
};
// Assigned in both live arms below; the third arm returns.
// Assigned in every live arm below; the error arm returns.
let needs_symbol: bool;

// Parse the name
Expand All @@ -598,15 +598,33 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O
debug_assert!(!estr.is_utf16);
value.name = estr.data;
needs_symbol = js_lexer::is_identifier(value.name.slice());
p.lexer.next()?;
} else if p.lexer.token == T::TOpenBracket {
// TypeScript allows computed enum member names when the
// expression is a string literal or a substitution-free
// template literal: "enum E { ['a'] = 1, [`b`] = 2 }".
p.lexer.next()?;
if p.lexer.token != T::TStringLiteral
&& p.lexer.token != T::TNoSubstitutionTemplateLiteral
{
p.lexer.expect(T::TStringLiteral)?;
return Err(err!("SyntaxError"));
}
let estr = p.lexer.to_utf8_e_string()?;
debug_assert!(!estr.is_utf16);
value.name = estr.data;
needs_symbol = js_lexer::is_identifier(value.name.slice());
p.lexer.next()?;
p.lexer.expect(T::TCloseBracket)?;
} else if p.lexer.is_identifier_or_keyword() {
value.name = js_ast::StoreStr::new(p.lexer.identifier);
needs_symbol = true;
p.lexer.next()?;
} else {
p.lexer.expect(T::TIdentifier)?;
// error early, name is still `undefined`
return Err(err!("SyntaxError"));
}
p.lexer.next()?;

// Identifiers can be referenced by other values
if !opts.is_typescript_declare && needs_symbol {
Expand Down
31 changes: 30 additions & 1 deletion test/bundler/transpiler/transpiler.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -512,8 +512,37 @@ describe("Bun.Transpiler", () => {
it("malformed enums", () => {
const err = ts.expectParseError;

err("enum Foo { [2]: 'hi' }", 'Expected identifier but found "["');
err("enum Foo { [2]: 'hi' }", 'Expected string but found "2"');
err("enum [] { a }", 'Expected identifier but found "["');
// Only string literals and substitution-free template literals are
// valid computed enum member names.
err("enum Foo { ['a' + 'b'] = 1 }", 'Expected "]" but found "+"');
err("enum Foo { [`t${1}`] = 1 }", 'Expected string but found "`t${"');
err("enum Foo { [Symbol.iterator] = 1 }", 'Expected string but found "Symbol"');
});

it("enum members with computed string literal names", () => {
const exp = ts.expectPrinted_;

exp(
"enum E { ['with space'] = 1 }",
'var E;\n((E) => {\n E[E["with space"] = 1] = "with space";\n})(E ||= {});\n',
);
exp("enum E { [`tmpl`] = 2 }", 'var E;\n((E) => {\n E[E["tmpl"] = 2] = "tmpl";\n})(E ||= {});\n');
// A computed name that is a valid identifier can be referenced by
// later members, same as a string literal name.
exp(
"enum E { ['A'] = 5, B = A * 2 }",
'var E;\n((E) => {\n E[E["A"] = 5] = "A";\n E[E["B"] = 10] = "B";\n})(E ||= {});\n',
);
exp(
"enum E { ['x'], ['y z'] }",
'var E;\n((E) => {\n E[E["x"] = 0] = "x";\n E[E["y z"] = 1] = "y z";\n})(E ||= {});\n',
);
exp(
"const enum CE { ['c'] = 9 } console.log(CE.c)",
'var CE;\n((CE) => {\n CE[CE["c"] = 9] = "c";\n})(CE ||= {});\nconsole.log(9 /* c */);\n',
);
});

it("doesn't crash with functions assigned to enum values", () => {
Expand Down
Loading