@babel/plugin-proposal-decorators
Version:
Compile class and object decorators to ES5
185 lines (181 loc) • 8.64 kB
JavaScript
import { declare } from '@babel/helper-plugin-utils';
import syntaxDecorators from '@babel/plugin-syntax-decorators';
import { createClassFeaturePlugin, FEATURES } from '@babel/helper-create-class-features-plugin';
import { template, types } from '@babel/core';
const buildClassDecorator = template.statement(`
DECORATOR(CLASS_REF = INNER) || CLASS_REF;
`);
const buildClassPrototype = template(`
CLASS_REF.prototype;
`);
const buildGetDescriptor = template(`
Object.getOwnPropertyDescriptor(TARGET, PROPERTY);
`);
const buildGetObjectInitializer = template(`
(TEMP = Object.getOwnPropertyDescriptor(TARGET, PROPERTY), (TEMP = TEMP ? TEMP.value : undefined), {
enumerable: true,
configurable: true,
writable: true,
initializer: function(){
return TEMP;
}
})
`);
const WARNING_CALLS = new WeakSet();
function applyEnsureOrdering(path) {
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 => !types.isIdentifier(decorator.expression));
if (identDecorators.length === 0) return;
return types.sequenceExpression(identDecorators.map(decorator => {
const expression = decorator.expression;
const id = decorator.expression = path.scope.generateDeclaredUidIdentifier("dec");
return types.assignmentExpression("=", id, expression);
}).concat([path.node]));
}
function applyClassDecorators(classPath) {
if (!hasClassDecorators(classPath.node)) return;
const decorators = classPath.node.decorators || [];
classPath.node.decorators = null;
const name = classPath.scope.generateDeclaredUidIdentifier("class");
return decorators.map(dec => dec.expression).reverse().reduce(function (acc, decorator) {
return buildClassDecorator({
CLASS_REF: types.cloneNode(name),
DECORATOR: types.cloneNode(decorator),
INNER: acc
}).expression;
}, classPath.node);
}
function hasClassDecorators(classNode) {
return !!classNode.decorators?.length;
}
function applyMethodDecorators(path, state) {
if (!hasMethodDecorators(path.node.body.body)) return;
return applyTargetDecorators(path, state, path.node.body.body);
}
function hasMethodDecorators(body) {
return body.some(node => node.decorators?.length);
}
function applyObjectDecorators(path, state) {
if (!hasMethodDecorators(path.node.properties)) return;
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) {
let decorators = [];
if (node.decorators != null) {
decorators = node.decorators;
node.decorators = null;
}
if (decorators.length === 0) return acc;
if (node.computed) {
throw path.buildCodeFrameError("Computed method/property decorators are not yet supported.");
}
const property = types.isLiteral(node.key) ? node.key : types.stringLiteral(node.key.name);
const target = path.isClass() && !node.static ? buildClassPrototype({
CLASS_REF: name
}).expression : name;
if (types.isClassProperty(node, {
static: false
})) {
const descriptor = path.scope.generateDeclaredUidIdentifier("descriptor");
const initializer = node.value ? types.functionExpression(null, [], types.blockStatement([types.returnStatement(node.value)])) : types.nullLiteral();
node.value = types.callExpression(state.addHelper("initializerWarningHelper"), [descriptor, types.thisExpression()]);
WARNING_CALLS.add(node.value);
acc.push(types.assignmentExpression("=", types.cloneNode(descriptor), types.callExpression(state.addHelper("applyDecoratedDescriptor"), [types.cloneNode(target), types.cloneNode(property), types.arrayExpression(decorators.map(dec => types.cloneNode(dec.expression))), types.objectExpression([types.objectProperty(types.identifier("configurable"), types.booleanLiteral(true)), types.objectProperty(types.identifier("enumerable"), types.booleanLiteral(true)), types.objectProperty(types.identifier("writable"), types.booleanLiteral(true)), types.objectProperty(types.identifier("initializer"), initializer)])])));
} else {
acc.push(types.callExpression(state.addHelper("applyDecoratedDescriptor"), [types.cloneNode(target), types.cloneNode(property), types.arrayExpression(decorators.map(dec => types.cloneNode(dec.expression))), types.isObjectProperty(node) || types.isClassProperty(node, {
static: true
}) ? buildGetObjectInitializer({
TEMP: path.scope.generateDeclaredUidIdentifier("init"),
TARGET: types.cloneNode(target),
PROPERTY: types.cloneNode(property)
}).expression : buildGetDescriptor({
TARGET: types.cloneNode(target),
PROPERTY: types.cloneNode(property)
}).expression, types.cloneNode(target)]));
}
return acc;
}, []);
return types.sequenceExpression([types.assignmentExpression("=", types.cloneNode(name), path.node), types.sequenceExpression(exprs), types.cloneNode(name)]);
}
function decoratedClassToExpression({
node,
scope
}) {
if (!hasClassDecorators(node) && !hasMethodDecorators(node.body.body)) {
return;
}
const ref = node.id ? types.cloneNode(node.id) : scope.generateUidIdentifier("class");
return types.variableDeclaration("let", [types.variableDeclarator(ref, types.toExpression(node))]);
}
const visitor = {
ExportDefaultDeclaration(path) {
const decl = path.get("declaration");
if (!decl.isClassDeclaration()) return;
const replacement = decoratedClassToExpression(decl);
if (replacement) {
const [varDeclPath] = path.replaceWithMultiple([replacement, types.exportNamedDeclaration(null, [types.exportSpecifier(types.cloneNode(replacement.declarations[0].id), types.identifier("default"))])]);
if (!decl.node.id) {
path.scope.registerDeclaration(varDeclPath);
}
}
},
ClassDeclaration(path) {
const replacement = decoratedClassToExpression(path);
if (replacement) {
const [newPath] = path.replaceWith(replacement);
const decl = newPath.get("declarations.0");
const id = decl.node.id;
const binding = path.scope.getOwnBinding(id.name);
binding.identifier = id;
binding.path = decl;
}
},
ClassExpression(path, state) {
const decoratedClass = applyEnsureOrdering(path) || applyClassDecorators(path) || applyMethodDecorators(path, state);
if (decoratedClass) path.replaceWith(decoratedClass);
},
ObjectExpression(path, state) {
const decoratedObject = applyEnsureOrdering(path) || applyObjectDecorators(path, state);
if (decoratedObject) path.replaceWith(decoratedObject);
},
AssignmentExpression(path, state) {
if (!WARNING_CALLS.has(path.node.right)) return;
path.replaceWith(types.callExpression(state.addHelper("initializerDefineProperty"), [types.cloneNode(path.get("left.object").node), types.stringLiteral(path.get("left.property").node.name || path.get("left.property").node.value), types.cloneNode(path.get("right.arguments.0").node), types.cloneNode(path.get("right.arguments.1").node)]));
},
CallExpression(path, state) {
if (path.node.arguments.length !== 3) return;
if (!WARNING_CALLS.has(path.node.arguments[2])) return;
if (path.node.callee.name !== state.addHelper("defineProperty").name) {
return;
}
path.replaceWith(types.callExpression(state.addHelper("initializerDefineProperty"), [types.cloneNode(path.get("arguments.0").node), types.cloneNode(path.get("arguments.1").node), types.cloneNode(path.get("arguments.2.arguments.0").node), types.cloneNode(path.get("arguments.2.arguments.1").node)]));
}
};
const index = declare((api, options) => {
api.assertVersion("^7.0.0-0 || ^8.0.0");
const {
version
} = options;
if (version === "legacy") {
return {
name: "proposal-decorators",
inherits: syntaxDecorators,
visitor: visitor
};
} else if (!version || version === "2023-11") {
api.assertVersion("^7.0.2 || ^8.0.0");
return createClassFeaturePlugin({
name: "proposal-decorators",
api,
feature: FEATURES.decorators,
inherits: syntaxDecorators,
decoratorVersion: version
});
} else {
throw new Error("The '.version' option must be one of 'legacy' or '2023-11'.");
}
});
export { index as default };
//# sourceMappingURL=index.js.map