210 lines
5.9 KiB
JavaScript
210 lines
5.9 KiB
JavaScript
var traverse = require("../../traverse");
|
|
var util = require("../../util");
|
|
var t = require("../../types");
|
|
var _ = require("lodash");
|
|
|
|
exports.ClassDeclaration = function (node, parent, file, scope) {
|
|
return t.variableDeclaration("var", [
|
|
t.variableDeclarator(node.id, buildClass(node, file, scope))
|
|
]);
|
|
};
|
|
|
|
exports.ClassExpression = function (node, parent, file, scope) {
|
|
return buildClass(node, file, scope);
|
|
};
|
|
|
|
var getMemberExpressionObject = function (node) {
|
|
while (t.isMemberExpression(node)) {
|
|
node = node.object;
|
|
}
|
|
return node;
|
|
};
|
|
|
|
var buildClass = function (node, file, scope) {
|
|
var superName = node.superClass;
|
|
var className = node.id || t.identifier(file.generateUid("class", scope));
|
|
|
|
var superClassArgument = node.superClass;
|
|
var superClassCallee = node.superClass;
|
|
|
|
if (superName) {
|
|
if (t.isMemberExpression(superName)) {
|
|
superClassArgument = superClassCallee = getMemberExpressionObject(superName);
|
|
} else if (!t.isIdentifier(superName)) {
|
|
superClassArgument = superName;
|
|
superClassCallee = superName = t.identifier(file.generateUid("ref", scope));
|
|
}
|
|
}
|
|
|
|
var container = util.template("class", {
|
|
CLASS_NAME: className
|
|
});
|
|
|
|
var block = container.callee.expression.body;
|
|
var body = block.body;
|
|
var constructor = body[0].declarations[0].init;
|
|
|
|
if (node.id) constructor.id = className;
|
|
|
|
var returnStatement = body.pop();
|
|
|
|
if (superName) {
|
|
body.push(t.expressionStatement(t.callExpression(file.addDeclaration("extends"), [className, superName])));
|
|
|
|
container.arguments.push(superClassArgument);
|
|
container.callee.expression.params.push(superClassCallee);
|
|
}
|
|
|
|
buildClassBody({
|
|
file: file,
|
|
body: body,
|
|
node: node,
|
|
className: className,
|
|
superName: superName,
|
|
constructor: constructor,
|
|
});
|
|
|
|
if (body.length === 1) {
|
|
// only a constructor so no need for a closure container
|
|
return constructor;
|
|
} else {
|
|
body.push(returnStatement);
|
|
return container;
|
|
}
|
|
};
|
|
|
|
var buildClassBody = function (opts) {
|
|
var file = opts.file;
|
|
var body = opts.body;
|
|
var node = opts.node;
|
|
var constructor = opts.constructor;
|
|
var className = opts.className;
|
|
var superName = opts.superName;
|
|
|
|
var instanceMutatorMap = {};
|
|
var staticMutatorMap = {};
|
|
var hasConstructor = false;
|
|
|
|
var classBody = node.body.body;
|
|
|
|
_.each(classBody, function (node) {
|
|
var methodName = node.key;
|
|
var method = node.value;
|
|
|
|
replaceInstanceSuperReferences(superName, node);
|
|
|
|
if (node.key.name === "constructor") {
|
|
if (node.kind === "") {
|
|
hasConstructor = true;
|
|
addConstructor(constructor, method);
|
|
} else {
|
|
throw file.errorWithNode(node, "illegal kind for constructor method");
|
|
}
|
|
} else {
|
|
var mutatorMap = instanceMutatorMap;
|
|
if (node.static) mutatorMap = staticMutatorMap;
|
|
|
|
var kind = node.kind;
|
|
|
|
if (kind === "") {
|
|
kind = "value";
|
|
util.pushMutatorMap(mutatorMap, methodName, "writable", t.identifier("true"));
|
|
}
|
|
|
|
util.pushMutatorMap(mutatorMap, methodName, kind, node);
|
|
}
|
|
});
|
|
|
|
if (!hasConstructor && superName) {
|
|
constructor.body.body.push(util.template("class-super-constructor-call", {
|
|
SUPER_NAME: superName
|
|
}, true));
|
|
}
|
|
|
|
var instanceProps;
|
|
var staticProps;
|
|
|
|
if (!_.isEmpty(instanceMutatorMap)) {
|
|
var protoId = util.template("prototype-identifier", {
|
|
CLASS_NAME: className
|
|
});
|
|
|
|
instanceProps = util.buildDefineProperties(instanceMutatorMap, protoId);
|
|
}
|
|
|
|
if (!_.isEmpty(staticMutatorMap)) {
|
|
staticProps = util.buildDefineProperties(staticMutatorMap, className);
|
|
}
|
|
|
|
if (instanceProps || staticProps) {
|
|
staticProps = staticProps || t.literal(null);
|
|
|
|
var args = [className, staticProps];
|
|
if (instanceProps) args.push(instanceProps);
|
|
|
|
body.push(t.expressionStatement(
|
|
t.callExpression(file.addDeclaration("class-props"), args)
|
|
));
|
|
}
|
|
};
|
|
|
|
var superIdentifier = function (superName, methodNode, node, parent) {
|
|
var methodName = methodNode.key;
|
|
|
|
if (parent.property === node) {
|
|
return;
|
|
} else if (t.isCallExpression(parent, { callee: node })) {
|
|
// super(); -> ClassName.prototype.MethodName.call(this);
|
|
parent.arguments.unshift(t.thisExpression());
|
|
|
|
if (methodName.name === "constructor") {
|
|
// constructor() { super(); }
|
|
return t.memberExpression(superName, t.identifier("call"));
|
|
} else {
|
|
node = superName;
|
|
|
|
// foo() { super(); }
|
|
if (!methodNode.static) {
|
|
node = t.memberExpression(node, t.identifier("prototype"));
|
|
}
|
|
|
|
node = t.memberExpression(node, methodName, methodNode.computed);
|
|
return t.memberExpression(node, t.identifier("call"));
|
|
}
|
|
} else if (t.isMemberExpression(parent) && !methodNode.static) {
|
|
// super.test -> ClassName.prototype.test
|
|
return t.memberExpression(superName, t.identifier("prototype"));
|
|
} else {
|
|
return superName;
|
|
}
|
|
};
|
|
|
|
var replaceInstanceSuperReferences = function (superName, methodNode) {
|
|
var method = methodNode.value;
|
|
|
|
superName = superName || t.identifier("Function");
|
|
|
|
traverse(method, function (node, parent) {
|
|
if (t.isIdentifier(node, { name: "super" })) {
|
|
return superIdentifier(superName, methodNode, node, parent);
|
|
} else if (t.isCallExpression(node)) {
|
|
var callee = node.callee;
|
|
if (!t.isMemberExpression(callee)) return;
|
|
if (callee.object.name !== "super") return;
|
|
|
|
// super.test(); -> ClassName.prototype.MethodName.call(this);
|
|
callee.property.name = callee.property.name + ".call";
|
|
node.arguments.unshift(t.thisExpression());
|
|
}
|
|
});
|
|
};
|
|
|
|
var addConstructor = function (construct, method) {
|
|
construct.defaults = method.defaults;
|
|
construct.params = method.params;
|
|
construct.body = method.body;
|
|
construct.rest = method.rest;
|
|
|
|
t.inherits(construct, method);
|
|
};
|