Skip to content

Commit 69c9e7f

Browse files
committed
allow decorators to come after export (#104)
1 parent 7baefdb commit 69c9e7f

4 files changed

Lines changed: 77 additions & 6 deletions

File tree

CHANGELOG.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,23 @@
11
# Changelog
22

3+
## Unreleased
4+
5+
* Allow decorators after the `export` keyword ([#104](https://github.com/evanw/esbuild/issues/104))
6+
7+
Previously esbuild's decorator parser followed the original behavior of TypeScript's experimental decorators feature, which only allowed decorators to come before the `export` keyword. However, the upcoming JavaScript decorators feature also allows decorators to come after the `export` keyword. And with TypeScript 5.0, TypeScript now also allows experimental decorators to come after the `export` keyword too. So esbuild now allows this as well:
8+
9+
```js
10+
// This old syntax has always been permitted:
11+
@decorator export class Foo {}
12+
@decorator export default class Foo {}
13+
14+
// This new syntax is now permitted too:
15+
export @decorator class Foo {}
16+
export default @decorator class Foo {}
17+
```
18+
19+
In addition, esbuild's decorator parser has been rewritten to fix several subtle and likely unimportant edge cases with esbuild's parsing of exports and decorators in TypeScript (e.g. TypeScript apparently does automatic semicolon insertion after `interface` and `export interface` but not after `export default interface`).
20+
321
## 0.19.7
422

523
* Add support for bundling code that uses import attributes ([#3384](https://github.com/evanw/esbuild/issues/3384))

internal/js_parser/js_parser.go

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6735,7 +6735,7 @@ func (p *parser) parseStmt(opts parseStmtOpts) js_ast.Stmt {
67356735
p.lexer.Next()
67366736

67376737
switch p.lexer.Token {
6738-
case js_lexer.TClass, js_lexer.TConst, js_lexer.TFunction, js_lexer.TVar:
6738+
case js_lexer.TClass, js_lexer.TConst, js_lexer.TFunction, js_lexer.TVar, js_lexer.TAt:
67396739
opts.isExport = true
67406740
return p.parseStmt(opts)
67416741

@@ -6877,11 +6877,13 @@ func (p *parser) parseStmt(opts parseStmtOpts) js_ast.Stmt {
68776877

68786878
// "export default class {}"
68796879
// "export default class Foo {}"
6880+
// "export default @x class {}"
6881+
// "export default @x class Foo {}"
68806882
// "export default function() {}"
68816883
// "export default function foo() {}"
68826884
// "export default interface Foo {}"
68836885
// "export default interface + 1"
6884-
if p.lexer.Token == js_lexer.TFunction || p.lexer.Token == js_lexer.TClass ||
6886+
if p.lexer.Token == js_lexer.TFunction || p.lexer.Token == js_lexer.TClass || p.lexer.Token == js_lexer.TAt ||
68856887
(p.options.ts.Parse && p.lexer.IsContextualKeyword("interface")) {
68866888
stmt := p.parseStmt(parseStmtOpts{
68876889
deferredDecorators: opts.deferredDecorators,
@@ -6921,7 +6923,7 @@ func (p *parser) parseStmt(opts parseStmtOpts) js_ast.Stmt {
69216923
// "export default abstract class {}"
69226924
// "export default abstract class Foo {}"
69236925
if p.options.ts.Parse && isIdentifier && name == "abstract" && !p.lexer.HasNewlineBefore {
6924-
if _, ok := expr.Data.(*js_ast.EIdentifier); ok && (p.lexer.Token == js_lexer.TClass || opts.deferredDecorators != nil) {
6926+
if _, ok := expr.Data.(*js_ast.EIdentifier); ok && p.lexer.Token == js_lexer.TClass {
69256927
stmt := p.parseClassStmt(loc, parseStmtOpts{
69266928
deferredDecorators: opts.deferredDecorators,
69276929
isNameOptional: true,
@@ -7048,6 +7050,13 @@ func (p *parser) parseStmt(opts parseStmtOpts) js_ast.Stmt {
70487050
scopeIndex := len(p.scopesInOrder)
70497051
decorators := p.parseDecorators(p.currentScope, logger.Range{}, 0)
70507052

7053+
// "@x export @y class Foo {}"
7054+
if opts.deferredDecorators != nil {
7055+
p.log.AddError(&p.tracker, logger.Range{Loc: loc, Len: 1}, "Decorators are not valid here")
7056+
p.discardScopesUpTo(scopeIndex)
7057+
return p.parseStmt(opts)
7058+
}
7059+
70517060
// If this turns out to be a "declare class" statement, we need to undo the
70527061
// scopes that were potentially pushed while parsing the decorator arguments.
70537062
// That can look like any one of the following:
@@ -7825,7 +7834,7 @@ func (p *parser) parseStmt(opts parseStmtOpts) js_ast.Stmt {
78257834
}
78267835

78277836
case "abstract":
7828-
if !p.lexer.HasNewlineBefore && (p.lexer.Token == js_lexer.TClass || opts.deferredDecorators != nil) {
7837+
if !p.lexer.HasNewlineBefore && p.lexer.Token == js_lexer.TClass {
78297838
return p.parseClassStmt(loc, opts)
78307839
}
78317840

internal/js_parser/js_parser_test.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2050,6 +2050,20 @@ func TestDecorators(t *testing.T) {
20502050
// Check ASI for "abstract"
20512051
expectParseError(t, "@x abstract class Foo {}", "<stdin>: ERROR: Expected \";\" but found \"class\"\n")
20522052
expectParseError(t, "@x abstract\nclass Foo {}", "<stdin>: ERROR: Decorators are not valid here\n")
2053+
2054+
// Check decorator locations in relation to the "export" keyword
2055+
expectPrinted(t, "@x export class Foo {}", "@x\nexport class Foo {\n}\n")
2056+
expectPrinted(t, "export @x class Foo {}", "@x\nexport class Foo {\n}\n")
2057+
expectPrinted(t, "@x export default class {}", "@x\nexport default class {\n}\n")
2058+
expectPrinted(t, "export default @x class {}", "@x\nexport default class {\n}\n")
2059+
expectPrinted(t, "@x export default class Foo {}", "@x\nexport default class Foo {\n}\n")
2060+
expectPrinted(t, "export default @x class Foo {}", "@x\nexport default class Foo {\n}\n")
2061+
expectPrinted(t, "export default (@x class {})", "export default (@x class {\n});\n")
2062+
expectPrinted(t, "export default (@x class Foo {})", "export default (@x class Foo {\n});\n")
2063+
expectParseError(t, "export @x default class {}", "<stdin>: ERROR: Unexpected \"default\"\n")
2064+
expectParseError(t, "@x export @y class Foo {}", "<stdin>: ERROR: Decorators are not valid here\n")
2065+
expectParseError(t, "@x export default abstract", "<stdin>: ERROR: Decorators are not valid here\n")
2066+
expectParseError(t, "@x export @y default class {}", "<stdin>: ERROR: Decorators are not valid here\n<stdin>: ERROR: Unexpected \"default\"\n")
20532067
}
20542068

20552069
func TestGenerator(t *testing.T) {

internal/js_parser/ts_parser_test.go

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1874,7 +1874,7 @@ func TestTSExperimentalDecorator(t *testing.T) {
18741874
expectParseErrorExperimentalDecoratorTS(t, "@dec enum foo {}", "<stdin>: ERROR: Decorators are not valid here\n")
18751875
expectParseErrorExperimentalDecoratorTS(t, "@dec namespace foo {}", "<stdin>: ERROR: Decorators are not valid here\n")
18761876
expectParseErrorExperimentalDecoratorTS(t, "@dec function foo() {}", "<stdin>: ERROR: Decorators are not valid here\n")
1877-
expectParseErrorExperimentalDecoratorTS(t, "@dec abstract", "<stdin>: ERROR: Expected \"class\" but found end of file\n")
1877+
expectParseErrorExperimentalDecoratorTS(t, "@dec abstract", "<stdin>: ERROR: Decorators are not valid here\n")
18781878
expectParseErrorExperimentalDecoratorTS(t, "@dec declare: x", "<stdin>: ERROR: Unexpected \":\"\n")
18791879
expectParseErrorExperimentalDecoratorTS(t, "@dec declare enum foo {}", "<stdin>: ERROR: Decorators are not valid here\n")
18801880
expectParseErrorExperimentalDecoratorTS(t, "@dec declare namespace foo {}", "<stdin>: ERROR: Decorators are not valid here\n")
@@ -1883,7 +1883,7 @@ func TestTSExperimentalDecorator(t *testing.T) {
18831883
expectParseErrorExperimentalDecoratorTS(t, "@dec export enum foo {}", "<stdin>: ERROR: Decorators are not valid here\n")
18841884
expectParseErrorExperimentalDecoratorTS(t, "@dec export namespace foo {}", "<stdin>: ERROR: Decorators are not valid here\n")
18851885
expectParseErrorExperimentalDecoratorTS(t, "@dec export function foo() {}", "<stdin>: ERROR: Decorators are not valid here\n")
1886-
expectParseErrorExperimentalDecoratorTS(t, "@dec export default abstract", "<stdin>: ERROR: Expected \"class\" but found end of file\n")
1886+
expectParseErrorExperimentalDecoratorTS(t, "@dec export default abstract", "<stdin>: ERROR: Decorators are not valid here\n")
18871887
expectParseErrorExperimentalDecoratorTS(t, "@dec export declare enum foo {}", "<stdin>: ERROR: Decorators are not valid here\n")
18881888
expectParseErrorExperimentalDecoratorTS(t, "@dec export declare namespace foo {}", "<stdin>: ERROR: Decorators are not valid here\n")
18891889
expectParseErrorExperimentalDecoratorTS(t, "@dec export declare function foo()", "<stdin>: ERROR: Decorators are not valid here\n")
@@ -2013,6 +2013,22 @@ func TestTSExperimentalDecorator(t *testing.T) {
20132013
// Check ASI for "abstract"
20142014
expectPrintedExperimentalDecoratorTS(t, "@x abstract class Foo {}", "let Foo = class {\n};\nFoo = __decorateClass([\n x\n], Foo);\n")
20152015
expectParseErrorExperimentalDecoratorTS(t, "@x abstract\nclass Foo {}", "<stdin>: ERROR: Decorators are not valid here\n")
2016+
2017+
// Check decorator locations in relation to the "export" keyword
2018+
expectPrintedExperimentalDecoratorTS(t, "@x export class Foo {}", "export let Foo = class {\n};\nFoo = __decorateClass([\n x\n], Foo);\n")
2019+
expectPrintedExperimentalDecoratorTS(t, "export @x class Foo {}", "export let Foo = class {\n};\nFoo = __decorateClass([\n x\n], Foo);\n")
2020+
expectPrintedExperimentalDecoratorTS(t, "@x export default class {}",
2021+
"let stdin_default = class {\n};\nstdin_default = __decorateClass([\n x\n], stdin_default);\nexport {\n stdin_default as default\n};\n")
2022+
expectPrintedExperimentalDecoratorTS(t, "export default @x class {}",
2023+
"let stdin_default = class {\n};\nstdin_default = __decorateClass([\n x\n], stdin_default);\nexport {\n stdin_default as default\n};\n")
2024+
expectPrintedExperimentalDecoratorTS(t, "@x export default class Foo {}", "let Foo = class {\n};\nFoo = __decorateClass([\n x\n], Foo);\nexport {\n Foo as default\n};\n")
2025+
expectPrintedExperimentalDecoratorTS(t, "export default @x class Foo {}", "let Foo = class {\n};\nFoo = __decorateClass([\n x\n], Foo);\nexport {\n Foo as default\n};\n")
2026+
expectParseErrorExperimentalDecoratorTS(t, "export default (@x class {})", "<stdin>: ERROR: Experimental decorators cannot be used in expression position in TypeScript\n")
2027+
expectParseErrorExperimentalDecoratorTS(t, "export default (@x class Foo {})", "<stdin>: ERROR: Experimental decorators cannot be used in expression position in TypeScript\n")
2028+
expectParseErrorExperimentalDecoratorTS(t, "export @x default class {}", "<stdin>: ERROR: Unexpected \"default\"\n")
2029+
expectParseErrorExperimentalDecoratorTS(t, "@x export @y class Foo {}", "<stdin>: ERROR: Decorators are not valid here\n")
2030+
expectParseErrorExperimentalDecoratorTS(t, "@x export default abstract", "<stdin>: ERROR: Decorators are not valid here\n")
2031+
expectParseErrorExperimentalDecoratorTS(t, "@x export @y default class {}", "<stdin>: ERROR: Decorators are not valid here\n<stdin>: ERROR: Unexpected \"default\"\n")
20162032
}
20172033

20182034
func TestTSDecorators(t *testing.T) {
@@ -2074,6 +2090,20 @@ func TestTSDecorators(t *testing.T) {
20742090
// Check ASI for "abstract"
20752091
expectPrintedTS(t, "@x abstract class Foo {}", "@x\nclass Foo {\n}\n")
20762092
expectParseErrorTS(t, "@x abstract\nclass Foo {}", "<stdin>: ERROR: Decorators are not valid here\n")
2093+
2094+
// Check decorator locations in relation to the "export" keyword
2095+
expectPrintedTS(t, "@x export class Foo {}", "@x\nexport class Foo {\n}\n")
2096+
expectPrintedTS(t, "export @x class Foo {}", "@x\nexport class Foo {\n}\n")
2097+
expectPrintedTS(t, "@x export default class {}", "@x\nexport default class {\n}\n")
2098+
expectPrintedTS(t, "export default @x class {}", "@x\nexport default class {\n}\n")
2099+
expectPrintedTS(t, "@x export default class Foo {}", "@x\nexport default class Foo {\n}\n")
2100+
expectPrintedTS(t, "export default @x class Foo {}", "@x\nexport default class Foo {\n}\n")
2101+
expectPrintedTS(t, "export default (@x class {})", "export default (@x class {\n});\n")
2102+
expectPrintedTS(t, "export default (@x class Foo {})", "export default (@x class Foo {\n});\n")
2103+
expectParseErrorTS(t, "export @x default class {}", "<stdin>: ERROR: Unexpected \"default\"\n")
2104+
expectParseErrorTS(t, "@x export @y class Foo {}", "<stdin>: ERROR: Decorators are not valid here\n")
2105+
expectParseErrorTS(t, "@x export default abstract", "<stdin>: ERROR: Decorators are not valid here\n")
2106+
expectParseErrorTS(t, "@x export @y default class {}", "<stdin>: ERROR: Decorators are not valid here\n<stdin>: ERROR: Unexpected \"default\"\n")
20772107
}
20782108

20792109
func TestTSTry(t *testing.T) {

0 commit comments

Comments
 (0)