Adds the 2021-12 transform for decorators

Implements the 2021-12 transform for the decorators proposal, with
support for the `accessor` keyword.
This commit is contained in:
Chris Hewell Garrett 2021-11-27 16:20:25 -05:00 committed by Nicolò Ribaudo
parent 3f3ce5f668
commit 3f7644823d
259 changed files with 7438 additions and 9 deletions

View File

@ -80,7 +80,10 @@ function extractElementDescriptor(
const properties: t.ObjectExpression["properties"] = [ const properties: t.ObjectExpression["properties"] = [
prop("kind", t.stringLiteral(t.isClassMethod(node) ? node.kind : "field")), prop("kind", t.stringLiteral(t.isClassMethod(node) ? node.kind : "field")),
prop("decorators", takeDecorators(node as Decorable)), prop("decorators", takeDecorators(node as Decorable)),
prop("static", node.static && t.booleanLiteral(true)), prop(
"static",
!t.isStaticBlock(node) && node.static && t.booleanLiteral(true),
),
prop("key", getKey(node)), prop("key", getKey(node)),
].filter(Boolean); ].filter(Boolean);

View File

@ -911,7 +911,7 @@ function replaceThisContext(
getSuperRef, getSuperRef,
getObjectRef() { getObjectRef() {
state.needsClassRef = true; state.needsClassRef = true;
return isStaticBlock || path.node.static return t.isStaticBlock(path.node) || path.node.static
? ref ? ref
: t.memberExpression(ref, t.identifier("prototype")); : t.memberExpression(ref, t.identifier("prototype"));
}, },
@ -931,7 +931,8 @@ function replaceThisContext(
export type PropNode = export type PropNode =
| t.ClassProperty | t.ClassProperty
| t.ClassPrivateMethod | t.ClassPrivateMethod
| t.ClassPrivateProperty; | t.ClassPrivateProperty
| t.StaticBlock;
export type PropPath = NodePath<PropNode>; export type PropPath = NodePath<PropNode>;
export function buildFieldsInitNodes( export function buildFieldsInitNodes(
@ -963,7 +964,7 @@ export function buildFieldsInitNodes(
for (const prop of props) { for (const prop of props) {
prop.isClassProperty() && ts.assertFieldTransformed(prop); prop.isClassProperty() && ts.assertFieldTransformed(prop);
const isStatic = prop.node.static; const isStatic = !t.isStaticBlock(prop.node) && prop.node.static;
const isInstance = !isStatic; const isInstance = !isStatic;
const isPrivate = prop.isPrivate(); const isPrivate = prop.isPrivate();
const isPublic = !isPrivate; const isPublic = !isPrivate;

View File

@ -170,7 +170,7 @@ export function createClassFeaturePlugin({
path.isPrivate() || path.isPrivate() ||
path.isStaticBlock?.() path.isStaticBlock?.()
) { ) {
props.push(path); props.push(path as PropPath);
} }
} }
} }
@ -246,7 +246,7 @@ export function createClassFeaturePlugin({
(referenceVisitor, state) => { (referenceVisitor, state) => {
if (isDecorated) return; if (isDecorated) return;
for (const prop of props) { for (const prop of props) {
if (prop.node.static) continue; if (t.isStaticBlock(prop.node) || prop.node.static) continue;
prop.traverse(referenceVisitor, state); prop.traverse(referenceVisitor, state);
} }
}, },

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,728 @@
/* @minVersion 7.16.6 */
/**
Enums are used in this file, but not assigned to vars to avoid non-hoistable values
CONSTRUCTOR = 0;
PUBLIC = 1;
PRIVATE = 2;
FIELD = 0;
ACCESSOR = 1;
METHOD = 2;
GETTER = 3;
SETTER = 4;
STATIC = 5;
*/
function createMetadataMethodsForProperty(metadataMap, kind, property) {
return {
getMetadata(key) {
if (typeof key !== "symbol") {
throw new TypeError("Metadata keys must be symbols, received: " + key);
}
var metadataForKey = metadataMap[key];
if (metadataForKey === undefined) return undefined;
if (kind === 1 /* PUBLIC */) {
var pub = metadataForKey.public;
if (pub !== undefined) {
return pub[property];
}
} else if (kind === 2 /* PRIVATE */) {
var priv = metadataForKey.private;
if (priv !== undefined) {
return priv.get(property);
}
} else if (Object.hasOwnProperty.call(metadataForKey, "constructor")) {
return metadataForKey.constructor;
}
},
setMetadata(key, value) {
if (typeof key !== "symbol") {
throw new TypeError("Metadata keys must be symbols, received: " + key);
}
var metadataForKey = metadataMap[key];
if (metadataForKey === undefined) {
metadataForKey = metadataMap[key] = {};
}
if (kind === 1 /* PUBLIC */) {
var pub = metadataForKey.public;
if (pub === undefined) {
pub = metadataForKey.public = Object.create(null);
}
pub[property] = value;
} else if (kind === 2 /* PRIVATE */) {
var priv = metadataForKey.priv;
if (priv === undefined) {
priv = metadataForKey.private = new Map();
}
priv.set(property, value);
} else {
metadataForKey.constructor = value;
}
},
};
}
function convertMetadataMapToFinal(obj, metadataMap) {
var parentMetadataMap = obj[Symbol.metadata];
var metadataKeys = Object.getOwnPropertySymbols(metadataMap);
if (metadataKeys.length === 0) return;
for (var i = 0; i < metadataKeys.length; i++) {
var key = metadataKeys[i];
var metaForKey = metadataMap[key];
var parentMetaForKey = parentMetadataMap ? parentMetadataMap[key] : null;
var pub = metaForKey.public;
var parentPub = parentMetaForKey ? parentMetaForKey.public : null;
if (pub && parentPub) {
Object.setPrototypeOf(pub, parentPub);
}
var priv = metaForKey.private;
if (priv) {
var privArr = Array.from(priv.values());
var parentPriv = parentMetaForKey ? parentMetaForKey.private : null;
if (parentPriv) {
privArr = privArr.concat(parentPriv);
}
metaForKey.private = privArr;
}
if (parentMetaForKey) {
Object.setPrototypeOf(metaForKey, parentMetaForKey);
}
}
if (parentMetadataMap) {
Object.setPrototypeOf(metadataMap, parentMetadataMap);
}
obj[Symbol.metadata] = metadataMap;
}
function createAddInitializerMethod(initializers) {
return function addInitializer(initializer) {
assertValidInitializer(initializer);
initializers.push(initializer);
};
}
function memberDecCtx(
base,
name,
desc,
metadataMap,
initializers,
kind,
isStatic,
isPrivate
) {
var kindStr;
switch (kind) {
case 1 /* ACCESSOR */:
kindStr = "accessor";
break;
case 2 /* METHOD */:
kindStr = "method";
break;
case 3 /* GETTER */:
kindStr = "getter";
break;
case 4 /* SETTER */:
kindStr = "setter";
break;
default:
kindStr = "field";
}
var ctx = {
kind: kindStr,
name: isPrivate ? "#" + name : name,
isStatic: isStatic,
isPrivate: isPrivate,
};
if (kind !== 0 /* FIELD */) {
ctx.addInitializer = createAddInitializerMethod(initializers);
}
var metadataKind, metadataName;
if (isPrivate) {
metadataKind = 2 /* PRIVATE */;
metadataName = Symbol(name);
var access = {};
if (kind === 0 /* FIELD */) {
access.get = desc.get;
access.set = desc.set;
} else if (kind === 2 /* METHOD */) {
access.get = function () {
return desc.value;
};
} else {
// replace with values that will go through the final getter and setter
if (kind === 1 /* ACCESSOR */ || kind === 3 /* GETTER */) {
access.get = function () {
return desc.get.call(this);
};
}
if (kind === 1 /* ACCESSOR */ || kind === 4 /* SETTER */) {
access.set = function (v) {
desc.set.call(this, v);
};
}
}
ctx.access = access;
} else {
metadataKind = 1 /* PUBLIC */;
metadataName = name;
}
return Object.assign(
ctx,
createMetadataMethodsForProperty(metadataMap, metadataKind, metadataName)
);
}
function assertValidInitializer(initializer) {
if (typeof initializer !== "function") {
throw new Error("initializers must be functions");
}
}
function assertValidReturnValue(kind, value) {
var type = typeof value;
if (kind === 1 /* ACCESSOR */) {
if (type !== "object" || value === null) {
throw new Error(
"accessor decorators must return an object with get, set, or initializer properties or undefined"
);
}
} else if (type !== "function") {
if (kind === 0 /* FIELD */) {
throw new Error(
"field decorators must return a initializer function or undefined"
);
} else {
throw new Error("method decorators must return a function or undefined");
}
}
}
function applyMemberDec(
ret,
base,
decInfo,
name,
kind,
isStatic,
isPrivate,
metadataMap,
initializers
) {
var decs = decInfo[0];
var desc, initializer, value;
if (isPrivate) {
if (kind === 0 /* FIELD */ || kind === 1 /* ACCESSOR */) {
desc = {
get: decInfo[3],
set: decInfo[4],
};
} else if (kind === 3 /* GETTER */) {
desc = {
get: decInfo[3],
};
} else if (kind === 4 /* SETTER */) {
desc = {
set: decInfo[3],
};
} else {
desc = {
value: decInfo[3],
};
}
} else if (kind !== 0 /* FIELD */) {
desc = Object.getOwnPropertyDescriptor(base, name);
}
if (kind === 1 /* ACCESSOR */) {
value = {
get: desc.get,
set: desc.set,
};
} else if (kind === 2 /* METHOD */) {
value = desc.value;
} else if (kind === 3 /* GETTER */) {
value = desc.get;
} else if (kind === 4 /* SETTER */) {
value = desc.set;
}
var ctx = memberDecCtx(
base,
name,
desc,
metadataMap,
initializers,
kind,
isStatic,
isPrivate
);
var newValue, get, set;
if (typeof decs === "function") {
newValue = decs(value, ctx);
if (newValue !== undefined) {
assertValidReturnValue(kind, newValue);
if (kind === 0 /* FIELD */) {
initializer = newValue;
} else if (kind === 1 /* ACCESSOR */) {
initializer = newValue.initializer;
get = newValue.get || value.get;
set = newValue.set || value.set;
value = { get: get, set: set };
} else {
value = newValue;
}
}
} else {
for (var i = 0; i < decs.length; i++) {
var dec = decs[i];
newValue = dec(value, ctx);
if (newValue !== undefined) {
assertValidReturnValue(kind, newValue);
var newInit;
if (kind === 0 /* FIELD */) {
newInit = newValue;
} else if (kind === 1 /* ACCESSOR */) {
newInit = newValue.initializer;
get = newValue.get || value.get;
set = newValue.set || value.set;
value = { get: get, set: set };
} else {
value = newValue;
}
if (newInit !== undefined) {
if (initializer === undefined) {
initializer = newInit;
} else if (typeof initializer === "function") {
initializer = [initializer, newInit];
} else {
initializer.push(newInit);
}
}
}
}
}
if (kind === 0 /* FIELD */ || kind === 1 /* ACCESSOR */) {
if (initializer === undefined) {
// If the initializer was undefined, sub in a dummy initializer
initializer = function (instance, init) {
return init;
};
} else if (typeof initializer !== "function") {
var ownInitializers = initializer;
initializer = function (instance, init) {
var value = init;
for (var i = 0; i < ownInitializers.length; i++) {
value = ownInitializers[i].call(instance, value);
}
return value;
};
} else {
var originalInitializer = initializer;
initializer = function (instance, init) {
return originalInitializer.call(instance, init);
};
}
ret.push(initializer);
}
if (kind !== 0 /* FIELD */) {
if (kind === 1 /* ACCESSOR */) {
desc.get = value.get;
desc.set = value.set;
} else if (kind === 2 /* METHOD */) {
desc.value = value;
} else if (kind === 3 /* GETTER */) {
desc.get = value;
} else if (kind === 4 /* SETTER */) {
desc.set = value;
}
if (isPrivate) {
if (kind === 1 /* ACCESSOR */) {
ret.push(function (instance, args) {
return value.get.call(instance, args);
});
ret.push(function (instance, args) {
return value.set.call(instance, args);
});
} else if (kind === 2 /* METHOD */) {
ret.push(value);
} else {
ret.push(function (instance, args) {
return value.call(instance, args);
});
}
} else {
Object.defineProperty(base, name, desc);
}
}
}
function applyMemberDecs(
ret,
Class,
protoMetadataMap,
staticMetadataMap,
decInfos
) {
var protoInitializers;
var staticInitializers;
var existingProtoNonFields = new Map();
var existingStaticNonFields = new Map();
for (var i = 0; i < decInfos.length; i++) {
var decInfo = decInfos[i];
// skip computed property names
if (!Array.isArray(decInfo)) continue;
var kind = decInfo[1];
var name = decInfo[2];
var isPrivate = decInfo.length > 3;
var isStatic = kind >= 5; /* STATIC */
var base;
var metadataMap;
var initializers;
if (isStatic) {
base = Class;
metadataMap = staticMetadataMap;
kind = kind - 5 /* STATIC */;
if (!staticInitializers) {
staticInitializers = [];
}
initializers = staticInitializers;
} else {
base = Class.prototype;
metadataMap = protoMetadataMap;
if (!protoInitializers) {
protoInitializers = [];
}
initializers = protoInitializers;
}
if (kind !== 0 /* FIELD */ && !isPrivate) {
var existingNonFields = isStatic
? existingStaticNonFields
: existingProtoNonFields;
var existingKind = existingNonFields.get(name) || 0;
if (
existingKind === true ||
(existingKind === 3 /* GETTER */ && kind !== 4) /* SETTER */ ||
(existingKind === 4 /* SETTER */ && kind !== 3) /* GETTER */
) {
throw new Error(
"Attempted to decorate a public method/accessor that has the same name as a previously decorated public method/accessor. This is not currently supported by the decorators plugin. Property name was: " +
name
);
} else if (!existingKind && kind > 2 /* METHOD */) {
existingNonFields.set(name, kind);
} else {
existingNonFields.set(name, true);
}
}
applyMemberDec(
ret,
base,
decInfo,
name,
kind,
isStatic,
isPrivate,
metadataMap,
initializers
);
}
if (protoInitializers) {
pushInitializers(ret, protoInitializers);
}
if (staticInitializers) {
pushInitializers(ret, staticInitializers);
}
}
function pushInitializers(ret, initializers) {
if (initializers.length > 0) {
// Slice the array, which means that `addInitializer` can no longer add
// additional initializers to the array
initializers = initializers.slice();
ret.push(function (instance) {
for (var i = 0; i < initializers.length; i++) {
initializers[i].call(instance, instance);
}
});
} else {
ret.push(function () {});
}
}
function applyClassDecs(ret, targetClass, metadataMap, classDecs) {
var initializers = [];
var newClass = targetClass;
var name = targetClass.name;
var ctx = Object.assign(
{
kind: "class",
name: name,
addInitializer: createAddInitializerMethod(initializers),
},
createMetadataMethodsForProperty(metadataMap, 0 /* CONSTRUCTOR */, name)
);
for (var i = 0; i < classDecs.length; i++) {
newClass = classDecs[i](newClass, ctx) || newClass;
}
ret.push(newClass);
if (initializers.length > 0) {
ret.push(function () {
for (var i = 0; i < initializers.length; i++) {
initializers[i].call(newClass, newClass);
}
});
} else {
ret.push(function () {});
}
}
/**
Basic usage:
applyDecs(
Class,
[
// member decorators
[
dec, // dec or array of decs
0, // kind of value being decorated
'prop', // name of public prop on class containing the value being decorated,
'#p', // the name of the private property (if is private, undefined otherwise),
]
],
[
// class decorators
dec1, dec2
]
)
```
Fully transpiled example:
```js
@dec
class Class {
@dec
a = 123;
@dec
#a = 123;
@dec
@dec2
accessor b = 123;
@dec
accessor #b = 123;
@dec
c() { console.log('c'); }
@dec
#c() { console.log('privC'); }
@dec
get d() { console.log('d'); }
@dec
get #d() { console.log('privD'); }
@dec
set e(v) { console.log('e'); }
@dec
set #e(v) { console.log('privE'); }
}
// becomes
let initializeInstance;
let initializeClass;
let initA;
let initPrivA;
let initB;
let initPrivB, getPrivB, setPrivB;
let privC;
let privD;
let privE;
let Class;
class _Class {
static {
let ret = applyDecs(
this,
[
[dec, 0, 'a'],
[dec, 0, 'a', (i) => i.#a, (i, v) => i.#a = v],
[[dec, dec2], 1, 'b'],
[dec, 1, 'b', (i) => i.#privBData, (i, v) => i.#privBData = v],
[dec, 2, 'c'],
[dec, 2, 'c', () => console.log('privC')],
[dec, 3, 'd'],
[dec, 3, 'd', () => console.log('privD')],
[dec, 4, 'e'],
[dec, 4, 'e', () => console.log('privE')],
],
[
dec
]
)
initA = ret[0];
initPrivA = ret[1];
initB = ret[2];
initPrivB = ret[3];
getPrivB = ret[4];
setPrivB = ret[5];
privC = ret[6];
privD = ret[7];
privE = ret[8];
initializeInstance = ret[9];
Class = ret[10]
initializeClass = ret[11];
}
a = (initializeInstance(this), initA(this, 123));
#a = initPrivA(this, 123);
#bData = initB(this, 123);
get b() { return this.#bData }
set b(v) { this.#bData = v }
#privBData = initPrivB(this, 123);
get #b() { return getPrivB(this); }
set #b(v) { setPrivB(this, v); }
c() { console.log('c'); }
#c(...args) { return privC(this, ...args) }
get d() { console.log('d'); }
get #d() { return privD(this); }
set e(v) { console.log('e'); }
set #e(v) { privE(this, v); }
}
initializeClass(Class);
*/
export default function applyDecs(targetClass, memberDecs, classDecs) {
var ret = [];
var staticMetadataMap = {};
if (memberDecs) {
var protoMetadataMap = {};
applyMemberDecs(
ret,
targetClass,
protoMetadataMap,
staticMetadataMap,
memberDecs
);
convertMetadataMapToFinal(targetClass.prototype, protoMetadataMap);
}
if (classDecs) {
applyClassDecs(ret, targetClass, staticMetadataMap, classDecs);
}
convertMetadataMapToFinal(targetClass, staticMetadataMap);
return ret;
}

View File

@ -0,0 +1,374 @@
These are notes about the implementation of the 2021-12 decorators transform.
The implementation's goals are (in descending order):
1. Being accurate to the actual proposal (e.g. not defining additional
properties unless required, matching semantics exactly, etc.). This includes
being able to work properly with private fields and methods.
2. Transpiling to a very minimal and minifiable output. This transform will
affect each and every decorated class, so ensuring that the output is not 10x
the size of the original is important.
3. Having good runtime performance. Decoration output has the potential to
drastically impact startup performance, since it runs whenever a decorated
class is defined. In addition, every instance of a decorated class may be
impacted for certain types of decorators.
All of these goals come somewhat at the expense of readability and can make the
implementation difficult to understand, so these notes are meant to document the
motivations behind the design.
## Overview
Given a simple decorated class like this one:
```js
@dec
class Class {
@dec a = 123;
@dec static #b() {
console.log('foo');
}
[someVal]() {}
@dec
@dec2
accessor #c = 456;
}
```
It's output would be something like the following:
```js
import { applyDecs } from '@babel/helpers';
let initInstance, initClass, initA, callB, computedKey, initC, getC, setC;
const elementDecs = [
[dec, 0, 'a'],
[dec, 7, 'x', '#b']
(computedKey = someVal, null)
[[dec, dec2], 1, 'y', '#c']
];
const classDecs = [dec];
let Class;
class _Class {
static {
let ret = applyDecs(
this,
elementDecs,
[dec]
);
initA = ret[0];
callB = ret[1];
initC = ret[2];
getC = ret[3];
setC = ret[4];
initInstance = ret[5];
Class = ret[6];
initClass = ret[7];
}
a = (initInstance(this), initA(this, 123));
static #b(...args) {
callB(this, args);
}
static x() {
console.log('foo');
}
[computedKey]() {}
#y = initC(this, 123);
get y() {
return this.#y;
}
set y(v) {
this.#y = v;
}
get #c() {
return getC(this);
}
set #c(v) {
setC(this, v);
}
static {
initClass(C);
}
}
```
Let's break this output down a bit:
```js
let initInstance, initClass, initA, callB, initC, getC, setC;
```
First, we need to setup some local variables outside of the class. These are
for:
- Decorated class field/accessor initializers
- Extra initializer functions added by `addInitializers`
- Private class methods
These are essentially all values that cannot be defined on the class itself via
`Object.defineProperty`, so we have to insert them into the class manually,
ahead of time and populate them when we run our decorators.
```js
const elementDecs = [
[dec, 0, 'a'],
[dec, 7, 'x', '#b']
(computedKey = someVal, null)
[[dec, dec2], 1, 'y', '#c']
];
const classDecs = [dec];
```
Next up, we define and evaluate the decorator member expressions. The reason we
do this _before_ defining the class is because we must interleave decorator
expressions with computed property key expressions, since computed properties
and decorators can run arbitrary code which can modify the runtime of subsequent
decorators or computed property keys.
```js
let Class;
class _Class {
```
This class is being decorated directly, which means that the decorator may
replace the class itself. Class bindings are not mutable, so we need to create a
new `let` variable for the decorated class.
```js
static {
let ret = applyDecs(
this,
elementDecs,
classDecs
);
initA = ret[0];
callB = ret[1];
initC = ret[2];
getC = ret[3];
setC = ret[4];
initInstance = ret[5];
Class = ret[6];
initClass = ret[7];
}
```
Next, we immediately define a `static` block which actually applies the
decorators. This is important because we must apply the decorators _after_ the
class prototype has been fully setup, but _before_ static fields are run, since
static fields should only see the decorated version of the class.
We apply the decorators to class elements and the class itself, and the
application returns an array of values that are used to populate all of the
local variables we defined earlier. The array's order is fully deterministic, so
we can assign the values based on an index we can calculate ahead of time.
We'll come back to `applyDecs` in a bit to dig into what its format is exactly,
but now let's dig into the new definitions of our class elements.
```js
a = (initInstance(this), initA(this, 123));
```
Alright, so previously this was a simple class field. Since it's the first field
on the class, we've updated it to immediately call `initInstance` in its
initializer. This calls any initializers added with `addInitializer` for all of
the per-class values (methods and accessors), which should all be setup on the
instance before class fields are assigned. Then, it calls `initA` to get the
initial value of the field, which allows initializers returned from the
decorator to intercept and decorate it. It's important that the initial value
is used/defined _within_ the class body, because initializers can now refer to
private class fields, e.g. `a = this.#b` is a valid field initializer and would
become `a = initA(this, this.#b)`, which would also be valid. We cannot
extract initializer code, or any other code, from the class body because of
this.
Overall, this decoration is pretty straightforward other than the fact that we
have to reference `initA` externally.
```js
static #b(...args) {
callB(this, args);
}
static x() {
console.log('foo');
}
```
Next up, we have a private static class method `#b`. This one is a bit more
complex, as our definition has been broken out into 2 parts:
1. `static #b`: This is the method itself, which being a private method we
cannot overwrite with `defineProperty`. We also can't convert it into a
private field because that would change its semantics (would make it
writable). So, we instead have it proxy to the locally scoped `callB`
variable, which will be populated with the fully decorated method.
2. `static x`: This contains the _code_ of the original method. Once again, this
code cannot be removed from the class body because it may reference private
identifiers. However, we have moved the code to a _public_ method, which means
we can now read its value using `Object.getOwnPropertyDescriptor`. Decorators
use this to get the initial implementation of the method, which can then be
wrapped with decorator code/logic. They then `delete` this temporary property,
which is necessary because no additional elements should be added to a class
definition.
The name for this method is unimportant, but because public method names
cannot be minified and we also need to pass the name into `applyDecs`, we
generate as small of a unique identifier as possible here, starting with 1
char names which are not taken and growing until we find one that is free.
```js
[computedKey]() {}
```
Next is the undecorated method with a computed key. This uses the previously
calculated and stored computed key.
```js
#y = initC(this, 123);
get y() {
return this.#y;
}
set y(v) {
this.#y = v;
}
get #c() {
return getC(this);
}
set #c(v) {
setC(this, v);
}
```
Next up, we have the output for `accessor #c`. This is the most complicated
case, since we have to transpile the decorators, the `accessor` keyword, and
target a private field. Breaking it down piece by piece:
```js
#y = initC(this, 123);
```
`accessor #c` desugars to a getter and setter which are backed by a new private
field, `#y`. Like before, the name of this field doesn't really matter, we'll
just generate a short, unique name. We call the decorated initializer for `#c`
and return that value to assign to the field.
```js
get y() {
return this.#y;
}
set y(v) {
this.#y = v;
}
```
Next we have a getter and setter named `y` which provide access to the backing
storage for the accessor. These are the base getter/setter for the accessor,
which the decorator will get via `Object.getOwnPropertyDescriptor`. They will be
deleted from the class fully, so again the name is not important here, just
needs to be short.
```js
get #c() {
return getC(this);
}
set #c(v) {
setC(this, v);
}
```
Next, we have the getter and setter for `#c` itself. These methods defer to
the `getC` and `setC` local variables, which will be the decorated versions of
the `get y` and `set y` methods from the previous step.
```js
static {
initClass(C);
}
```
Finally, we call `initClass` in another static block, running any class and
static method initializers on the final class. This is done in a static block
for convenience with class expressions, but it could run immediately after the
class is defined.
Ok, so now that we understand the general output, let's go back to `applyDecs`:
```js
const elementDecs = [
[dec, 0, 'a'],
[dec, 7, 'x', '#b']
(computedKey = someVal, null)
[[dec, dec2], 1, 'y', '#c']
];
const classDecs = [dec];
// ...
let ret = applyDecs(
this,
elementDecs,
classDecs
);
```
`applyDecs` takes all of the decorators for the class and applies them. It
receives the following arguments:
1. The class itself
2. Decorators to apply to class elements
3. Decorators to apply to the class itself
The format of the data is designed to be as minimal as possible. Here's an
annotated version of the member descriptors:
```js
[
// List of decorators to apply to the field. Array if multiple decorators,
// otherwise just the single decorator itself.
dec,
// The type of the decorator, represented as an enum. Static-ness is also
// encoded by adding 5 to the values
// 0 === FIELD
// 1 === ACCESSOR
// 2 === METHOD
// 3 === GETTER
// 4 === SETTER
// 5 === FIELD + STATIC
// 6 === ACCESSOR + STATIC
// 7 === METHOD + STATIC
// 8 === GETTER + STATIC
// 9 === SETTER + STATIC
1,
// The name of the public property that can be used to access the value.
// For public members this is the actual name, for private members this is
// the name of the public property which can be used to access the value
// (see descriptions of #b and #c above)
'y',
// Optional fourth value, this is the spelling of the private element's name,
// which signals that the element is private to `applyDecs` and is used in the
// decorator's context object
'#c'
],
```
Static and prototype decorators are all described like this. For class
decorators, it's just the list of decorators since no other context
is necessary.

View File

@ -22,7 +22,9 @@
"dependencies": { "dependencies": {
"@babel/helper-create-class-features-plugin": "workspace:^", "@babel/helper-create-class-features-plugin": "workspace:^",
"@babel/helper-plugin-utils": "workspace:^", "@babel/helper-plugin-utils": "workspace:^",
"@babel/plugin-syntax-decorators": "workspace:^" "@babel/helper-replace-supers": "workspace:^",
"@babel/plugin-syntax-decorators": "workspace:^",
"charcodes": "^0.2.0"
}, },
"peerDependencies": { "peerDependencies": {
"@babel/core": "^7.0.0-0" "@babel/core": "^7.0.0-0"

View File

@ -7,6 +7,7 @@ import {
FEATURES, FEATURES,
} from "@babel/helper-create-class-features-plugin"; } from "@babel/helper-create-class-features-plugin";
import legacyVisitor from "./transformer-legacy"; import legacyVisitor from "./transformer-legacy";
import transformer2021_12 from "./transformer-2021-12";
export default declare((api, options) => { export default declare((api, options) => {
api.assertVersion(7); api.assertVersion(7);
@ -16,7 +17,7 @@ export default declare((api, options) => {
throw new Error("'legacy' must be a boolean."); throw new Error("'legacy' must be a boolean.");
} }
const { decoratorsBeforeExport } = options; const { decoratorsBeforeExport, version } = options;
if (decoratorsBeforeExport === undefined) { if (decoratorsBeforeExport === undefined) {
if (!legacy) { if (!legacy) {
throw new Error( throw new Error(
@ -37,6 +38,10 @@ export default declare((api, options) => {
} }
if (legacy) { if (legacy) {
if (version !== undefined) {
throw new Error("'version' can't be used with legacy decorators");
}
return { return {
name: "proposal-decorators", name: "proposal-decorators",
inherits: syntaxDecorators, inherits: syntaxDecorators,
@ -47,6 +52,12 @@ export default declare((api, options) => {
}; };
} }
if (version === "2021-12") {
return transformer2021_12(api, options);
} else if (!(version === "2018-09" || version === undefined)) {
throw new Error("Unsupported decorators version: " + version);
}
return createClassFeaturePlugin({ return createClassFeaturePlugin({
name: "proposal-decorators", name: "proposal-decorators",

View File

@ -0,0 +1,987 @@
import type { NodePath } from "@babel/traverse";
import { types as t } from "@babel/core";
import syntaxDecorators from "@babel/plugin-syntax-decorators";
import ReplaceSupers from "@babel/helper-replace-supers";
import * as charCodes from "charcodes";
type ClassDecoratableElement =
| t.ClassMethod
| t.ClassPrivateMethod
| t.ClassProperty
| t.ClassPrivateProperty
| t.ClassAccessorProperty;
type ClassElement =
| ClassDecoratableElement
| t.TSDeclareMethod
| t.TSIndexSignature
| t.StaticBlock;
type classUidGenerator = <B extends boolean>(
isPrivate: B,
) => B extends true ? t.PrivateName : t.Identifier;
function incrementId(id: number[], idx = id.length - 1): void {
// If index is -1, id needs an additional character, unshift A
if (idx === -1) {
id.unshift(charCodes.uppercaseA);
return;
}
const current = id[idx];
if (current === charCodes.uppercaseZ) {
// if current is Z, skip to a
id[idx] = charCodes.lowercaseA;
} else if (current === charCodes.lowercaseZ) {
// if current is z, reset to A and carry the 1
id[idx] = charCodes.uppercaseA;
incrementId(id, idx - 1);
} else {
// else, increment by one
id[idx] = current + 1;
}
}
/**
* Generates a new element name that is unique to the given class. This can be
* used to create extra class fields and methods for the implementation, while
* keeping the length of those names as small as possible. This is important for
* minification purposes, since public names cannot be safely renamed/minified.
*
* Names are split into two namespaces, public and private. Static and non-static
* names are shared in the same namespace, because this is true for private names
* (you cannot have #x and static #x in the same class) and it's not worth the
* extra complexity for public names.
*/
function createUidGeneratorForClass(
body: NodePath<ClassElement>[],
): (isPrivate: boolean) => t.Identifier | t.PrivateName {
let currentPublicId: number[], currentPrivateId: number[];
const publicNames = new Set<string>();
const privateNames = new Set<string>();
for (const element of body) {
if (
element.node.type === "TSIndexSignature" ||
element.node.type === "StaticBlock"
) {
continue;
}
const { key } = element.node;
if (key.type === "PrivateName") {
privateNames.add(key.id.name);
} else if (key.type === "Identifier") {
publicNames.add(key.name);
}
}
return (isPrivate: boolean): t.Identifier | t.PrivateName => {
let currentId: number[], names: Set<String>;
if (isPrivate) {
if (!currentPrivateId) {
currentPrivateId = [charCodes.uppercaseA];
}
currentId = currentPrivateId;
names = privateNames;
} else {
if (!currentPublicId) {
currentPublicId = [charCodes.uppercaseA];
}
currentId = currentPublicId;
names = publicNames;
}
let reifiedId = String.fromCharCode(...currentId);
while (names.has(reifiedId)) {
incrementId(currentId);
reifiedId = String.fromCharCode(...currentId);
}
incrementId(currentId);
if (isPrivate) {
return t.privateName(t.identifier(reifiedId));
} else {
return t.identifier(reifiedId);
}
};
}
/**
* Wraps the above generator function so that it's run lazily the first time
* it's actually required. Several types of decoration do not require this, so it
* saves iterating the class elements an additional time and allocating the space
* for the Sets of element names.
*/
function createLazyUidGeneratorForClass(
body: NodePath<ClassElement>[],
): classUidGenerator {
let generator: (isPrivate: boolean) => t.Identifier | t.PrivateName;
const lazyGenerator = (isPrivate: boolean): t.Identifier | t.PrivateName => {
if (!generator) {
generator = createUidGeneratorForClass(body);
}
return generator(isPrivate);
};
return lazyGenerator as unknown as classUidGenerator;
}
/**
* Takes a class definition and replaces it with an equivalent class declaration
* which is then assigned to a local variable. This allows us to reassign the
* local variable with the decorated version of the class. The class definition
* retains its original name so that `toString` is not affected, other
* references to the class are renamed instead.
*/
function replaceClassWithVar(
path: NodePath<t.ClassDeclaration | t.ClassExpression>,
): [t.Identifier, NodePath<t.ClassDeclaration | t.ClassExpression>] {
if (path.type === "ClassDeclaration") {
const varId = path.scope.generateUidIdentifierBasedOnNode(path.node.id);
const classId = t.identifier(path.node.id.name);
path.scope.rename(classId.name, varId.name);
path.insertBefore(
t.variableDeclaration("let", [t.variableDeclarator(varId)]),
);
path.get("id").replaceWith(classId);
return [t.cloneNode(varId), path];
} else {
let className: string;
let varId: t.Identifier;
if (path.node.id) {
className = path.node.id.name;
varId = generateLocalVarId(path, className);
path.scope.rename(className, varId.name);
} else if (
path.parentPath.node.type === "VariableDeclarator" &&
path.parentPath.node.id.type === "Identifier"
) {
className = path.parentPath.node.id.name;
varId = generateLocalVarId(path, className);
} else {
varId = generateLocalVarId(path, "decorated_class");
}
const newClassExpr = t.classExpression(
className && t.identifier(className),
path.node.superClass,
path.node.body,
);
const [newPath] = path.replaceWith(
t.sequenceExpression([newClassExpr, varId]),
);
return [t.cloneNode(varId), newPath.get("expressions.0")];
}
}
function generateClassProperty(
key: t.PrivateName | t.Identifier,
value: t.Expression | undefined,
isStatic: boolean,
): t.ClassPrivateProperty | t.ClassProperty {
if (key.type === "PrivateName") {
return t.classPrivateProperty(key, value, undefined, isStatic);
} else {
return t.classProperty(key, value, undefined, undefined, isStatic);
}
}
function addProxyAccessorsFor(
element: NodePath<ClassDecoratableElement>,
originalKey: t.PrivateName | t.Expression,
targetKey: t.PrivateName,
isComputed = false,
): void {
const { static: isStatic } = element.node;
const getterBody = t.blockStatement([
t.returnStatement(
t.memberExpression(t.thisExpression(), t.cloneNode(targetKey)),
),
]);
const setterBody = t.blockStatement([
t.expressionStatement(
t.assignmentExpression(
"=",
t.memberExpression(t.thisExpression(), t.cloneNode(targetKey)),
t.identifier("v"),
),
),
]);
let getter: t.ClassMethod | t.ClassPrivateMethod,
setter: t.ClassMethod | t.ClassPrivateMethod;
if (originalKey.type === "PrivateName") {
getter = t.classPrivateMethod(
"get",
t.cloneNode(originalKey),
[],
getterBody,
isStatic,
);
setter = t.classPrivateMethod(
"set",
t.cloneNode(originalKey),
[t.identifier("v")],
setterBody,
isStatic,
);
} else {
getter = t.classMethod(
"get",
t.cloneNode(originalKey),
[],
getterBody,
isComputed,
isStatic,
);
setter = t.classMethod(
"set",
t.cloneNode(originalKey),
[t.identifier("v")],
setterBody,
isComputed,
isStatic,
);
}
element.insertAfter(setter);
element.insertAfter(getter);
}
function extractProxyAccessorsFor(
targetKey: t.PrivateName,
): t.FunctionExpression[] {
return [
t.functionExpression(
undefined,
[],
t.blockStatement([
t.returnStatement(
t.memberExpression(t.thisExpression(), t.cloneNode(targetKey)),
),
]),
),
t.functionExpression(
undefined,
[t.identifier("value")],
t.blockStatement([
t.expressionStatement(
t.assignmentExpression(
"=",
t.memberExpression(t.thisExpression(), t.cloneNode(targetKey)),
t.identifier("value"),
),
),
]),
),
];
}
const FIELD = 0;
const ACCESSOR = 1;
const METHOD = 2;
const GETTER = 3;
const SETTER = 4;
const STATIC = 5;
function getElementKind(element: NodePath<ClassDecoratableElement>): number {
switch (element.node.type) {
case "ClassProperty":
case "ClassPrivateProperty":
return FIELD;
case "ClassAccessorProperty":
return ACCESSOR;
case "ClassMethod":
case "ClassPrivateMethod":
if (element.node.kind === "get") {
return GETTER;
} else if (element.node.kind === "set") {
return SETTER;
} else {
return METHOD;
}
}
}
function generateLocalVarId(path: NodePath, name: string): t.Identifier {
const varId = path.scope.generateUidIdentifier(name);
path.scope.parent.push({ id: varId });
return t.cloneNode(varId);
}
// Information about the decorators applied to an element
interface DecoratorInfo {
// The expressions of the decorators themselves
decorators: t.Expression[];
// The kind of the decorated value, matches the kind value passed to applyDecs
kind: number;
// whether or not the field is static
isStatic: boolean;
// The name of the decorator
name: t.StringLiteral | t.Expression;
privateMethods: t.FunctionExpression | t.FunctionExpression[] | undefined;
// The names of local variables that will be used/returned from the decoration
locals: t.Identifier | t.Identifier[] | undefined;
}
// Information about a computed property key. These must be evaluated
// interspersed with decorator expressions, which is why they get added to the
// array of DecoratorInfos later on.
interface ComputedPropInfo {
localComputedNameId: t.Identifier;
keyNode: t.Expression;
}
function isDecoratorInfo(
info: DecoratorInfo | ComputedPropInfo,
): info is DecoratorInfo {
return "decorators" in info;
}
function generateDecorationExprs(
info: (DecoratorInfo | ComputedPropInfo)[],
): t.ArrayExpression {
return t.arrayExpression(
info.filter(isDecoratorInfo).map(el => {
const decs =
el.decorators.length > 1
? t.arrayExpression(el.decorators)
: el.decorators[0];
const kind = el.isStatic ? el.kind + STATIC : el.kind;
const decInfo = [decs, t.numericLiteral(kind), el.name];
const { privateMethods } = el;
if (Array.isArray(privateMethods)) {
decInfo.push(...privateMethods);
} else if (privateMethods) {
decInfo.push(privateMethods);
}
return t.arrayExpression(decInfo);
}),
);
}
function extractElementLocalAssignments(
decorationInfo: (DecoratorInfo | ComputedPropInfo)[],
) {
const locals: t.Identifier[] = [];
for (const el of decorationInfo) {
if ("locals" in el && el.locals) {
if (Array.isArray(el.locals)) {
locals.push(...el.locals);
} else {
locals.push(el.locals);
}
}
}
return locals;
}
function addCallAccessorsFor(
element: NodePath,
key: t.PrivateName,
getId: t.Identifier,
setId: t.Identifier,
) {
element.insertAfter(
t.classPrivateMethod(
"get",
t.cloneNode(key),
[],
t.blockStatement([
t.expressionStatement(
t.callExpression(t.cloneNode(getId), [t.thisExpression()]),
),
]),
),
);
element.insertAfter(
t.classPrivateMethod(
"set",
t.cloneNode(key),
[t.identifier("v")],
t.blockStatement([
t.expressionStatement(
t.callExpression(t.cloneNode(setId), [
t.thisExpression(),
t.identifier("v"),
]),
),
]),
),
);
}
function isNotTsParameter(
node: t.Identifier | t.Pattern | t.RestElement | t.TSParameterProperty,
): node is t.Identifier | t.Pattern | t.RestElement {
return node.type !== "TSParameterProperty";
}
function movePrivateAccessor(
element: NodePath<t.ClassPrivateMethod>,
key: t.PrivateName,
methodLocalVar: t.Identifier,
isStatic: boolean,
) {
let params: (t.Identifier | t.RestElement)[];
let block: t.Statement[];
if (element.node.kind === "set") {
params = [t.identifier("v")];
block = [
t.expressionStatement(
t.callExpression(methodLocalVar, [
t.thisExpression(),
t.identifier("v"),
]),
),
];
} else {
params = [];
block = [
t.returnStatement(t.callExpression(methodLocalVar, [t.thisExpression()])),
];
}
element.replaceWith(
t.classPrivateMethod(
element.node.kind,
t.cloneNode(key),
params,
t.blockStatement(block),
isStatic,
),
);
}
function isClassDecoratableElementPath(
path: NodePath<ClassElement>,
): path is NodePath<ClassDecoratableElement> {
const { type } = path;
return (
type !== "TSDeclareMethod" &&
type !== "TSIndexSignature" &&
type !== "StaticBlock"
);
}
function transformClass(
path: NodePath<t.ClassExpression | t.ClassDeclaration>,
state: any,
constantSuper: boolean,
): NodePath {
const body = path.get("body.body");
const classDecorators = path.node.decorators;
let hasElementDecorators = false;
const generateClassUid = createLazyUidGeneratorForClass(body);
// Iterate over the class to see if we need to decorate it, and also to
// transform simple auto accessors which are not decorated
for (const element of body) {
if (!isClassDecoratableElementPath(element)) {
continue;
}
if (element.node.decorators) {
hasElementDecorators = true;
} else if (element.node.type === "ClassAccessorProperty") {
const { key, value, static: isStatic } = element.node;
const newId = generateClassUid(true);
const valueNode = value ? t.cloneNode(value) : undefined;
const newField = generateClassProperty(newId, valueNode, isStatic);
const [newPath] = element.replaceWith(newField);
addProxyAccessorsFor(newPath, key, newId, element.node.computed);
}
}
// If nothing is decorated, return
if (!classDecorators && !hasElementDecorators) return;
const elementDecoratorInfo: (DecoratorInfo | ComputedPropInfo)[] = [];
let firstFieldPath:
| NodePath<t.ClassProperty | t.ClassPrivateProperty>
| undefined;
let constructorPath: NodePath<t.ClassMethod> | undefined;
let requiresProtoInit = false;
let requiresStaticInit = false;
let hasComputedProps = false;
const decoratedPrivateMethods = new Set<string>();
let protoInitLocal: t.Identifier,
staticInitLocal: t.Identifier,
classInitLocal: t.Identifier,
classLocal: t.Identifier;
if (classDecorators) {
classInitLocal = generateLocalVarId(path, "initClass");
const [localId, classPath] = replaceClassWithVar(path);
path = classPath;
classLocal = localId;
path.node.decorators = null;
} else {
if (!path.node.id) {
path.node.id = path.scope.generateUidIdentifier("Class");
}
classLocal = t.cloneNode(path.node.id);
}
if (hasElementDecorators) {
for (const element of body) {
if (!isClassDecoratableElementPath(element)) {
continue;
}
let { key } = element.node;
const kind = getElementKind(element);
const decorators = element.get("decorators");
const isPrivate = key.type === "PrivateName";
const isComputed =
"computed" in element.node && element.node.computed === true;
const isStatic = !!element.node.static;
let name = "computedKey";
if (isPrivate) {
name = (key as t.PrivateName).id.name;
} else if (key.type === "Identifier") {
name = key.name;
}
if (
element.node.type === "ClassMethod" &&
element.node.kind === "constructor"
) {
constructorPath = element as NodePath<t.ClassMethod>;
}
if (isComputed) {
const keyPath = element.get("key");
const localComputedNameId = generateLocalVarId(keyPath, name);
keyPath.replaceWith(localComputedNameId);
elementDecoratorInfo.push({
localComputedNameId: t.cloneNode(localComputedNameId),
keyNode: t.cloneNode(key as t.Expression),
});
key = localComputedNameId;
hasComputedProps = true;
}
if (Array.isArray(decorators)) {
let locals: t.Identifier | t.Identifier[];
let privateMethods: t.FunctionExpression | t.FunctionExpression[];
if (kind === ACCESSOR) {
const { value } = element.node as t.ClassAccessorProperty;
const params: t.Expression[] = [t.thisExpression()];
if (value) {
params.push(t.cloneNode(value));
}
const newId = generateClassUid(true);
const newFieldInitId = generateLocalVarId(element, `init_${name}`);
const newValue = t.callExpression(
t.cloneNode(newFieldInitId),
params,
);
const newField = generateClassProperty(newId, newValue, isStatic);
const [newPath] = element.replaceWith(newField);
if (isPrivate) {
privateMethods = extractProxyAccessorsFor(newId);
const getId = generateLocalVarId(newPath, `get_${name}`);
const setId = generateLocalVarId(newPath, `set_${name}`);
addCallAccessorsFor(newPath, key as t.PrivateName, getId, setId);
locals = [newFieldInitId, getId, setId];
} else {
addProxyAccessorsFor(newPath, key, newId, isComputed);
locals = newFieldInitId;
}
} else if (kind === FIELD) {
const initId = generateLocalVarId(element, `init_${name}`);
const valuePath = (
element as NodePath<t.ClassProperty | t.ClassPrivateProperty>
).get("value");
valuePath.replaceWith(
t.callExpression(
t.cloneNode(initId),
[t.thisExpression(), valuePath.node].filter(v => v),
),
);
locals = initId;
if (isPrivate) {
privateMethods = extractProxyAccessorsFor(key as t.PrivateName);
}
} else if (isPrivate) {
locals = generateLocalVarId(element, `call_${name}`);
const replaceSupers = new ReplaceSupers({
constantSuper,
methodPath: element,
objectRef: classLocal,
superRef: path.node.superClass,
file: state,
refToPreserve: classLocal,
});
replaceSupers.replace();
const {
params,
body,
async: isAsync,
} = element.node as t.ClassPrivateMethod;
privateMethods = t.functionExpression(
undefined,
params.filter(isNotTsParameter),
body,
isAsync,
);
if (kind === GETTER || kind === SETTER) {
movePrivateAccessor(
element as NodePath<t.ClassPrivateMethod>,
t.cloneNode(key as t.PrivateName),
t.cloneNode(locals),
isStatic,
);
} else {
const node = element.node as t.ClassPrivateMethod;
// Unshift
path.node.body.body.unshift(
t.classPrivateProperty(
key as t.PrivateName,
t.cloneNode(locals),
[],
node.static,
),
);
decoratedPrivateMethods.add((key as t.PrivateName).id.name);
element.remove();
}
}
let nameExpr: t.Expression;
if (isComputed) {
nameExpr = t.cloneNode(key as t.Expression);
} else if (key.type === "PrivateName") {
nameExpr = t.stringLiteral(key.id.name);
} else if (key.type === "Identifier") {
nameExpr = t.stringLiteral(key.name);
} else {
nameExpr = t.cloneNode(key as t.Expression);
}
elementDecoratorInfo.push({
kind,
decorators: decorators.map(d => d.node.expression),
name: nameExpr,
isStatic,
privateMethods,
locals,
});
if (kind !== FIELD) {
if (isStatic) {
requiresStaticInit = true;
} else {
requiresProtoInit = true;
}
}
if (element.node) {
element.node.decorators = null;
}
if (!firstFieldPath && (kind === FIELD || kind === ACCESSOR)) {
firstFieldPath = element as NodePath<
t.ClassProperty | t.ClassPrivateProperty
>;
}
}
}
}
if (hasComputedProps) {
const assignments: t.AssignmentExpression[] = [];
for (const info of elementDecoratorInfo) {
if (isDecoratorInfo(info)) {
const { decorators } = info;
const newDecorators: t.Identifier[] = [];
for (const decorator of decorators) {
const localComputedNameId = generateLocalVarId(path, "dec");
assignments.push(
t.assignmentExpression("=", localComputedNameId, decorator),
);
newDecorators.push(t.cloneNode(localComputedNameId));
}
info.decorators = newDecorators;
} else {
assignments.push(
t.assignmentExpression("=", info.localComputedNameId, info.keyNode),
);
}
}
path.insertBefore(assignments);
}
const elementDecorations = generateDecorationExprs(elementDecoratorInfo);
const classDecorations = t.arrayExpression(
(classDecorators || []).map(d => d.expression),
);
const locals: t.Identifier[] =
extractElementLocalAssignments(elementDecoratorInfo);
if (classDecorators) {
locals.push(classLocal, classInitLocal);
}
if (requiresProtoInit) {
protoInitLocal = generateLocalVarId(path, "initProto");
locals.push(protoInitLocal);
const protoInitCall = t.callExpression(t.cloneNode(protoInitLocal), [
t.thisExpression(),
]);
if (firstFieldPath) {
const value = firstFieldPath.get("value");
const body: t.Expression[] = [protoInitCall];
if (value.node) {
body.push(value.node);
}
value.replaceWith(t.sequenceExpression(body));
} else if (constructorPath) {
if (path.node.superClass) {
let found = false;
path.traverse({
Super(path) {
const { parentPath } = path;
if (found || parentPath.node.type !== "CallExpression") return;
found = true;
const prop = generateLocalVarId(path, "super");
parentPath.replaceWith(
t.sequenceExpression([
t.assignmentExpression("=", t.cloneNode(prop), parentPath.node),
protoInitCall,
t.cloneNode(prop),
]),
);
},
});
} else {
constructorPath.node.body.body.unshift(
t.expressionStatement(protoInitCall),
);
}
} else {
const body: t.Statement[] = [t.expressionStatement(protoInitCall)];
if (path.node.superClass) {
body.unshift(
t.expressionStatement(
t.callExpression(t.super(), [
t.spreadElement(t.identifier("args")),
]),
),
);
}
path.node.body.body.unshift(
t.classMethod(
"constructor",
t.identifier("constructor"),
[t.restElement(t.identifier("args"))],
t.blockStatement(body),
),
);
}
}
if (requiresStaticInit) {
staticInitLocal = generateLocalVarId(path, "initStatic");
locals.push(staticInitLocal);
}
const staticBlock = t.staticBlock(
[
t.expressionStatement(
t.assignmentExpression(
"=",
t.arrayPattern(locals),
t.callExpression(state.addHelper("applyDecs"), [
t.thisExpression(),
elementDecorations,
classDecorations,
]),
),
),
requiresStaticInit &&
t.expressionStatement(
t.callExpression(t.cloneNode(staticInitLocal), [t.thisExpression()]),
),
].filter(v => v),
);
path.node.body.body.unshift(staticBlock as unknown as ClassElement);
if (classInitLocal) {
path.node.body.body.push(
t.staticBlock([
t.expressionStatement(
t.callExpression(t.cloneNode(classInitLocal), []),
),
]),
);
}
if (decoratedPrivateMethods.size > 0) {
path.traverse({
PrivateName(path) {
if (!decoratedPrivateMethods.has(path.node.id.name)) return;
const parentPath = path.parentPath;
const parentParentPath = parentPath.parentPath;
if (
// this.bar().#x = 123;
(parentParentPath.node.type === "AssignmentExpression" &&
parentParentPath.node.left === parentPath.node) ||
// this.#x++;
parentParentPath.node.type === "UpdateExpression" ||
// ([...this.#x] = foo);
parentParentPath.node.type === "RestElement" ||
// ([this.#x] = foo);
parentParentPath.node.type === "ArrayPattern" ||
// ({ a: this.#x } = bar);
(parentParentPath.node.type === "ObjectProperty" &&
parentParentPath.node.value === parentPath.node &&
parentParentPath.parentPath.type === "ObjectPattern") ||
// for (this.#x of []);
(parentParentPath.node.type === "ForOfStatement" &&
parentParentPath.node.left === parentPath.node)
) {
throw path.buildCodeFrameError(
`Decorated private methods are not updatable, but "#${path.node.id.name}" is updated via this expression.`,
);
}
},
});
}
// Recrawl the scope to make sure new identifiers are properly synced
path.scope.crawl();
return path;
}
export default function (
{ assertVersion, assumption },
{ decoratorsBeforeExport, loose },
) {
assertVersion("^7.16.0");
const VISITED = new WeakSet<NodePath>();
const constantSuper = assumption("constantSuper") ?? loose;
return {
name: "proposal-decorators",
inherits: syntaxDecorators,
manipulateOptions({ generatorOpts }) {
generatorOpts.decoratorsBeforeExport = decoratorsBeforeExport;
},
visitor: {
ClassDeclaration(path: NodePath<t.ClassDeclaration>, state: any) {
if (VISITED.has(path)) return;
const newPath = transformClass(path, state, constantSuper);
if (newPath) {
VISITED.add(newPath);
}
},
ClassExpression(path: NodePath<t.ClassExpression>, state: any) {
if (VISITED.has(path)) return;
const newPath = transformClass(path, state, constantSuper);
if (newPath) {
VISITED.add(newPath);
}
},
},
};
}

View File

@ -0,0 +1,11 @@
{
"plugins": [
[
"proposal-decorators",
{ "version": "2021-12", "decoratorsBeforeExport": false }
],
"proposal-class-properties",
"proposal-private-methods",
"proposal-class-static-block"
]
}

View File

@ -0,0 +1,54 @@
function dec({ get, set }, context) {
context.addInitializer((instance) => {
instance[context.name + 'Context'] = context;
});
return {
get() {
return get.call(this) + 1;
},
set(v) {
set.call(this, v + 1);
},
initializer(v) {
return v ? v : 1;
}
}
}
class Foo {
@dec
accessor #a;
@dec
accessor #b = 123;
}
let foo = new Foo();
const aContext = foo['#aContext'];
const bContext = foo['#bContext'];
expect(aContext.access.get.call(foo)).toBe(2);
aContext.access.set.call(foo, 123);
expect(aContext.access.get.call(foo)).toBe(125);
expect(aContext.name).toBe('#a');
expect(aContext.kind).toBe('accessor');
expect(aContext.isStatic).toBe(false);
expect(aContext.isPrivate).toBe(true);
expect(typeof aContext.addInitializer).toBe('function');
expect(typeof aContext.setMetadata).toBe('function');
expect(typeof aContext.getMetadata).toBe('function');
expect(bContext.access.get.call(foo)).toBe(124);
bContext.access.set.call(foo, 123);
expect(bContext.access.get.call(foo)).toBe(125);
expect(bContext.name).toBe('#b');
expect(bContext.kind).toBe('accessor');
expect(bContext.isStatic).toBe(false);
expect(bContext.isPrivate).toBe(true);
expect(typeof bContext.addInitializer).toBe('function');
expect(typeof bContext.setMetadata).toBe('function');
expect(typeof bContext.getMetadata).toBe('function');

View File

@ -0,0 +1,7 @@
class Foo {
@dec
accessor #a;
@dec
accessor #b = 123;
}

View File

@ -0,0 +1,59 @@
var _init_a, _get_a, _set_a, _init_b, _get_b, _set_b, _initProto;
var _A = /*#__PURE__*/new WeakMap();
var _a = /*#__PURE__*/new WeakMap();
var _B = /*#__PURE__*/new WeakMap();
var _b = /*#__PURE__*/new WeakMap();
class Foo {
constructor() {
babelHelpers.classPrivateFieldInitSpec(this, _b, {
get: _get_b2,
set: _set_b2
});
babelHelpers.classPrivateFieldInitSpec(this, _a, {
get: _get_a2,
set: _set_a2
});
babelHelpers.classPrivateFieldInitSpec(this, _A, {
writable: true,
value: (_initProto(this), _init_a(this))
});
babelHelpers.classPrivateFieldInitSpec(this, _B, {
writable: true,
value: _init_b(this, 123)
});
}
}
function _set_a2(v) {
_set_a(this, v);
}
function _get_a2() {
_get_a(this);
}
function _set_b2(v) {
_set_b(this, v);
}
function _get_b2() {
_get_b(this);
}
(() => {
[_init_a, _get_a, _set_a, _init_b, _get_b, _set_b, _initProto] = babelHelpers.applyDecs(Foo, [[dec, 1, "a", function () {
return babelHelpers.classPrivateFieldGet(this, _A);
}, function (value) {
babelHelpers.classPrivateFieldSet(this, _A, value);
}], [dec, 1, "b", function () {
return babelHelpers.classPrivateFieldGet(this, _B);
}, function (value) {
babelHelpers.classPrivateFieldSet(this, _B, value);
}]], []);
})();

View File

@ -0,0 +1,75 @@
function dec({ get, set }, context) {
context.addInitializer((instance) => {
instance[context.name + 'Context'] = context;
});
return {
get() {
return get.call(this) + 1;
},
set(v) {
set.call(this, v + 1);
},
initializer(v) {
return v ? v : 1;
}
}
}
class Foo {
@dec
accessor a;
@dec
accessor b = 123;
@dec
accessor ['c'] = 456;
}
let foo = new Foo();
const aContext = foo['aContext'];
const bContext = foo['bContext'];
const cContext = foo['cContext'];
expect(foo.a).toBe(2);
foo.a = 123;
expect(foo.a).toBe(125);
expect(aContext.name).toBe('a');
expect(aContext.kind).toBe('accessor');
expect(aContext.isStatic).toBe(false);
expect(aContext.isPrivate).toBe(false);
expect(typeof aContext.addInitializer).toBe('function');
expect(typeof aContext.setMetadata).toBe('function');
expect(typeof aContext.getMetadata).toBe('function');
expect(foo.hasOwnProperty('a')).toBe(false);
expect(Foo.prototype.hasOwnProperty('a')).toBe(true);
expect(foo.b).toBe(124);
foo.b = 123;
expect(foo.b).toBe(125);
expect(bContext.name).toBe('b');
expect(bContext.kind).toBe('accessor');
expect(bContext.isStatic).toBe(false);
expect(bContext.isPrivate).toBe(false);
expect(typeof bContext.addInitializer).toBe('function');
expect(typeof bContext.setMetadata).toBe('function');
expect(typeof bContext.getMetadata).toBe('function');
expect(foo.hasOwnProperty('b')).toBe(false);
expect(Foo.prototype.hasOwnProperty('b')).toBe(true);
expect(foo.c).toBe(457);
foo.c = 456;
expect(foo.c).toBe(458);
expect(cContext.name).toBe('c');
expect(cContext.kind).toBe('accessor');
expect(cContext.isStatic).toBe(false);
expect(cContext.isPrivate).toBe(false);
expect(typeof cContext.addInitializer).toBe('function');
expect(typeof cContext.setMetadata).toBe('function');
expect(typeof cContext.getMetadata).toBe('function');
expect(foo.hasOwnProperty('c')).toBe(false);
expect(Foo.prototype.hasOwnProperty('c')).toBe(true);

View File

@ -0,0 +1,10 @@
class Foo {
@dec
accessor a;
@dec
accessor b = 123;
@dec
accessor ['c'] = 456;
}

View File

@ -0,0 +1,58 @@
var _init_a, _init_b, _computedKey, _init_computedKey, _dec, _dec2, _dec3, _initProto;
_dec = dec
_dec2 = dec
_computedKey = 'c'
_dec3 = dec
var _A = /*#__PURE__*/new WeakMap();
var _B = /*#__PURE__*/new WeakMap();
var _C = /*#__PURE__*/new WeakMap();
class Foo {
constructor() {
babelHelpers.classPrivateFieldInitSpec(this, _A, {
writable: true,
value: (_initProto(this), _init_a(this))
});
babelHelpers.classPrivateFieldInitSpec(this, _B, {
writable: true,
value: _init_b(this, 123)
});
babelHelpers.classPrivateFieldInitSpec(this, _C, {
writable: true,
value: _init_computedKey(this, 456)
});
}
get a() {
return babelHelpers.classPrivateFieldGet(this, _A);
}
set a(v) {
babelHelpers.classPrivateFieldSet(this, _A, v);
}
get b() {
return babelHelpers.classPrivateFieldGet(this, _B);
}
set b(v) {
babelHelpers.classPrivateFieldSet(this, _B, v);
}
get [_computedKey]() {
return babelHelpers.classPrivateFieldGet(this, _C);
}
set [_computedKey](v) {
babelHelpers.classPrivateFieldSet(this, _C, v);
}
}
(() => {
[_init_a, _init_b, _init_computedKey, _initProto] = babelHelpers.applyDecs(Foo, [[_dec, 1, "a"], [_dec2, 1, "b"], [_dec3, 1, _computedKey]], []);
})();

View File

@ -0,0 +1,52 @@
function dec({ get, set }, context) {
context.addInitializer((instance) => {
instance[context.name + 'Context'] = context;
});
return {
get() {
return get.call(this) + 1;
},
set(v) {
set.call(this, v + 1);
},
initializer(v) {
return v ? v : 1;
}
}
}
class Foo {
@dec
static accessor #a;
@dec
static accessor #b = 123;
}
const aContext = Foo['#aContext'];
const bContext = Foo['#bContext'];
expect(aContext.access.get.call(Foo)).toBe(2);
aContext.access.set.call(Foo, 123);
expect(aContext.access.get.call(Foo)).toBe(125);
expect(aContext.name).toBe('#a');
expect(aContext.kind).toBe('accessor');
expect(aContext.isStatic).toBe(true);
expect(aContext.isPrivate).toBe(true);
expect(typeof aContext.addInitializer).toBe('function');
expect(typeof aContext.setMetadata).toBe('function');
expect(typeof aContext.getMetadata).toBe('function');
expect(bContext.access.get.call(Foo)).toBe(124);
bContext.access.set.call(Foo, 123);
expect(bContext.access.get.call(Foo)).toBe(125);
expect(bContext.name).toBe('#b');
expect(bContext.kind).toBe('accessor');
expect(bContext.isStatic).toBe(true);
expect(bContext.isPrivate).toBe(true);
expect(typeof bContext.addInitializer).toBe('function');
expect(typeof bContext.setMetadata).toBe('function');
expect(typeof bContext.getMetadata).toBe('function');

View File

@ -0,0 +1,7 @@
class Foo {
@dec
static accessor #a;
@dec
static accessor #b = 123;
}

View File

@ -0,0 +1,58 @@
var _init_a, _get_a, _set_a, _init_b, _get_b, _set_b, _initStatic;
var _a = /*#__PURE__*/new WeakMap();
var _b = /*#__PURE__*/new WeakMap();
class Foo {
constructor() {
babelHelpers.classPrivateFieldInitSpec(this, _b, {
get: _get_b2,
set: _set_b2
});
babelHelpers.classPrivateFieldInitSpec(this, _a, {
get: _get_a2,
set: _set_a2
});
}
}
function _set_a2(v) {
_set_a(this, v);
}
function _get_a2() {
_get_a(this);
}
function _set_b2(v) {
_set_b(this, v);
}
function _get_b2() {
_get_b(this);
}
(() => {
[_init_a, _get_a, _set_a, _init_b, _get_b, _set_b, _initStatic] = babelHelpers.applyDecs(Foo, [[dec, 6, "a", function () {
return babelHelpers.classStaticPrivateFieldSpecGet(this, Foo, _A);
}, function (value) {
babelHelpers.classStaticPrivateFieldSpecSet(this, Foo, _A, value);
}], [dec, 6, "b", function () {
return babelHelpers.classStaticPrivateFieldSpecGet(this, Foo, _B);
}, function (value) {
babelHelpers.classStaticPrivateFieldSpecSet(this, Foo, _B, value);
}]], []);
_initStatic(Foo);
})();
var _A = {
writable: true,
value: _init_a(Foo)
};
var _B = {
writable: true,
value: _init_b(Foo, 123)
};

View File

@ -0,0 +1,70 @@
function dec({ get, set }, context) {
context.addInitializer((instance) => {
instance[context.name + 'Context'] = context;
});
return {
get() {
return get.call(this) + 1;
},
set(v) {
set.call(this, v + 1);
},
initializer(v) {
return v ? v : 1;
}
}
}
class Foo {
@dec
static accessor a;
@dec
static accessor b = 123;
@dec
static accessor ['c'] = 456;
}
const aContext = Foo['aContext'];
const bContext = Foo['bContext'];
const cContext = Foo['cContext'];
expect(Foo.a).toBe(2);
Foo.a = 123;
expect(Foo.a).toBe(125);
expect(aContext.name).toBe('a');
expect(aContext.kind).toBe('accessor');
expect(aContext.isStatic).toBe(true);
expect(aContext.isPrivate).toBe(false);
expect(typeof aContext.addInitializer).toBe('function');
expect(typeof aContext.setMetadata).toBe('function');
expect(typeof aContext.getMetadata).toBe('function');
expect(Foo.hasOwnProperty('a')).toBe(true);
expect(Foo.b).toBe(124);
Foo.b = 123;
expect(Foo.b).toBe(125);
expect(bContext.name).toBe('b');
expect(bContext.kind).toBe('accessor');
expect(bContext.isStatic).toBe(true);
expect(bContext.isPrivate).toBe(false);
expect(typeof bContext.addInitializer).toBe('function');
expect(typeof bContext.setMetadata).toBe('function');
expect(typeof bContext.getMetadata).toBe('function');
expect(Foo.hasOwnProperty('b')).toBe(true);
expect(Foo.c).toBe(457);
Foo.c = 456;
expect(Foo.c).toBe(458);
expect(cContext.name).toBe('c');
expect(cContext.kind).toBe('accessor');
expect(cContext.isStatic).toBe(true);
expect(cContext.isPrivate).toBe(false);
expect(typeof cContext.addInitializer).toBe('function');
expect(typeof cContext.setMetadata).toBe('function');
expect(typeof cContext.getMetadata).toBe('function');
expect(Foo.hasOwnProperty('c')).toBe(true);

View File

@ -0,0 +1,10 @@
class Foo {
@dec
static accessor a;
@dec
static accessor b = 123;
@dec
static accessor ['c'] = 456;
}

View File

@ -0,0 +1,52 @@
var _init_a, _init_b, _computedKey, _init_computedKey, _dec, _dec2, _dec3, _initStatic;
_dec = dec
_dec2 = dec
_computedKey = 'c'
_dec3 = dec
class Foo {
static get a() {
return babelHelpers.classStaticPrivateFieldSpecGet(this, Foo, _A);
}
static set a(v) {
babelHelpers.classStaticPrivateFieldSpecSet(this, Foo, _A, v);
}
static get b() {
return babelHelpers.classStaticPrivateFieldSpecGet(this, Foo, _B);
}
static set b(v) {
babelHelpers.classStaticPrivateFieldSpecSet(this, Foo, _B, v);
}
static get [_computedKey]() {
return babelHelpers.classStaticPrivateFieldSpecGet(this, Foo, _C);
}
static set [_computedKey](v) {
babelHelpers.classStaticPrivateFieldSpecSet(this, Foo, _C, v);
}
}
(() => {
[_init_a, _init_b, _init_computedKey, _initStatic] = babelHelpers.applyDecs(Foo, [[_dec, 6, "a"], [_dec2, 6, "b"], [_dec3, 6, _computedKey]], []);
_initStatic(Foo);
})();
var _A = {
writable: true,
value: _init_a(Foo)
};
var _B = {
writable: true,
value: _init_b(Foo, 123)
};
var _C = {
writable: true,
value: _init_computedKey(Foo, 456)
};

View File

@ -0,0 +1,31 @@
class Foo {
accessor #a;
accessor #b = 123;
getA() {
return this.#a;
}
setA(v) {
this.#a = v;
}
getB() {
return this.#b;
}
setB(v) {
this.#b = v;
}
}
let foo = new Foo();
expect(foo.getA()).toBe(undefined);
foo.setA(123)
expect(foo.getA()).toBe(123);
expect(foo.getB()).toBe(123);
foo.setB(456)
expect(foo.getB()).toBe(456);

View File

@ -0,0 +1,5 @@
class Foo {
accessor #a;
accessor #b = 123;
}

View File

@ -0,0 +1,45 @@
var _A = /*#__PURE__*/new WeakMap();
var _a = /*#__PURE__*/new WeakMap();
var _B = /*#__PURE__*/new WeakMap();
var _b = /*#__PURE__*/new WeakMap();
class Foo {
constructor() {
babelHelpers.classPrivateFieldInitSpec(this, _b, {
get: _get_b,
set: _set_b
});
babelHelpers.classPrivateFieldInitSpec(this, _a, {
get: _get_a,
set: _set_a
});
babelHelpers.classPrivateFieldInitSpec(this, _A, {
writable: true,
value: void 0
});
babelHelpers.classPrivateFieldInitSpec(this, _B, {
writable: true,
value: 123
});
}
}
function _get_a() {
return babelHelpers.classPrivateFieldGet(this, _A);
}
function _set_a(v) {
babelHelpers.classPrivateFieldSet(this, _A, v);
}
function _get_b() {
return babelHelpers.classPrivateFieldGet(this, _B);
}
function _set_b(v) {
babelHelpers.classPrivateFieldSet(this, _B, v);
}

View File

@ -0,0 +1,27 @@
class Foo {
accessor a;
accessor b = 123;
accessor ['c'] = 456;
}
let foo = new Foo();
expect(foo.a).toBe(undefined);
foo.a = 123;
expect(foo.a).toBe(123);
expect(foo.hasOwnProperty('a')).toBe(false);
expect(Foo.prototype.hasOwnProperty('a')).toBe(true);
expect(foo.b).toBe(123);
foo.b = 456
expect(foo.b).toBe(456);
expect(foo.hasOwnProperty('b')).toBe(false);
expect(Foo.prototype.hasOwnProperty('b')).toBe(true);
expect(foo.c).toBe(456);
foo.c = 789
expect(foo.c).toBe(789);
expect(foo.hasOwnProperty('c')).toBe(false);
expect(Foo.prototype.hasOwnProperty('c')).toBe(true);

View File

@ -0,0 +1,7 @@
class Foo {
accessor a;
accessor b = 123;
accessor ['c'] = 456;
}

View File

@ -0,0 +1,47 @@
var _A = /*#__PURE__*/new WeakMap();
var _B = /*#__PURE__*/new WeakMap();
var _C = /*#__PURE__*/new WeakMap();
class Foo {
constructor() {
babelHelpers.classPrivateFieldInitSpec(this, _A, {
writable: true,
value: void 0
});
babelHelpers.classPrivateFieldInitSpec(this, _B, {
writable: true,
value: 123
});
babelHelpers.classPrivateFieldInitSpec(this, _C, {
writable: true,
value: 456
});
}
get a() {
return babelHelpers.classPrivateFieldGet(this, _A);
}
set a(v) {
babelHelpers.classPrivateFieldSet(this, _A, v);
}
get b() {
return babelHelpers.classPrivateFieldGet(this, _B);
}
set b(v) {
babelHelpers.classPrivateFieldSet(this, _B, v);
}
get 'c'() {
return babelHelpers.classPrivateFieldGet(this, _C);
}
set 'c'(v) {
babelHelpers.classPrivateFieldSet(this, _C, v);
}
}

View File

@ -0,0 +1,29 @@
class Foo {
static accessor #a;
static accessor #b = 123;
static getA() {
return this.#a;
}
static setA(v) {
this.#a = v;
}
static getB() {
return this.#b;
}
static setB(v) {
this.#b = v;
}
}
expect(Foo.getA()).toBe(undefined);
Foo.setA(123)
expect(Foo.getA()).toBe(123);
expect(Foo.getB()).toBe(123);
Foo.setB(456)
expect(Foo.getB()).toBe(456);

View File

@ -0,0 +1,5 @@
class Foo {
static accessor #a;
static accessor #b = 123;
}

View File

@ -0,0 +1,34 @@
class Foo {}
function _get_a() {
return babelHelpers.classStaticPrivateFieldSpecGet(this, Foo, _A);
}
function _set_a(v) {
babelHelpers.classStaticPrivateFieldSpecSet(this, Foo, _A, v);
}
function _get_b() {
return babelHelpers.classStaticPrivateFieldSpecGet(this, Foo, _B);
}
function _set_b(v) {
babelHelpers.classStaticPrivateFieldSpecSet(this, Foo, _B, v);
}
var _b = {
get: _get_b,
set: _set_b
};
var _a = {
get: _get_a,
set: _set_a
};
var _A = {
writable: true,
value: void 0
};
var _B = {
writable: true,
value: 123
};

View File

@ -0,0 +1,22 @@
class Foo {
static accessor a;
static accessor b = 123;
static accessor ['c'] = 456;
}
expect(Foo.a).toBe(undefined);
Foo.a = 123;
expect(Foo.a).toBe(123);
expect(Foo.hasOwnProperty('a')).toBe(true);
expect(Foo.b).toBe(123);
Foo.b = 456
expect(Foo.b).toBe(456);
expect(Foo.hasOwnProperty('b')).toBe(true);
expect(Foo.c).toBe(456);
Foo.c = 789
expect(Foo.c).toBe(789);
expect(Foo.hasOwnProperty('c')).toBe(true);

View File

@ -0,0 +1,7 @@
class Foo {
static accessor a;
static accessor b = 123;
static accessor ['c'] = 456;
}

View File

@ -0,0 +1,39 @@
class Foo {
static get a() {
return babelHelpers.classStaticPrivateFieldSpecGet(this, Foo, _A);
}
static set a(v) {
babelHelpers.classStaticPrivateFieldSpecSet(this, Foo, _A, v);
}
static get b() {
return babelHelpers.classStaticPrivateFieldSpecGet(this, Foo, _B);
}
static set b(v) {
babelHelpers.classStaticPrivateFieldSpecSet(this, Foo, _B, v);
}
static get 'c'() {
return babelHelpers.classStaticPrivateFieldSpecGet(this, Foo, _C);
}
static set 'c'(v) {
babelHelpers.classStaticPrivateFieldSpecSet(this, Foo, _C, v);
}
}
var _A = {
writable: true,
value: void 0
};
var _B = {
writable: true,
value: 123
};
var _C = {
writable: true,
value: 456
};

View File

@ -0,0 +1,11 @@
const A = @dec class A {}
const B = @dec class C {}
const D = @dec class {}
const E = (@dec class {}, 123);
const F = [@dec class G {}, @dec class {}];
const H = @dec class extends I {};
const J = @dec class K extends L {};
function classFactory() {
return @dec class {}
}

View File

@ -0,0 +1,51 @@
var _initClass, _A, _class, _temp, _initClass2, _C, _class2, _temp2, _initClass3, _D, _class3, _temp3, _initClass4, _decorated_class, _class4, _temp4, _initClass5, _G, _class5, _temp5, _initClass6, _decorated_class2, _class6, _temp6, _initClass7, _H, _class7, _temp7, _initClass8, _K, _class8, _temp8;
const A = ((_temp = _class = class A {}, (() => {
[_A, _initClass] = babelHelpers.applyDecs(_class, [], [dec]);
})(), (() => {
_initClass();
})(), _temp), _A);
const B = ((_temp2 = _class2 = class C {}, (() => {
[_C, _initClass2] = babelHelpers.applyDecs(_class2, [], [dec]);
})(), (() => {
_initClass2();
})(), _temp2), _C);
const D = ((_temp3 = _class3 = class D {}, (() => {
[_D, _initClass3] = babelHelpers.applyDecs(_class3, [], [dec]);
})(), (() => {
_initClass3();
})(), _temp3), _D);
const E = (((_temp4 = _class4 = class {}, (() => {
[_decorated_class, _initClass4] = babelHelpers.applyDecs(_class4, [], [dec]);
})(), (() => {
_initClass4();
})(), _temp4), _decorated_class), 123);
const F = [((_temp5 = _class5 = class G {}, (() => {
[_G, _initClass5] = babelHelpers.applyDecs(_class5, [], [dec]);
})(), (() => {
_initClass5();
})(), _temp5), _G), ((_temp6 = _class6 = class {}, (() => {
[_decorated_class2, _initClass6] = babelHelpers.applyDecs(_class6, [], [dec]);
})(), (() => {
_initClass6();
})(), _temp6), _decorated_class2)];
const H = ((_temp7 = _class7 = class H extends I {}, (() => {
[_H, _initClass7] = babelHelpers.applyDecs(_class7, [], [dec]);
})(), (() => {
_initClass7();
})(), _temp7), _H);
const J = ((_temp8 = _class8 = class K extends L {}, (() => {
[_K, _initClass8] = babelHelpers.applyDecs(_class8, [], [dec]);
})(), (() => {
_initClass8();
})(), _temp8), _K);
function classFactory() {
var _initClass9, _decorated_class3, _class9, _temp9;
return (_temp9 = _class9 = class {}, (() => {
[_decorated_class3, _initClass9] = babelHelpers.applyDecs(_class9, [], [dec]);
})(), (() => {
_initClass9();
})(), _temp9), _decorated_class3;
}

View File

@ -0,0 +1,18 @@
let count = 0;
function dec1(Klass) {
expect(++count).toBe(1);
expect(Klass.name).toBe('Bar');
}
@dec1
class Bar {}
function dec2(Klass) {
expect(++count).toBe(2);
expect(Klass.name).toBe('Foo');
expect(Object.getPrototypeOf(Klass)).toBe(Bar);
}
@dec2
class Foo extends Bar {}

View File

@ -0,0 +1,5 @@
@dec1
class Bar {}
@dec2
class Foo extends Bar {}

View File

@ -0,0 +1,25 @@
var _initClass, _initClass2;
let _Bar;
class Bar {}
(() => {
[_Bar, _initClass] = babelHelpers.applyDecs(Bar, [], [dec1]);
})();
(() => {
_initClass();
})();
let _Foo;
class Foo extends _Bar {}
(() => {
[_Foo, _initClass2] = babelHelpers.applyDecs(Foo, [], [dec2]);
})();
(() => {
_initClass2();
})();

View File

@ -0,0 +1,41 @@
function dec1(Foo, { addInitializer }) {
expect(Foo.field).toBe(undefined);
addInitializer(() => {
Foo.initField = 123;
});
}
@dec1
class Foo {
static {
expect(this.initField).toBe(undefined);
}
static field = 123;
}
expect(Foo.initField).toBe(123);
expect(Foo.field).toBe(123);
function dec2(Bar, { addInitializer }) {
expect(Bar.field).toBe(123);
expect(Bar.otherField).toBe(undefined);
expect(Bar.initField).toBe(123);
addInitializer(() => {
Bar.initField = 456;
});
}
@dec2
class Bar extends Foo {
static {
expect(this.initField).toBe(123);
this.otherField = 456;
}
static field = 456;
}
expect(Bar.initField).toBe(456);
expect(Bar.field).toBe(456);
expect(Bar.otherField).toBe(456);

View File

@ -0,0 +1,13 @@
@dec
class Foo {
static field = 123;
}
@dec
class Bar extends Foo {
static {
this.otherField = 456;
}
static field = 123;
}

View File

@ -0,0 +1,33 @@
var _initClass, _initClass2;
let _Foo;
class Foo {}
(() => {
[_Foo, _initClass] = babelHelpers.applyDecs(Foo, [], [dec]);
})();
babelHelpers.defineProperty(Foo, "field", 123);
(() => {
_initClass();
})();
let _Bar;
class Bar extends _Foo {}
(() => {
[_Bar, _initClass2] = babelHelpers.applyDecs(Bar, [], [dec]);
})();
(() => {
Bar.otherField = 456;
})();
babelHelpers.defineProperty(Bar, "field", 123);
(() => {
_initClass2();
})();

View File

@ -0,0 +1,11 @@
{
"plugins": [
[
"proposal-decorators",
{ "version": "2021-12", "decoratorsBeforeExport": false }
],
"proposal-class-properties",
"proposal-private-methods",
"proposal-class-static-block"
]
}

View File

@ -0,0 +1,17 @@
let replaced;
function dec(Klass) {
replaced = class extends Klass {};
return replaced;
}
const Foo = @dec class Bar {
static bar = new Bar();
};
const foo = new Foo();
expect(Foo).toBe(replaced);
expect(Foo.bar).toBeInstanceOf(replaced);
expect(foo).toBeInstanceOf(replaced);

View File

@ -0,0 +1,6 @@
const Foo = @dec class Bar {
bar = new Bar();
};
const foo = new Foo();

View File

@ -0,0 +1,13 @@
var _initClass, _Bar, _class, _temp;
const Foo = ((_temp = _class = class Bar {
constructor() {
babelHelpers.defineProperty(this, "bar", new _Bar());
}
}, (() => {
[_Bar, _initClass] = babelHelpers.applyDecs(_class, [], [dec]);
})(), (() => {
_initClass();
})(), _temp), _Bar);
const foo = new Foo();

View File

@ -0,0 +1,19 @@
let replaced;
function dec(Klass) {
replaced = class extends Klass {};
return replaced;
}
@dec
class Foo {
static foo = new Foo();
}
const foo = new Foo();
expect(Foo).toBe(replaced);
expect(Foo.foo).toBeInstanceOf(replaced);
expect(foo).toBeInstanceOf(replaced);

View File

@ -0,0 +1,6 @@
@dec
class Foo {
static foo = new Foo();
}
const foo = new Foo();

View File

@ -0,0 +1,17 @@
var _initClass;
let _Foo;
class Foo {}
(() => {
[_Foo, _initClass] = babelHelpers.applyDecs(Foo, [], [dec]);
})();
babelHelpers.defineProperty(Foo, "foo", new _Foo());
(() => {
_initClass();
})();
const foo = new _Foo();

View File

@ -0,0 +1,33 @@
var i = 0;
function getKey() {
return (i++).toString();
}
let elements = [];
function dec(fn, context) {
elements.push({ fn, context });
}
class Foo {
@dec
[getKey()]() {
return 1;
}
@dec
[getKey()]() {
return 2;
}
}
expect(elements).toHaveLength(2);
expect(elements[0].context.name).toBe("0");
expect(elements[0].fn()).toBe(1);
expect(elements[1].context.name).toBe("1");
expect(elements[1].fn()).toBe(2);
expect(i).toBe(2);

View File

@ -0,0 +1,11 @@
class Foo {
@dec
[getKey()]() {
return 1;
}
@dec
[getKey()]() {
return 2;
}
}

View File

@ -0,0 +1,25 @@
var _computedKey, _computedKey2, _dec, _dec2, _initProto;
_computedKey = getKey()
_dec = dec
_computedKey2 = getKey()
_dec2 = dec
class Foo {
constructor(...args) {
_initProto(this);
}
[_computedKey]() {
return 1;
}
[_computedKey2]() {
return 2;
}
}
(() => {
[_initProto] = babelHelpers.applyDecs(Foo, [[_dec, 2, _computedKey], [_dec2, 2, _computedKey2]], []);
})();

View File

@ -0,0 +1,29 @@
expect(() => {
var i = 0;
var j = 0;
function getKeyI() {
return (i++).toString();
}
function getKeyJ() {
return (j++).toString();
}
let elements = [];
function dec(fn, context) {
elements.push({ fn, context });
}
class Foo {
@dec
[getKeyI()]() {
return 1;
}
@dec
[getKeyJ()]() {
return 2;
}
}
}).toThrow("Attempted to decorate a public method/accessor that has the same name as a previously decorated public method/accessor. This is not currently supported by the decorators plugin. Property name was: 0")

View File

@ -0,0 +1,11 @@
class Foo {
@dec
[getKeyI()]() {
return 1;
}
@dec
[getKeyJ()]() {
return 2;
}
}

View File

@ -0,0 +1,25 @@
var _computedKey, _computedKey2, _dec, _dec2, _initProto;
_computedKey = getKeyI()
_dec = dec
_computedKey2 = getKeyJ()
_dec2 = dec
class Foo {
constructor(...args) {
_initProto(this);
}
[_computedKey]() {
return 1;
}
[_computedKey2]() {
return 2;
}
}
(() => {
[_initProto] = babelHelpers.applyDecs(Foo, [[_dec, 2, _computedKey], [_dec2, 2, _computedKey2]], []);
})();

View File

@ -0,0 +1,23 @@
let elements = [];
function dec(val, context) {
elements.push({ val, context });
}
class Foo {
@dec
a = 123;
@dec
a() {
return 1;
}
}
expect(elements).toHaveLength(2);
expect(elements[0].context.name).toBe("a");
expect(elements[0].val).toBe(undefined);
expect(elements[1].context.name).toBe("a");
expect(elements[1].val()).toBe(1);

View File

@ -0,0 +1,9 @@
class Foo {
@dec
a = 123;
@dec
a() {
return 1;
}
}

View File

@ -0,0 +1,16 @@
var _init_a, _initProto;
class Foo {
constructor() {
babelHelpers.defineProperty(this, "a", (_initProto(this), _init_a(this, 123)));
}
a() {
return 1;
}
}
(() => {
[_init_a, _initProto] = babelHelpers.applyDecs(Foo, [[dec, 0, "a"], [dec, 2, "a"]], []);
})();

View File

@ -0,0 +1,19 @@
expect(() => {
let elements = [];
function dec(val, context) {
elements.push({ val, context });
}
class Foo {
@dec
a() {
return 1;
}
@dec
a() {
return 2;
}
}
}).toThrow("Attempted to decorate a public method/accessor that has the same name as a previously decorated public method/accessor. This is not currently supported by the decorators plugin. Property name was: a")

View File

@ -0,0 +1,11 @@
class Foo {
@dec
a() {
return 1;
}
@dec
a() {
return 2;
}
}

View File

@ -0,0 +1,20 @@
var _initProto;
class Foo {
constructor(...args) {
_initProto(this);
}
a() {
return 1;
}
a() {
return 2;
}
}
(() => {
[_initProto] = babelHelpers.applyDecs(Foo, [[dec, 2, "a"], [dec, 2, "a"]], []);
})();

View File

@ -0,0 +1,11 @@
{
"plugins": [
[
"proposal-decorators",
{ "version": "2021-12", "decoratorsBeforeExport": false }
],
"proposal-class-properties",
"proposal-private-methods",
"proposal-class-static-block"
]
}

View File

@ -0,0 +1,11 @@
{
"plugins": [
[
"proposal-decorators",
{ "version": "2021-12", "decoratorsBeforeExport": false }
],
"proposal-class-properties",
"proposal-private-methods",
"proposal-class-static-block"
]
}

View File

@ -0,0 +1,41 @@
function dec(_v, context) {
return function (v) {
this[context.name + 'Context'] = context;
return (v || 1) + 1;
}
}
class Foo {
@dec
#a;
@dec
#b = 123;
}
let foo = new Foo();
const aContext = foo['#aContext'];
const bContext = foo['#bContext'];
expect(aContext.access.get.call(foo)).toBe(2);
aContext.access.set.call(foo, 123);
expect(aContext.access.get.call(foo)).toBe(123);
expect(aContext.name).toBe('#a');
expect(aContext.kind).toBe('field');
expect(aContext.isStatic).toBe(false);
expect(aContext.isPrivate).toBe(true);
expect(typeof aContext.addInitializer).toBe('undefined');
expect(typeof aContext.setMetadata).toBe('function');
expect(typeof aContext.getMetadata).toBe('function');
expect(bContext.access.get.call(foo)).toBe(124);
bContext.access.set.call(foo, 123);
expect(bContext.access.get.call(foo)).toBe(123);
expect(bContext.name).toBe('#b');
expect(bContext.kind).toBe('field');
expect(bContext.isStatic).toBe(false);
expect(bContext.isPrivate).toBe(true);
expect(typeof aContext.addInitializer).toBe('undefined');
expect(typeof aContext.setMetadata).toBe('function');
expect(typeof aContext.getMetadata).toBe('function');

View File

@ -0,0 +1,7 @@
class Foo {
@dec
#a;
@dec
#b = 123;
}

View File

@ -0,0 +1,31 @@
var _init_a, _init_b;
var _a = /*#__PURE__*/new WeakMap();
var _b = /*#__PURE__*/new WeakMap();
class Foo {
constructor() {
babelHelpers.classPrivateFieldInitSpec(this, _a, {
writable: true,
value: _init_a(this)
});
babelHelpers.classPrivateFieldInitSpec(this, _b, {
writable: true,
value: _init_b(this, 123)
});
}
}
(() => {
[_init_a, _init_b] = babelHelpers.applyDecs(Foo, [[dec, 0, "a", function () {
return babelHelpers.classPrivateFieldGet(this, _a);
}, function (value) {
babelHelpers.classPrivateFieldSet(this, _a, value);
}], [dec, 0, "b", function () {
return babelHelpers.classPrivateFieldGet(this, _b);
}, function (value) {
babelHelpers.classPrivateFieldSet(this, _b, value);
}]], []);
})();

View File

@ -0,0 +1,62 @@
function dec(_v, context) {
return function (v) {
this[context.name + 'Context'] = context;
return (v || 1) + 1;
}
}
class Foo {
@dec
a;
@dec
b = 123;
@dec
['c'] = 456;
}
let foo = new Foo();
const aContext = foo['aContext'];
const bContext = foo['bContext'];
const cContext = foo['cContext'];
expect(foo.a).toBe(2);
foo.a = 123;
expect(foo.a).toBe(123);
expect(aContext.name).toBe('a');
expect(aContext.kind).toBe('field');
expect(aContext.isStatic).toBe(false);
expect(aContext.isPrivate).toBe(false);
expect(typeof aContext.addInitializer).toBe('undefined');
expect(typeof aContext.setMetadata).toBe('function');
expect(typeof aContext.getMetadata).toBe('function');
expect(foo.hasOwnProperty('a')).toBe(true);
expect(Foo.prototype.hasOwnProperty('a')).toBe(false);
expect(foo.b).toBe(124);
foo.b = 123;
expect(foo.b).toBe(123);
expect(bContext.name).toBe('b');
expect(bContext.kind).toBe('field');
expect(bContext.isStatic).toBe(false);
expect(bContext.isPrivate).toBe(false);
expect(typeof bContext.addInitializer).toBe('undefined');
expect(typeof bContext.setMetadata).toBe('function');
expect(typeof bContext.getMetadata).toBe('function');
expect(foo.hasOwnProperty('b')).toBe(true);
expect(Foo.prototype.hasOwnProperty('b')).toBe(false);
expect(foo.c).toBe(457);
foo.c = 456;
expect(foo.c).toBe(456);
expect(cContext.name).toBe('c');
expect(cContext.kind).toBe('field');
expect(cContext.isStatic).toBe(false);
expect(cContext.isPrivate).toBe(false);
expect(typeof cContext.addInitializer).toBe('undefined');
expect(typeof cContext.setMetadata).toBe('function');
expect(typeof cContext.getMetadata).toBe('function');
expect(foo.hasOwnProperty('c')).toBe(true);
expect(Foo.prototype.hasOwnProperty('c')).toBe(false);

View File

@ -0,0 +1,10 @@
class Foo {
@dec
a;
@dec
b = 123;
@dec
['c'] = 456;
}

View File

@ -0,0 +1,19 @@
var _init_a, _init_b, _computedKey, _init_computedKey, _dec, _dec2, _dec3;
_dec = dec
_dec2 = dec
_computedKey = 'c'
_dec3 = dec
class Foo {
constructor() {
babelHelpers.defineProperty(this, "a", _init_a(this));
babelHelpers.defineProperty(this, "b", _init_b(this, 123));
babelHelpers.defineProperty(this, _computedKey, _init_computedKey(this, 456));
}
}
(() => {
[_init_a, _init_b, _init_computedKey] = babelHelpers.applyDecs(Foo, [[_dec, 0, "a"], [_dec2, 0, "b"], [_dec3, 0, _computedKey]], []);
})();

View File

@ -0,0 +1,39 @@
function dec(_v, context) {
return function (v) {
this[context.name + 'Context'] = context;
return (v || 1) + 1;
}
}
class Foo {
@dec
static #a;
@dec
static #b = 123;
}
const aContext = Foo['#aContext'];
const bContext = Foo['#bContext'];
expect(aContext.access.get.call(Foo)).toBe(2);
aContext.access.set.call(Foo, 123);
expect(aContext.access.get.call(Foo)).toBe(123);
expect(aContext.name).toBe('#a');
expect(aContext.kind).toBe('field');
expect(aContext.isStatic).toBe(true);
expect(aContext.isPrivate).toBe(true);
expect(typeof aContext.addInitializer).toBe('undefined');
expect(typeof aContext.setMetadata).toBe('function');
expect(typeof aContext.getMetadata).toBe('function');
expect(bContext.access.get.call(Foo)).toBe(124);
bContext.access.set.call(Foo, 123);
expect(bContext.access.get.call(Foo)).toBe(123);
expect(bContext.name).toBe('#b');
expect(bContext.kind).toBe('field');
expect(bContext.isStatic).toBe(true);
expect(bContext.isPrivate).toBe(true);
expect(typeof aContext.addInitializer).toBe('undefined');
expect(typeof aContext.setMetadata).toBe('function');
expect(typeof aContext.getMetadata).toBe('function');

View File

@ -0,0 +1,7 @@
class Foo {
@dec
static #a;
@dec
static #b = 123;
}

View File

@ -0,0 +1,24 @@
var _init_a, _init_b;
class Foo {}
(() => {
[_init_a, _init_b] = babelHelpers.applyDecs(Foo, [[dec, 5, "a", function () {
return babelHelpers.classStaticPrivateFieldSpecGet(this, Foo, _a);
}, function (value) {
babelHelpers.classStaticPrivateFieldSpecSet(this, Foo, _a, value);
}], [dec, 5, "b", function () {
return babelHelpers.classStaticPrivateFieldSpecGet(this, Foo, _b);
}, function (value) {
babelHelpers.classStaticPrivateFieldSpecSet(this, Foo, _b, value);
}]], []);
})();
var _a = {
writable: true,
value: _init_a(Foo)
};
var _b = {
writable: true,
value: _init_b(Foo, 123)
};

View File

@ -0,0 +1,57 @@
function dec(_v, context) {
return function (v) {
this[context.name + 'Context'] = context;
return (v || 1) + 1;
}
}
class Foo {
@dec
static a;
@dec
static b = 123;
@dec
static ['c'] = 456;
}
const aContext = Foo['aContext'];
const bContext = Foo['bContext'];
const cContext = Foo['cContext'];
expect(Foo.a).toBe(2);
Foo.a = 123;
expect(Foo.a).toBe(123);
expect(aContext.name).toBe('a');
expect(aContext.kind).toBe('field');
expect(aContext.isStatic).toBe(true);
expect(aContext.isPrivate).toBe(false);
expect(typeof aContext.addInitializer).toBe('undefined');
expect(typeof aContext.setMetadata).toBe('function');
expect(typeof aContext.getMetadata).toBe('function');
expect(Foo.hasOwnProperty('a')).toBe(true);
expect(Foo.b).toBe(124);
Foo.b = 123;
expect(Foo.b).toBe(123);
expect(bContext.name).toBe('b');
expect(bContext.kind).toBe('field');
expect(bContext.isStatic).toBe(true);
expect(bContext.isPrivate).toBe(false);
expect(typeof bContext.addInitializer).toBe('undefined');
expect(typeof bContext.setMetadata).toBe('function');
expect(typeof bContext.getMetadata).toBe('function');
expect(Foo.hasOwnProperty('b')).toBe(true);
expect(Foo.c).toBe(457);
Foo.c = 456;
expect(Foo.c).toBe(456);
expect(cContext.name).toBe('c');
expect(cContext.kind).toBe('field');
expect(cContext.isStatic).toBe(true);
expect(cContext.isPrivate).toBe(false);
expect(typeof cContext.addInitializer).toBe('undefined');
expect(typeof cContext.setMetadata).toBe('function');
expect(typeof cContext.getMetadata).toBe('function');
expect(Foo.hasOwnProperty('c')).toBe(true);

View File

@ -0,0 +1,10 @@
class Foo {
@dec
static a;
@dec
static b = 123;
@dec
static ['c'] = 456;
}

View File

@ -0,0 +1,16 @@
var _init_a, _init_b, _computedKey, _init_computedKey, _dec, _dec2, _dec3;
_dec = dec
_dec2 = dec
_computedKey = 'c'
_dec3 = dec
class Foo {}
(() => {
[_init_a, _init_b, _init_computedKey] = babelHelpers.applyDecs(Foo, [[_dec, 5, "a"], [_dec2, 5, "b"], [_dec3, 5, _computedKey]], []);
})();
babelHelpers.defineProperty(Foo, "a", _init_a(Foo));
babelHelpers.defineProperty(Foo, "b", _init_b(Foo, 123));
babelHelpers.defineProperty(Foo, _computedKey, _init_computedKey(Foo, 456));

View File

@ -0,0 +1,11 @@
{
"plugins": [
[
"proposal-decorators",
{ "version": "2021-12", "decoratorsBeforeExport": false }
],
"proposal-class-properties",
"proposal-private-methods",
"proposal-class-static-block"
]
}

View File

@ -0,0 +1,67 @@
function dec(value, context) {
context.addInitializer((instance) => {
instance[context.name + '_' + context.kind + 'Context'] = context;
});
if (context.kind === 'getter') {
return function () {
return value.call(this) + 1;
}
} else {
return function (v) {
return value.call(this, v + 1);
}
}
}
class Foo {
value = 1;
@dec
get #a() {
return this.value;
}
@dec
set #a(v) {
this.value = v;
}
getA() {
return this.#a;
}
setA(v) {
this.#a = v;
}
}
let foo = new Foo();
const a_getterContext = foo['#a_getterContext'];
const a_setterContext = foo['#a_setterContext'];
expect(a_getterContext.access.get.call(foo)).toBe(2);
expect(foo.getA()).toBe(2);
a_setterContext.access.set.call(foo, 123);
expect(a_getterContext.access.get.call(foo)).toBe(125);
expect(foo.getA()).toBe(125);
foo.setA(456);
expect(a_getterContext.access.get.call(foo)).toBe(458);
expect(foo.getA()).toBe(458);
expect(a_getterContext.name).toBe('#a');
expect(a_getterContext.kind).toBe('getter');
expect(a_getterContext.isStatic).toBe(false);
expect(a_getterContext.isPrivate).toBe(true);
expect(typeof a_getterContext.addInitializer).toBe('function');
expect(typeof a_getterContext.setMetadata).toBe('function');
expect(typeof a_getterContext.getMetadata).toBe('function');
expect(a_setterContext.name).toBe('#a');
expect(a_setterContext.kind).toBe('setter');
expect(a_setterContext.isStatic).toBe(false);
expect(a_setterContext.isPrivate).toBe(true);
expect(typeof a_setterContext.addInitializer).toBe('function');
expect(typeof a_setterContext.setMetadata).toBe('function');
expect(typeof a_setterContext.getMetadata).toBe('function');

View File

@ -0,0 +1,21 @@
class Foo {
value = 1;
@dec
get #a() {
return this.value;
}
@dec
set #a(v) {
this.value = v;
}
getA() {
return this.#a;
}
setA(v) {
this.#a = v;
}
}

View File

@ -0,0 +1,40 @@
var _call_a, _call_a2, _initProto;
var _a = /*#__PURE__*/new WeakMap();
class Foo {
constructor(...args) {
babelHelpers.classPrivateFieldInitSpec(this, _a, {
get: _get_a,
set: _set_a
});
babelHelpers.defineProperty(this, "value", 1);
_initProto(this);
}
getA() {
return babelHelpers.classPrivateFieldGet(this, _a);
}
setA(v) {
babelHelpers.classPrivateFieldSet(this, _a, v);
}
}
function _get_a() {
return _call_a(this);
}
function _set_a(v) {
_call_a2(this, v);
}
(() => {
[_call_a, _call_a2, _initProto] = babelHelpers.applyDecs(Foo, [[dec, 3, "a", function () {
return this.value;
}], [dec, 4, "a", function (v) {
this.value = v;
}]], []);
})();

View File

@ -0,0 +1,88 @@
function dec(value, context) {
context.addInitializer((instance) => {
instance[context.name + '_' + context.kind + 'Context'] = context;
});
if (context.kind === 'getter') {
return function () {
return value.call(this) + 1;
}
} else {
return function (v) {
return value.call(this, v + 1);
}
}
}
class Foo {
value = 1;
@dec
get a() {
return this.value;
}
@dec
set a(v) {
this.value = v;
}
@dec
get ['b']() {
return this.value;
}
@dec
set ['b'](v) {
this.value = v;
}
}
let foo = new Foo();
const a_getterContext = foo['a_getterContext'];
const a_setterContext = foo['a_setterContext'];
const b_getterContext = foo['b_getterContext'];
const b_setterContext = foo['b_setterContext'];
expect(foo.a).toBe(2);
expect(foo.b).toBe(2);
foo.a = 123;
expect(foo.a).toBe(125);
expect(foo.b).toBe(125);
foo.b = 456;
expect(foo.a).toBe(458);
expect(foo.b).toBe(458);
expect(a_getterContext.name).toBe('a');
expect(a_getterContext.kind).toBe('getter');
expect(a_getterContext.isStatic).toBe(false);
expect(a_getterContext.isPrivate).toBe(false);
expect(typeof a_getterContext.addInitializer).toBe('function');
expect(typeof a_getterContext.setMetadata).toBe('function');
expect(typeof a_getterContext.getMetadata).toBe('function');
expect(a_setterContext.name).toBe('a');
expect(a_setterContext.kind).toBe('setter');
expect(a_setterContext.isStatic).toBe(false);
expect(a_setterContext.isPrivate).toBe(false);
expect(typeof a_setterContext.addInitializer).toBe('function');
expect(typeof a_setterContext.setMetadata).toBe('function');
expect(typeof a_setterContext.getMetadata).toBe('function');
expect(b_getterContext.name).toBe('b');
expect(b_getterContext.kind).toBe('getter');
expect(b_getterContext.isStatic).toBe(false);
expect(b_getterContext.isPrivate).toBe(false);
expect(typeof b_getterContext.addInitializer).toBe('function');
expect(typeof b_getterContext.setMetadata).toBe('function');
expect(typeof b_getterContext.getMetadata).toBe('function');
expect(b_setterContext.name).toBe('b');
expect(b_setterContext.kind).toBe('setter');
expect(b_setterContext.isStatic).toBe(false);
expect(b_setterContext.isPrivate).toBe(false);
expect(typeof b_setterContext.addInitializer).toBe('function');
expect(typeof b_setterContext.setMetadata).toBe('function');
expect(typeof b_setterContext.getMetadata).toBe('function');

View File

@ -0,0 +1,23 @@
class Foo {
value = 1;
@dec
get a() {
return this.value;
}
@dec
set a(v) {
this.value = v;
}
@dec
get ['b']() {
return this.value;
}
@dec
set ['b'](v) {
this.value = v;
}
}

View File

@ -0,0 +1,37 @@
var _computedKey, _computedKey2, _dec, _dec2, _dec3, _dec4, _initProto;
_dec = dec
_dec2 = dec
_computedKey = 'b'
_dec3 = dec
_computedKey2 = 'b'
_dec4 = dec
class Foo {
constructor(...args) {
babelHelpers.defineProperty(this, "value", 1);
_initProto(this);
}
get a() {
return this.value;
}
set a(v) {
this.value = v;
}
get [_computedKey]() {
return this.value;
}
set [_computedKey2](v) {
this.value = v;
}
}
(() => {
[_initProto] = babelHelpers.applyDecs(Foo, [[_dec, 3, "a"], [_dec2, 4, "a"], [_dec3, 3, _computedKey], [_dec4, 4, _computedKey2]], []);
})();

View File

@ -0,0 +1,65 @@
function dec(value, context) {
context.addInitializer((instance) => {
instance[context.name + '_' + context.kind + 'Context'] = context;
});
if (context.kind === 'getter') {
return function () {
return value.call(this) + 1;
}
} else {
return function (v) {
return value.call(this, v + 1);
}
}
}
class Foo {
static value = 1;
@dec
static get #a() {
return this.value;
}
@dec
static set #a(v) {
this.value = v;
}
static getA() {
return this.#a;
}
static setA(v) {
this.#a = v;
}
}
const a_getterContext = Foo['#a_getterContext'];
const a_setterContext = Foo['#a_setterContext'];
expect(a_getterContext.access.get.call(Foo)).toBe(2);
expect(Foo.getA()).toBe(2);
a_setterContext.access.set.call(Foo, 123);
expect(a_getterContext.access.get.call(Foo)).toBe(125);
expect(Foo.getA()).toBe(125);
Foo.setA(456);
expect(a_getterContext.access.get.call(Foo)).toBe(458);
expect(Foo.getA()).toBe(458);
expect(a_getterContext.name).toBe('#a');
expect(a_getterContext.kind).toBe('getter');
expect(a_getterContext.isStatic).toBe(true);
expect(a_getterContext.isPrivate).toBe(true);
expect(typeof a_getterContext.addInitializer).toBe('function');
expect(typeof a_getterContext.setMetadata).toBe('function');
expect(typeof a_getterContext.getMetadata).toBe('function');
expect(a_setterContext.name).toBe('#a');
expect(a_setterContext.kind).toBe('setter');
expect(a_setterContext.isStatic).toBe(true);
expect(a_setterContext.isPrivate).toBe(true);
expect(typeof a_setterContext.addInitializer).toBe('function');
expect(typeof a_setterContext.setMetadata).toBe('function');
expect(typeof a_setterContext.getMetadata).toBe('function');

View File

@ -0,0 +1,21 @@
class Foo {
static value = 1;
@dec
static get #a() {
return this.value;
}
@dec
static set #a(v) {
this.value = v;
}
static getA() {
return this.#a;
}
static setA(v) {
this.#a = v;
}
}

View File

@ -0,0 +1,37 @@
var _call_a, _call_a2, _initStatic;
class Foo {
static getA() {
return babelHelpers.classStaticPrivateFieldSpecGet(this, Foo, _a);
}
static setA(v) {
babelHelpers.classStaticPrivateFieldSpecSet(this, Foo, _a, v);
}
}
function _get_a() {
return _call_a(this);
}
function _set_a(v) {
_call_a2(this, v);
}
var _a = {
get: _get_a,
set: _set_a
};
(() => {
[_call_a, _call_a2, _initStatic] = babelHelpers.applyDecs(Foo, [[dec, 8, "a", function () {
return this.value;
}], [dec, 9, "a", function (v) {
this.value = v;
}]], []);
_initStatic(Foo);
})();
babelHelpers.defineProperty(Foo, "value", 1);

View File

@ -0,0 +1,87 @@
function dec(value, context) {
context.addInitializer((instance) => {
instance[context.name + '_' + context.kind + 'Context'] = context;
});
if (context.kind === 'getter') {
return function () {
return value.call(this) + 1;
}
} else {
return function (v) {
return value.call(this, v + 1);
}
}
}
class Foo {
static value = 1;
@dec
static get a() {
return this.value;
}
@dec
static set a(v) {
this.value = v;
}
@dec
static get ['b']() {
return this.value;
}
@dec
static set ['b'](v) {
this.value = v;
}
}
const a_getterContext = Foo['a_getterContext'];
const a_setterContext = Foo['a_setterContext'];
const b_getterContext = Foo['b_getterContext'];
const b_setterContext = Foo['b_setterContext'];
expect(Foo.a).toBe(2);
expect(Foo.b).toBe(2);
Foo.a = 123;
expect(Foo.a).toBe(125);
expect(Foo.b).toBe(125);
Foo.b = 456;
expect(Foo.a).toBe(458);
expect(Foo.b).toBe(458);
expect(a_getterContext.name).toBe('a');
expect(a_getterContext.kind).toBe('getter');
expect(a_getterContext.isStatic).toBe(true);
expect(a_getterContext.isPrivate).toBe(false);
expect(typeof a_getterContext.addInitializer).toBe('function');
expect(typeof a_getterContext.setMetadata).toBe('function');
expect(typeof a_getterContext.getMetadata).toBe('function');
expect(a_setterContext.name).toBe('a');
expect(a_setterContext.kind).toBe('setter');
expect(a_setterContext.isStatic).toBe(true);
expect(a_setterContext.isPrivate).toBe(false);
expect(typeof a_setterContext.addInitializer).toBe('function');
expect(typeof a_setterContext.setMetadata).toBe('function');
expect(typeof a_setterContext.getMetadata).toBe('function');
expect(b_getterContext.name).toBe('b');
expect(b_getterContext.kind).toBe('getter');
expect(b_getterContext.isStatic).toBe(true);
expect(b_getterContext.isPrivate).toBe(false);
expect(typeof b_getterContext.addInitializer).toBe('function');
expect(typeof b_getterContext.setMetadata).toBe('function');
expect(typeof b_getterContext.getMetadata).toBe('function');
expect(b_setterContext.name).toBe('b');
expect(b_setterContext.kind).toBe('setter');
expect(b_setterContext.isStatic).toBe(true);
expect(b_setterContext.isPrivate).toBe(false);
expect(typeof b_setterContext.addInitializer).toBe('function');
expect(typeof b_setterContext.setMetadata).toBe('function');
expect(typeof b_setterContext.getMetadata).toBe('function');

View File

@ -0,0 +1,23 @@
class Foo {
static value = 1;
@dec
static get a() {
return this.value;
}
@dec
static set a(v) {
this.value = v;
}
@dec
static get ['b']() {
return this.value;
}
@dec
static set ['b'](v) {
this.value = v;
}
}

View File

@ -0,0 +1,35 @@
var _computedKey, _computedKey2, _dec, _dec2, _dec3, _dec4, _initStatic;
_dec = dec
_dec2 = dec
_computedKey = 'b'
_dec3 = dec
_computedKey2 = 'b'
_dec4 = dec
class Foo {
static get a() {
return this.value;
}
static set a(v) {
this.value = v;
}
static get [_computedKey]() {
return this.value;
}
static set [_computedKey2](v) {
this.value = v;
}
}
(() => {
[_initStatic] = babelHelpers.applyDecs(Foo, [[_dec, 8, "a"], [_dec2, 9, "a"], [_dec3, 8, _computedKey], [_dec4, 9, _computedKey2]], []);
_initStatic(Foo);
})();
babelHelpers.defineProperty(Foo, "value", 1);

View File

@ -0,0 +1,11 @@
{
"plugins": [
[
"proposal-decorators",
{ "version": "2021-12", "decoratorsBeforeExport": false }
],
"proposal-class-properties",
"proposal-private-methods",
"proposal-class-static-block"
]
}

View File

@ -0,0 +1,39 @@
function dec(get, context) {
context.addInitializer((instance) => {
instance[context.name + 'Context'] = context;
});
return function () {
return get.call(this) + 1;
}
}
class Foo {
value = 1;
@dec
get #a() {
return this.value;
}
getA() {
return this.#a;
}
}
let foo = new Foo();
const aContext = foo['#aContext'];
expect(aContext.access.get.call(foo)).toBe(2);
expect(foo.getA()).toBe(2);
foo.value = 123;
expect(aContext.access.get.call(foo)).toBe(124);
expect(foo.getA()).toBe(124);
expect(aContext.name).toBe('#a');
expect(aContext.kind).toBe('getter');
expect(aContext.isStatic).toBe(false);
expect(aContext.isPrivate).toBe(true);
expect(typeof aContext.addInitializer).toBe('function');
expect(typeof aContext.setMetadata).toBe('function');
expect(typeof aContext.getMetadata).toBe('function');

View File

@ -0,0 +1,12 @@
class Foo {
value = 1;
@dec
get #a() {
return this.value;
}
getA() {
return this.#a;
}
}

View File

@ -0,0 +1,30 @@
var _call_a, _initProto;
var _a = /*#__PURE__*/new WeakMap();
class Foo {
constructor(...args) {
babelHelpers.classPrivateFieldInitSpec(this, _a, {
get: _get_a,
set: void 0
});
babelHelpers.defineProperty(this, "value", 1);
_initProto(this);
}
getA() {
return babelHelpers.classPrivateFieldGet(this, _a);
}
}
function _get_a() {
return _call_a(this);
}
(() => {
[_call_a, _initProto] = babelHelpers.applyDecs(Foo, [[dec, 3, "a", function () {
return this.value;
}]], []);
})();

View File

@ -0,0 +1,50 @@
function dec(get, context) {
context.addInitializer((instance) => {
instance[context.name + 'Context'] = context;
});
return function () {
return get.call(this) + 1;
}
}
class Foo {
value = 1;
@dec
get a() {
return this.value;
}
@dec
get ['b']() {
return this.value;
}
}
let foo = new Foo();
const aContext = foo['aContext'];
const bContext = foo['bContext'];
expect(foo.a).toBe(2);
expect(foo.b).toBe(2);
foo.value = 123;
expect(foo.a).toBe(124);
expect(foo.b).toBe(124);
expect(aContext.name).toBe('a');
expect(aContext.kind).toBe('getter');
expect(aContext.isStatic).toBe(false);
expect(aContext.isPrivate).toBe(false);
expect(typeof aContext.addInitializer).toBe('function');
expect(typeof aContext.setMetadata).toBe('function');
expect(typeof aContext.getMetadata).toBe('function');
expect(bContext.name).toBe('b');
expect(bContext.kind).toBe('getter');
expect(bContext.isStatic).toBe(false);
expect(bContext.isPrivate).toBe(false);
expect(typeof bContext.addInitializer).toBe('function');
expect(typeof bContext.setMetadata).toBe('function');
expect(typeof bContext.getMetadata).toBe('function');

View File

@ -0,0 +1,13 @@
class Foo {
value = 1;
@dec
get a() {
return this.value;
}
@dec
get ['b']() {
return this.value;
}
}

View File

@ -0,0 +1,26 @@
var _computedKey, _dec, _dec2, _initProto;
_dec = dec
_computedKey = 'b'
_dec2 = dec
class Foo {
constructor(...args) {
babelHelpers.defineProperty(this, "value", 1);
_initProto(this);
}
get a() {
return this.value;
}
get [_computedKey]() {
return this.value;
}
}
(() => {
[_initProto] = babelHelpers.applyDecs(Foo, [[_dec, 3, "a"], [_dec2, 3, _computedKey]], []);
})();

View File

@ -0,0 +1,38 @@
function dec(get, context) {
context.addInitializer((instance) => {
instance[context.name + 'Context'] = context;
});
return function () {
return get.call(this) + 1;
}
}
class Foo {
static value = 1;
@dec
static get #a() {
return this.value;
}
static getA() {
return this.#a;
}
}
const aContext = Foo['#aContext'];
expect(aContext.access.get.call(Foo)).toBe(2);
expect(Foo.getA()).toBe(2);
Foo.value = 123;
expect(aContext.access.get.call(Foo)).toBe(124);
expect(Foo.getA()).toBe(124);
expect(aContext.name).toBe('#a');
expect(aContext.kind).toBe('getter');
expect(aContext.isStatic).toBe(true);
expect(aContext.isPrivate).toBe(true);
expect(typeof aContext.addInitializer).toBe('function');
expect(typeof aContext.setMetadata).toBe('function');
expect(typeof aContext.getMetadata).toBe('function');

View File

@ -0,0 +1,12 @@
class Foo {
static value = 1;
@dec
static get #a() {
return this.value;
}
static getA() {
return this.#a;
}
}

View File

@ -0,0 +1,27 @@
var _call_a, _initStatic;
class Foo {
static getA() {
return babelHelpers.classStaticPrivateFieldSpecGet(this, Foo, _a);
}
}
function _get_a() {
return _call_a(this);
}
var _a = {
get: _get_a,
set: void 0
};
(() => {
[_call_a, _initStatic] = babelHelpers.applyDecs(Foo, [[dec, 8, "a", function () {
return this.value;
}]], []);
_initStatic(Foo);
})();
babelHelpers.defineProperty(Foo, "value", 1);

View File

@ -0,0 +1,48 @@
function dec(get, context) {
context.addInitializer((instance) => {
instance[context.name + 'Context'] = context;
});
return function () {
return get.call(this) + 1;
}
}
class Foo {
static value = 1;
@dec
static get a() {
return this.value;
}
@dec
static get ['b']() {
return this.value;
}
}
const aContext = Foo['aContext'];
const bContext = Foo['bContext'];
expect(Foo.a).toBe(2);
expect(Foo.b).toBe(2);
Foo.value = 123;
expect(Foo.a).toBe(124);
expect(Foo.b).toBe(124);
expect(aContext.name).toBe('a');
expect(aContext.kind).toBe('getter');
expect(aContext.isStatic).toBe(true);
expect(aContext.isPrivate).toBe(false);
expect(typeof aContext.addInitializer).toBe('function');
expect(typeof aContext.setMetadata).toBe('function');
expect(typeof aContext.getMetadata).toBe('function');
expect(bContext.name).toBe('b');
expect(bContext.kind).toBe('getter');
expect(bContext.isStatic).toBe(true);
expect(bContext.isPrivate).toBe(false);
expect(typeof bContext.addInitializer).toBe('function');
expect(typeof bContext.setMetadata).toBe('function');
expect(typeof bContext.getMetadata).toBe('function');

View File

@ -0,0 +1,13 @@
class Foo {
static value = 1;
@dec
static get a() {
return this.value;
}
@dec
static get ['b']() {
return this.value;
}
}

Some files were not shown because too many files have changed in this diff Show More