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) {
|
||||
return !!(path.node.decorators && path.node.decorators.length);
|
||||
import { types as t, template } from "@babel/core";
|
||||
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({
|
||||
//classes: 1 << 0,
|
||||
@ -39,14 +39,18 @@ export function isLoose(file, feature) {
|
||||
}
|
||||
|
||||
export function verifyUsedFeatures(path, file) {
|
||||
if (hasDecorators(path) && !hasFeature(file, FEATURES.decorators)) {
|
||||
throw path.buildCodeFrameError("Decorators are not enabled.");
|
||||
}
|
||||
if (hasOwnDecorators(path)) {
|
||||
if (!hasFeature(file, FEATURES.decorators)) {
|
||||
throw path.buildCodeFrameError("Decorators are not enabled.");
|
||||
}
|
||||
|
||||
if (hasFeature(file, FEATURES.decorators)) {
|
||||
throw new Error(
|
||||
"@babel/plugin-class-features doesn't support decorators yet.",
|
||||
);
|
||||
if (path.isPrivate()) {
|
||||
throw path.buildCodeFrameError(
|
||||
`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
|
||||
|
||||
@ -318,6 +318,7 @@ export function buildFieldsInitNodes(
|
||||
) {
|
||||
const staticNodes = [];
|
||||
const instanceNodes = [];
|
||||
let needsClassRef = false;
|
||||
|
||||
for (const prop of props) {
|
||||
const isStatic = prop.node.static;
|
||||
@ -329,19 +330,23 @@ export function buildFieldsInitNodes(
|
||||
|
||||
switch (true) {
|
||||
case isStatic && isPrivate && isField && loose:
|
||||
needsClassRef = true;
|
||||
staticNodes.push(
|
||||
buildPrivateFieldInitLoose(t.cloneNode(ref), prop, privateNamesMap),
|
||||
);
|
||||
break;
|
||||
case isStatic && isPrivate && isField && !loose:
|
||||
needsClassRef = true;
|
||||
staticNodes.push(
|
||||
buildPrivateStaticFieldInitSpec(prop, privateNamesMap),
|
||||
);
|
||||
break;
|
||||
case isStatic && isPublic && isField && loose:
|
||||
needsClassRef = true;
|
||||
staticNodes.push(buildPublicFieldInitLoose(t.cloneNode(ref), prop));
|
||||
break;
|
||||
case isStatic && isPublic && isField && !loose:
|
||||
needsClassRef = true;
|
||||
staticNodes.push(
|
||||
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 { types as t } from "@babel/core";
|
||||
import splitExportDeclaration from "@babel/helper-split-export-declaration";
|
||||
import {
|
||||
buildPrivateNamesNodes,
|
||||
buildPrivateNamesMap,
|
||||
transformPrivateNamesUsage,
|
||||
buildFieldsInitNodes,
|
||||
} from "./fields";
|
||||
import {
|
||||
hasOwnDecorators,
|
||||
buildDecoratedClass,
|
||||
hasDecorators,
|
||||
} from "./decorators";
|
||||
import { injectInitialization, extractComputedKeys } from "./misc";
|
||||
import {
|
||||
enableFeature,
|
||||
@ -54,7 +59,9 @@ export function createClassFeaturePlugin({
|
||||
const loose = isLoose(this.file, FEATURES.fields);
|
||||
|
||||
let constructor;
|
||||
let isDecorated = hasOwnDecorators(path.node);
|
||||
const props = [];
|
||||
const elements = [];
|
||||
const computedPaths = [];
|
||||
const privateNames = new Set();
|
||||
const body = path.get("body");
|
||||
@ -75,14 +82,19 @@ export function createClassFeaturePlugin({
|
||||
privateNames.add(name);
|
||||
}
|
||||
|
||||
if (path.isProperty() || path.isPrivate()) {
|
||||
props.push(path);
|
||||
} else if (path.isClassMethod({ kind: "constructor" })) {
|
||||
if (path.isClassMethod({ kind: "constructor" })) {
|
||||
constructor = path;
|
||||
} else {
|
||||
elements.push(path);
|
||||
if (path.isProperty() || path.isPrivate()) {
|
||||
props.push(path);
|
||||
}
|
||||
}
|
||||
|
||||
if (!isDecorated) isDecorated = hasOwnDecorators(path.node);
|
||||
}
|
||||
|
||||
if (!props.length) return;
|
||||
if (!props.length && !isDecorated) return;
|
||||
|
||||
let ref;
|
||||
|
||||
@ -93,13 +105,9 @@ export function createClassFeaturePlugin({
|
||||
ref = path.node.id;
|
||||
}
|
||||
|
||||
const keysNodes = extractComputedKeys(
|
||||
ref,
|
||||
path,
|
||||
computedPaths,
|
||||
this.file,
|
||||
);
|
||||
|
||||
// NODE: These three functions don't support decorators yet,
|
||||
// but verifyUsedFeatures throws if there are both
|
||||
// decorators and private fields.
|
||||
const privateNamesMap = buildPrivateNamesMap(props);
|
||||
const privateNamesNodes = buildPrivateNamesNodes(
|
||||
privateNamesMap,
|
||||
@ -109,19 +117,34 @@ export function createClassFeaturePlugin({
|
||||
|
||||
transformPrivateNamesUsage(ref, path, privateNamesMap, loose, state);
|
||||
|
||||
const { staticNodes, instanceNodes } = buildFieldsInitNodes(
|
||||
ref,
|
||||
props,
|
||||
privateNamesMap,
|
||||
state,
|
||||
loose,
|
||||
);
|
||||
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,
|
||||
props,
|
||||
privateNamesMap,
|
||||
state,
|
||||
loose,
|
||||
));
|
||||
}
|
||||
|
||||
if (instanceNodes.length > 0) {
|
||||
injectInitialization(
|
||||
path,
|
||||
constructor,
|
||||
instanceNodes,
|
||||
(referenceVisitor, state) => {
|
||||
if (isDecorated) return;
|
||||
for (const prop of props) {
|
||||
if (prop.node.static) continue;
|
||||
prop.traverse(referenceVisitor, state);
|
||||
@ -130,28 +153,7 @@ export function createClassFeaturePlugin({
|
||||
);
|
||||
}
|
||||
|
||||
for (const prop of props) {
|
||||
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 = wrapClass(path);
|
||||
path.insertBefore(keysNodes);
|
||||
path.insertAfter([...privateNamesNodes, ...staticNodes]);
|
||||
},
|
||||
@ -161,6 +163,25 @@ export function createClassFeaturePlugin({
|
||||
|
||||
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 {
|
||||
constructor() {
|
||||
var _this = this;
|
||||
@ -22,7 +20,7 @@ class MyClass {
|
||||
|
||||
var _myAsyncMethod = new WeakMap();
|
||||
|
||||
_class = class MyClass2 {
|
||||
(class MyClass2 {
|
||||
constructor() {
|
||||
var _this2 = this;
|
||||
|
||||
@ -40,7 +38,7 @@ _class = class MyClass2 {
|
||||
});
|
||||
}
|
||||
|
||||
};
|
||||
});
|
||||
|
||||
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 }); }
|
||||
|
||||
@ -14,7 +14,7 @@ function _initializerWarningHelper(descriptor, context) { throw new Error('Decor
|
||||
|
||||
function dec() {}
|
||||
|
||||
let A = (_class = (_temp = (_Symbol$search = Symbol.search, _class2 =
|
||||
let A = (_class = (_temp = (_Symbol$search = Symbol.search,
|
||||
/*#__PURE__*/
|
||||
function () {
|
||||
"use strict";
|
||||
|
||||
@ -16,8 +16,7 @@
|
||||
],
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.0.0",
|
||||
"@babel/helper-replace-supers": "^7.1.0",
|
||||
"@babel/helper-split-export-declaration": "^7.0.0",
|
||||
"@babel/helper-create-class-features-plugin": "^7.2.1",
|
||||
"@babel/plugin-syntax-decorators": "^7.2.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
||||
@ -1,6 +1,11 @@
|
||||
/* eslint-disable local-rules/plugin-name */
|
||||
|
||||
import { declare } from "@babel/helper-plugin-utils";
|
||||
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";
|
||||
|
||||
export default declare((api, options) => {
|
||||
@ -31,14 +36,26 @@ export default declare((api, options) => {
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
name: "proposal-decorators",
|
||||
inherits: syntaxDecorators,
|
||||
if (legacy) {
|
||||
return {
|
||||
name: "proposal-decorators",
|
||||
inherits: syntaxDecorators,
|
||||
manipulateOptions({ generatorOpts }) {
|
||||
generatorOpts.decoratorsBeforeExport = decoratorsBeforeExport;
|
||||
},
|
||||
visitor: legacyVisitor,
|
||||
};
|
||||
}
|
||||
|
||||
manipulateOptions({ generatorOpts }) {
|
||||
return createClassFeaturePlugin({
|
||||
name: "proposal-decorators",
|
||||
|
||||
feature: FEATURES.decorators,
|
||||
// loose: options.loose, Not supported
|
||||
|
||||
manipulateOptions({ generatorOpts, parserOpts }) {
|
||||
parserOpts.plugins.push(["decorators", { decoratorsBeforeExport }]);
|
||||
generatorOpts.decoratorsBeforeExport = decoratorsBeforeExport;
|
||||
},
|
||||
|
||||
visitor: legacy ? legacyVisitor : visitor,
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
@ -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