private void validateOptionalExpression(Node n) { if (n.isEmpty()) { validateChildless(n); } else { validateExpression(n); } }
private Node tryFoldArrayAccess(Node n, Node left, Node right) { // If GETPROP/GETELEM is used as assignment target the array literal is // acting as a temporary we can't fold it here: // "[][0] += 1" if (NodeUtil.isAssignmentTarget(n)) { return n; } if (!right.isNumber()) { // Sometimes people like to use complex expressions to index into // arrays, or strings to index into array methods. return n; } double index = right.getDouble(); int intIndex = (int) index; if (intIndex != index) { report(INVALID_GETELEM_INDEX_ERROR, right); return n; } if (intIndex < 0) { report(INDEX_OUT_OF_BOUNDS_ERROR, right); return n; } Node current = left.getFirstChild(); Node elem = null; for (int i = 0; current != null; i++) { if (i != intIndex) { if (mayHaveSideEffects(current)) { return n; } } else { elem = current; } current = current.getNext(); } if (elem == null) { report(INDEX_OUT_OF_BOUNDS_ERROR, right); return n; } if (elem.isEmpty()) { elem = NodeUtil.newUndefinedNode(elem); } else { left.removeChild(elem); } // Replace the entire GETELEM with the value n.getParent().replaceChild(n, elem); reportCodeChange(); return elem; }
private void validateClass(Node n) { validateEs6Feature("classes", n); validateNodeType(Token.CLASS, n); validateChildCount(n); Node name = n.getFirstChild(); if (name.isEmpty()) { validateChildless(name); } else { validateName(name); } Node superClass = name.getNext(); if (superClass.isEmpty()) { validateChildless(superClass); } else { validateExpression(superClass); } validateClassMembers(n.getLastChild()); }
/** Add blocks to IF, WHILE, DO, etc. */ private void normalizeBlocks(Node n) { if (NodeUtil.isControlStructure(n) && !n.isLabel() && !n.isSwitch()) { for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (NodeUtil.isControlStructureCodeBlock(n, c) && !c.isBlock()) { Node newBlock = IR.block().srcref(n); n.replaceChild(c, newBlock); newBlock.setIsAddedBlock(true); if (!c.isEmpty()) { newBlock.addChildrenToFront(c); } c = newBlock; reportChange(); } } } }
private void validateClassMember(Node n) { if (n.getType() == Token.MEMBER_FUNCTION_DEF || n.getType() == Token.GETTER_DEF || n.getType() == Token.SETTER_DEF) { validateChildCount(n); validateFunctionExpression(n.getFirstChild()); } else if (n.getType() == Token.MEMBER_VARIABLE_DEF) { validateChildless(n); } else if (n.isComputedProp()) { validateComputedPropClassMethod(n); } else if (n.isEmpty()) { // Empty is allowed too. } else { violation("Class contained member of invalid type " + Token.name(n.getType()), n); } }
private void validateArrayPattern(int type, Node n) { validateNodeType(Token.ARRAY_PATTERN, n); for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { // When the array pattern is a direct child of a var/let/const node, // the last element is the RHS of the assignment. if (c == n.getLastChild() && NodeUtil.isNameDeclaration(n.getParent())) { validateExpression(c); } else if (c.isRest()) { validateRest(c); } else if (c.isEmpty()) { validateChildless(c); } else { // The members of the array pattern can be simple names, // or nested array/object patterns, e.g. "var [a,[b,c]]=[1,[2,3]];" validateNameDeclarationChild(type, c); } } }
// exp1, exp1 static Node fuseExpressionIntoExpression(Node exp1, Node exp2) { if (exp2.isEmpty()) { return exp1; } Node comma = new Node(Token.COMMA, exp1); comma.useSourceInfoIfMissingFrom(exp2); // We can just join the new comma expression with another comma but // lets keep all the comma's in a straight line. That way we can use // tree comparison. if (exp2.isComma()) { Node leftMostChild = exp2; while (leftMostChild.isComma()) { leftMostChild = leftMostChild.getFirstChild(); } Node parent = leftMostChild.getParent(); comma.addChildToBack(leftMostChild.detachFromParent()); parent.addChildToFront(comma); return exp2; } else { comma.addChildToBack(exp2); return comma; } }
private void visitClass(Node classNode, Node parent) { 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; // 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); 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); return; } Verify.verify(NodeUtil.isStatement(insertionPoint)); className.detachFromParent(); Node constructor = null; JSDocInfo ctorJSDocInfo = null; for (Node member : classMembers.children()) { if (member.getString().equals("constructor")) { ctorJSDocInfo = member.getJSDocInfo(); constructor = member.getFirstChild().detachFromParent(); if (!anonymous) { constructor.replaceChild(constructor.getFirstChild(), className); } } else { String qualifiedMemberName; if (member.isStaticMember()) { if (NodeUtil.referencesThis(member.getFirstChild())) { compiler.report(JSError.make(member, STATIC_METHOD_REFERENCES_THIS)); } qualifiedMemberName = Joiner.on(".").join(fullClassName, member.getString()); } else { qualifiedMemberName = Joiner.on(".").join(fullClassName, "prototype", member.getString()); } Node assign = IR.assign( NodeUtil.newQualifiedNameNode( compiler.getCodingConvention(), qualifiedMemberName, /* basis node */ member, /* original name */ member.getString()), member.getFirstChild().detachFromParent()); assign.srcref(member); JSDocInfo info = member.getJSDocInfo(); if (info != null) { info.setAssociatedNode(assign); assign.setJSDocInfo(info); } Node newNode = NodeUtil.newExpr(assign); insertionPoint.getParent().addChildAfter(newNode, insertionPoint); insertionPoint = newNode; } } if (constructor == null) { Node name = anonymous ? IR.name("").srcref(className) : className; constructor = IR.function(name, IR.paramList().srcref(classNode), IR.block().srcref(classNode)); } JSDocInfo classJSDoc = classNode.getJSDocInfo(); JSDocInfoBuilder newInfo = (classJSDoc != null) ? JSDocInfoBuilder.copyFrom(classJSDoc) : new JSDocInfoBuilder(true); newInfo.recordConstructor(); if (!superClassName.isEmpty()) { if (!superClassName.isQualifiedName()) { compiler.report(JSError.make(superClassName, DYNAMIC_EXTENDS_TYPE)); return; } Node superClassString = IR.string(superClassName.getQualifiedName()); if (newInfo.isInterfaceRecorded()) { newInfo.recordExtendedInterface( new JSTypeExpression( new Node(Token.BANG, superClassString), superClassName.getSourceFileName())); } else { // TODO(mattloring) Remove dependency on Closure Library. Node inherits = NodeUtil.newQualifiedNameNode(compiler.getCodingConvention(), "goog.inherits"); Node inheritsCall = IR.exprResult(IR.call(inherits, className.cloneTree(), superClassName.cloneTree())); inheritsCall.useSourceInfoIfMissingFromForTree(classNode); parent.addChildAfter(inheritsCall, classNode); newInfo.recordBaseType( new JSTypeExpression( new Node(Token.BANG, superClassString), superClassName.getSourceFileName())); } } // 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)); } } parent.replaceChild(classNode, constructor); if (NodeUtil.isStatement(constructor)) { constructor.setJSDocInfo(newInfo.build(constructor)); } 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 (parent.isAssign()) { // The constructor function is the RHS of an assignment. // Add the JSDoc to the ASSIGN node. parent.setJSDocInfo(newInfo.build(parent)); } else { throw new IllegalStateException("Unexpected parent node " + parent); } compiler.reportCodeChange(); }
@Override public void visit(NodeTraversal t, Node n, Node parent) { // VOID nodes appear when there are extra semicolons at the BLOCK level. // I've been unable to think of any cases where this indicates a bug, // and apparently some people like keeping these semicolons around, // so we'll allow it. if (n.isEmpty() || n.isComma()) { return; } if (parent == null) { return; } int pt = parent.getType(); if (pt == Token.COMMA) { Node gramps = parent.getParent(); if (gramps.isCall() && parent == gramps.getFirstChild()) { // Semantically, a direct call to eval is different from an indirect // call to an eval. See Ecma-262 S15.1.2.1. So it's ok for the first // expression to a comma to be a no-op if it's used to indirect // an eval. if (n == parent.getFirstChild() && parent.getChildCount() == 2 && n.getNext().isName() && "eval".equals(n.getNext().getString())) { return; } } if (n == parent.getLastChild()) { for (Node an : parent.getAncestors()) { int ancestorType = an.getType(); if (ancestorType == Token.COMMA) continue; if (ancestorType != Token.EXPR_RESULT && ancestorType != Token.BLOCK) return; else break; } } } else if (pt != Token.EXPR_RESULT && pt != Token.BLOCK) { if (pt == Token.FOR && parent.getChildCount() == 4 && (n == parent.getFirstChild() || n == parent.getFirstChild().getNext().getNext())) { // Fall through and look for warnings for the 1st and 3rd child // of a for. } else { return; // it might be ok to not have a side-effect } } boolean isSimpleOp = NodeUtil.isSimpleOperatorType(n.getType()); if (isSimpleOp || !NodeUtil.mayHaveSideEffects(n, t.getCompiler())) { if (n.isQualifiedName() && n.getJSDocInfo() != null) { // This no-op statement was there so that JSDoc information could // be attached to the name. This check should not complain about it. return; } else if (n.isExprResult()) { // we already reported the problem when we visited the child. return; } String msg = "This code lacks side-effects. Is there a bug?"; if (n.isString()) { msg = "Is there a missing '+' on the previous line?"; } else if (isSimpleOp) { msg = "The result of the '" + Token.name(n.getType()).toLowerCase() + "' operator is not being used."; } t.getCompiler().report(t.makeError(n, level, USELESS_CODE_ERROR, msg)); // TODO(johnlenz): determine if it is necessary to // try to protect side-effect free statements as well. if (!NodeUtil.isStatement(n)) { problemNodes.add(n); } } }
boolean hasSuperClass() { return !superClassNameNode.isEmpty(); }
/** * Classes are processed in 3 phases: * * <ol> * <li>The class name is extracted. * <li>Class members are processed and rewritten. * <li>The constructor is built. * </ol> */ private void visitClass(Node classNode, Node parent) { checkClassReassignment(classNode); // Collect Metadata ClassDeclarationMetadata metadata = ClassDeclarationMetadata.create(classNode, parent); if (metadata == null || metadata.fullClassName == null) { cannotConvert( parent, "Can only convert classes that are declarations or the right hand" + " side of a simple assignment."); return; } if (metadata.hasSuperClass() && !metadata.superClassNameNode.isQualifiedName()) { compiler.report(JSError.make(metadata.superClassNameNode, DYNAMIC_EXTENDS_TYPE)); return; } boolean useUnique = NodeUtil.isStatement(classNode) && !NodeUtil.isInFunction(classNode); String uniqueFullClassName = useUnique ? getUniqueClassName(metadata.fullClassName) : metadata.fullClassName; Node classNameAccess = NodeUtil.newQName(compiler, uniqueFullClassName); Node prototypeAccess = NodeUtil.newPropertyAccess(compiler, classNameAccess, "prototype"); Preconditions.checkState( NodeUtil.isStatement(metadata.insertionPoint), "insertion point must be a statement: %s", metadata.insertionPoint); Node constructor = null; JSDocInfo ctorJSDocInfo = null; // Process all members of the class Node classMembers = classNode.getLastChild(); Map<String, JSDocInfo> prototypeMembersToDeclare = new LinkedHashMap<>(); Map<String, JSDocInfo> classMembersToDeclare = new LinkedHashMap<>(); for (Node member : classMembers.children()) { if (member.isEmpty()) { continue; } Preconditions.checkState( member.isMemberFunctionDef() || member.isGetterDef() || member.isSetterDef() || (member.isComputedProp() && !member.getBooleanProp(Node.COMPUTED_PROP_VARIABLE)), "Member variables should have been transpiled earlier: ", member); if (member.isGetterDef() || member.isSetterDef()) { JSTypeExpression typeExpr = getTypeFromGetterOrSetter(member).clone(); addToDefinePropertiesObject(metadata, member); Map<String, JSDocInfo> membersToDeclare = member.isStaticMember() ? classMembersToDeclare : prototypeMembersToDeclare; JSDocInfo existingJSDoc = membersToDeclare.get(member.getString()); JSTypeExpression existingType = existingJSDoc == null ? null : existingJSDoc.getType(); if (existingType != null && !existingType.equals(typeExpr)) { compiler.report(JSError.make(member, CONFLICTING_GETTER_SETTER_TYPE, member.getString())); } else { JSDocInfoBuilder jsDoc = new JSDocInfoBuilder(false); jsDoc.recordType(typeExpr); if (member.getJSDocInfo() != null && member.getJSDocInfo().isExport()) { jsDoc.recordExport(); } if (member.isStaticMember()) { jsDoc.recordNoCollapse(); } membersToDeclare.put(member.getString(), jsDoc.build()); } } else if (member.isMemberFunctionDef() && member.getString().equals("constructor")) { ctorJSDocInfo = member.getJSDocInfo(); constructor = member.getFirstChild().detachFromParent(); if (!metadata.anonymous) { // Turns class Foo { constructor: function() {} } into function Foo() {}, // i.e. attaches the name the ctor function. constructor.replaceChild(constructor.getFirstChild(), metadata.classNameNode.cloneNode()); } } else { Node qualifiedMemberAccess = getQualifiedMemberAccess(compiler, member, classNameAccess, prototypeAccess); Node method = member.getLastChild().detachFromParent(); Node assign = IR.assign(qualifiedMemberAccess, method); assign.useSourceInfoIfMissingFromForTree(member); JSDocInfo info = member.getJSDocInfo(); if (member.isStaticMember() && NodeUtil.referencesThis(assign.getLastChild())) { JSDocInfoBuilder memberDoc = JSDocInfoBuilder.maybeCopyFrom(info); memberDoc.recordThisType( new JSTypeExpression( new Node(Token.BANG, new Node(Token.QMARK)), member.getSourceFileName())); info = memberDoc.build(); } if (info != null) { assign.setJSDocInfo(info); } Node newNode = NodeUtil.newExpr(assign); metadata.insertNodeAndAdvance(newNode); } } // Add declarations for properties that were defined with a getter and/or setter, // so that the typechecker knows those properties exist on the class. // This is a temporary solution. Eventually, the type checker should understand // Object.defineProperties calls directly. for (Map.Entry<String, JSDocInfo> entry : prototypeMembersToDeclare.entrySet()) { String declaredMember = entry.getKey(); Node declaration = IR.getprop(prototypeAccess.cloneTree(), IR.string(declaredMember)); declaration.setJSDocInfo(entry.getValue()); metadata.insertNodeAndAdvance( IR.exprResult(declaration).useSourceInfoIfMissingFromForTree(classNode)); } for (Map.Entry<String, JSDocInfo> entry : classMembersToDeclare.entrySet()) { String declaredMember = entry.getKey(); Node declaration = IR.getprop(classNameAccess.cloneTree(), IR.string(declaredMember)); declaration.setJSDocInfo(entry.getValue()); metadata.insertNodeAndAdvance( IR.exprResult(declaration).useSourceInfoIfMissingFromForTree(classNode)); } if (metadata.definePropertiesObjForPrototype.hasChildren()) { Node definePropsCall = IR.exprResult( IR.call( NodeUtil.newQName(compiler, "Object.defineProperties"), prototypeAccess.cloneTree(), metadata.definePropertiesObjForPrototype)); definePropsCall.useSourceInfoIfMissingFromForTree(classNode); metadata.insertNodeAndAdvance(definePropsCall); } if (metadata.definePropertiesObjForClass.hasChildren()) { Node definePropsCall = IR.exprResult( IR.call( NodeUtil.newQName(compiler, "Object.defineProperties"), classNameAccess.cloneTree(), metadata.definePropertiesObjForClass)); definePropsCall.useSourceInfoIfMissingFromForTree(classNode); metadata.insertNodeAndAdvance(definePropsCall); } Preconditions.checkNotNull(constructor); JSDocInfo classJSDoc = NodeUtil.getBestJSDocInfo(classNode); JSDocInfoBuilder newInfo = JSDocInfoBuilder.maybeCopyFrom(classJSDoc); newInfo.recordConstructor(); if (metadata.hasSuperClass()) { String superClassString = metadata.superClassNameNode.getQualifiedName(); if (newInfo.isInterfaceRecorded()) { newInfo.recordExtendedInterface( new JSTypeExpression( new Node(Token.BANG, IR.string(superClassString)), metadata.superClassNameNode.getSourceFileName())); } else { Node inherits = IR.call( NodeUtil.newQName(compiler, INHERITS), NodeUtil.newQName(compiler, metadata.fullClassName), NodeUtil.newQName(compiler, superClassString)); Node inheritsCall = IR.exprResult(inherits); compiler.needsEs6Runtime = true; inheritsCall.useSourceInfoIfMissingFromForTree(classNode); Node enclosingStatement = NodeUtil.getEnclosingStatement(classNode); enclosingStatement.getParent().addChildAfter(inheritsCall, enclosingStatement); newInfo.recordBaseType( new JSTypeExpression( new Node(Token.BANG, IR.string(superClassString)), metadata.superClassNameNode.getSourceFileName())); } } // 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)); } newInfo.mergePropertyBitfieldFrom(ctorJSDocInfo); } if (NodeUtil.isStatement(classNode)) { constructor.getFirstChild().setString(""); Node ctorVar = IR.let(metadata.classNameNode.cloneNode(), constructor); ctorVar.useSourceInfoIfMissingFromForTree(classNode); parent.replaceChild(classNode, ctorVar); } else { parent.replaceChild(classNode, constructor); } if (NodeUtil.isStatement(constructor)) { constructor.setJSDocInfo(newInfo.build()); } 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()); } else if (constructor.getParent().isName()) { // Is a newly created VAR node. Node var = constructor.getParent().getParent(); var.setJSDocInfo(newInfo.build()); } else if (parent.isAssign()) { // The constructor function is the RHS of an assignment. // Add the JSDoc to the ASSIGN node. parent.setJSDocInfo(newInfo.build()); } else { throw new IllegalStateException("Unexpected parent node " + parent); } compiler.reportCodeChange(); }
private void transformLoopClosure() { if (loopObjectMap.isEmpty()) { return; } for (Node loopNode : loopObjectMap.keySet()) { // Introduce objects to reflect the captured scope variables. // Fields are initially left as undefined to avoid cases like: // var $jscomp$loop$0 = {i: 0, j: $jscomp$loop$0.i} // They are initialized lazily by changing declarations into assignments // later. LoopObject object = loopObjectMap.get(loopNode); Node objectLit = IR.objectlit(); Node objectLitNextIteration = IR.objectlit(); for (Var var : object.vars) { objectLit.addChildToBack(IR.stringKey(var.name, IR.name("undefined"))); objectLitNextIteration.addChildToBack( IR.stringKey(var.name, IR.getprop(IR.name(object.name), IR.string(var.name)))); } Node updateLoopObject = IR.assign(IR.name(object.name), objectLitNextIteration); loopNode .getParent() .addChildBefore( IR.var(IR.name(object.name), objectLit).useSourceInfoIfMissingFromForTree(loopNode), loopNode); if (NodeUtil.isVanillaFor(loopNode)) { // For // The initializer is pulled out and placed prior to the loop. Node initializer = loopNode.getFirstChild(); loopNode.replaceChild(initializer, IR.empty()); if (!initializer.isEmpty()) { loopNode.getParent().addChildBefore(initializer, loopNode); } Node increment = loopNode.getChildAtIndex(2); if (increment.isEmpty()) { loopNode.replaceChild( increment, updateLoopObject.useSourceInfoIfMissingFromForTree(loopNode)); } else { Node placeHolder = IR.empty(); loopNode.replaceChild(increment, placeHolder); loopNode.replaceChild( placeHolder, IR.comma(updateLoopObject, increment).useSourceInfoIfMissingFromForTree(loopNode)); } } else if (loopNode.isDo()) { // do-while, put at the end of the block loopNode .getFirstChild() .addChildToBack( IR.exprResult(updateLoopObject).useSourceInfoIfMissingFromForTree(loopNode)); } else { // For-in, for-of or while, put at the end of the block loopNode .getLastChild() .addChildToBack( IR.exprResult(updateLoopObject).useSourceInfoIfMissingFromForTree(loopNode)); } // For captured variables, change declarations to assignments on the // corresponding field of the introduced object. Rename all references // accordingly. for (Var var : object.vars) { for (Node reference : referenceMap.get(var)) { // For-of and for-in declarations are not altered, since they are // used as temporary variables for assignment. if (NodeUtil.isEnhancedFor(loopNode) && loopNode.getFirstChild() == reference.getParent()) { loopNode .getLastChild() .addChildToFront( IR.exprResult( IR.assign( IR.getprop(IR.name(object.name), IR.string(var.name)), IR.name(var.name))) .useSourceInfoIfMissingFromForTree(reference)); } else { if (NodeUtil.isNameDeclaration(reference.getParent())) { Node declaration = reference.getParent(); Node grandParent = declaration.getParent(); // Normalize: "let i = 0, j = 0;" becomes "let i = 0; let j = 0;" while (declaration.getChildCount() > 1) { Node name = declaration.getLastChild(); grandParent.addChildAfter( IR.declaration(name.detachFromParent(), declaration.getType()) .useSourceInfoIfMissingFromForTree(declaration), declaration); } // Change declaration to assignment, or just drop it if there's // no initial value. if (reference.hasChildren()) { declaration = reference.getParent(); // Might have changed now Node newReference = IR.name(var.name); Node replacement = IR.exprResult(IR.assign(newReference, reference.removeFirstChild())) .useSourceInfoIfMissingFromForTree(declaration); grandParent.replaceChild(declaration, replacement); reference = newReference; } else { grandParent.removeChild(declaration); } } // Change reference to GETPROP. reference .getParent() .replaceChild( reference, IR.getprop(IR.name(object.name), IR.string(var.name)) .useSourceInfoIfMissingFromForTree(reference)); } } } } // Create wrapper functions and call them. for (Node function : functionLoopObjectsMap.keySet()) { Node returnNode = IR.returnNode(); Collection<LoopObject> objects = functionLoopObjectsMap.get(function); Node[] objectNames = new Node[objects.size()]; Node[] objectNamesForCall = new Node[objects.size()]; int i = 0; for (LoopObject object : objects) { objectNames[i] = IR.name(object.name); objectNamesForCall[i] = IR.name(object.name); i++; } Node iife = IR.function(IR.name(""), IR.paramList(objectNames), IR.block(returnNode)); Node call = IR.call(iife, objectNamesForCall); call.putBooleanProp(Node.FREE_CALL, true); function .getParent() .replaceChild(function, call.useSourceInfoIfMissingFromForTree(function)); returnNode.addChildToFront(function); } }
/** * Classes are processed in 3 phases: 1) The class name is extracted. 2) Class members are * processed and rewritten. 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.newQualifiedNameNode( compiler.getCodingConvention(), Joiner.on(".").join(uniqueFullClassName, member.getString())); } else { qualifiedMemberName = NodeUtil.newQualifiedNameNode( compiler.getCodingConvention(), Joiner.on(".").join(uniqueFullClassName, "prototype", member.getString())); } method = member.getFirstChild().detachFromParent(); } else if (member.isComputedProp()) { if (member.isStaticMember()) { qualifiedMemberName = IR.getelem( NodeUtil.newQualifiedNameNode( compiler.getCodingConvention(), uniqueFullClassName), member.removeFirstChild()); } else { qualifiedMemberName = IR.getelem( NodeUtil.newQualifiedNameNode( compiler.getCodingConvention(), 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.newQualifiedNameNode(compiler.getCodingConvention(), INHERITS), NodeUtil.newQualifiedNameNode(compiler.getCodingConvention(), fullClassName), NodeUtil.newQualifiedNameNode(compiler.getCodingConvention(), 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.newQualifiedNameNode(compiler.getCodingConvention(), COPY_PROP), NodeUtil.newQualifiedNameNode(compiler.getCodingConvention(), fullClassName), NodeUtil.newQualifiedNameNode(compiler.getCodingConvention(), 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 { throw new IllegalStateException("Unexpected parent node " + parent); } compiler.reportCodeChange(); }
private void visitArrayPattern(NodeTraversal t, Node arrayPattern, Node parent) { Node rhs, nodeToDetach; if (NodeUtil.isNameDeclaration(parent) && !NodeUtil.isEnhancedFor(parent.getParent())) { // The array pattern is the only child, because Es6SplitVariableDeclarations // has already run. Preconditions.checkState(arrayPattern.getNext() == null); rhs = arrayPattern.getLastChild(); nodeToDetach = parent; } else if (parent.isAssign()) { rhs = arrayPattern.getNext(); nodeToDetach = parent.getParent(); Preconditions.checkState(nodeToDetach.isExprResult()); } else if (parent.isArrayPattern() || parent.isDefaultValue() || parent.isStringKey()) { // This is a nested array pattern. Don't do anything now; we'll visit it // after visiting the parent. return; } else if (NodeUtil.isEnhancedFor(parent) || NodeUtil.isEnhancedFor(parent.getParent())) { visitDestructuringPatternInEnhancedFor(arrayPattern); return; } else { Preconditions.checkState(parent.isCatch() || parent.isForOf()); cannotConvertYet( arrayPattern, "ARRAY_PATTERN that is a child of a " + Token.name(parent.getType())); return; } // Convert 'var [x, y] = rhs' to: // var temp = rhs; // var x = temp[0]; // var y = temp[1]; String tempVarName = DESTRUCTURING_TEMP_VAR + (destructuringVarCounter++); Node tempDecl = IR.var(IR.name(tempVarName), rhs.detachFromParent()) .useSourceInfoIfMissingFromForTree(arrayPattern); nodeToDetach.getParent().addChildBefore(tempDecl, nodeToDetach); int i = 0; for (Node child = arrayPattern.getFirstChild(), next; child != null; child = next, i++) { next = child.getNext(); if (child.isEmpty()) { continue; } Node newLHS, newRHS; if (child.isDefaultValue()) { Node getElem = IR.getelem(IR.name(tempVarName), IR.number(i)); // [x = defaultValue] = rhs; // becomes // var temp = rhs; // x = (temp[0] === undefined) ? defaultValue : temp[0]; newLHS = child.getFirstChild().detachFromParent(); newRHS = defaultValueHook(getElem, child.getLastChild().detachFromParent()); } else if (child.isRest()) { newLHS = child.detachFromParent(); newLHS.setType(Token.NAME); // [].slice.call(temp, i) newRHS = IR.call( IR.getprop(IR.getprop(IR.arraylit(), IR.string("slice")), IR.string("call")), IR.name(tempVarName), IR.number(i)); } else { newLHS = child.detachFromParent(); newRHS = IR.getelem(IR.name(tempVarName), IR.number(i)); } Node newNode; if (parent.isAssign()) { Node assignment = IR.assign(newLHS, newRHS); newNode = IR.exprResult(assignment); } else { newNode = IR.declaration(newLHS, newRHS, parent.getType()); } newNode.useSourceInfoIfMissingFromForTree(arrayPattern); nodeToDetach.getParent().addChildBefore(newNode, nodeToDetach); // Explicitly visit the LHS of the new node since it may be a nested // destructuring pattern. visit(t, newLHS, newLHS.getParent()); } nodeToDetach.detachFromParent(); compiler.reportCodeChange(); }