Example #1
0
 @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();
     }
   }
 }
Example #6
0
 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);
   }
 }
Example #7
0
 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();
  }