Skip to content

Commit 5e7cf25

Browse files
committed
fix #3760: implement decorator metadata proposal
1 parent bd0b13b commit 5e7cf25

File tree

8 files changed

+659
-323
lines changed

8 files changed

+659
-323
lines changed

CHANGELOG.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,31 @@
22

33
## Unreleased
44

5+
* Implement the decorator metadata proposal ([#3760](https://github.com/evanw/esbuild/issues/3760))
6+
7+
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:
8+
9+
```js
10+
// Shim the "Symbol.metadata" symbol
11+
Symbol.metadata ??= Symbol('Symbol.metadata')
12+
13+
const track = (_, context) => {
14+
(context.metadata.names ||= []).push(context.name)
15+
}
16+
17+
class Foo {
18+
@track foo = 1
19+
@track bar = 2
20+
}
21+
22+
// Prints ["foo", "bar"]
23+
console.log(Foo[Symbol.metadata].names)
24+
```
25+
26+
**⚠️ WARNING ⚠️**
27+
28+
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.
29+
530
* Fix bundled decorators in derived classes ([#3768](https://github.com/evanw/esbuild/issues/3768))
631
732
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:

internal/bundler_tests/snapshots/snapshots_lower.txt

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -627,10 +627,10 @@ var _foo_dec, _init, _foo;
627627
_foo_dec = [dec];
628628
var _Foo = class _Foo {
629629
constructor() {
630-
__privateAdd(this, _foo, __runInitializers(_init, 6, this, _Foo)), __runInitializers(_init, 9, this);
630+
__privateAdd(this, _foo, __runInitializers(_init, 8, this, _Foo)), __runInitializers(_init, 11, this);
631631
}
632632
};
633-
_init = [, , ,];
633+
_init = __decoratorStart(null);
634634
_foo = new WeakMap();
635635
__decorateElement(_init, 4, "foo", _foo_dec, _Foo, _foo);
636636
var Foo = _Foo;
@@ -641,10 +641,10 @@ var _foo_dec, _init;
641641
_foo_dec = [dec];
642642
var _Foo = class _Foo {
643643
constructor() {
644-
__publicField(this, "foo", __runInitializers(_init, 6, this, _Foo)), __runInitializers(_init, 9, this);
644+
__publicField(this, "foo", __runInitializers(_init, 8, this, _Foo)), __runInitializers(_init, 11, this);
645645
}
646646
};
647-
_init = [, , ,];
647+
_init = __decoratorStart(null);
648648
__decorateElement(_init, 5, "foo", _foo_dec, _Foo);
649649
var Foo = _Foo;
650650

@@ -660,7 +660,7 @@ var _Foo = class _Foo {
660660
return _Foo;
661661
}
662662
};
663-
_init = [, , ,];
663+
_init = __decoratorStart(null);
664664
__decorateElement(_init, 1, "foo", _foo_dec, _Foo);
665665
var Foo = _Foo;
666666

@@ -670,10 +670,10 @@ var _foo_dec, _init, _foo;
670670
_foo_dec = [dec];
671671
var _Foo = class _Foo {
672672
};
673-
_init = [, , ,];
673+
_init = __decoratorStart(null);
674674
_foo = new WeakMap();
675675
__decorateElement(_init, 12, "foo", _foo_dec, _Foo, _foo);
676-
__privateAdd(_Foo, _foo, __runInitializers(_init, 6, _Foo, _Foo)), __runInitializers(_init, 9, _Foo);
676+
__privateAdd(_Foo, _foo, __runInitializers(_init, 8, _Foo, _Foo)), __runInitializers(_init, 11, _Foo);
677677
var Foo = _Foo;
678678

679679
---------- /out/base-static-field.js ----------
@@ -682,9 +682,9 @@ var _foo_dec, _init;
682682
_foo_dec = [dec];
683683
var _Foo = class _Foo {
684684
};
685-
_init = [, , ,];
685+
_init = __decoratorStart(null);
686686
__decorateElement(_init, 13, "foo", _foo_dec, _Foo);
687-
__publicField(_Foo, "foo", __runInitializers(_init, 6, _Foo, _Foo)), __runInitializers(_init, 9, _Foo);
687+
__publicField(_Foo, "foo", __runInitializers(_init, 8, _Foo, _Foo)), __runInitializers(_init, 11, _Foo);
688688
var Foo = _Foo;
689689

690690
---------- /out/base-static-method.js ----------
@@ -696,7 +696,7 @@ var _Foo = class _Foo {
696696
return _Foo;
697697
}
698698
};
699-
_init = [, , ,];
699+
_init = __decoratorStart(null);
700700
__decorateElement(_init, 9, "foo", _foo_dec, _Foo);
701701
__runInitializers(_init, 3, _Foo);
702702
var Foo = _Foo;
@@ -707,10 +707,10 @@ var _foo_dec, _a, _init, _foo;
707707
var _Foo = class _Foo extends (_a = Bar, _foo_dec = [dec], _a) {
708708
constructor() {
709709
super(...arguments);
710-
__privateAdd(this, _foo, __runInitializers(_init, 6, this, _Foo)), __runInitializers(_init, 9, this);
710+
__privateAdd(this, _foo, __runInitializers(_init, 8, this, _Foo)), __runInitializers(_init, 11, this);
711711
}
712712
};
713-
_init = [, , ,];
713+
_init = __decoratorStart(_a);
714714
_foo = new WeakMap();
715715
__decorateElement(_init, 4, "foo", _foo_dec, _Foo, _foo);
716716
var Foo = _Foo;
@@ -721,10 +721,10 @@ var _foo_dec, _a, _init;
721721
var _Foo = class _Foo extends (_a = Bar, _foo_dec = [dec], _a) {
722722
constructor() {
723723
super(...arguments);
724-
__publicField(this, "foo", __runInitializers(_init, 6, this, _Foo)), __runInitializers(_init, 9, this);
724+
__publicField(this, "foo", __runInitializers(_init, 8, this, _Foo)), __runInitializers(_init, 11, this);
725725
}
726726
};
727-
_init = [, , ,];
727+
_init = __decoratorStart(_a);
728728
__decorateElement(_init, 5, "foo", _foo_dec, _Foo);
729729
var Foo = _Foo;
730730

@@ -740,7 +740,7 @@ var _Foo = class _Foo extends (_a = Bar, _foo_dec = [dec], _a) {
740740
return _Foo;
741741
}
742742
};
743-
_init = [, , ,];
743+
_init = __decoratorStart(_a);
744744
__decorateElement(_init, 1, "foo", _foo_dec, _Foo);
745745
var Foo = _Foo;
746746

@@ -749,20 +749,20 @@ var Foo = _Foo;
749749
var _foo_dec, _a, _init, _foo;
750750
var _Foo = class _Foo extends (_a = Bar, _foo_dec = [dec], _a) {
751751
};
752-
_init = [, , ,];
752+
_init = __decoratorStart(_a);
753753
_foo = new WeakMap();
754754
__decorateElement(_init, 12, "foo", _foo_dec, _Foo, _foo);
755-
__privateAdd(_Foo, _foo, __runInitializers(_init, 6, _Foo, _Foo)), __runInitializers(_init, 9, _Foo);
755+
__privateAdd(_Foo, _foo, __runInitializers(_init, 8, _Foo, _Foo)), __runInitializers(_init, 11, _Foo);
756756
var Foo = _Foo;
757757

758758
---------- /out/derived-static-field.js ----------
759759
// derived-static-field.js
760760
var _foo_dec, _a, _init;
761761
var _Foo = class _Foo extends (_a = Bar, _foo_dec = [dec], _a) {
762762
};
763-
_init = [, , ,];
763+
_init = __decoratorStart(_a);
764764
__decorateElement(_init, 13, "foo", _foo_dec, _Foo);
765-
__publicField(_Foo, "foo", __runInitializers(_init, 6, _Foo, _Foo)), __runInitializers(_init, 9, _Foo);
765+
__publicField(_Foo, "foo", __runInitializers(_init, 8, _Foo, _Foo)), __runInitializers(_init, 11, _Foo);
766766
var Foo = _Foo;
767767

768768
---------- /out/derived-static-method.js ----------
@@ -773,7 +773,7 @@ var _Foo = class _Foo extends (_a = Bar, _foo_dec = [dec], _a) {
773773
return _Foo;
774774
}
775775
};
776-
_init = [, , ,];
776+
_init = __decoratorStart(_a);
777777
__decorateElement(_init, 9, "foo", _foo_dec, _Foo);
778778
__runInitializers(_init, 3, _Foo);
779779
var Foo = _Foo;

internal/js_parser/js_parser_lower_class.go

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -631,6 +631,7 @@ type lowerClassContext struct {
631631
defaultName ast.LocRef
632632

633633
ctor *js_ast.EFunction
634+
extendsRef ast.Ref
634635
parameterFields []js_ast.Stmt
635636
instanceMembers []js_ast.Stmt
636637
instancePrivateMethods []js_ast.Stmt
@@ -682,6 +683,7 @@ type lowerClassContext struct {
682683
func (p *parser) lowerClass(stmt js_ast.Stmt, expr js_ast.Expr, result visitClassResult, nameToKeep string) ([]js_ast.Stmt, js_ast.Expr) {
683684
ctx := lowerClassContext{
684685
nameToKeep: nameToKeep,
686+
extendsRef: ast.InvalidRef,
685687
decoratorContextRef: ast.InvalidRef,
686688
privateInstanceMethodRef: ast.InvalidRef,
687689
privateStaticMethodRef: ast.InvalidRef,
@@ -857,7 +859,7 @@ func (ctx *lowerClassContext) lowerField(
857859
}
858860
args := []js_ast.Expr{
859861
{Loc: loc, Data: &js_ast.EIdentifier{Ref: ctx.decoratorContextRef}},
860-
{Loc: loc, Data: &js_ast.ENumber{Value: float64((3 + 2*initializerIndex) << 1)}},
862+
{Loc: loc, Data: &js_ast.ENumber{Value: float64((4 + 2*initializerIndex) << 1)}},
861863
value,
862864
}
863865
if _, ok := init.Data.(*js_ast.EUndefined); !ok {
@@ -929,7 +931,7 @@ func (ctx *lowerClassContext) lowerField(
929931
}
930932
memberExpr = js_ast.JoinWithComma(memberExpr, p.callRuntime(loc, "__runInitializers", []js_ast.Expr{
931933
{Loc: loc, Data: &js_ast.EIdentifier{Ref: ctx.decoratorContextRef}},
932-
{Loc: loc, Data: &js_ast.ENumber{Value: float64(((4 + 2*initializerIndex) << 1) | 1)}},
934+
{Loc: loc, Data: &js_ast.ENumber{Value: float64(((5 + 2*initializerIndex) << 1) | 1)}},
933935
value,
934936
}))
935937
p.recordUsage(ctx.decoratorContextRef)
@@ -1402,13 +1404,13 @@ func (ctx *lowerClassContext) hoistComputedProperties(p *parser, classLoweringIn
14021404
// __publicField(Foo, _a);
14031405
//
14041406
if ctx.computedPropertyChain.Data != nil && ctx.class.ExtendsOrNil.Data != nil {
1405-
ref := p.generateTempRef(tempRefNeedsDeclare, "")
1407+
ctx.extendsRef = p.generateTempRef(tempRefNeedsDeclare, "")
14061408
ctx.class.ExtendsOrNil = js_ast.JoinWithComma(js_ast.JoinWithComma(
1407-
js_ast.Assign(js_ast.Expr{Loc: ctx.class.ExtendsOrNil.Loc, Data: &js_ast.EIdentifier{Ref: ref}}, ctx.class.ExtendsOrNil),
1409+
js_ast.Assign(js_ast.Expr{Loc: ctx.class.ExtendsOrNil.Loc, Data: &js_ast.EIdentifier{Ref: ctx.extendsRef}}, ctx.class.ExtendsOrNil),
14081410
ctx.computedPropertyChain),
1409-
js_ast.Expr{Loc: ctx.class.ExtendsOrNil.Loc, Data: &js_ast.EIdentifier{Ref: ref}})
1410-
p.recordUsage(ref)
1411-
p.recordUsage(ref)
1411+
js_ast.Expr{Loc: ctx.class.ExtendsOrNil.Loc, Data: &js_ast.EIdentifier{Ref: ctx.extendsRef}})
1412+
p.recordUsage(ctx.extendsRef)
1413+
p.recordUsage(ctx.extendsRef)
14121414
ctx.computedPropertyChain = js_ast.Expr{}
14131415
}
14141416
return
@@ -2143,13 +2145,18 @@ func (ctx *lowerClassContext) finishAndGenerateCode(p *parser, result visitClass
21432145

21442146
// If there are JavaScript decorators, start by allocating a context object
21452147
if ctx.decoratorContextRef != ast.InvalidRef {
2148+
base := js_ast.Expr{Loc: ctx.classLoc, Data: js_ast.ENullShared}
2149+
if ctx.class.ExtendsOrNil.Data != nil {
2150+
if ctx.extendsRef == ast.InvalidRef {
2151+
ctx.extendsRef = p.generateTempRef(tempRefNeedsDeclare, "")
2152+
ctx.class.ExtendsOrNil = js_ast.Assign(js_ast.Expr{Loc: ctx.class.ExtendsOrNil.Loc, Data: &js_ast.EIdentifier{Ref: ctx.extendsRef}}, ctx.class.ExtendsOrNil)
2153+
p.recordUsage(ctx.extendsRef)
2154+
}
2155+
base.Data = &js_ast.EIdentifier{Ref: ctx.extendsRef}
2156+
}
21462157
suffixExprs = append(suffixExprs, js_ast.Assign(
21472158
js_ast.Expr{Loc: ctx.classLoc, Data: &js_ast.EIdentifier{Ref: ctx.decoratorContextRef}},
2148-
js_ast.Expr{Loc: ctx.classLoc, Data: &js_ast.EArray{IsSingleLine: true, Items: []js_ast.Expr{
2149-
{Loc: ctx.classLoc, Data: js_ast.EMissingShared}, // classExtraInitializers
2150-
{Loc: ctx.classLoc, Data: js_ast.EMissingShared}, // staticMethodExtraInitializers
2151-
{Loc: ctx.classLoc, Data: js_ast.EMissingShared}, // instanceMethodExtraInitializers
2152-
}}},
2159+
p.callRuntime(ctx.classLoc, "__decoratorStart", []js_ast.Expr{base}),
21532160
))
21542161
p.recordUsage(ctx.decoratorContextRef)
21552162
}

internal/js_parser/js_parser_test.go

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2077,7 +2077,7 @@ func TestDecorators(t *testing.T) {
20772077
_Foo_decorators = [dec];
20782078
class Foo {
20792079
}
2080-
_init = [, , ,];
2080+
_init = __decoratorStart(null);
20812081
Foo = __decorateElement(_init, 0, "Foo", _Foo_decorators, Foo);
20822082
__runInitializers(_init, 1, Foo);
20832083
`)
@@ -2086,10 +2086,10 @@ __runInitializers(_init, 1, Foo);
20862086
_x_dec = [dec];
20872087
class Foo {
20882088
constructor() {
2089-
__publicField(this, "x", __runInitializers(_init, 6, this)), __runInitializers(_init, 9, this);
2089+
__publicField(this, "x", __runInitializers(_init, 8, this)), __runInitializers(_init, 11, this);
20902090
}
20912091
}
2092-
_init = [, , ,];
2092+
_init = __decoratorStart(null);
20932093
__decorateElement(_init, 5, "x", _x_dec, Foo);
20942094
`)
20952095
expectPrintedWithUnsupportedFeatures(t, compat.Decorators, "class Foo { @dec x() {} }",
@@ -2102,18 +2102,18 @@ class Foo {
21022102
x() {
21032103
}
21042104
}
2105-
_init = [, , ,];
2105+
_init = __decoratorStart(null);
21062106
__decorateElement(_init, 1, "x", _x_dec, Foo);
21072107
`)
21082108
expectPrintedWithUnsupportedFeatures(t, compat.Decorators, "class Foo { @dec accessor x }",
21092109
`var _x_dec, _init, _x;
21102110
_x_dec = [dec];
21112111
class Foo {
21122112
constructor() {
2113-
__privateAdd(this, _x, __runInitializers(_init, 6, this)), __runInitializers(_init, 9, this);
2113+
__privateAdd(this, _x, __runInitializers(_init, 8, this)), __runInitializers(_init, 11, this);
21142114
}
21152115
}
2116-
_init = [, , ,];
2116+
_init = __decoratorStart(null);
21172117
_x = new WeakMap();
21182118
__decorateElement(_init, 4, "x", _x_dec, Foo, _x);
21192119
`)
@@ -2122,9 +2122,9 @@ __decorateElement(_init, 4, "x", _x_dec, Foo, _x);
21222122
_x_dec = [dec];
21232123
class Foo {
21242124
}
2125-
_init = [, , ,];
2125+
_init = __decoratorStart(null);
21262126
__decorateElement(_init, 13, "x", _x_dec, Foo);
2127-
__publicField(Foo, "x", __runInitializers(_init, 6, Foo)), __runInitializers(_init, 9, Foo);
2127+
__publicField(Foo, "x", __runInitializers(_init, 8, Foo)), __runInitializers(_init, 11, Foo);
21282128
`)
21292129
expectPrintedWithUnsupportedFeatures(t, compat.Decorators, "class Foo { @dec static x() {} }",
21302130
`var _x_dec, _init;
@@ -2133,7 +2133,7 @@ class Foo {
21332133
static x() {
21342134
}
21352135
}
2136-
_init = [, , ,];
2136+
_init = __decoratorStart(null);
21372137
__decorateElement(_init, 9, "x", _x_dec, Foo);
21382138
__runInitializers(_init, 3, Foo);
21392139
`)
@@ -2142,10 +2142,10 @@ __runInitializers(_init, 3, Foo);
21422142
_x_dec = [dec];
21432143
class Foo {
21442144
}
2145-
_init = [, , ,];
2145+
_init = __decoratorStart(null);
21462146
_x = new WeakMap();
21472147
__decorateElement(_init, 12, "x", _x_dec, Foo, _x);
2148-
__privateAdd(Foo, _x, __runInitializers(_init, 6, Foo)), __runInitializers(_init, 9, Foo);
2148+
__privateAdd(Foo, _x, __runInitializers(_init, 8, Foo)), __runInitializers(_init, 11, Foo);
21492149
`)
21502150

21512151
// Check ASI for "abstract"

0 commit comments

Comments
 (0)