@Override public void visit(NodeTraversal t, Node n, Node parent) { switch (n.getType()) { case Token.OBJECTLIT: for (Node child : n.children()) { if (child.isComputedProp()) { visitObjectWithComputedProperty(n, parent); break; } } break; case Token.STRING_KEY: visitStringKey(n); break; case Token.CLASS: visitClass(n, parent); break; case Token.PARAM_LIST: visitParamList(n, parent); break; case Token.ARRAYLIT: case Token.NEW: case Token.CALL: for (Node child : n.children()) { if (child.isSpread()) { visitArrayLitOrCallWithSpread(n, parent); break; } } break; } }
@Override public void visit(NodeTraversal t, Node n, Node parent) { switch (n.getType()) { case Token.OBJECTLIT: for (Node child : n.children()) { if (child.isComputedProp()) { visitObjectWithComputedProperty(n, parent); break; } } break; case Token.MEMBER_DEF: if (parent.isObjectLit()) { visitMemberDefInObjectLit(n, parent); } break; case Token.FOR_OF: visitForOf(n, parent); break; case Token.SUPER: visitSuper(n, parent); break; case Token.STRING_KEY: visitStringKey(n); break; case Token.CLASS: for (Node member = n.getLastChild().getFirstChild(); member != null; member = member.getNext()) { if (member.isGetterDef() || member.isSetterDef() || member.getBooleanProp(Node.COMPUTED_PROP_GETTER) || member.getBooleanProp(Node.COMPUTED_PROP_SETTER)) { cannotConvert(member, "getters or setters in class definitions"); return; } } visitClass(n, parent); break; case Token.ARRAYLIT: case Token.NEW: case Token.CALL: for (Node child : n.children()) { if (child.isSpread()) { visitArrayLitOrCallWithSpread(n, parent); break; } } break; case Token.TEMPLATELIT: Es6TemplateLiterals.visitTemplateLiteral(t, n); break; case Token.ARRAY_PATTERN: visitArrayPattern(t, n, parent); break; case Token.OBJECT_PATTERN: visitObjectPattern(t, n, parent); break; } }
private void visitObjectWithComputedProperty(Node obj, Node parent) { Preconditions.checkArgument(obj.isObjectLit()); List<Node> props = new ArrayList<>(); Node currElement = obj.getFirstChild(); while (currElement != null) { if (currElement.getBooleanProp(Node.COMPUTED_PROP_GETTER) || currElement.getBooleanProp(Node.COMPUTED_PROP_SETTER)) { cannotConvertYet(currElement, "computed getter/setter"); return; } else if (currElement.isGetterDef() || currElement.isSetterDef()) { currElement = currElement.getNext(); } else { Node nextNode = currElement.getNext(); obj.removeChild(currElement); props.add(currElement); currElement = nextNode; } } String objName = FRESH_COMP_PROP_VAR + compiler.getUniqueNameIdSupplier().get(); props = Lists.reverse(props); Node result = IR.name(objName); for (Node propdef : props) { if (propdef.isComputedProp()) { Node propertyExpression = propdef.removeFirstChild(); Node value = propdef.removeFirstChild(); result = IR.comma(IR.assign(IR.getelem(IR.name(objName), propertyExpression), value), result); } else { if (!propdef.hasChildren()) { Node name = IR.name(propdef.getString()).useSourceInfoIfMissingFrom(propdef); propdef.addChildToBack(name); } Node val = propdef.removeFirstChild(); propdef.setType(Token.STRING); int type = propdef.isQuotedString() ? Token.GETELEM : Token.GETPROP; Node access = new Node(type, IR.name(objName), propdef); result = IR.comma(IR.assign(access, val), result); } } Node statement = obj; while (!NodeUtil.isStatement(statement)) { statement = statement.getParent(); } result.useSourceInfoIfMissingFromForTree(obj); parent.replaceChild(obj, result); Node var = IR.var(IR.name(objName), obj); var.useSourceInfoIfMissingFromForTree(statement); statement.getParent().addChildBefore(var, statement); compiler.reportCodeChange(); }
/** * Constructs a Node that represents an access to the given class member, qualified by either the * static or the instance access context, depending on whether the member is static. * * <p><b>WARNING:</b> {@code member} may be modified/destroyed by this method, do not use it * afterwards. */ static Node getQualifiedMemberAccess( AbstractCompiler compiler, Node member, Node staticAccess, Node instanceAccess) { Node context = member.isStaticMember() ? staticAccess : instanceAccess; context = context.cloneTree(); if (member.isComputedProp()) { return IR.getelem(context, member.removeFirstChild()); } else { return NodeUtil.newPropertyAccess(compiler, context, member.getString()); } }
/** * Makes sure that the keys for listeners and hostAttributes blocks are quoted to avoid renaming. */ private void quoteListenerAndHostAttributeKeys(Node objLit) { Preconditions.checkState(objLit.isObjectLit()); for (Node keyNode : objLit.children()) { if (keyNode.isComputedProp()) { continue; } if (!keyNode.getString().equals("listeners") && !keyNode.getString().equals("hostAttributes")) { continue; } for (Node keyToQuote : keyNode.getFirstChild().children()) { keyToQuote.setQuotedString(); } } }
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 validateNameDeclarationChild(int type, Node n) { if (n.isName()) { // Don't use validateName here since this NAME node may have // a child. validateNonEmptyString(n); validateMaximumChildCount(n, 1); if (n.hasChildren()) { validateExpression(n.getFirstChild()); } } else if (n.isArrayPattern()) { validateArrayPattern(type, n); } else if (n.isObjectPattern()) { validateObjectPattern(type, n); } else if (n.isDefaultValue()) { validateDefaultValue(type, n); } else if (n.isComputedProp()) { validateObjectPatternComputedPropKey(type, n); } else { violation("Invalid child for " + Token.name(type) + " node", n); } }
private static boolean isDeclarationHelper(Node node) { Node parent = node.getParent(); // Special case for class B extends A, A is not a declaration. if (parent.isClass() && node != parent.getFirstChild()) { return false; } // This condition can be true during InlineVariables. if (parent.getParent() == null) { return false; } if (NodeUtil.isNameDeclaration(parent.getParent()) && node == parent.getLastChild()) { // Unless it is something like "for (var/let/const a of x){}", // this is the RHS of a var/let/const and thus not a declaration. if (parent.getParent().getParent() == null || !parent.getParent().getParent().isForOf()) { return false; } } // Special cases for destructuring patterns. if (parent.isDestructuringPattern() || (parent.isStringKey() && parent.getParent().isObjectPattern()) || (parent.isComputedProp() && parent.getParent().isObjectPattern() && node == parent.getLastChild()) || (parent.isDefaultValue() && node == parent.getFirstChild())) { return isDeclarationHelper(parent); } // Special case for arrow function if (parent.isArrowFunction()) { return node == parent.getFirstChild(); } return DECLARATION_PARENTS.contains(parent.getType()); }
/** * 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(); }
/** * 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 visitObjectPattern(NodeTraversal t, Node objectPattern, Node parent) { Node rhs, nodeToDetach; if (NodeUtil.isNameDeclaration(parent) && !NodeUtil.isEnhancedFor(parent.getParent())) { rhs = objectPattern.getLastChild(); nodeToDetach = parent; } else if (parent.isAssign() && parent.getParent().isExprResult()) { rhs = parent.getLastChild(); nodeToDetach = parent.getParent(); } else if (parent.isStringKey() || parent.isArrayPattern() || parent.isDefaultValue()) { // Nested object pattern; do nothing. We will visit it after rewriting the parent. return; } else if (NodeUtil.isEnhancedFor(parent) || NodeUtil.isEnhancedFor(parent.getParent())) { visitDestructuringPatternInEnhancedFor(objectPattern); return; } else { Preconditions.checkState(parent.isCatch(), parent); cannotConvertYet( objectPattern, "OBJECT_PATTERN that is a child of a " + Token.name(parent.getType())); return; } // Convert 'var {a: b, c: d} = rhs' to: // var temp = rhs; // var b = temp.a; // var d = temp.c; String tempVarName = DESTRUCTURING_TEMP_VAR + (destructuringVarCounter++); Node tempDecl = IR.var(IR.name(tempVarName), rhs.detachFromParent()) .useSourceInfoIfMissingFromForTree(objectPattern); nodeToDetach.getParent().addChildBefore(tempDecl, nodeToDetach); for (Node child = objectPattern.getFirstChild(), next; child != null; child = next) { next = child.getNext(); Node newLHS, newRHS; if (child.isStringKey()) { Preconditions.checkState(child.hasChildren()); Node getprop = new Node( child.isQuotedString() ? Token.GETELEM : Token.GETPROP, IR.name(tempVarName), IR.string(child.getString())); Node value = child.removeFirstChild(); if (!value.isDefaultValue()) { newLHS = value; newRHS = getprop; } else { newLHS = value.removeFirstChild(); Node defaultValue = value.removeFirstChild(); newRHS = defaultValueHook(getprop, defaultValue); } } else if (child.isComputedProp()) { if (child.getLastChild().isDefaultValue()) { newLHS = child.getLastChild().removeFirstChild(); Node getelem = IR.getelem(IR.name(tempVarName), child.removeFirstChild()); String intermediateTempVarName = DESTRUCTURING_TEMP_VAR + (destructuringVarCounter++); Node intermediateDecl = IR.var(IR.name(intermediateTempVarName), getelem); intermediateDecl.useSourceInfoIfMissingFromForTree(child); nodeToDetach.getParent().addChildBefore(intermediateDecl, nodeToDetach); newRHS = defaultValueHook( IR.name(intermediateTempVarName), child.getLastChild().removeFirstChild()); } else { newRHS = IR.getelem(IR.name(tempVarName), child.removeFirstChild()); newLHS = child.removeFirstChild(); } } else if (child.isDefaultValue()) { newLHS = child.removeFirstChild(); Node defaultValue = child.removeFirstChild(); Node getprop = IR.getprop(IR.name(tempVarName), IR.string(newLHS.getString())); newRHS = defaultValueHook(getprop, defaultValue); } else { throw new IllegalStateException("Unexpected OBJECT_PATTERN child: " + child); } Node newNode; if (NodeUtil.isNameDeclaration(parent)) { newNode = IR.declaration(newLHS, newRHS, parent.getType()); } else if (parent.isAssign()) { newNode = IR.exprResult(IR.assign(newLHS, newRHS)); } else { throw new IllegalStateException("not reached"); } newNode.useSourceInfoIfMissingFromForTree(child); 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(); }