* 3) The constructor is built.
*/
private void visitClass(Node classNode, Node parent) {
checkClassReassignment(classNode);
// Collect Metadata
Node className = classNode.getFirstChild();
Node superClassName = className.getNext();
Node classMembers = classNode.getLastChild();
// This is a statement node. We insert methods of the
// transpiled class after this node.
Node insertionPoint;
if (!superClassName.isEmpty() && !superClassName.isQualifiedName()) {
compiler.report(JSError.make(superClassName, DYNAMIC_EXTENDS_TYPE));
return;
}
// The fully qualified name of the class, which will be used in the output.
// May come from the class itself or the LHS of an assignment.
String fullClassName = null;
// Whether the constructor function in the output should be anonymous.
boolean anonymous;
// If this is a class statement, or a class expression in a simple
// assignment or var statement, convert it. In any other case, the
// code is too dynamic, so just call cannotConvert.
if (NodeUtil.isStatement(classNode)) {
fullClassName = className.getString();
anonymous = false;
insertionPoint = classNode;
} else if (parent.isAssign() && parent.getParent().isExprResult()) {
// Add members after the EXPR_RESULT node:
// example.C = class {}; example.C.prototype.foo = function() {};
fullClassName = parent.getFirstChild().getQualifiedName();
if (fullClassName == null) {
cannotConvert(parent, "Can only convert classes that are declarations or the right hand"
+ " side of a simple assignment.");
return;
}
anonymous = true;
insertionPoint = parent.getParent();
} else if (parent.isName()) {
// Add members after the 'var' statement.
// var C = class {}; C.prototype.foo = function() {};
fullClassName = parent.getString();
anonymous = true;
insertionPoint = parent.getParent();
} else {
cannotConvert(parent, "Can only convert classes that are declarations or the right hand"
+ " side of a simple assignment.");
return;
}
if (!className.isEmpty() && !className.getString().equals(fullClassName)) {
// cannot bind two class names in the case of: var Foo = class Bar {};
cannotConvertYet(classNode, "named class in an assignment");
return;
}
boolean useUnique = NodeUtil.isStatement(classNode) && !isInFunction(classNode);
String uniqueFullClassName = useUnique ? getUniqueClassName(fullClassName) : fullClassName;
String superClassString = superClassName.getQualifiedName();
Verify.verify(NodeUtil.isStatement(insertionPoint));
Node constructor = null;
JSDocInfo ctorJSDocInfo = null;
// Process all members of the class
for (Node member : classMembers.children()) {
if (member.isEmpty()) {
continue;
}
if (member.isMemberDef() && member.getString().equals("constructor")) {
ctorJSDocInfo = member.getJSDocInfo();
constructor = member.getFirstChild().detachFromParent();
if (!anonymous) {
constructor.replaceChild(
constructor.getFirstChild(), className.cloneNode());
}
} else {
Node qualifiedMemberName;
Node method;
if (member.isMemberDef()) {
if (member.isStaticMember()) {
qualifiedMemberName = NodeUtil.newQName(
compiler,
Joiner.on(".").join(
uniqueFullClassName,
member.getString()));
} else {
qualifiedMemberName = NodeUtil.newQName(
compiler,
Joiner.on(".").join(
uniqueFullClassName,
"prototype",
member.getString()));
}
method = member.getFirstChild().detachFromParent();
} else if (member.isComputedProp()) {
if (member.isStaticMember()) {
qualifiedMemberName = IR.getelem(
NodeUtil.newQName(
compiler,
uniqueFullClassName),
member.removeFirstChild());
} else {
qualifiedMemberName = IR.getelem(
NodeUtil.newQName(
compiler,
Joiner.on('.').join(uniqueFullClassName, "prototype")),
member.removeFirstChild());
}
method = member.getLastChild().detachFromParent();
} else {
throw new IllegalStateException("Unexpected class member: " + member);
}
Node assign = IR.assign(qualifiedMemberName, method);
assign.useSourceInfoIfMissingFromForTree(member);
JSDocInfo info = member.getJSDocInfo();
if (member.isStaticMember() && NodeUtil.referencesThis(assign.getLastChild())) {
JSDocInfoBuilder memberDoc;
if (info == null) {
memberDoc = new JSDocInfoBuilder(true);
} else {
memberDoc = JSDocInfoBuilder.copyFrom(info);
}
memberDoc.recordThisType(
new JSTypeExpression(new Node(Token.BANG, new Node(Token.QMARK)),
member.getSourceFileName()));
info = memberDoc.build(assign);
}
if (info != null) {
info.setAssociatedNode(assign);
assign.setJSDocInfo(info);
}
Node newNode = NodeUtil.newExpr(assign);
insertionPoint.getParent().addChildAfter(newNode, insertionPoint);
insertionPoint = newNode;
}
}
// Rewrite constructor
if (constructor == null) {
Node body = IR.block();
if (!superClassName.isEmpty()) {
Node superCall = baseCall(classNode, "constructor", null);
body.addChildToBack(IR.exprResult(superCall));
}
Node name = anonymous
? IR.name("").srcref(className) : className.detachFromParent();
constructor = IR.function(
name,
IR.paramList(),
body).useSourceInfoIfMissingFromForTree(classNode);
}
JSDocInfo classJSDoc = classNode.getJSDocInfo();
JSDocInfoBuilder newInfo = (classJSDoc != null) ?
JSDocInfoBuilder.copyFrom(classJSDoc) :
new JSDocInfoBuilder(true);
newInfo.recordConstructor();
if (!superClassName.isEmpty()) {
if (newInfo.isInterfaceRecorded()) {
newInfo.recordExtendedInterface(new JSTypeExpression(new Node(Token.BANG,
IR.string(superClassString)),
superClassName.getSourceFileName()));
} else {
Node inherits = IR.call(
NodeUtil.newQName(compiler, INHERITS),
NodeUtil.newQName(compiler, fullClassName),
NodeUtil.newQName(compiler, superClassString));
Node inheritsCall = IR.exprResult(inherits);
inheritsCall.useSourceInfoIfMissingFromForTree(classNode);
Node enclosingStatement = NodeUtil.getEnclosingStatement(classNode);
enclosingStatement.getParent().addChildAfter(inheritsCall, enclosingStatement);
newInfo.recordBaseType(new JSTypeExpression(new Node(Token.BANG,
IR.string(superClassString)),
superClassName.getSourceFileName()));
Node copyProps = IR.call(
NodeUtil.newQName(compiler, COPY_PROP),
NodeUtil.newQName(compiler, fullClassName),
NodeUtil.newQName(compiler, superClassString));
copyProps.useSourceInfoIfMissingFromForTree(classNode);
enclosingStatement.getParent().addChildAfter(
IR.exprResult(copyProps).srcref(classNode), enclosingStatement);
}
}
// Classes are @struct by default.
if (!newInfo.isUnrestrictedRecorded() && !newInfo.isDictRecorded() &&
!newInfo.isStructRecorded()) {
newInfo.recordStruct();
}
if (ctorJSDocInfo != null) {
newInfo.recordSuppressions(ctorJSDocInfo.getSuppressions());
for (String param : ctorJSDocInfo.getParameterNames()) {
newInfo.recordParameter(param, ctorJSDocInfo.getParameterType(param));
}
}
insertionPoint = constructor;
if (NodeUtil.isStatement(classNode)) {
constructor.getFirstChild().setString("");
Node ctorVar = IR.var(IR.name(fullClassName), constructor);
ctorVar.useSourceInfoIfMissingFromForTree(classNode);
parent.replaceChild(classNode, ctorVar);
} else {
parent.replaceChild(classNode, constructor);
}
if (NodeUtil.isStatement(constructor)) {
insertionPoint.setJSDocInfo(newInfo.build(insertionPoint));
} else if (parent.isName()) {
// The constructor function is the RHS of a var statement.
// Add the JSDoc to the VAR node.
Node var = parent.getParent();
var.setJSDocInfo(newInfo.build(var));
} else if (constructor.getParent().isName()) {
// Is a newly created VAR node.
Node var = constructor.getParent().getParent();
var.setJSDocInfo(newInfo.build(var));
} else if (parent.isAssign()) {
// The constructor function is the RHS of an assignment.
// Add the JSDoc to the ASSIGN node.
parent.setJSDocInfo(newInfo.build(parent));
} else {