feat(es/compat): Implement decorator metadata proposal (#8097)

**Description:**

This PR implements the [decorator
metadata](https://github.com/tc39/proposal-decorator-metadata) proposal,
that is now at Stage 3.

As the decorator metadata proposal is a small extension of the decorator
proposal and is not possible to compile the metadata without transpiling
decorators, I opted to implement it into the existing decorator
transformer (and helper)

**Related issue:**

 - Closes #7957
This commit is contained in:
Alessandro Chitolina 2023-10-16 22:38:53 +02:00 committed by GitHub
parent 7fe9c76c37
commit 9c029ef095
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 226 additions and 89 deletions

View File

@ -35,6 +35,7 @@ function applyDecs2203RFactory() {
kind,
isStatic,
isPrivate,
metadata,
value
) {
var kindStr;
@ -61,6 +62,7 @@ function applyDecs2203RFactory() {
name: isPrivate ? "#" + name : name,
static: isStatic,
private: isPrivate,
metadata: metadata,
};
var decoratorFinishedRef = { v: false };
@ -168,7 +170,8 @@ function applyDecs2203RFactory() {
kind,
isStatic,
isPrivate,
initializers
initializers,
metadata
) {
var decs = decInfo[0];
@ -221,6 +224,7 @@ function applyDecs2203RFactory() {
kind,
isStatic,
isPrivate,
metadata,
value
);
@ -251,6 +255,7 @@ function applyDecs2203RFactory() {
kind,
isStatic,
isPrivate,
metadata,
value
);
@ -345,7 +350,7 @@ function applyDecs2203RFactory() {
}
}
function applyMemberDecs(Class, decInfos) {
function applyMemberDecs(Class, decInfos, metadata) {
var ret = [];
var protoInitializers;
var staticInitializers;
@ -415,7 +420,8 @@ function applyDecs2203RFactory() {
kind,
isStatic,
isPrivate,
initializers
initializers,
metadata
);
}
@ -435,7 +441,7 @@ function applyDecs2203RFactory() {
}
}
function applyClassDecs(targetClass, classDecs) {
function applyClassDecs(targetClass, classDecs, metadata) {
if (classDecs.length > 0) {
var initializers = [];
var newClass = targetClass;
@ -452,6 +458,7 @@ function applyDecs2203RFactory() {
initializers,
decoratorFinishedRef
),
metadata,
});
} finally {
decoratorFinishedRef.v = true;
@ -464,7 +471,7 @@ function applyDecs2203RFactory() {
}
return [
newClass,
defineMetadata(newClass, metadata),
function () {
for (var i = 0; i < initializers.length; i++) {
initializers[i].call(newClass);
@ -476,6 +483,14 @@ function applyDecs2203RFactory() {
// so we don't have to return an empty array here.
}
function defineMetadata(Class, metadata) {
return Object.defineProperty(
Class,
Symbol.metadata || Symbol.for("Symbol.metadata"),
{ configurable: true, enumerable: true, value: metadata }
);
}
/**
Basic usage:
@ -622,21 +637,31 @@ function applyDecs2203RFactory() {
initializeClass(Class);
*/
return function applyDecs2203R(targetClass, memberDecs, classDecs) {
return function applyDecs2203R(targetClass, memberDecs, classDecs, parentClass) {
if (parentClass !== void 0) {
var parentMetadata =
parentClass[Symbol.metadata || Symbol.for("Symbol.metadata")];
}
var metadata = Object.create(
parentMetadata === void 0 ? null : parentMetadata
);
var e = applyMemberDecs(targetClass, memberDecs, metadata);
if (!classDecs.length) defineMetadata(targetClass, metadata);
return {
e: applyMemberDecs(targetClass, memberDecs),
e: e,
// Lazily apply class decorations so that member init locals can be properly bound.
get c() {
return applyClassDecs(targetClass, classDecs);
return applyClassDecs(targetClass, classDecs, metadata);
},
};
};
}
function _apply_decs_2203_r(targetClass, memberDecs, classDecs) {
function _apply_decs_2203_r(targetClass, memberDecs, classDecs, parentClass) {
return (_apply_decs_2203_r = applyDecs2203RFactory())(
targetClass,
memberDecs,
classDecs
classDecs,
parentClass
);
}

View File

@ -9,8 +9,9 @@ use swc_common::{util::take::Take, Spanned, SyntaxContext, DUMMY_SP};
use swc_ecma_ast::*;
use swc_ecma_transforms_base::{helper, helper_expr};
use swc_ecma_utils::{
constructor::inject_after_super, default_constructor, prepend_stmt, private_ident,
prop_name_to_expr_value, quote_ident, replace_ident, ExprFactory, IdentExt, IdentRenamer,
alias_ident_for, constructor::inject_after_super, default_constructor, prepend_stmt,
private_ident, prop_name_to_expr_value, quote_ident, replace_ident, ExprFactory, IdentExt,
IdentRenamer,
};
use swc_ecma_visit::{as_folder, noop_visit_mut_type, Fold, VisitMut, VisitMutWith};
@ -53,6 +54,8 @@ struct ClassState {
class_lhs: Vec<Option<Pat>>,
class_decorators: Vec<Option<ExprOrSpread>>,
super_class: Option<Ident>,
}
impl Decorator202203 {
@ -154,6 +157,10 @@ impl Decorator202203 {
.as_arg(),
);
if let Some(super_class) = self.state.super_class.as_ref() {
combined_args.push(super_class.clone().as_arg());
}
let e_pat = if e_lhs.is_empty() {
None
} else {
@ -323,6 +330,27 @@ impl Decorator202203 {
unreachable!()
}
fn handle_super_class(&mut self, class: &mut Class) {
if let Some(super_class) = class.super_class.take() {
let id = alias_ident_for(&super_class, "_super");
self.extra_vars.push(VarDeclarator {
span: DUMMY_SP,
name: Pat::Ident(id.clone().into()),
init: None,
definite: false,
});
class.super_class = Some(Box::new(Expr::Assign(AssignExpr {
span: DUMMY_SP,
op: AssignOp::Assign,
left: PatOrExpr::Pat(Box::new(Pat::Ident(id.clone().into()))),
right: super_class,
})));
self.state.super_class = Some(id);
}
}
fn handle_class_expr(&mut self, class: &mut Class, ident: Option<&Ident>) -> Ident {
debug_assert!(
!class.decorators.is_empty(),
@ -361,6 +389,7 @@ impl Decorator202203 {
let decorators = self.preserve_side_effect_of_decorators(class.decorators.take());
self.state.class_decorators.extend(decorators);
self.handle_super_class(class);
{
let call_stmt = CallExpr {
@ -417,6 +446,7 @@ impl Decorator202203 {
self.state.class_lhs.push(Some(init_class.clone().into()));
self.state.class_decorators.extend(decorators);
self.handle_super_class(&mut c.class);
let mut body = c.class.body.take();

View File

@ -1,5 +1,5 @@
var _A, __, __1, __2, _C, __3, __11, __21, _class, __4, __12, __22, _class1, __5, __13, __23, _G, __6, __14, __24, _class2, __7, __15, __25, _class3, __8, __16, __26, _K, __9, __17, __27;
var _initClass, _A1, _initClass1, _C1, _initClass2, _class4, _initClass3, _class5, _initClass4, _G1, _initClass5, _class6, _initClass6, _class7, _initClass7, _K1;
var _initClass, _A1, _initClass1, _C1, _initClass2, _class4, _initClass3, _class5, _initClass4, _G1, _initClass5, _class6, _initClass6, _class7, _I, _initClass7, _K1, _L;
const dec = ()=>{};
const A = ((_A = class A {
}, __ = {
@ -81,12 +81,12 @@ const F = [
value: _initClass5()
}, _class2), _class6)
];
const H = ((_class3 = class extends I {
const H = ((_class3 = class extends (_I = I) {
}, __8 = {
writable: true,
value: { c: [_class7, _initClass6] } = _apply_decs_2203_r(_class3, [], [
dec
])
], _I)
}, __16 = {
writable: true,
value: (()=>{})()
@ -94,12 +94,12 @@ const H = ((_class3 = class extends I {
writable: true,
value: _initClass6()
}, _class3), _class7);
const J = ((_K = class K extends L {
const J = ((_K = class K extends (_L = L) {
}, __9 = {
writable: true,
value: { c: [_K1, _initClass7] } = _apply_decs_2203_r(_K, [], [
dec
])
], _L)
}, __17 = {
writable: true,
value: (()=>{})()

View File

@ -1,5 +1,5 @@
var _A, __, __1, _C, __2, __11, _class, __3, __12, _class1, __4, __13, _G, __5, __14, _class2, __6, __15, _class3, __7, __16, _K, __8, __17;
var _initClass, _A1, _initClass1, _C1, _initClass2, _class4, _initClass3, _class5, _initClass4, _G1, _initClass5, _class6, _initClass6, _class7, _initClass7, _K1;
var _initClass, _A1, _initClass1, _C1, _initClass2, _class4, _initClass3, _class5, _initClass4, _G1, _initClass5, _class6, _initClass6, _class7, _I, _initClass7, _K1, _L;
const dec = ()=>{};
const A = ((_A = class A {
}, __ = {
@ -63,22 +63,22 @@ const F = [
value: _initClass5()
}, _class2), _class6)
];
const H = ((_class3 = class extends I {
const H = ((_class3 = class extends (_I = I) {
}, __7 = {
writable: true,
value: { c: [_class7, _initClass6] } = _apply_decs_2203_r(_class3, [], [
dec
])
], _I)
}, __16 = {
writable: true,
value: _initClass6()
}, _class3), _class7);
const J = ((_K = class K extends L {
const J = ((_K = class K extends (_L = L) {
}, __8 = {
writable: true,
value: { c: [_K1, _initClass7] } = _apply_decs_2203_r(_K, [], [
dec
])
], _L)
}, __17 = {
writable: true,
value: _initClass7()

View File

@ -1,12 +1,12 @@
var _initClass, _initClass1;
var _initClass, _initClass1, _Bar;
const dec1 = ()=>{};
const dec2 = ()=>{};
let _Bar;
let _Bar1;
class Bar {
}
var __ = {
writable: true,
value: { c: [_Bar, _initClass] } = _apply_decs_2203_r(Bar, [], [
value: { c: [_Bar1, _initClass] } = _apply_decs_2203_r(Bar, [], [
dec1
])
};
@ -15,13 +15,13 @@ var __1 = {
value: _initClass()
};
let _Foo;
class Foo extends _Bar {
class Foo extends (_Bar = _Bar1) {
}
var __2 = {
writable: true,
value: { c: [_Foo, _initClass1] } = _apply_decs_2203_r(Foo, [], [
dec2
])
], _Bar)
};
var __11 = {
writable: true,

View File

@ -1,10 +1,10 @@
var _class, __, _class1, __1;
var _initClass, _initClass1;
var _initClass, _initClass1, _Foo;
const dec = ()=>{};
let _Foo;
let _Foo1;
new (_class = class extends _identity {
constructor(){
super(_Foo), _initClass();
super(_Foo1), _initClass();
}
}, __ = {
writable: true,
@ -13,7 +13,7 @@ new (_class = class extends _identity {
}
var __ = {
writable: true,
value: { c: [_Foo, _initClass] } = _apply_decs_2203_r(Foo, [], [
value: { c: [_Foo1, _initClass] } = _apply_decs_2203_r(Foo, [], [
dec
])
};
@ -28,13 +28,14 @@ new (_class1 = class extends _identity {
}, __1 = {
writable: true,
value: (()=>{
class Bar extends _Foo {
var _ref;
class Bar extends (_ref = _Foo = _Foo1) {
}
var __ = {
writable: true,
value: { c: [_Bar, _initClass1] } = _apply_decs_2203_r(Bar, [], [
dec
])
], _Foo)
};
_define_property(Bar, "field", ((()=>{
Bar.otherField = 456;

View File

@ -1,4 +1,4 @@
var _initClass, _A, _initClass1, _C, _initClass2, _class, _initClass3, _class1, _initClass4, _G, _initClass5, _class2, _initClass6, _class3, _initClass7, _K;
var _initClass, _A, _initClass1, _C, _initClass2, _class, _initClass3, _class1, _initClass4, _G, _initClass5, _class2, _initClass6, _class3, _I, _initClass7, _K, _L;
const dec = ()=>{};
const A = (class A {
static{
@ -68,22 +68,22 @@ const F = [
}
}, _class2)
];
const H = (class extends I {
const H = (class extends (_I = I) {
static{
({ c: [_class3, _initClass6] } = _apply_decs_2203_r(this, [], [
dec
]));
], _I));
}
static{}
static{
_initClass6();
}
}, _class3);
const J = (class K extends L {
const J = (class K extends (_L = L) {
static{
({ c: [_K, _initClass7] } = _apply_decs_2203_r(this, [], [
dec
]));
], _L));
}
static{}
static{

View File

@ -1,4 +1,4 @@
var _initClass, _A, _initClass1, _C, _initClass2, _class, _initClass3, _class1, _initClass4, _G, _initClass5, _class2, _initClass6, _class3, _initClass7, _K;
var _initClass, _A, _initClass1, _C, _initClass2, _class, _initClass3, _class1, _initClass4, _G, _initClass5, _class2, _initClass6, _class3, _I, _initClass7, _K, _L;
const dec = ()=>{};
const A = (class A {
static{
@ -62,21 +62,21 @@ const F = [
}
}, _class2)
];
const H = (class extends I {
const H = (class extends (_I = I) {
static{
({ c: [_class3, _initClass6] } = _apply_decs_2203_r(this, [], [
dec
]));
], _I));
}
static{
_initClass6();
}
}, _class3);
const J = (class K extends L {
const J = (class K extends (_L = L) {
static{
({ c: [_K, _initClass7] } = _apply_decs_2203_r(this, [], [
dec
]));
], _L));
}
static{
_initClass7();

View File

@ -1,9 +1,9 @@
var _initClass, _initClass1;
var _initClass, _initClass1, _Bar;
const dec = ()=>{};
let _Bar;
let _Bar1;
class Bar {
static{
({ c: [_Bar, _initClass] } = _apply_decs_2203_r(this, [], [
({ c: [_Bar1, _initClass] } = _apply_decs_2203_r(this, [], [
dec1
]));
}
@ -12,11 +12,11 @@ class Bar {
}
}
let _Foo;
class Foo extends _Bar {
class Foo extends (_Bar = _Bar1) {
static{
({ c: [_Foo, _initClass1] } = _apply_decs_2203_r(this, [], [
dec2
]));
], _Bar));
}
static{
_initClass1();

View File

@ -1,14 +1,14 @@
var _initClass, _initClass1;
var _initClass, _initClass1, _Foo;
const dec = ()=>{};
let _Foo;
let _Foo1;
new class extends _identity {
constructor(){
super(_Foo), _initClass();
super(_Foo1), _initClass();
}
static{
class Foo {
static{
({ c: [_Foo, _initClass] } = _apply_decs_2203_r(this, [], [
({ c: [_Foo1, _initClass] } = _apply_decs_2203_r(this, [], [
dec
]));
}
@ -22,11 +22,11 @@ new class extends _identity {
super(_Bar), _initClass1();
}
static{
class Bar extends _Foo {
class Bar extends (_Foo = _Foo1) {
static{
({ c: [_Bar, _initClass1] } = _apply_decs_2203_r(this, [], [
dec
]));
], _Foo));
}
static field = ((()=>{
this.otherField = 456;

View File

@ -0,0 +1,11 @@
function dec(_, ctx) {
ctx.metadata.foo = 3;
}
Symbol.metadata = Symbol();
@dec
class A {}
expect(A[Symbol.metadata]).toEqual({ foo: 3 });
expect(Object.getPrototypeOf(A[Symbol.metadata])).toBe(null);

View File

@ -0,0 +1,14 @@
function dec(_, ctx) {
console.error(ctx);
ctx.metadata.foo = 3;
}
Symbol.metadata = Symbol();
class A {
@dec
foo;
}
expect(A[Symbol.metadata]).toEqual({ foo: 3 });
expect(Object.getPrototypeOf(A[Symbol.metadata])).toBe(null);

View File

@ -0,0 +1,5 @@
Symbol.metadata = Symbol();
class A {}
expect(A.hasOwnProperty(Symbol.metadata)).toBe(false);

View File

@ -0,0 +1,8 @@
{
"plugins": [
["proposal-decorators", { "version": "2022-03" }],
"proposal-class-properties",
"proposal-private-methods",
"proposal-class-static-block"
]
}

View File

@ -0,0 +1,15 @@
function dec(v) {
return (_, ctx) => {
ctx.metadata.foo = v;
};
}
Symbol.metadata = Symbol();
class B {}
@dec(3)
class A extends B {}
expect(A[Symbol.metadata]).toEqual({ foo: 3 });
expect(Object.getPrototypeOf(A[Symbol.metadata])).toBe(null);

View File

@ -0,0 +1,18 @@
function dec(v) {
return (_, ctx) => {
ctx.metadata.foo = v;
};
}
Symbol.metadata = Symbol();
@dec(2)
class B {}
@dec(3)
class A extends B {}
expect(A[Symbol.metadata]).toEqual({ foo: 3 });
expect(Object.getPrototypeOf(A[Symbol.metadata])).toBe(B[Symbol.metadata]);
expect(B[Symbol.metadata]).toEqual({ foo: 2 });
expect(Object.getPrototypeOf(B[Symbol.metadata])).toBe(null);

View File

@ -1,11 +1,11 @@
var _initClass;
var _initClass, _Bar;
const dec = ()=>{};
let _Foo;
class Foo extends Bar {
class Foo extends (_Bar = Bar) {
static{
({ c: [_Foo, _initClass] } = _apply_decs_2203_r(this, [], [
dec
]));
], _Bar));
}
constructor(){
let foo = super();

View File

@ -18,7 +18,7 @@
CLASS = 10; // only used in assertValidReturnValue
*/
export function _apply_decs_2203_r(targetClass, memberDecs, classDecs) {
export function _apply_decs_2203_r(targetClass, memberDecs, classDecs, parentClass) {
function createAddInitializerMethod(initializers, decoratorFinishedRef) {
return function addInitializer(initializer) {
assertNotFinished(decoratorFinishedRef, "addInitializer");
@ -27,7 +27,7 @@ export function _apply_decs_2203_r(targetClass, memberDecs, classDecs) {
};
}
function memberDec(dec, name, desc, initializers, kind, isStatic, isPrivate, value) {
function memberDec(dec, name, desc, initializers, kind, isStatic, isPrivate, metadata, value) {
var kindStr;
switch (kind) {
@ -136,7 +136,7 @@ export function _apply_decs_2203_r(targetClass, memberDecs, classDecs) {
}
}
function applyMemberDec(ret, base, decInfo, name, kind, isStatic, isPrivate, initializers) {
function applyMemberDec(ret, base, decInfo, name, kind, isStatic, isPrivate, initializers, metadata) {
var decs = decInfo[0];
var desc, init, value;
@ -168,7 +168,7 @@ export function _apply_decs_2203_r(targetClass, memberDecs, classDecs) {
var newValue, get, set;
if (typeof decs === "function") {
newValue = memberDec(decs, name, desc, initializers, kind, isStatic, isPrivate, value);
newValue = memberDec(decs, name, desc, initializers, kind, isStatic, isPrivate, metadata, value);
if (newValue !== void 0) {
assertValidReturnValue(kind, newValue);
@ -189,7 +189,7 @@ export function _apply_decs_2203_r(targetClass, memberDecs, classDecs) {
for (var i = decs.length - 1; i >= 0; i--) {
var dec = decs[i];
newValue = memberDec(dec, name, desc, initializers, kind, isStatic, isPrivate, value);
newValue = memberDec(dec, name, desc, initializers, kind, isStatic, isPrivate, metadata, value);
if (newValue !== void 0) {
assertValidReturnValue(kind, newValue);
@ -280,7 +280,7 @@ export function _apply_decs_2203_r(targetClass, memberDecs, classDecs) {
}
}
function applyMemberDecs(Class, decInfos) {
function applyMemberDecs(Class, decInfos, metadata) {
var ret = [];
var protoInitializers;
var staticInitializers;
@ -336,7 +336,7 @@ export function _apply_decs_2203_r(targetClass, memberDecs, classDecs) {
}
}
applyMemberDec(ret, base, decInfo, name, kind, isStatic, isPrivate, initializers);
applyMemberDec(ret, base, decInfo, name, kind, isStatic, isPrivate, initializers, metadata);
}
pushInitializers(ret, protoInitializers);
@ -353,7 +353,7 @@ export function _apply_decs_2203_r(targetClass, memberDecs, classDecs) {
}
}
function applyClassDecs(targetClass, classDecs) {
function applyClassDecs(targetClass, classDecs, metadata) {
if (classDecs.length > 0) {
var initializers = [];
var newClass = targetClass;
@ -363,7 +363,7 @@ export function _apply_decs_2203_r(targetClass, memberDecs, classDecs) {
var decoratorFinishedRef = { v: false };
try {
var nextNewClass = classDecs[i](newClass, { kind: "class", name: name, addInitializer: createAddInitializerMethod(initializers, decoratorFinishedRef) });
var nextNewClass = classDecs[i](newClass, { kind: "class", name: name, addInitializer: createAddInitializerMethod(initializers, decoratorFinishedRef), metadata });
} finally {
decoratorFinishedRef.v = true;
}
@ -374,7 +374,7 @@ export function _apply_decs_2203_r(targetClass, memberDecs, classDecs) {
}
}
return [newClass, function() {
return [defineMetadata(newClass, metadata), function() {
for (var i = 0; i < initializers.length; i++) initializers[i].call(newClass);
}];
}
@ -382,6 +382,10 @@ export function _apply_decs_2203_r(targetClass, memberDecs, classDecs) {
// so we don't have to return an empty array here.
}
function defineMetadata(Class, metadata) {
return Object.defineProperty(Class, Symbol.metadata || Symbol.for("Symbol.metadata"), { configurable: true, enumerable: true, value: metadata });
}
/**
Basic usage:
@ -528,17 +532,23 @@ export function _apply_decs_2203_r(targetClass, memberDecs, classDecs) {
initializeClass(Class);
*/
_apply_decs_2203_r = function(targetClass, memberDecs, classDecs) {
_apply_decs_2203_r = function(targetClass, memberDecs, classDecs, parentClass) {
if (parentClass !== void 0) {
var parentMetadata = parentClass[Symbol.metadata || Symbol.for("Symbol.metadata")];
}
var metadata = Object.create(parentMetadata === void 0 ? null : parentMetadata);
var e = applyMemberDecs(targetClass, memberDecs, metadata);
if (!classDecs.length) defineMetadata(targetClass, metadata);
return {
e: applyMemberDecs(targetClass, memberDecs),
e: e,
// Lazily apply class decorations so that member init locals can be properly bound.
get c() {
return applyClassDecs(targetClass, classDecs);
return applyClassDecs(targetClass, classDecs, metadata);
}
};
};
return _apply_decs_2203_r(targetClass, memberDecs, classDecs);
return _apply_decs_2203_r(targetClass, memberDecs, classDecs, parentClass);
}
export { _apply_decs_2203_r as _ };