Skip to content

Commit

Permalink
fix #3760: implement decorator metadata proposal
Browse files Browse the repository at this point in the history
  • Loading branch information
evanw committed May 15, 2024
1 parent bd0b13b commit 5e7cf25
Show file tree
Hide file tree
Showing 8 changed files with 659 additions and 323 deletions.
25 changes: 25 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,31 @@

## Unreleased

* Implement the decorator metadata proposal ([#3760](https://github.com/evanw/esbuild/issues/3760))

This release implements the [decorator metadata proposal](https://github.com/tc39/proposal-decorator-metadata), which is a sub-proposal of the [decorators proposal](https://github.com/tc39/proposal-decorators). Microsoft shipped the decorators proposal in [TypeScript 5.0](https://devblogs.microsoft.com/typescript/announcing-typescript-5-0/#decorators) and the decorator metadata proposal in [TypeScript 5.2](https://devblogs.microsoft.com/typescript/announcing-typescript-5-2/#decorator-metadata), so it's important that esbuild also supports both of these features. Here's a quick example:

```js
// Shim the "Symbol.metadata" symbol
Symbol.metadata ??= Symbol('Symbol.metadata')

const track = (_, context) => {
(context.metadata.names ||= []).push(context.name)
}

class Foo {
@track foo = 1
@track bar = 2
}

// Prints ["foo", "bar"]
console.log(Foo[Symbol.metadata].names)
```
**⚠️ WARNING ⚠️**
This proposal has been marked as "stage 3" which means "recommended for implementation". However, it's still a work in progress and isn't a part of JavaScript yet, so keep in mind that any code that uses JavaScript decorator metadata may need to be updated as the feature continues to evolve. If/when that happens, I will update esbuild's implementation to match the specification. I will not be supporting old versions of the specification.
* Fix bundled decorators in derived classes ([#3768](https://github.com/evanw/esbuild/issues/3768))
In certain cases, bundling code that uses decorators in a derived class with a class body that references its own class name could previously generate code that crashes at run-time due to an incorrect variable name. This problem has been fixed. Here is an example of code that was compiled incorrectly before this fix:
Expand Down
40 changes: 20 additions & 20 deletions internal/bundler_tests/snapshots/snapshots_lower.txt
Original file line number Diff line number Diff line change
Expand Up @@ -627,10 +627,10 @@ var _foo_dec, _init, _foo;
_foo_dec = [dec];
var _Foo = class _Foo {
constructor() {
__privateAdd(this, _foo, __runInitializers(_init, 6, this, _Foo)), __runInitializers(_init, 9, this);
__privateAdd(this, _foo, __runInitializers(_init, 8, this, _Foo)), __runInitializers(_init, 11, this);
}
};
_init = [, , ,];
_init = __decoratorStart(null);
_foo = new WeakMap();
__decorateElement(_init, 4, "foo", _foo_dec, _Foo, _foo);
var Foo = _Foo;
Expand All @@ -641,10 +641,10 @@ var _foo_dec, _init;
_foo_dec = [dec];
var _Foo = class _Foo {
constructor() {
__publicField(this, "foo", __runInitializers(_init, 6, this, _Foo)), __runInitializers(_init, 9, this);
__publicField(this, "foo", __runInitializers(_init, 8, this, _Foo)), __runInitializers(_init, 11, this);
}
};
_init = [, , ,];
_init = __decoratorStart(null);
__decorateElement(_init, 5, "foo", _foo_dec, _Foo);
var Foo = _Foo;

Expand All @@ -660,7 +660,7 @@ var _Foo = class _Foo {
return _Foo;
}
};
_init = [, , ,];
_init = __decoratorStart(null);
__decorateElement(_init, 1, "foo", _foo_dec, _Foo);
var Foo = _Foo;

Expand All @@ -670,10 +670,10 @@ var _foo_dec, _init, _foo;
_foo_dec = [dec];
var _Foo = class _Foo {
};
_init = [, , ,];
_init = __decoratorStart(null);
_foo = new WeakMap();
__decorateElement(_init, 12, "foo", _foo_dec, _Foo, _foo);
__privateAdd(_Foo, _foo, __runInitializers(_init, 6, _Foo, _Foo)), __runInitializers(_init, 9, _Foo);
__privateAdd(_Foo, _foo, __runInitializers(_init, 8, _Foo, _Foo)), __runInitializers(_init, 11, _Foo);
var Foo = _Foo;

---------- /out/base-static-field.js ----------
Expand All @@ -682,9 +682,9 @@ var _foo_dec, _init;
_foo_dec = [dec];
var _Foo = class _Foo {
};
_init = [, , ,];
_init = __decoratorStart(null);
__decorateElement(_init, 13, "foo", _foo_dec, _Foo);
__publicField(_Foo, "foo", __runInitializers(_init, 6, _Foo, _Foo)), __runInitializers(_init, 9, _Foo);
__publicField(_Foo, "foo", __runInitializers(_init, 8, _Foo, _Foo)), __runInitializers(_init, 11, _Foo);
var Foo = _Foo;

---------- /out/base-static-method.js ----------
Expand All @@ -696,7 +696,7 @@ var _Foo = class _Foo {
return _Foo;
}
};
_init = [, , ,];
_init = __decoratorStart(null);
__decorateElement(_init, 9, "foo", _foo_dec, _Foo);
__runInitializers(_init, 3, _Foo);
var Foo = _Foo;
Expand All @@ -707,10 +707,10 @@ var _foo_dec, _a, _init, _foo;
var _Foo = class _Foo extends (_a = Bar, _foo_dec = [dec], _a) {
constructor() {
super(...arguments);
__privateAdd(this, _foo, __runInitializers(_init, 6, this, _Foo)), __runInitializers(_init, 9, this);
__privateAdd(this, _foo, __runInitializers(_init, 8, this, _Foo)), __runInitializers(_init, 11, this);
}
};
_init = [, , ,];
_init = __decoratorStart(_a);
_foo = new WeakMap();
__decorateElement(_init, 4, "foo", _foo_dec, _Foo, _foo);
var Foo = _Foo;
Expand All @@ -721,10 +721,10 @@ var _foo_dec, _a, _init;
var _Foo = class _Foo extends (_a = Bar, _foo_dec = [dec], _a) {
constructor() {
super(...arguments);
__publicField(this, "foo", __runInitializers(_init, 6, this, _Foo)), __runInitializers(_init, 9, this);
__publicField(this, "foo", __runInitializers(_init, 8, this, _Foo)), __runInitializers(_init, 11, this);
}
};
_init = [, , ,];
_init = __decoratorStart(_a);
__decorateElement(_init, 5, "foo", _foo_dec, _Foo);
var Foo = _Foo;

Expand All @@ -740,7 +740,7 @@ var _Foo = class _Foo extends (_a = Bar, _foo_dec = [dec], _a) {
return _Foo;
}
};
_init = [, , ,];
_init = __decoratorStart(_a);
__decorateElement(_init, 1, "foo", _foo_dec, _Foo);
var Foo = _Foo;

Expand All @@ -749,20 +749,20 @@ var Foo = _Foo;
var _foo_dec, _a, _init, _foo;
var _Foo = class _Foo extends (_a = Bar, _foo_dec = [dec], _a) {
};
_init = [, , ,];
_init = __decoratorStart(_a);
_foo = new WeakMap();
__decorateElement(_init, 12, "foo", _foo_dec, _Foo, _foo);
__privateAdd(_Foo, _foo, __runInitializers(_init, 6, _Foo, _Foo)), __runInitializers(_init, 9, _Foo);
__privateAdd(_Foo, _foo, __runInitializers(_init, 8, _Foo, _Foo)), __runInitializers(_init, 11, _Foo);
var Foo = _Foo;

---------- /out/derived-static-field.js ----------
// derived-static-field.js
var _foo_dec, _a, _init;
var _Foo = class _Foo extends (_a = Bar, _foo_dec = [dec], _a) {
};
_init = [, , ,];
_init = __decoratorStart(_a);
__decorateElement(_init, 13, "foo", _foo_dec, _Foo);
__publicField(_Foo, "foo", __runInitializers(_init, 6, _Foo, _Foo)), __runInitializers(_init, 9, _Foo);
__publicField(_Foo, "foo", __runInitializers(_init, 8, _Foo, _Foo)), __runInitializers(_init, 11, _Foo);
var Foo = _Foo;

---------- /out/derived-static-method.js ----------
Expand All @@ -773,7 +773,7 @@ var _Foo = class _Foo extends (_a = Bar, _foo_dec = [dec], _a) {
return _Foo;
}
};
_init = [, , ,];
_init = __decoratorStart(_a);
__decorateElement(_init, 9, "foo", _foo_dec, _Foo);
__runInitializers(_init, 3, _Foo);
var Foo = _Foo;
Expand Down
31 changes: 19 additions & 12 deletions internal/js_parser/js_parser_lower_class.go
Original file line number Diff line number Diff line change
Expand Up @@ -631,6 +631,7 @@ type lowerClassContext struct {
defaultName ast.LocRef

ctor *js_ast.EFunction
extendsRef ast.Ref
parameterFields []js_ast.Stmt
instanceMembers []js_ast.Stmt
instancePrivateMethods []js_ast.Stmt
Expand Down Expand Up @@ -682,6 +683,7 @@ type lowerClassContext struct {
func (p *parser) lowerClass(stmt js_ast.Stmt, expr js_ast.Expr, result visitClassResult, nameToKeep string) ([]js_ast.Stmt, js_ast.Expr) {
ctx := lowerClassContext{
nameToKeep: nameToKeep,
extendsRef: ast.InvalidRef,
decoratorContextRef: ast.InvalidRef,
privateInstanceMethodRef: ast.InvalidRef,
privateStaticMethodRef: ast.InvalidRef,
Expand Down Expand Up @@ -857,7 +859,7 @@ func (ctx *lowerClassContext) lowerField(
}
args := []js_ast.Expr{
{Loc: loc, Data: &js_ast.EIdentifier{Ref: ctx.decoratorContextRef}},
{Loc: loc, Data: &js_ast.ENumber{Value: float64((3 + 2*initializerIndex) << 1)}},
{Loc: loc, Data: &js_ast.ENumber{Value: float64((4 + 2*initializerIndex) << 1)}},
value,
}
if _, ok := init.Data.(*js_ast.EUndefined); !ok {
Expand Down Expand Up @@ -929,7 +931,7 @@ func (ctx *lowerClassContext) lowerField(
}
memberExpr = js_ast.JoinWithComma(memberExpr, p.callRuntime(loc, "__runInitializers", []js_ast.Expr{
{Loc: loc, Data: &js_ast.EIdentifier{Ref: ctx.decoratorContextRef}},
{Loc: loc, Data: &js_ast.ENumber{Value: float64(((4 + 2*initializerIndex) << 1) | 1)}},
{Loc: loc, Data: &js_ast.ENumber{Value: float64(((5 + 2*initializerIndex) << 1) | 1)}},
value,
}))
p.recordUsage(ctx.decoratorContextRef)
Expand Down Expand Up @@ -1402,13 +1404,13 @@ func (ctx *lowerClassContext) hoistComputedProperties(p *parser, classLoweringIn
// __publicField(Foo, _a);
//
if ctx.computedPropertyChain.Data != nil && ctx.class.ExtendsOrNil.Data != nil {
ref := p.generateTempRef(tempRefNeedsDeclare, "")
ctx.extendsRef = p.generateTempRef(tempRefNeedsDeclare, "")
ctx.class.ExtendsOrNil = js_ast.JoinWithComma(js_ast.JoinWithComma(
js_ast.Assign(js_ast.Expr{Loc: ctx.class.ExtendsOrNil.Loc, Data: &js_ast.EIdentifier{Ref: ref}}, ctx.class.ExtendsOrNil),
js_ast.Assign(js_ast.Expr{Loc: ctx.class.ExtendsOrNil.Loc, Data: &js_ast.EIdentifier{Ref: ctx.extendsRef}}, ctx.class.ExtendsOrNil),
ctx.computedPropertyChain),
js_ast.Expr{Loc: ctx.class.ExtendsOrNil.Loc, Data: &js_ast.EIdentifier{Ref: ref}})
p.recordUsage(ref)
p.recordUsage(ref)
js_ast.Expr{Loc: ctx.class.ExtendsOrNil.Loc, Data: &js_ast.EIdentifier{Ref: ctx.extendsRef}})
p.recordUsage(ctx.extendsRef)
p.recordUsage(ctx.extendsRef)
ctx.computedPropertyChain = js_ast.Expr{}
}
return
Expand Down Expand Up @@ -2143,13 +2145,18 @@ func (ctx *lowerClassContext) finishAndGenerateCode(p *parser, result visitClass

// If there are JavaScript decorators, start by allocating a context object
if ctx.decoratorContextRef != ast.InvalidRef {
base := js_ast.Expr{Loc: ctx.classLoc, Data: js_ast.ENullShared}
if ctx.class.ExtendsOrNil.Data != nil {
if ctx.extendsRef == ast.InvalidRef {
ctx.extendsRef = p.generateTempRef(tempRefNeedsDeclare, "")
ctx.class.ExtendsOrNil = js_ast.Assign(js_ast.Expr{Loc: ctx.class.ExtendsOrNil.Loc, Data: &js_ast.EIdentifier{Ref: ctx.extendsRef}}, ctx.class.ExtendsOrNil)
p.recordUsage(ctx.extendsRef)
}
base.Data = &js_ast.EIdentifier{Ref: ctx.extendsRef}
}
suffixExprs = append(suffixExprs, js_ast.Assign(
js_ast.Expr{Loc: ctx.classLoc, Data: &js_ast.EIdentifier{Ref: ctx.decoratorContextRef}},
js_ast.Expr{Loc: ctx.classLoc, Data: &js_ast.EArray{IsSingleLine: true, Items: []js_ast.Expr{
{Loc: ctx.classLoc, Data: js_ast.EMissingShared}, // classExtraInitializers
{Loc: ctx.classLoc, Data: js_ast.EMissingShared}, // staticMethodExtraInitializers
{Loc: ctx.classLoc, Data: js_ast.EMissingShared}, // instanceMethodExtraInitializers
}}},
p.callRuntime(ctx.classLoc, "__decoratorStart", []js_ast.Expr{base}),
))
p.recordUsage(ctx.decoratorContextRef)
}
Expand Down
22 changes: 11 additions & 11 deletions internal/js_parser/js_parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2077,7 +2077,7 @@ func TestDecorators(t *testing.T) {
_Foo_decorators = [dec];
class Foo {
}
_init = [, , ,];
_init = __decoratorStart(null);
Foo = __decorateElement(_init, 0, "Foo", _Foo_decorators, Foo);
__runInitializers(_init, 1, Foo);
`)
Expand All @@ -2086,10 +2086,10 @@ __runInitializers(_init, 1, Foo);
_x_dec = [dec];
class Foo {
constructor() {
__publicField(this, "x", __runInitializers(_init, 6, this)), __runInitializers(_init, 9, this);
__publicField(this, "x", __runInitializers(_init, 8, this)), __runInitializers(_init, 11, this);
}
}
_init = [, , ,];
_init = __decoratorStart(null);
__decorateElement(_init, 5, "x", _x_dec, Foo);
`)
expectPrintedWithUnsupportedFeatures(t, compat.Decorators, "class Foo { @dec x() {} }",
Expand All @@ -2102,18 +2102,18 @@ class Foo {
x() {
}
}
_init = [, , ,];
_init = __decoratorStart(null);
__decorateElement(_init, 1, "x", _x_dec, Foo);
`)
expectPrintedWithUnsupportedFeatures(t, compat.Decorators, "class Foo { @dec accessor x }",
`var _x_dec, _init, _x;
_x_dec = [dec];
class Foo {
constructor() {
__privateAdd(this, _x, __runInitializers(_init, 6, this)), __runInitializers(_init, 9, this);
__privateAdd(this, _x, __runInitializers(_init, 8, this)), __runInitializers(_init, 11, this);
}
}
_init = [, , ,];
_init = __decoratorStart(null);
_x = new WeakMap();
__decorateElement(_init, 4, "x", _x_dec, Foo, _x);
`)
Expand All @@ -2122,9 +2122,9 @@ __decorateElement(_init, 4, "x", _x_dec, Foo, _x);
_x_dec = [dec];
class Foo {
}
_init = [, , ,];
_init = __decoratorStart(null);
__decorateElement(_init, 13, "x", _x_dec, Foo);
__publicField(Foo, "x", __runInitializers(_init, 6, Foo)), __runInitializers(_init, 9, Foo);
__publicField(Foo, "x", __runInitializers(_init, 8, Foo)), __runInitializers(_init, 11, Foo);
`)
expectPrintedWithUnsupportedFeatures(t, compat.Decorators, "class Foo { @dec static x() {} }",
`var _x_dec, _init;
Expand All @@ -2133,7 +2133,7 @@ class Foo {
static x() {
}
}
_init = [, , ,];
_init = __decoratorStart(null);
__decorateElement(_init, 9, "x", _x_dec, Foo);
__runInitializers(_init, 3, Foo);
`)
Expand All @@ -2142,10 +2142,10 @@ __runInitializers(_init, 3, Foo);
_x_dec = [dec];
class Foo {
}
_init = [, , ,];
_init = __decoratorStart(null);
_x = new WeakMap();
__decorateElement(_init, 12, "x", _x_dec, Foo, _x);
__privateAdd(Foo, _x, __runInitializers(_init, 6, Foo)), __runInitializers(_init, 9, Foo);
__privateAdd(Foo, _x, __runInitializers(_init, 8, Foo)), __runInitializers(_init, 11, Foo);
`)

// Check ASI for "abstract"
Expand Down
Loading

0 comments on commit 5e7cf25

Please sign in to comment.