This commit is contained in:
2022-07-18 02:50:52 +00:00
parent befd344ab0
commit 06181b34d6
8569 changed files with 818704 additions and 352705 deletions
+396
View File
@@ -0,0 +1,396 @@
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, _initStatic, _init_a, _call_b, _computedKey, _init_c, _get_c, _set_c;
let _dec = dec,
_dec2 = dec,
_computedKey = someVal,
_dec3 = dec,
_dec4 = dec2;
let _Class;
class Class {
static {
[
_init_a,
_call_b,
_init_c,
_get_c,
_set_c,
_Class,
_initClass,
_initProto,
_initStatic,
] = _applyDecs(Class,
[
[_dec, 0, "a"],
[
_dec2,
7,
"b",
function () {
console.log('foo');
}
],
[
[_dec4, _dec5],
1,
"c",
function () {
return this.#a;
},
function (value) {
this.#a = value;
}
]
],
[dec]
);
_initStatic(Class);
}
a = (initInstance(this), _init_a(this, 123));
static #b(...args) {
_call_b(this, args);
}
[_computedKey]() {}
#a = _init_c(this, 123);
get #c() {
return _get_c(this);
}
set #c(v) {
_set_c(this, v);
}
static {
initClass(C);
}
}
```
Let's break this output down a bit:
```js
let initInstance, initClass, _init_a, _call_b, _init_c, _get_c, _set_c;
```
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
let _dec = dec,
_dec2 = dec,
_computedKey = someVal,
_dec3 = dec,
_dec4 = dec2;
```
Next up, we define and evaluate the decorator 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 {
[
_init_a,
_call_b,
_init_c,
_get_c,
_set_c,
_Class,
_initClass,
_initProto,
_initStatic,
] = _applyDecs(Class,
[
[_dec, 0, "a"],
[
_dec2,
7,
"b",
function () {
console.log('foo');
}
],
[
[_dec4, _dec5],
1,
"c",
function () {
return this.#a;
},
function (value) {
this.#a = value;
}
]
],
[dec]
);
_initStatic(Class);
}
```
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.
Another important thing to note here is that we're passing some functions here.
These are for private methods and accessors, which cannot be replaced directly
so we have to extract their code so it can be decorated. Because we define these
within the static block, they can access any private identifiers which were
defined within the class, so it's not an issue that we're extracting the method
logic here.
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), _init_a(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 `_init_a` 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 = _init_a(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 `_init_a` externally.
```js
static #b(...args) {
_call_b(this, args);
}
```
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 `_call_b`
variable, which will be populated with the fully decorated method.
2. The definition of the method, kept in `_call_b`. As we mentioned above, the
original method's code is moved during the decoration process, and the wrapped
version is populated in `_call_b` and called whenever the private method is
called.
```js
[_computedKey]() {}
```
Next is the undecorated method with a computed key. This uses the previously
calculated and stored computed key.
```js
#a = _init_c(this, 123);
get #c() {
return _get_c(this);
}
set #c(v) {
_set_c(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
#a = _init_c(this, 123);
```
`accessor #c` desugars to a getter and setter which are backed by a new private
field, `#a`. 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 #c() {
return _get_c(this);
}
set #c(v) {
_set_c(this, v);
}
```
Next, we have the getter and setter for `#c` itself. These methods defer to
the `_get_c` and `_set_c` local variables, which will be the decorated versions
of the two getter functions that we passed for decoration in the static block
above. Those two functions are essentially just accessors for the private `#a`
field, but the decorator may add additional logic to them.
```js
static {
_initClass(_Class);
}
```
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
[
_init_a,
_call_b,
_init_c,
_get_c,
_set_c,
_Class,
_initClass,
_initProto,
_initStatic,
] = _applyDecs(Class,
[
[_dec, 0, "a"],
[
_dec2,
7,
"b",
function () {
console.log('foo');
}
],
[
[_dec4, _dec5],
1,
"c",
function () {
return this.#a;
},
function (value) {
this.#a = value;
}
]
],
[dec]
);
```
`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 member
'y',
// Optional fourth and fifth values, these are functions passed for private
// decorators
function() {}
],
```
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.
+1 -1
View File
@@ -2,7 +2,7 @@
> Compile class and object decorators to ES5
See our website [@babel/plugin-proposal-decorators](https://babeljs.io/docs/en/next/babel-plugin-proposal-decorators.html) for more information.
See our website [@babel/plugin-proposal-decorators](https://babeljs.io/docs/en/babel-plugin-proposal-decorators) for more information.
## Install
+19 -50
View File
@@ -7,72 +7,41 @@ exports.default = void 0;
var _helperPluginUtils = require("@babel/helper-plugin-utils");
var _pluginSyntaxDecorators = _interopRequireDefault(require("@babel/plugin-syntax-decorators"));
var _pluginSyntaxDecorators = require("@babel/plugin-syntax-decorators");
var _helperCreateClassFeaturesPlugin = require("@babel/helper-create-class-features-plugin");
var _transformerLegacy = _interopRequireDefault(require("./transformer-legacy"));
var _transformerLegacy = require("./transformer-legacy");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var _transformer = require("./transformer-2021-12");
var _default = (0, _helperPluginUtils.declare)((api, options) => {
api.assertVersion(7);
{
var {
legacy
} = options;
}
const {
legacy = false
version
} = options;
if (typeof legacy !== "boolean") {
throw new Error("'legacy' must be a boolean.");
}
const {
decoratorsBeforeExport
} = options;
if (decoratorsBeforeExport === undefined) {
if (!legacy) {
throw new Error("The decorators plugin requires a 'decoratorsBeforeExport' option," + " whose value must be a boolean. If you want to use the legacy" + " decorators semantics, you can set the 'legacy: true' option.");
}
} else {
if (legacy) {
throw new Error("'decoratorsBeforeExport' can't be used with legacy decorators.");
}
if (typeof decoratorsBeforeExport !== "boolean") {
throw new Error("'decoratorsBeforeExport' must be a boolean.");
}
}
if (legacy) {
if (legacy || version === "legacy") {
return {
name: "proposal-decorators",
inherits: _pluginSyntaxDecorators.default,
manipulateOptions({
generatorOpts
}) {
generatorOpts.decoratorsBeforeExport = decoratorsBeforeExport;
},
visitor: _transformerLegacy.default
};
} else if (version === "2021-12") {
return (0, _transformer.default)(api, options);
} else {
return (0, _helperCreateClassFeaturesPlugin.createClassFeaturePlugin)({
name: "proposal-decorators",
api,
feature: _helperCreateClassFeaturesPlugin.FEATURES.decorators,
inherits: _pluginSyntaxDecorators.default
});
}
return (0, _helperCreateClassFeaturesPlugin.createClassFeaturePlugin)({
name: "proposal-decorators",
feature: _helperCreateClassFeaturesPlugin.FEATURES.decorators,
manipulateOptions({
generatorOpts,
parserOpts
}) {
parserOpts.plugins.push(["decorators", {
decoratorsBeforeExport
}]);
generatorOpts.decoratorsBeforeExport = decoratorsBeforeExport;
}
});
});
exports.default = _default;
@@ -0,0 +1,645 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = _default;
var _core = require("@babel/core");
var _pluginSyntaxDecorators = require("@babel/plugin-syntax-decorators");
var _helperReplaceSupers = require("@babel/helper-replace-supers");
var _helperSplitExportDeclaration = require("@babel/helper-split-export-declaration");
function incrementId(id, idx = id.length - 1) {
if (idx === -1) {
id.unshift(65);
return;
}
const current = id[idx];
if (current === 90) {
id[idx] = 97;
} else if (current === 122) {
id[idx] = 65;
incrementId(id, idx - 1);
} else {
id[idx] = current + 1;
}
}
function createPrivateUidGeneratorForClass(classPath) {
const currentPrivateId = [];
const privateNames = new Set();
classPath.traverse({
PrivateName(path) {
privateNames.add(path.node.id.name);
}
});
return () => {
let reifiedId;
do {
incrementId(currentPrivateId);
reifiedId = String.fromCharCode(...currentPrivateId);
} while (privateNames.has(reifiedId));
return _core.types.privateName(_core.types.identifier(reifiedId));
};
}
function createLazyPrivateUidGeneratorForClass(classPath) {
let generator;
return () => {
if (!generator) {
generator = createPrivateUidGeneratorForClass(classPath);
}
return generator();
};
}
function replaceClassWithVar(path) {
if (path.type === "ClassDeclaration") {
const varId = path.scope.generateUidIdentifierBasedOnNode(path.node.id);
const classId = _core.types.identifier(path.node.id.name);
path.scope.rename(classId.name, varId.name);
path.insertBefore(_core.types.variableDeclaration("let", [_core.types.variableDeclarator(varId)]));
path.get("id").replaceWith(classId);
return [_core.types.cloneNode(varId), path];
} else {
let className;
let varId;
if (path.node.id) {
className = path.node.id.name;
varId = path.scope.parent.generateDeclaredUidIdentifier(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 = path.scope.parent.generateDeclaredUidIdentifier(className);
} else {
varId = path.scope.parent.generateDeclaredUidIdentifier("decorated_class");
}
const newClassExpr = _core.types.classExpression(className && _core.types.identifier(className), path.node.superClass, path.node.body);
const [newPath] = path.replaceWith(_core.types.sequenceExpression([newClassExpr, varId]));
return [_core.types.cloneNode(varId), newPath.get("expressions.0")];
}
}
function generateClassProperty(key, value, isStatic) {
if (key.type === "PrivateName") {
return _core.types.classPrivateProperty(key, value, undefined, isStatic);
} else {
return _core.types.classProperty(key, value, undefined, undefined, isStatic);
}
}
function addProxyAccessorsFor(element, originalKey, targetKey, isComputed = false) {
const {
static: isStatic
} = element.node;
const getterBody = _core.types.blockStatement([_core.types.returnStatement(_core.types.memberExpression(_core.types.thisExpression(), _core.types.cloneNode(targetKey)))]);
const setterBody = _core.types.blockStatement([_core.types.expressionStatement(_core.types.assignmentExpression("=", _core.types.memberExpression(_core.types.thisExpression(), _core.types.cloneNode(targetKey)), _core.types.identifier("v")))]);
let getter, setter;
if (originalKey.type === "PrivateName") {
getter = _core.types.classPrivateMethod("get", _core.types.cloneNode(originalKey), [], getterBody, isStatic);
setter = _core.types.classPrivateMethod("set", _core.types.cloneNode(originalKey), [_core.types.identifier("v")], setterBody, isStatic);
} else {
getter = _core.types.classMethod("get", _core.types.cloneNode(originalKey), [], getterBody, isComputed, isStatic);
setter = _core.types.classMethod("set", _core.types.cloneNode(originalKey), [_core.types.identifier("v")], setterBody, isComputed, isStatic);
}
element.insertAfter(setter);
element.insertAfter(getter);
}
function extractProxyAccessorsFor(targetKey) {
return [_core.types.functionExpression(undefined, [], _core.types.blockStatement([_core.types.returnStatement(_core.types.memberExpression(_core.types.thisExpression(), _core.types.cloneNode(targetKey)))])), _core.types.functionExpression(undefined, [_core.types.identifier("value")], _core.types.blockStatement([_core.types.expressionStatement(_core.types.assignmentExpression("=", _core.types.memberExpression(_core.types.thisExpression(), _core.types.cloneNode(targetKey)), _core.types.identifier("value")))]))];
}
const FIELD = 0;
const ACCESSOR = 1;
const METHOD = 2;
const GETTER = 3;
const SETTER = 4;
const STATIC = 5;
function getElementKind(element) {
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 isDecoratorInfo(info) {
return "decorators" in info;
}
function filteredOrderedDecoratorInfo(info) {
const filtered = info.filter(isDecoratorInfo);
return [...filtered.filter(el => el.isStatic && el.kind >= ACCESSOR && el.kind <= SETTER), ...filtered.filter(el => !el.isStatic && el.kind >= ACCESSOR && el.kind <= SETTER), ...filtered.filter(el => el.isStatic && el.kind === FIELD), ...filtered.filter(el => !el.isStatic && el.kind === FIELD)];
}
function generateDecorationExprs(info) {
return _core.types.arrayExpression(filteredOrderedDecoratorInfo(info).map(el => {
const decs = el.decorators.length > 1 ? _core.types.arrayExpression(el.decorators) : el.decorators[0];
const kind = el.isStatic ? el.kind + STATIC : el.kind;
const decInfo = [decs, _core.types.numericLiteral(kind), el.name];
const {
privateMethods
} = el;
if (Array.isArray(privateMethods)) {
decInfo.push(...privateMethods);
} else if (privateMethods) {
decInfo.push(privateMethods);
}
return _core.types.arrayExpression(decInfo);
}));
}
function extractElementLocalAssignments(decorationInfo) {
const localIds = [];
for (const el of filteredOrderedDecoratorInfo(decorationInfo)) {
const {
locals
} = el;
if (Array.isArray(locals)) {
localIds.push(...locals);
} else if (locals !== undefined) {
localIds.push(locals);
}
}
return localIds;
}
function addCallAccessorsFor(element, key, getId, setId) {
element.insertAfter(_core.types.classPrivateMethod("get", _core.types.cloneNode(key), [], _core.types.blockStatement([_core.types.returnStatement(_core.types.callExpression(_core.types.cloneNode(getId), [_core.types.thisExpression()]))])));
element.insertAfter(_core.types.classPrivateMethod("set", _core.types.cloneNode(key), [_core.types.identifier("v")], _core.types.blockStatement([_core.types.expressionStatement(_core.types.callExpression(_core.types.cloneNode(setId), [_core.types.thisExpression(), _core.types.identifier("v")]))])));
}
function isNotTsParameter(node) {
return node.type !== "TSParameterProperty";
}
function movePrivateAccessor(element, key, methodLocalVar, isStatic) {
let params;
let block;
if (element.node.kind === "set") {
params = [_core.types.identifier("v")];
block = [_core.types.expressionStatement(_core.types.callExpression(methodLocalVar, [_core.types.thisExpression(), _core.types.identifier("v")]))];
} else {
params = [];
block = [_core.types.returnStatement(_core.types.callExpression(methodLocalVar, [_core.types.thisExpression()]))];
}
element.replaceWith(_core.types.classPrivateMethod(element.node.kind, _core.types.cloneNode(key), params, _core.types.blockStatement(block), isStatic));
}
function isClassDecoratableElementPath(path) {
const {
type
} = path;
return type !== "TSDeclareMethod" && type !== "TSIndexSignature" && type !== "StaticBlock";
}
function staticBlockToIIFE(block) {
return _core.types.callExpression(_core.types.arrowFunctionExpression([], _core.types.blockStatement(block.body)), []);
}
function maybeSequenceExpression(exprs) {
if (exprs.length === 0) return _core.types.unaryExpression("void", _core.types.numericLiteral(0));
if (exprs.length === 1) return exprs[0];
return _core.types.sequenceExpression(exprs);
}
function transformClass(path, state, constantSuper) {
const body = path.get("body.body");
const classDecorators = path.node.decorators;
let hasElementDecorators = false;
const generateClassPrivateUid = createLazyPrivateUidGeneratorForClass(path);
for (const element of body) {
if (!isClassDecoratableElementPath(element)) {
continue;
}
if (element.node.decorators && element.node.decorators.length > 0) {
hasElementDecorators = true;
} else if (element.node.type === "ClassAccessorProperty") {
const {
key,
value,
static: isStatic,
computed
} = element.node;
const newId = generateClassPrivateUid();
const valueNode = value ? _core.types.cloneNode(value) : undefined;
const newField = generateClassProperty(newId, valueNode, isStatic);
const [newPath] = element.replaceWith(newField);
addProxyAccessorsFor(newPath, key, newId, computed);
}
}
if (!classDecorators && !hasElementDecorators) return;
const elementDecoratorInfo = [];
let firstFieldPath;
let constructorPath;
let requiresProtoInit = false;
let requiresStaticInit = false;
const decoratedPrivateMethods = new Set();
let protoInitLocal, staticInitLocal, classInitLocal, classLocal;
const assignments = [];
const scopeParent = path.scope.parent;
const memoiseExpression = (expression, hint) => {
const localEvaluatedId = scopeParent.generateDeclaredUidIdentifier(hint);
assignments.push(_core.types.assignmentExpression("=", localEvaluatedId, expression));
return _core.types.cloneNode(localEvaluatedId);
};
if (classDecorators) {
classInitLocal = scopeParent.generateDeclaredUidIdentifier("initClass");
const [localId, classPath] = replaceClassWithVar(path);
path = classPath;
classLocal = localId;
path.node.decorators = null;
for (const classDecorator of classDecorators) {
if (!scopeParent.isStatic(classDecorator.expression)) {
classDecorator.expression = memoiseExpression(classDecorator.expression, "dec");
}
}
} else {
if (!path.node.id) {
path.node.id = path.scope.generateUidIdentifier("Class");
}
classLocal = _core.types.cloneNode(path.node.id);
}
if (hasElementDecorators) {
for (const element of body) {
if (!isClassDecoratableElementPath(element)) {
continue;
}
const {
node
} = element;
const decorators = element.get("decorators");
const hasDecorators = Array.isArray(decorators) && decorators.length > 0;
if (hasDecorators) {
for (const decoratorPath of decorators) {
if (!scopeParent.isStatic(decoratorPath.node.expression)) {
decoratorPath.node.expression = memoiseExpression(decoratorPath.node.expression, "dec");
}
}
}
const isComputed = "computed" in element.node && element.node.computed === true;
if (isComputed) {
if (!scopeParent.isStatic(node.key)) {
node.key = memoiseExpression(node.key, "computedKey");
}
}
const kind = getElementKind(element);
const {
key
} = node;
const isPrivate = key.type === "PrivateName";
const isStatic = !!element.node.static;
let name = "computedKey";
if (isPrivate) {
name = key.id.name;
} else if (!isComputed && key.type === "Identifier") {
name = key.name;
}
if (element.isClassMethod({
kind: "constructor"
})) {
constructorPath = element;
}
if (hasDecorators) {
let locals;
let privateMethods;
if (kind === ACCESSOR) {
const {
value
} = element.node;
const params = [_core.types.thisExpression()];
if (value) {
params.push(_core.types.cloneNode(value));
}
const newId = generateClassPrivateUid();
const newFieldInitId = element.scope.parent.generateDeclaredUidIdentifier(`init_${name}`);
const newValue = _core.types.callExpression(_core.types.cloneNode(newFieldInitId), params);
const newField = generateClassProperty(newId, newValue, isStatic);
const [newPath] = element.replaceWith(newField);
if (isPrivate) {
privateMethods = extractProxyAccessorsFor(newId);
const getId = newPath.scope.parent.generateDeclaredUidIdentifier(`get_${name}`);
const setId = newPath.scope.parent.generateDeclaredUidIdentifier(`set_${name}`);
addCallAccessorsFor(newPath, key, getId, setId);
locals = [newFieldInitId, getId, setId];
} else {
addProxyAccessorsFor(newPath, key, newId, isComputed);
locals = newFieldInitId;
}
} else if (kind === FIELD) {
const initId = element.scope.parent.generateDeclaredUidIdentifier(`init_${name}`);
const valuePath = element.get("value");
valuePath.replaceWith(_core.types.callExpression(_core.types.cloneNode(initId), [_core.types.thisExpression(), valuePath.node].filter(v => v)));
locals = initId;
if (isPrivate) {
privateMethods = extractProxyAccessorsFor(key);
}
} else if (isPrivate) {
locals = element.scope.parent.generateDeclaredUidIdentifier(`call_${name}`);
const replaceSupers = new _helperReplaceSupers.default({
constantSuper,
methodPath: element,
objectRef: classLocal,
superRef: path.node.superClass,
file: state,
refToPreserve: classLocal
});
replaceSupers.replace();
const {
params,
body,
async: isAsync
} = element.node;
privateMethods = _core.types.functionExpression(undefined, params.filter(isNotTsParameter), body, isAsync);
if (kind === GETTER || kind === SETTER) {
movePrivateAccessor(element, _core.types.cloneNode(key), _core.types.cloneNode(locals), isStatic);
} else {
const node = element.node;
path.node.body.body.unshift(_core.types.classPrivateProperty(key, _core.types.cloneNode(locals), [], node.static));
decoratedPrivateMethods.add(key.id.name);
element.remove();
}
}
let nameExpr;
if (isComputed) {
nameExpr = _core.types.cloneNode(key);
} else if (key.type === "PrivateName") {
nameExpr = _core.types.stringLiteral(key.id.name);
} else if (key.type === "Identifier") {
nameExpr = _core.types.stringLiteral(key.name);
} else {
nameExpr = _core.types.cloneNode(key);
}
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;
}
}
}
}
const elementDecorations = generateDecorationExprs(elementDecoratorInfo);
const classDecorations = _core.types.arrayExpression((classDecorators || []).map(d => d.expression));
const locals = extractElementLocalAssignments(elementDecoratorInfo);
if (requiresProtoInit) {
protoInitLocal = scopeParent.generateDeclaredUidIdentifier("initProto");
locals.push(protoInitLocal);
const protoInitCall = _core.types.callExpression(_core.types.cloneNode(protoInitLocal), [_core.types.thisExpression()]);
if (firstFieldPath) {
const value = firstFieldPath.get("value");
const body = [protoInitCall];
if (value.node) {
body.push(value.node);
}
value.replaceWith(_core.types.sequenceExpression(body));
} else if (constructorPath) {
if (path.node.superClass) {
path.traverse({
CallExpression: {
exit(path) {
if (!path.get("callee").isSuper()) return;
path.replaceWith(_core.types.callExpression(_core.types.cloneNode(protoInitLocal), [path.node]));
path.skip();
}
}
});
} else {
constructorPath.node.body.body.unshift(_core.types.expressionStatement(protoInitCall));
}
} else {
const body = [_core.types.expressionStatement(protoInitCall)];
if (path.node.superClass) {
body.unshift(_core.types.expressionStatement(_core.types.callExpression(_core.types.super(), [_core.types.spreadElement(_core.types.identifier("args"))])));
}
path.node.body.body.unshift(_core.types.classMethod("constructor", _core.types.identifier("constructor"), [_core.types.restElement(_core.types.identifier("args"))], _core.types.blockStatement(body)));
}
}
if (requiresStaticInit) {
staticInitLocal = scopeParent.generateDeclaredUidIdentifier("initStatic");
locals.push(staticInitLocal);
}
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 (parentParentPath.node.type === "AssignmentExpression" && parentParentPath.node.left === parentPath.node || parentParentPath.node.type === "UpdateExpression" || parentParentPath.node.type === "RestElement" || parentParentPath.node.type === "ArrayPattern" || parentParentPath.node.type === "ObjectProperty" && parentParentPath.node.value === parentPath.node && parentParentPath.parentPath.type === "ObjectPattern" || 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.`);
}
}
});
}
let classInitInjected = false;
const classInitCall = classInitLocal && _core.types.callExpression(_core.types.cloneNode(classInitLocal), []);
const originalClass = path.node;
if (classDecorators) {
locals.push(classLocal, classInitLocal);
const statics = [];
let staticBlocks = [];
path.get("body.body").forEach(element => {
if (element.isStaticBlock()) {
staticBlocks.push(element.node);
element.remove();
return;
}
const isProperty = element.isClassProperty() || element.isClassPrivateProperty();
if ((isProperty || element.isClassPrivateMethod()) && element.node.static) {
if (isProperty && staticBlocks.length > 0) {
const allValues = staticBlocks.map(staticBlockToIIFE);
if (element.node.value) allValues.push(element.node.value);
element.node.value = maybeSequenceExpression(allValues);
staticBlocks = [];
}
element.node.static = false;
statics.push(element.node);
element.remove();
}
});
if (statics.length > 0 || staticBlocks.length > 0) {
const staticsClass = _core.template.expression.ast`
class extends ${state.addHelper("identity")} {}
`;
staticsClass.body.body = [_core.types.staticBlock([_core.types.toStatement(path.node, false)]), ...statics];
const constructorBody = [];
const newExpr = _core.types.newExpression(staticsClass, []);
if (staticBlocks.length > 0) {
constructorBody.push(...staticBlocks.map(staticBlockToIIFE));
}
if (classInitCall) {
classInitInjected = true;
constructorBody.push(classInitCall);
}
if (constructorBody.length > 0) {
constructorBody.unshift(_core.types.callExpression(_core.types.super(), [_core.types.cloneNode(classLocal)]));
staticsClass.body.body.push(_core.types.classMethod("constructor", _core.types.identifier("constructor"), [], _core.types.blockStatement([_core.types.expressionStatement(_core.types.sequenceExpression(constructorBody))])));
} else {
newExpr.arguments.push(_core.types.cloneNode(classLocal));
}
path.replaceWith(newExpr);
}
}
if (!classInitInjected && classInitCall) {
path.node.body.body.push(_core.types.staticBlock([_core.types.expressionStatement(classInitCall)]));
}
originalClass.body.body.unshift(_core.types.staticBlock([_core.types.expressionStatement(_core.types.assignmentExpression("=", _core.types.arrayPattern(locals), _core.types.callExpression(state.addHelper("applyDecs"), [_core.types.thisExpression(), elementDecorations, classDecorations]))), requiresStaticInit && _core.types.expressionStatement(_core.types.callExpression(_core.types.cloneNode(staticInitLocal), [_core.types.thisExpression()]))].filter(Boolean)));
path.insertBefore(assignments.map(expr => _core.types.expressionStatement(expr)));
path.scope.crawl();
return path;
}
function _default({
assertVersion,
assumption
}, {
loose
}) {
var _assumption;
assertVersion("^7.16.0");
const VISITED = new WeakSet();
const constantSuper = (_assumption = assumption("constantSuper")) != null ? _assumption : loose;
return {
name: "proposal-decorators",
inherits: _pluginSyntaxDecorators.default,
visitor: {
"ExportNamedDeclaration|ExportDefaultDeclaration"(path) {
var _declaration$decorato;
const {
declaration
} = path.node;
if ((declaration == null ? void 0 : declaration.type) === "ClassDeclaration" && ((_declaration$decorato = declaration.decorators) == null ? void 0 : _declaration$decorato.length) > 0) {
(0, _helperSplitExportDeclaration.default)(path);
}
},
Class(path, state) {
if (VISITED.has(path)) return;
const newPath = transformClass(path, state, constantSuper);
if (newPath) VISITED.add(newPath);
}
}
};
}
+16 -9
View File
@@ -7,9 +7,10 @@ exports.default = void 0;
var _core = require("@babel/core");
const buildClassDecorator = (0, _core.template)(`
const buildClassDecorator = _core.template.statement(`
DECORATOR(CLASS_REF = INNER) || CLASS_REF;
`);
const buildClassPrototype = (0, _core.template)(`
CLASS_REF.prototype;
`);
@@ -29,7 +30,7 @@ const buildGetObjectInitializer = (0, _core.template)(`
const WARNING_CALLS = new WeakSet();
function applyEnsureOrdering(path) {
const decorators = (path.isClass() ? [path].concat(path.get("body.body")) : path.get("properties")).reduce((acc, prop) => acc.concat(prop.node.decorators || []), []);
const decorators = (path.isClass() ? [path, ...path.get("body.body")] : path.get("properties")).reduce((acc, prop) => acc.concat(prop.node.decorators || []), []);
const identDecorators = decorators.filter(decorator => !_core.types.isIdentifier(decorator.expression));
if (identDecorators.length === 0) return;
return _core.types.sequenceExpression(identDecorators.map(decorator => {
@@ -72,14 +73,19 @@ function hasMethodDecorators(body) {
function applyObjectDecorators(path, state) {
if (!hasMethodDecorators(path.node.properties)) return;
return applyTargetDecorators(path, state, path.node.properties);
return applyTargetDecorators(path, state, path.node.properties.filter(prop => prop.type !== "SpreadElement"));
}
function applyTargetDecorators(path, state, decoratedProps) {
const name = path.scope.generateDeclaredUidIdentifier(path.isClass() ? "class" : "obj");
const exprs = decoratedProps.reduce(function (acc, node) {
const decorators = node.decorators || [];
node.decorators = null;
let decorators = [];
if (node.decorators != null) {
decorators = node.decorators;
node.decorators = null;
}
if (decorators.length === 0) return acc;
if (node.computed) {
@@ -98,9 +104,9 @@ function applyTargetDecorators(path, state, decoratedProps) {
const initializer = node.value ? _core.types.functionExpression(null, [], _core.types.blockStatement([_core.types.returnStatement(node.value)])) : _core.types.nullLiteral();
node.value = _core.types.callExpression(state.addHelper("initializerWarningHelper"), [descriptor, _core.types.thisExpression()]);
WARNING_CALLS.add(node.value);
acc = acc.concat([_core.types.assignmentExpression("=", descriptor, _core.types.callExpression(state.addHelper("applyDecoratedDescriptor"), [_core.types.cloneNode(target), _core.types.cloneNode(property), _core.types.arrayExpression(decorators.map(dec => _core.types.cloneNode(dec.expression))), _core.types.objectExpression([_core.types.objectProperty(_core.types.identifier("configurable"), _core.types.booleanLiteral(true)), _core.types.objectProperty(_core.types.identifier("enumerable"), _core.types.booleanLiteral(true)), _core.types.objectProperty(_core.types.identifier("writable"), _core.types.booleanLiteral(true)), _core.types.objectProperty(_core.types.identifier("initializer"), initializer)])]))]);
acc.push(_core.types.assignmentExpression("=", _core.types.cloneNode(descriptor), _core.types.callExpression(state.addHelper("applyDecoratedDescriptor"), [_core.types.cloneNode(target), _core.types.cloneNode(property), _core.types.arrayExpression(decorators.map(dec => _core.types.cloneNode(dec.expression))), _core.types.objectExpression([_core.types.objectProperty(_core.types.identifier("configurable"), _core.types.booleanLiteral(true)), _core.types.objectProperty(_core.types.identifier("enumerable"), _core.types.booleanLiteral(true)), _core.types.objectProperty(_core.types.identifier("writable"), _core.types.booleanLiteral(true)), _core.types.objectProperty(_core.types.identifier("initializer"), initializer)])])));
} else {
acc = acc.concat(_core.types.callExpression(state.addHelper("applyDecoratedDescriptor"), [_core.types.cloneNode(target), _core.types.cloneNode(property), _core.types.arrayExpression(decorators.map(dec => _core.types.cloneNode(dec.expression))), _core.types.isObjectProperty(node) || _core.types.isClassProperty(node, {
acc.push(_core.types.callExpression(state.addHelper("applyDecoratedDescriptor"), [_core.types.cloneNode(target), _core.types.cloneNode(property), _core.types.arrayExpression(decorators.map(dec => _core.types.cloneNode(dec.expression))), _core.types.isObjectProperty(node) || _core.types.isClassProperty(node, {
static: true
}) ? buildGetObjectInitializer({
TEMP: path.scope.generateDeclaredUidIdentifier("init"),
@@ -129,7 +135,7 @@ function decoratedClassToExpression({
return _core.types.variableDeclaration("let", [_core.types.variableDeclarator(ref, _core.types.toExpression(node))]);
}
var _default = {
const visitor = {
ExportDefaultDeclaration(path) {
const decl = path.get("declaration");
if (!decl.isClassDeclaration()) return;
@@ -153,7 +159,7 @@ var _default = {
},
ClassExpression(path, state) {
const decoratedClass = applyEnsureOrdering(path) || applyClassDecorators(path, state) || applyMethodDecorators(path, state);
const decoratedClass = applyEnsureOrdering(path) || applyClassDecorators(path) || applyMethodDecorators(path, state);
if (decoratedClass) path.replaceWith(decoratedClass);
},
@@ -179,4 +185,5 @@ var _default = {
}
};
var _default = visitor;
exports.default = _default;
+32 -22
View File
@@ -1,59 +1,68 @@
{
"_args": [
[
"@babel/plugin-proposal-decorators@7.10.4",
"/mnt/Foxconn/Digitalent/Deverloper/liff-push_2series"
"@babel/plugin-proposal-decorators@7.18.6",
"/home/node/nuxt"
]
],
"_from": "@babel/plugin-proposal-decorators@7.10.4",
"_id": "@babel/plugin-proposal-decorators@7.10.4",
"_from": "@babel/plugin-proposal-decorators@7.18.6",
"_id": "@babel/plugin-proposal-decorators@7.18.6",
"_inBundle": false,
"_integrity": "sha512-JHTWjQngOPv+ZQQqOGv2x6sCCr4IYWy7S1/VH6BE9ZfkoLrdQ2GpEP3tfb5M++G9PwvqjhY8VC/C3tXm+/eHvA==",
"_integrity": "sha512-gAdhsjaYmiZVxx5vTMiRfj31nB7LhwBJFMSLzeDxc7X4tKLixup0+k9ughn0RcpBrv9E3PBaXJW7jF5TCihAOg==",
"_location": "/@babel/plugin-proposal-decorators",
"_phantomChildren": {},
"_requested": {
"type": "version",
"registry": true,
"raw": "@babel/plugin-proposal-decorators@7.10.4",
"raw": "@babel/plugin-proposal-decorators@7.18.6",
"name": "@babel/plugin-proposal-decorators",
"escapedName": "@babel%2fplugin-proposal-decorators",
"scope": "@babel",
"rawSpec": "7.10.4",
"rawSpec": "7.18.6",
"saveSpec": null,
"fetchSpec": "7.10.4"
"fetchSpec": "7.18.6"
},
"_requiredBy": [
"/@nuxt/babel-preset-app"
],
"_resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.10.4.tgz",
"_spec": "7.10.4",
"_where": "/mnt/Foxconn/Digitalent/Deverloper/liff-push_2series",
"_resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.18.6.tgz",
"_spec": "7.18.6",
"_where": "/home/node/nuxt",
"author": {
"name": "Logan Smyth",
"email": "loganfsmyth@gmail.com"
"name": "The Babel Team",
"url": "https://babel.dev/team"
},
"bugs": {
"url": "https://github.com/babel/babel/issues"
},
"dependencies": {
"@babel/helper-create-class-features-plugin": "^7.10.4",
"@babel/helper-plugin-utils": "^7.10.4",
"@babel/plugin-syntax-decorators": "^7.10.4"
"@babel/helper-create-class-features-plugin": "^7.18.6",
"@babel/helper-plugin-utils": "^7.18.6",
"@babel/helper-replace-supers": "^7.18.6",
"@babel/helper-split-export-declaration": "^7.18.6",
"@babel/plugin-syntax-decorators": "^7.18.6"
},
"description": "Compile class and object decorators to ES5",
"devDependencies": {
"@babel/core": "^7.10.4",
"@babel/helper-plugin-test-runner": "^7.10.4"
"@babel/core": "^7.18.6",
"@babel/helper-plugin-test-runner": "^7.18.6",
"@babel/traverse": "^7.18.6",
"@types/charcodes": "^0.2.0",
"babel-plugin-polyfill-es-shims": "^0.6.1",
"charcodes": "^0.2.0",
"object.getownpropertydescriptors": "^2.1.1"
},
"gitHead": "7fd40d86a0d03ff0e9c3ea16b29689945433d4df",
"homepage": "https://github.com/babel/babel#readme",
"engines": {
"node": ">=6.9.0"
},
"homepage": "https://babel.dev/docs/en/next/babel-plugin-proposal-decorators",
"keywords": [
"babel",
"babel-plugin",
"decorators"
],
"license": "MIT",
"main": "lib/index.js",
"main": "./lib/index.js",
"name": "@babel/plugin-proposal-decorators",
"peerDependencies": {
"@babel/core": "^7.0.0-0"
@@ -66,5 +75,6 @@
"url": "git+https://github.com/babel/babel.git",
"directory": "packages/babel-plugin-proposal-decorators"
},
"version": "7.10.4"
"type": "commonjs",
"version": "7.18.6"
}