Move decorators transform to @babel/helper-create-class-features-plugin (#9059)
* Move decorators to @babel/plugin-class-features * Minor refactoring * Use the new helper package
This commit is contained in:
parent
9b005dedfd
commit
d1d3c823cc
@ -1,3 +1,154 @@
|
|||||||
export function hasDecorators(path) {
|
import { types as t, template } from "@babel/core";
|
||||||
return !!(path.node.decorators && path.node.decorators.length);
|
import ReplaceSupers from "@babel/helper-replace-supers";
|
||||||
|
|
||||||
|
export function hasOwnDecorators(node) {
|
||||||
|
return !!(node.decorators && node.decorators.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function hasDecorators(node) {
|
||||||
|
return hasOwnDecorators(node) || node.body.body.some(hasOwnDecorators);
|
||||||
|
}
|
||||||
|
|
||||||
|
function prop(key, value) {
|
||||||
|
if (!value) return null;
|
||||||
|
return t.objectProperty(t.identifier(key), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function value(body, params = [], async, generator) {
|
||||||
|
const method = t.objectMethod("method", t.identifier("value"), params, body);
|
||||||
|
method.async = !!async;
|
||||||
|
method.generator = !!generator;
|
||||||
|
return method;
|
||||||
|
}
|
||||||
|
|
||||||
|
function takeDecorators(node) {
|
||||||
|
let result;
|
||||||
|
if (node.decorators && node.decorators.length > 0) {
|
||||||
|
result = t.arrayExpression(
|
||||||
|
node.decorators.map(decorator => decorator.expression),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
node.decorators = undefined;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getKey(node) {
|
||||||
|
if (node.computed) {
|
||||||
|
return node.key;
|
||||||
|
} else if (t.isIdentifier(node.key)) {
|
||||||
|
return t.stringLiteral(node.key.name);
|
||||||
|
} else {
|
||||||
|
return t.stringLiteral(String(node.key.value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: This function can be easily bound as .bind(file, classRef, superRef)
|
||||||
|
// to make it easier to use it in a loop.
|
||||||
|
function extractElementDescriptor(/* this: File, */ classRef, superRef, path) {
|
||||||
|
const { node, scope } = path;
|
||||||
|
const isMethod = path.isClassMethod();
|
||||||
|
|
||||||
|
if (path.isPrivate()) {
|
||||||
|
throw path.buildCodeFrameError(
|
||||||
|
`Private ${
|
||||||
|
isMethod ? "methods" : "fields"
|
||||||
|
} in decorated classes are not supported yet.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
new ReplaceSupers(
|
||||||
|
{
|
||||||
|
methodPath: path,
|
||||||
|
methodNode: node,
|
||||||
|
objectRef: classRef,
|
||||||
|
isStatic: node.static,
|
||||||
|
superRef,
|
||||||
|
scope,
|
||||||
|
file: this,
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
).replace();
|
||||||
|
|
||||||
|
const properties = [
|
||||||
|
prop("kind", t.stringLiteral(isMethod ? node.kind : "field")),
|
||||||
|
prop("decorators", takeDecorators(node)),
|
||||||
|
prop("static", node.static && t.booleanLiteral(true)),
|
||||||
|
prop("key", getKey(node)),
|
||||||
|
isMethod
|
||||||
|
? value(node.body, node.params, node.async, node.generator)
|
||||||
|
: node.value
|
||||||
|
? value(template.ast`{ return ${node.value} }`)
|
||||||
|
: prop("value", scope.buildUndefinedNode()),
|
||||||
|
].filter(Boolean);
|
||||||
|
|
||||||
|
path.remove();
|
||||||
|
|
||||||
|
return t.objectExpression(properties);
|
||||||
|
}
|
||||||
|
|
||||||
|
function addDecorateHelper(file) {
|
||||||
|
try {
|
||||||
|
return file.addHelper("decorate");
|
||||||
|
} catch (err) {
|
||||||
|
if (err.code === "BABEL_HELPER_UNKNOWN") {
|
||||||
|
err.message +=
|
||||||
|
"\n '@babel/plugin-transform-decorators' in non-legacy mode" +
|
||||||
|
" requires '@babel/core' version ^7.0.2 and you appear to be using" +
|
||||||
|
" an older version.";
|
||||||
|
}
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildDecoratedClass(ref, path, elements, file) {
|
||||||
|
const { node, scope } = path;
|
||||||
|
const initializeId = scope.generateUidIdentifier("initialize");
|
||||||
|
const isDeclaration = node.id && path.isDeclaration();
|
||||||
|
const isStrict = path.isInStrictMode();
|
||||||
|
const { superClass } = node;
|
||||||
|
|
||||||
|
node.type = "ClassDeclaration";
|
||||||
|
if (!node.id) node.id = t.cloneNode(ref);
|
||||||
|
|
||||||
|
let superId;
|
||||||
|
if (superClass) {
|
||||||
|
superId = scope.generateUidIdentifierBasedOnNode(node.superClass, "super");
|
||||||
|
node.superClass = superId;
|
||||||
|
}
|
||||||
|
|
||||||
|
const classDecorators = takeDecorators(node);
|
||||||
|
const definitions = t.arrayExpression(
|
||||||
|
elements.map(extractElementDescriptor.bind(file, node.id, superId)),
|
||||||
|
);
|
||||||
|
|
||||||
|
let replacement = template.expression.ast`
|
||||||
|
${addDecorateHelper(file)}(
|
||||||
|
${classDecorators || t.nullLiteral()},
|
||||||
|
function (${initializeId}, ${superClass ? superId : null}) {
|
||||||
|
${node}
|
||||||
|
return { F: ${t.cloneNode(node.id)}, d: ${definitions} };
|
||||||
|
},
|
||||||
|
${superClass}
|
||||||
|
)
|
||||||
|
`;
|
||||||
|
let classPathDesc = "arguments.1.body.body.0";
|
||||||
|
|
||||||
|
if (!isStrict) {
|
||||||
|
replacement.arguments[1].body.directives.push(
|
||||||
|
t.directive(t.directiveLiteral("use strict")),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isDeclaration) {
|
||||||
|
replacement = template.ast`let ${ref} = ${replacement}`;
|
||||||
|
classPathDesc = "declarations.0.init." + classPathDesc;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
instanceNodes: [template.statement.ast`${initializeId}(this)`],
|
||||||
|
wrapClass(path) {
|
||||||
|
path.replaceWith(replacement);
|
||||||
|
return path.get(classPathDesc);
|
||||||
|
},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { hasDecorators } from "./decorators";
|
import { hasOwnDecorators } from "./decorators";
|
||||||
|
|
||||||
export const FEATURES = Object.freeze({
|
export const FEATURES = Object.freeze({
|
||||||
//classes: 1 << 0,
|
//classes: 1 << 0,
|
||||||
@ -39,15 +39,19 @@ export function isLoose(file, feature) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function verifyUsedFeatures(path, file) {
|
export function verifyUsedFeatures(path, file) {
|
||||||
if (hasDecorators(path) && !hasFeature(file, FEATURES.decorators)) {
|
if (hasOwnDecorators(path)) {
|
||||||
|
if (!hasFeature(file, FEATURES.decorators)) {
|
||||||
throw path.buildCodeFrameError("Decorators are not enabled.");
|
throw path.buildCodeFrameError("Decorators are not enabled.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasFeature(file, FEATURES.decorators)) {
|
if (path.isPrivate()) {
|
||||||
throw new Error(
|
throw path.buildCodeFrameError(
|
||||||
"@babel/plugin-class-features doesn't support decorators yet.",
|
`Private ${
|
||||||
|
path.isClassMethod() ? "methods" : "fields"
|
||||||
|
} in decorated classes are not supported yet.`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// NOTE: We can't use path.isPrivateMethod() because it isn't supported in <7.2.0
|
// NOTE: We can't use path.isPrivateMethod() because it isn't supported in <7.2.0
|
||||||
if (path.isPrivate() && path.isMethod()) {
|
if (path.isPrivate() && path.isMethod()) {
|
||||||
|
|||||||
@ -318,6 +318,7 @@ export function buildFieldsInitNodes(
|
|||||||
) {
|
) {
|
||||||
const staticNodes = [];
|
const staticNodes = [];
|
||||||
const instanceNodes = [];
|
const instanceNodes = [];
|
||||||
|
let needsClassRef = false;
|
||||||
|
|
||||||
for (const prop of props) {
|
for (const prop of props) {
|
||||||
const isStatic = prop.node.static;
|
const isStatic = prop.node.static;
|
||||||
@ -329,19 +330,23 @@ export function buildFieldsInitNodes(
|
|||||||
|
|
||||||
switch (true) {
|
switch (true) {
|
||||||
case isStatic && isPrivate && isField && loose:
|
case isStatic && isPrivate && isField && loose:
|
||||||
|
needsClassRef = true;
|
||||||
staticNodes.push(
|
staticNodes.push(
|
||||||
buildPrivateFieldInitLoose(t.cloneNode(ref), prop, privateNamesMap),
|
buildPrivateFieldInitLoose(t.cloneNode(ref), prop, privateNamesMap),
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case isStatic && isPrivate && isField && !loose:
|
case isStatic && isPrivate && isField && !loose:
|
||||||
|
needsClassRef = true;
|
||||||
staticNodes.push(
|
staticNodes.push(
|
||||||
buildPrivateStaticFieldInitSpec(prop, privateNamesMap),
|
buildPrivateStaticFieldInitSpec(prop, privateNamesMap),
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case isStatic && isPublic && isField && loose:
|
case isStatic && isPublic && isField && loose:
|
||||||
|
needsClassRef = true;
|
||||||
staticNodes.push(buildPublicFieldInitLoose(t.cloneNode(ref), prop));
|
staticNodes.push(buildPublicFieldInitLoose(t.cloneNode(ref), prop));
|
||||||
break;
|
break;
|
||||||
case isStatic && isPublic && isField && !loose:
|
case isStatic && isPublic && isField && !loose:
|
||||||
|
needsClassRef = true;
|
||||||
staticNodes.push(
|
staticNodes.push(
|
||||||
buildPublicFieldInitSpec(t.cloneNode(ref), prop, state),
|
buildPublicFieldInitSpec(t.cloneNode(ref), prop, state),
|
||||||
);
|
);
|
||||||
@ -397,5 +402,27 @@ export function buildFieldsInitNodes(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return { staticNodes, instanceNodes };
|
return {
|
||||||
|
staticNodes,
|
||||||
|
instanceNodes,
|
||||||
|
wrapClass(path) {
|
||||||
|
for (const prop of props) {
|
||||||
|
prop.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!needsClassRef) return path;
|
||||||
|
|
||||||
|
if (path.isClassExpression()) {
|
||||||
|
path.scope.push({ id: ref });
|
||||||
|
path.replaceWith(
|
||||||
|
t.assignmentExpression("=", t.cloneNode(ref), path.node),
|
||||||
|
);
|
||||||
|
} else if (!path.node.id) {
|
||||||
|
// Anonymous class declaration
|
||||||
|
path.node.id = ref;
|
||||||
|
}
|
||||||
|
|
||||||
|
return path;
|
||||||
|
},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,11 +1,16 @@
|
|||||||
import nameFunction from "@babel/helper-function-name";
|
import nameFunction from "@babel/helper-function-name";
|
||||||
import { types as t } from "@babel/core";
|
import splitExportDeclaration from "@babel/helper-split-export-declaration";
|
||||||
import {
|
import {
|
||||||
buildPrivateNamesNodes,
|
buildPrivateNamesNodes,
|
||||||
buildPrivateNamesMap,
|
buildPrivateNamesMap,
|
||||||
transformPrivateNamesUsage,
|
transformPrivateNamesUsage,
|
||||||
buildFieldsInitNodes,
|
buildFieldsInitNodes,
|
||||||
} from "./fields";
|
} from "./fields";
|
||||||
|
import {
|
||||||
|
hasOwnDecorators,
|
||||||
|
buildDecoratedClass,
|
||||||
|
hasDecorators,
|
||||||
|
} from "./decorators";
|
||||||
import { injectInitialization, extractComputedKeys } from "./misc";
|
import { injectInitialization, extractComputedKeys } from "./misc";
|
||||||
import {
|
import {
|
||||||
enableFeature,
|
enableFeature,
|
||||||
@ -54,7 +59,9 @@ export function createClassFeaturePlugin({
|
|||||||
const loose = isLoose(this.file, FEATURES.fields);
|
const loose = isLoose(this.file, FEATURES.fields);
|
||||||
|
|
||||||
let constructor;
|
let constructor;
|
||||||
|
let isDecorated = hasOwnDecorators(path.node);
|
||||||
const props = [];
|
const props = [];
|
||||||
|
const elements = [];
|
||||||
const computedPaths = [];
|
const computedPaths = [];
|
||||||
const privateNames = new Set();
|
const privateNames = new Set();
|
||||||
const body = path.get("body");
|
const body = path.get("body");
|
||||||
@ -75,14 +82,19 @@ export function createClassFeaturePlugin({
|
|||||||
privateNames.add(name);
|
privateNames.add(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (path.isClassMethod({ kind: "constructor" })) {
|
||||||
|
constructor = path;
|
||||||
|
} else {
|
||||||
|
elements.push(path);
|
||||||
if (path.isProperty() || path.isPrivate()) {
|
if (path.isProperty() || path.isPrivate()) {
|
||||||
props.push(path);
|
props.push(path);
|
||||||
} else if (path.isClassMethod({ kind: "constructor" })) {
|
|
||||||
constructor = path;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!props.length) return;
|
if (!isDecorated) isDecorated = hasOwnDecorators(path.node);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!props.length && !isDecorated) return;
|
||||||
|
|
||||||
let ref;
|
let ref;
|
||||||
|
|
||||||
@ -93,13 +105,9 @@ export function createClassFeaturePlugin({
|
|||||||
ref = path.node.id;
|
ref = path.node.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
const keysNodes = extractComputedKeys(
|
// NODE: These three functions don't support decorators yet,
|
||||||
ref,
|
// but verifyUsedFeatures throws if there are both
|
||||||
path,
|
// decorators and private fields.
|
||||||
computedPaths,
|
|
||||||
this.file,
|
|
||||||
);
|
|
||||||
|
|
||||||
const privateNamesMap = buildPrivateNamesMap(props);
|
const privateNamesMap = buildPrivateNamesMap(props);
|
||||||
const privateNamesNodes = buildPrivateNamesNodes(
|
const privateNamesNodes = buildPrivateNamesNodes(
|
||||||
privateNamesMap,
|
privateNamesMap,
|
||||||
@ -109,19 +117,34 @@ export function createClassFeaturePlugin({
|
|||||||
|
|
||||||
transformPrivateNamesUsage(ref, path, privateNamesMap, loose, state);
|
transformPrivateNamesUsage(ref, path, privateNamesMap, loose, state);
|
||||||
|
|
||||||
const { staticNodes, instanceNodes } = buildFieldsInitNodes(
|
let keysNodes, staticNodes, instanceNodes, wrapClass;
|
||||||
|
|
||||||
|
if (isDecorated) {
|
||||||
|
staticNodes = keysNodes = [];
|
||||||
|
({ instanceNodes, wrapClass } = buildDecoratedClass(
|
||||||
|
ref,
|
||||||
|
path,
|
||||||
|
elements,
|
||||||
|
this.file,
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
keysNodes = extractComputedKeys(ref, path, computedPaths, this.file);
|
||||||
|
({ staticNodes, instanceNodes, wrapClass } = buildFieldsInitNodes(
|
||||||
ref,
|
ref,
|
||||||
props,
|
props,
|
||||||
privateNamesMap,
|
privateNamesMap,
|
||||||
state,
|
state,
|
||||||
loose,
|
loose,
|
||||||
);
|
));
|
||||||
|
}
|
||||||
|
|
||||||
if (instanceNodes.length > 0) {
|
if (instanceNodes.length > 0) {
|
||||||
injectInitialization(
|
injectInitialization(
|
||||||
path,
|
path,
|
||||||
constructor,
|
constructor,
|
||||||
instanceNodes,
|
instanceNodes,
|
||||||
(referenceVisitor, state) => {
|
(referenceVisitor, state) => {
|
||||||
|
if (isDecorated) return;
|
||||||
for (const prop of props) {
|
for (const prop of props) {
|
||||||
if (prop.node.static) continue;
|
if (prop.node.static) continue;
|
||||||
prop.traverse(referenceVisitor, state);
|
prop.traverse(referenceVisitor, state);
|
||||||
@ -130,28 +153,7 @@ export function createClassFeaturePlugin({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const prop of props) {
|
path = wrapClass(path);
|
||||||
prop.remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
keysNodes.length === 0 &&
|
|
||||||
staticNodes.length === 0 &&
|
|
||||||
privateNamesNodes.length === 0
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (path.isClassExpression()) {
|
|
||||||
path.scope.push({ id: ref });
|
|
||||||
path.replaceWith(
|
|
||||||
t.assignmentExpression("=", t.cloneNode(ref), path.node),
|
|
||||||
);
|
|
||||||
} else if (!path.node.id) {
|
|
||||||
// Anonymous class declaration
|
|
||||||
path.node.id = ref;
|
|
||||||
}
|
|
||||||
|
|
||||||
path.insertBefore(keysNodes);
|
path.insertBefore(keysNodes);
|
||||||
path.insertAfter([...privateNamesNodes, ...staticNodes]);
|
path.insertAfter([...privateNamesNodes, ...staticNodes]);
|
||||||
},
|
},
|
||||||
@ -161,6 +163,25 @@ export function createClassFeaturePlugin({
|
|||||||
|
|
||||||
throw path.buildCodeFrameError(`Unknown PrivateName "${path}"`);
|
throw path.buildCodeFrameError(`Unknown PrivateName "${path}"`);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
ExportDefaultDeclaration(path) {
|
||||||
|
if (this.file.get(versionKey) !== version) return;
|
||||||
|
|
||||||
|
const decl = path.get("declaration");
|
||||||
|
|
||||||
|
if (decl.isClassDeclaration() && hasDecorators(decl.node)) {
|
||||||
|
if (decl.node.id) {
|
||||||
|
// export default class Foo {}
|
||||||
|
// -->
|
||||||
|
// class Foo {} export { Foo as default }
|
||||||
|
splitExportDeclaration(path);
|
||||||
|
} else {
|
||||||
|
// Annyms class declarations can be
|
||||||
|
// transformed as if they were expressions
|
||||||
|
decl.node.type = "ClassExpression";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,3 @@
|
|||||||
var _class;
|
|
||||||
|
|
||||||
class MyClass {
|
class MyClass {
|
||||||
constructor() {
|
constructor() {
|
||||||
var _this = this;
|
var _this = this;
|
||||||
@ -22,7 +20,7 @@ class MyClass {
|
|||||||
|
|
||||||
var _myAsyncMethod = new WeakMap();
|
var _myAsyncMethod = new WeakMap();
|
||||||
|
|
||||||
_class = class MyClass2 {
|
(class MyClass2 {
|
||||||
constructor() {
|
constructor() {
|
||||||
var _this2 = this;
|
var _this2 = this;
|
||||||
|
|
||||||
@ -40,7 +38,7 @@ _class = class MyClass2 {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
});
|
||||||
|
|
||||||
var _myAsyncMethod2 = new WeakMap();
|
var _myAsyncMethod2 = new WeakMap();
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
var _class, _descriptor, _class2, _Symbol$search, _temp;
|
var _class, _descriptor, _Symbol$search, _temp;
|
||||||
|
|
||||||
function _initializerDefineProperty(target, property, descriptor, context) { if (!descriptor) return; Object.defineProperty(target, property, { enumerable: descriptor.enumerable, configurable: descriptor.configurable, writable: descriptor.writable, value: descriptor.initializer ? descriptor.initializer.call(context) : void 0 }); }
|
function _initializerDefineProperty(target, property, descriptor, context) { if (!descriptor) return; Object.defineProperty(target, property, { enumerable: descriptor.enumerable, configurable: descriptor.configurable, writable: descriptor.writable, value: descriptor.initializer ? descriptor.initializer.call(context) : void 0 }); }
|
||||||
|
|
||||||
@ -14,7 +14,7 @@ function _initializerWarningHelper(descriptor, context) { throw new Error('Decor
|
|||||||
|
|
||||||
function dec() {}
|
function dec() {}
|
||||||
|
|
||||||
let A = (_class = (_temp = (_Symbol$search = Symbol.search, _class2 =
|
let A = (_class = (_temp = (_Symbol$search = Symbol.search,
|
||||||
/*#__PURE__*/
|
/*#__PURE__*/
|
||||||
function () {
|
function () {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|||||||
@ -16,8 +16,7 @@
|
|||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/helper-plugin-utils": "^7.0.0",
|
"@babel/helper-plugin-utils": "^7.0.0",
|
||||||
"@babel/helper-replace-supers": "^7.1.0",
|
"@babel/helper-create-class-features-plugin": "^7.2.1",
|
||||||
"@babel/helper-split-export-declaration": "^7.0.0",
|
|
||||||
"@babel/plugin-syntax-decorators": "^7.2.0"
|
"@babel/plugin-syntax-decorators": "^7.2.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
|
|||||||
@ -1,6 +1,11 @@
|
|||||||
|
/* eslint-disable local-rules/plugin-name */
|
||||||
|
|
||||||
import { declare } from "@babel/helper-plugin-utils";
|
import { declare } from "@babel/helper-plugin-utils";
|
||||||
import syntaxDecorators from "@babel/plugin-syntax-decorators";
|
import syntaxDecorators from "@babel/plugin-syntax-decorators";
|
||||||
import visitor from "./transformer";
|
import {
|
||||||
|
createClassFeaturePlugin,
|
||||||
|
FEATURES,
|
||||||
|
} from "@babel/helper-create-class-features-plugin";
|
||||||
import legacyVisitor from "./transformer-legacy";
|
import legacyVisitor from "./transformer-legacy";
|
||||||
|
|
||||||
export default declare((api, options) => {
|
export default declare((api, options) => {
|
||||||
@ -31,14 +36,26 @@ export default declare((api, options) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (legacy) {
|
||||||
return {
|
return {
|
||||||
name: "proposal-decorators",
|
name: "proposal-decorators",
|
||||||
inherits: syntaxDecorators,
|
inherits: syntaxDecorators,
|
||||||
|
|
||||||
manipulateOptions({ generatorOpts }) {
|
manipulateOptions({ generatorOpts }) {
|
||||||
generatorOpts.decoratorsBeforeExport = decoratorsBeforeExport;
|
generatorOpts.decoratorsBeforeExport = decoratorsBeforeExport;
|
||||||
},
|
},
|
||||||
|
visitor: legacyVisitor,
|
||||||
visitor: legacy ? legacyVisitor : visitor,
|
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return createClassFeaturePlugin({
|
||||||
|
name: "proposal-decorators",
|
||||||
|
|
||||||
|
feature: FEATURES.decorators,
|
||||||
|
// loose: options.loose, Not supported
|
||||||
|
|
||||||
|
manipulateOptions({ generatorOpts, parserOpts }) {
|
||||||
|
parserOpts.plugins.push(["decorators", { decoratorsBeforeExport }]);
|
||||||
|
generatorOpts.decoratorsBeforeExport = decoratorsBeforeExport;
|
||||||
|
},
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,239 +0,0 @@
|
|||||||
import { types as t, template } from "@babel/core";
|
|
||||||
import splitExportDeclaration from "@babel/helper-split-export-declaration";
|
|
||||||
import ReplaceSupers from "@babel/helper-replace-supers";
|
|
||||||
|
|
||||||
function prop(key, value) {
|
|
||||||
if (!value) return null;
|
|
||||||
return t.objectProperty(t.identifier(key), value);
|
|
||||||
}
|
|
||||||
|
|
||||||
function value(body, params = [], async, generator) {
|
|
||||||
const method = t.objectMethod("method", t.identifier("value"), params, body);
|
|
||||||
method.async = !!async;
|
|
||||||
method.generator = !!generator;
|
|
||||||
return method;
|
|
||||||
}
|
|
||||||
|
|
||||||
function hasDecorators({ node }) {
|
|
||||||
if (node.decorators && node.decorators.length > 0) return true;
|
|
||||||
|
|
||||||
const body = node.body.body;
|
|
||||||
for (let i = 0; i < body.length; i++) {
|
|
||||||
const method = body[i];
|
|
||||||
if (method.decorators && method.decorators.length > 0) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function takeDecorators({ node }) {
|
|
||||||
let result;
|
|
||||||
if (node.decorators && node.decorators.length > 0) {
|
|
||||||
result = t.arrayExpression(
|
|
||||||
node.decorators.map(decorator => decorator.expression),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
node.decorators = undefined;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getKey(node) {
|
|
||||||
if (node.computed) {
|
|
||||||
return node.key;
|
|
||||||
} else if (t.isIdentifier(node.key)) {
|
|
||||||
return t.stringLiteral(node.key.name);
|
|
||||||
} else {
|
|
||||||
return t.stringLiteral(String(node.key.value));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getSingleElementDefinition(path, superRef, classRef, file) {
|
|
||||||
const { node, scope } = path;
|
|
||||||
const isMethod = path.isClassMethod();
|
|
||||||
|
|
||||||
if (path.isPrivate()) {
|
|
||||||
throw path.buildCodeFrameError(
|
|
||||||
`Private ${
|
|
||||||
isMethod ? "methods" : "fields"
|
|
||||||
} in decorated classes are not supported yet.`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
new ReplaceSupers(
|
|
||||||
{
|
|
||||||
methodPath: path,
|
|
||||||
methodNode: node,
|
|
||||||
objectRef: classRef,
|
|
||||||
isStatic: node.static,
|
|
||||||
superRef,
|
|
||||||
scope,
|
|
||||||
file,
|
|
||||||
},
|
|
||||||
true,
|
|
||||||
).replace();
|
|
||||||
|
|
||||||
const properties = [
|
|
||||||
prop("kind", t.stringLiteral(isMethod ? node.kind : "field")),
|
|
||||||
prop("decorators", takeDecorators(path)),
|
|
||||||
prop("static", node.static && t.booleanLiteral(true)),
|
|
||||||
prop("key", getKey(node)),
|
|
||||||
isMethod
|
|
||||||
? value(node.body, node.params, node.async, node.generator)
|
|
||||||
: node.value
|
|
||||||
? value(template.ast`{ return ${node.value} }`)
|
|
||||||
: prop("value", scope.buildUndefinedNode()),
|
|
||||||
].filter(Boolean);
|
|
||||||
|
|
||||||
return t.objectExpression(properties);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getElementsDefinitions(path, fId, file) {
|
|
||||||
const elements = [];
|
|
||||||
for (const p of path.get("body.body")) {
|
|
||||||
if (!p.isClassMethod({ kind: "constructor" })) {
|
|
||||||
elements.push(
|
|
||||||
getSingleElementDefinition(p, path.node.superClass, fId, file),
|
|
||||||
);
|
|
||||||
p.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return t.arrayExpression(elements);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getConstructorPath(path) {
|
|
||||||
return path
|
|
||||||
.get("body.body")
|
|
||||||
.find(path => path.isClassMethod({ kind: "constructor" }));
|
|
||||||
}
|
|
||||||
|
|
||||||
const bareSupersVisitor = {
|
|
||||||
CallExpression(path, { initializeInstanceElements }) {
|
|
||||||
if (path.get("callee").isSuper()) {
|
|
||||||
path.insertAfter(t.cloneNode(initializeInstanceElements));
|
|
||||||
|
|
||||||
// Sometimes this path gets requeued (e.g. in (super(), foo)), and
|
|
||||||
// it leads to infinite recursion.
|
|
||||||
path.skip();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Function(path) {
|
|
||||||
if (!path.isArrowFunctionExpression()) path.skip();
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
function insertInitializeInstanceElements(path, initializeInstanceId) {
|
|
||||||
const isBase = !path.node.superClass;
|
|
||||||
const initializeInstanceElements = t.callExpression(initializeInstanceId, [
|
|
||||||
t.thisExpression(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const constructorPath = getConstructorPath(path);
|
|
||||||
if (constructorPath) {
|
|
||||||
if (isBase) {
|
|
||||||
constructorPath
|
|
||||||
.get("body")
|
|
||||||
.unshiftContainer("body", [
|
|
||||||
t.expressionStatement(initializeInstanceElements),
|
|
||||||
]);
|
|
||||||
} else {
|
|
||||||
constructorPath.traverse(bareSupersVisitor, {
|
|
||||||
initializeInstanceElements,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const constructor = isBase
|
|
||||||
? t.classMethod(
|
|
||||||
"constructor",
|
|
||||||
t.identifier("constructor"),
|
|
||||||
[],
|
|
||||||
t.blockStatement([t.expressionStatement(initializeInstanceElements)]),
|
|
||||||
)
|
|
||||||
: t.classMethod(
|
|
||||||
"constructor",
|
|
||||||
t.identifier("constructor"),
|
|
||||||
[t.restElement(t.identifier("args"))],
|
|
||||||
t.blockStatement([
|
|
||||||
t.expressionStatement(
|
|
||||||
t.callExpression(t.Super(), [
|
|
||||||
t.spreadElement(t.identifier("args")),
|
|
||||||
]),
|
|
||||||
),
|
|
||||||
t.expressionStatement(initializeInstanceElements),
|
|
||||||
]),
|
|
||||||
);
|
|
||||||
path.node.body.body.push(constructor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function transformClass(path, file) {
|
|
||||||
const isDeclaration = path.node.id && path.isDeclaration();
|
|
||||||
const isStrict = path.isInStrictMode();
|
|
||||||
const { superClass } = path.node;
|
|
||||||
|
|
||||||
path.node.type = "ClassDeclaration";
|
|
||||||
if (!path.node.id) path.node.id = path.scope.generateUidIdentifier("class");
|
|
||||||
|
|
||||||
const initializeId = path.scope.generateUidIdentifier("initialize");
|
|
||||||
const superId =
|
|
||||||
superClass &&
|
|
||||||
path.scope.generateUidIdentifierBasedOnNode(path.node.superClass, "super");
|
|
||||||
|
|
||||||
if (superClass) path.node.superClass = superId;
|
|
||||||
|
|
||||||
const classDecorators = takeDecorators(path);
|
|
||||||
const definitions = getElementsDefinitions(path, path.node.id, file);
|
|
||||||
|
|
||||||
insertInitializeInstanceElements(path, initializeId);
|
|
||||||
|
|
||||||
const expr = template.expression.ast`
|
|
||||||
${addDecorateHelper(file)}(
|
|
||||||
${classDecorators || t.nullLiteral()},
|
|
||||||
function (${initializeId}, ${superClass ? superId : null}) {
|
|
||||||
${path.node}
|
|
||||||
return { F: ${t.cloneNode(path.node.id)}, d: ${definitions} };
|
|
||||||
},
|
|
||||||
${superClass}
|
|
||||||
)
|
|
||||||
`;
|
|
||||||
if (!isStrict) {
|
|
||||||
expr.arguments[1].body.directives.push(
|
|
||||||
t.directive(t.directiveLiteral("use strict")),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return isDeclaration ? template.ast`let ${path.node.id} = ${expr}` : expr;
|
|
||||||
}
|
|
||||||
|
|
||||||
function addDecorateHelper(file) {
|
|
||||||
try {
|
|
||||||
return file.addHelper("decorate");
|
|
||||||
} catch (err) {
|
|
||||||
if (err.code === "BABEL_HELPER_UNKNOWN") {
|
|
||||||
err.message +=
|
|
||||||
"\n '@babel/plugin-transform-decorators' in non-legacy mode" +
|
|
||||||
" requires '@babel/core' version ^7.0.2 and you appear to be using" +
|
|
||||||
" an older version.";
|
|
||||||
}
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
|
||||||
ExportDefaultDeclaration(path) {
|
|
||||||
let decl = path.get("declaration");
|
|
||||||
if (!decl.isClassDeclaration() || !hasDecorators(decl)) return;
|
|
||||||
|
|
||||||
if (decl.node.id) decl = splitExportDeclaration(path);
|
|
||||||
|
|
||||||
decl.replaceWith(transformClass(decl, this.file));
|
|
||||||
},
|
|
||||||
|
|
||||||
Class(path) {
|
|
||||||
if (hasDecorators(path)) {
|
|
||||||
path.replaceWith(transformClass(path, this.file));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
Loading…
x
Reference in New Issue
Block a user