2014-11-09 12:06:56 +11:00

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);
};