private void maybeRewriteClassDefinition(Node n, Node target, Node value) {
   if (isGoogDefineClass(value)) {
     if (!target.isQualifiedName()) {
       compiler.report(JSError.make(n, GOOG_CLASS_TARGET_INVALID));
     }
     ClassDefinition def = extractClassDefinition(target, value);
     if (def != null) {
       value.detachFromParent();
       target.detachFromParent();
       rewriteGoogDefineClass(n, def);
     }
   }
 }
  private void addToDefinePropertiesObject(ClassDeclarationMetadata metadata, Node member) {
    Node obj =
        member.isStaticMember()
            ? metadata.definePropertiesObjForClass
            : metadata.definePropertiesObjForPrototype;
    Node prop = NodeUtil.getFirstPropMatchingKey(obj, member.getString());
    if (prop == null) {
      prop =
          IR.objectlit(
              IR.stringKey("configurable", IR.trueNode()),
              IR.stringKey("enumerable", IR.trueNode()));
      obj.addChildToBack(IR.stringKey(member.getString(), prop));
    }

    Node function = member.getLastChild();
    JSDocInfoBuilder info = JSDocInfoBuilder.maybeCopyFrom(NodeUtil.getBestJSDocInfo(function));

    info.recordThisType(
        new JSTypeExpression(
            new Node(Token.BANG, IR.string(metadata.fullClassName)), member.getSourceFileName()));
    Node stringKey =
        IR.stringKey(member.isGetterDef() ? "get" : "set", function.detachFromParent());
    stringKey.setJSDocInfo(info.build());
    prop.addChildToBack(stringKey);
    prop.useSourceInfoIfMissingFromForTree(member);
  }
Example #3
0
  @Override
  public void visit(NodeTraversal t, Node n, Node parent) {
    // System.out.println(n);
    switch (n.getType()) {
      case Token.FUNCTION:
        Node functionNameNode = n.getFirstChild();
        String currFunctionName = functionNameNode.getString();

        if (!anonymizedFnNodes.contains(n)) {
          if (currFunctionName.length() == 0) {
            Set<String> closures = findClosures(n);

            // System.out.println("closures" + closures);

            n.detachFromParent(); // detach anonymous function from.

            // change all references to 'this' in method to $$_self
            boolean thisChanged = changeThisTo$$_self(n);

            // clone the parameters of modified $$$anonym(originalParams..., closures..) so that
            // we can use it for parameters of function call below

            Node clonedorigParamNode = n.getChildAtIndex(1).cloneTree();
            clonedorigParamNode.setType(Token.LP);

            // parent
            // give 'n' a name and attach to end
            // e.g. function $$anonym(originalParams..., closures..)
            String anonymName = "$$anonym" + anonymCount++;
            Node funcNameNode = Node.newString(Token.NAME, anonymName);
            n.replaceChild(n.getFirstChild(), funcNameNode);

            Node parametersNode = n.getChildAtIndex(1);
            addParamsToMethod(closures, parametersNode, thisChanged, "$$_self");

            root.getFirstChild().addChildrenToBack(n);

            // replace original anonymous call to new anonymous function that closes over
            // params and then makes a call to our named function
            Node newAnonymNode =
                createAnonymWithParamCall(closures, anonymName, clonedorigParamNode, thisChanged);
            parent.addChildrenToBack(newAnonymNode);

            // add to list of anonymized nodes so that we can ignore it on second pass
            anonymizedFnNodes.add(newAnonymNode);

            compiler.reportCodeChange();
          } else { // named function just find closures and add as parameter
            // TODO
          }
        }
    }
  }
  /** Converts ES6 arrow functions to standard anonymous ES3 functions. */
  private void visitArrowFunction(NodeTraversal t, Node n) {
    n.setIsArrowFunction(false);
    Node body = n.getLastChild();
    if (!body.isBlock()) {
      body.detachFromParent();
      body = IR.block(IR.returnNode(body).srcref(body)).srcref(body);
      n.addChildToBack(body);
    }

    UpdateThisAndArgumentsReferences updater = new UpdateThisAndArgumentsReferences();
    NodeTraversal.traverse(compiler, body, updater);
    addVarDecls(t, updater.changedThis, updater.changedArguments);

    compiler.reportCodeChange();
  }
  private Node tryUnfoldAssignOp(Node n, Node left, Node right) {
    if (late) {
      return n;
    }

    if (!n.hasChildren() || n.getSecondChild() != n.getLastChild()) {
      return n;
    }

    if (mayHaveSideEffects(left)) {
      return n;
    }

    // Tries to convert x += y -> x = x + y;
    Token op = NodeUtil.getOpFromAssignmentOp(n);
    Node replacement =
        IR.assign(
            left.detachFromParent(),
            new Node(op, left.cloneTree(), right.detachFromParent()).srcref(n));
    n.getParent().replaceChild(n, replacement);
    reportCodeChange();

    return replacement;
  }
Example #6
0
  /** Converts ES6 arrow functions to standard anonymous ES3 functions. */
  private void visitArrowFunction(NodeTraversal t, Node n) {
    n.setIsArrowFunction(false);
    Node body = n.getLastChild();
    if (!body.isBlock()) {
      body.detachFromParent();
      Node newBody = IR.block(IR.returnNode(body).srcref(body)).srcref(body);
      n.addChildToBack(newBody);
    }

    UpdateThisNodes thisUpdater = new UpdateThisNodes();
    NodeTraversal.traverse(compiler, body, thisUpdater);
    if (thisUpdater.changed) {
      addThisVar(t);
    }

    compiler.reportCodeChange();
  }
  private void replaceOriginalJqueryEachCall(Node n, Node expandedBlock) {
    // Check to see if the return value of the original jQuery.expandedEach
    // call is used. If so, we need to wrap each loop expansion in an anonymous
    // function and return the original objectToLoopOver.
    if (n.getParent().isExprResult()) {
      Node parent = n.getParent();
      Node grandparent = parent.getParent();
      Node insertAfter = parent;
      while (expandedBlock.hasChildren()) {
        Node child = expandedBlock.getFirstChild().detachFromParent();
        grandparent.addChildAfter(child, insertAfter);
        insertAfter = child;
      }
      grandparent.removeChild(parent);
    } else {
      // Return the original object
      Node callTarget = n.getFirstChild();
      Node objectToLoopOver = callTarget.getNext();

      objectToLoopOver.detachFromParent();
      Node ret = IR.returnNode(objectToLoopOver).srcref(callTarget);
      expandedBlock.addChildToBack(ret);

      // Wrap all of the expanded loop calls in a new anonymous function
      Node fnc =
          IR.function(
              IR.name("").srcref(callTarget), IR.paramList().srcref(callTarget), expandedBlock);
      n.replaceChild(callTarget, fnc);
      n.putBooleanProp(Node.FREE_CALL, true);

      // remove any other pre-existing call arguments
      while (fnc.getNext() != null) {
        n.removeChildAfter(fnc);
      }
    }
    compiler.reportCodeChange();
  }
  // 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 Node tryFoldUnaryOperator(Node n) {
    Preconditions.checkState(n.hasOneChild(), n);

    Node left = n.getFirstChild();
    Node parent = n.getParent();

    if (left == null) {
      return n;
    }

    TernaryValue leftVal = NodeUtil.getPureBooleanValue(left);
    if (leftVal == TernaryValue.UNKNOWN) {
      return n;
    }

    switch (n.getType()) {
      case NOT:
        // Don't fold !0 and !1 back to false.
        if (late && left.isNumber()) {
          double numValue = left.getDouble();
          if (numValue == 0 || numValue == 1) {
            return n;
          }
        }
        Node replacementNode = NodeUtil.booleanNode(!leftVal.toBoolean(true));
        parent.replaceChild(n, replacementNode);
        reportCodeChange();
        return replacementNode;
      case POS:
        if (NodeUtil.isNumericResult(left)) {
          // POS does nothing to numeric values.
          parent.replaceChild(n, left.detachFromParent());
          reportCodeChange();
          return left;
        }
        return n;
      case NEG:
        if (left.isName()) {
          if (left.getString().equals("Infinity")) {
            // "-Infinity" is valid and a literal, don't modify it.
            return n;
          } else if (left.getString().equals("NaN")) {
            // "-NaN" is "NaN".
            n.removeChild(left);
            parent.replaceChild(n, left);
            reportCodeChange();
            return left;
          }
        }

        if (left.isNumber()) {
          double negNum = -left.getDouble();

          Node negNumNode = IR.number(negNum);
          parent.replaceChild(n, negNumNode);
          reportCodeChange();
          return negNumNode;
        } else {
          // left is not a number node, so do not replace, but warn the
          // user because they can't be doing anything good
          report(NEGATING_A_NON_NUMBER_ERROR, left);
          return n;
        }
      case BITNOT:
        try {
          double val = left.getDouble();
          if (val >= Integer.MIN_VALUE && val <= Integer.MAX_VALUE) {
            int intVal = (int) val;
            if (intVal == val) {
              Node notIntValNode = IR.number(~intVal);
              parent.replaceChild(n, notIntValNode);
              reportCodeChange();
              return notIntValNode;
            } else {
              report(FRACTIONAL_BITWISE_OPERAND, left);
              return n;
            }
          } else {
            report(BITWISE_OPERAND_OUT_OF_RANGE, left);
            return n;
          }
        } catch (UnsupportedOperationException ex) {
          // left is not a number node, so do not replace, but warn the
          // user because they can't be doing anything good
          report(NEGATING_A_NON_NUMBER_ERROR, left);
          return n;
        }
      default:
        return n;
    }
  }
  private Node tryFoldObjectPropAccess(Node n, Node left, Node right) {
    Preconditions.checkArgument(NodeUtil.isGet(n));

    if (!left.isObjectLit() || !right.isString()) {
      return n;
    }

    if (NodeUtil.isAssignmentTarget(n)) {
      // If GETPROP/GETELEM is used as assignment target the object literal is
      // acting as a temporary we can't fold it here:
      //    "{a:x}.a += 1" is not "x += 1"
      return n;
    }

    // find the last definition in the object literal
    Node key = null;
    Node value = null;
    for (Node c = left.getFirstChild(); c != null; c = c.getNext()) {
      if (c.getString().equals(right.getString())) {
        switch (c.getType()) {
          case SETTER_DEF:
            continue;
          case GETTER_DEF:
          case STRING_KEY:
            if (value != null && mayHaveSideEffects(value)) {
              // The previously found value had side-effects
              return n;
            }
            key = c;
            value = key.getFirstChild();
            break;
          default:
            throw new IllegalStateException();
        }
      } else if (mayHaveSideEffects(c.getFirstChild())) {
        // We don't handle the side-effects here as they might need a temporary
        // or need to be reordered.
        return n;
      }
    }

    // Didn't find a definition of the name in the object literal, it might
    // be coming from the Object prototype
    if (value == null) {
      return n;
    }

    if (value.isFunction() && NodeUtil.referencesThis(value)) {
      // 'this' may refer to the object we are trying to remove
      return n;
    }

    Node replacement = value.detachFromParent();
    if (key.isGetterDef()) {
      replacement = IR.call(replacement);
      replacement.putBooleanProp(Node.FREE_CALL, true);
    }

    n.getParent().replaceChild(n, replacement);
    reportCodeChange();
    return n;
  }
Example #11
0
  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();
  }
  /**
   * Declares global variables to serve as aliases for the values in an object literal, optionally
   * removing all of the object literal's keys and values.
   *
   * @param alias The object literal's flattened name (e.g. "a$b$c")
   * @param objlit The OBJLIT node
   * @param varNode The VAR node to which new global variables should be added as children
   * @param nameToAddAfter The child of {@code varNode} after which new variables should be added
   *     (may be null)
   * @param varParent {@code varNode}'s parent
   * @return The number of variables added
   */
  private int declareVarsForObjLitValues(
      Name objlitName,
      String alias,
      Node objlit,
      Node varNode,
      Node nameToAddAfter,
      Node varParent) {
    int numVars = 0;
    int arbitraryNameCounter = 0;
    boolean discardKeys = !objlitName.shouldKeepKeys();

    for (Node key = objlit.getFirstChild(), nextKey; key != null; key = nextKey) {
      Node value = key.getFirstChild();
      nextKey = key.getNext();

      // A get or a set can not be rewritten as a VAR.
      if (key.isGetterDef() || key.isSetterDef()) {
        continue;
      }

      // We generate arbitrary names for keys that aren't valid JavaScript
      // identifiers, since those keys are never referenced. (If they were,
      // this object literal's child names wouldn't be collapsible.) The only
      // reason that we don't eliminate them entirely is the off chance that
      // their values are expressions that have side effects.
      boolean isJsIdentifier = !key.isNumber() && TokenStream.isJSIdentifier(key.getString());
      String propName = isJsIdentifier ? key.getString() : String.valueOf(++arbitraryNameCounter);

      // If the name cannot be collapsed, skip it.
      String qName = objlitName.getFullName() + '.' + propName;
      Name p = nameMap.get(qName);
      if (p != null && !p.canCollapse()) {
        continue;
      }

      String propAlias = appendPropForAlias(alias, propName);
      Node refNode = null;
      if (discardKeys) {
        objlit.removeChild(key);
        value.detachFromParent();
      } else {
        // Substitute a reference for the value.
        refNode = IR.name(propAlias);
        if (key.getBooleanProp(Node.IS_CONSTANT_NAME)) {
          refNode.putBooleanProp(Node.IS_CONSTANT_NAME, true);
        }

        key.replaceChild(value, refNode);
      }

      // Declare the collapsed name as a variable with the original value.
      Node nameNode = IR.name(propAlias);
      nameNode.addChildToFront(value);
      if (key.getBooleanProp(Node.IS_CONSTANT_NAME)) {
        nameNode.putBooleanProp(Node.IS_CONSTANT_NAME, true);
      }
      Node newVar = IR.var(nameNode).useSourceInfoIfMissingFromForTree(key);
      if (nameToAddAfter != null) {
        varParent.addChildAfter(newVar, nameToAddAfter);
      } else {
        varParent.addChildBefore(newVar, varNode);
      }
      compiler.reportCodeChange();
      nameToAddAfter = newVar;

      // Update the global name's node ancestry if it hasn't already been
      // done. (Duplicate keys in an object literal can bring us here twice
      // for the same global name.)
      if (isJsIdentifier && p != null) {
        if (!discardKeys) {
          Ref newAlias = p.getDeclaration().cloneAndReclassify(Ref.Type.ALIASING_GET);
          newAlias.node = refNode;
          p.addRef(newAlias);
        }

        p.getDeclaration().node = nameNode;

        if (value.isFunction()) {
          checkForHosedThisReferences(value, key.getJSDocInfo(), p);
        }
      }

      numVars++;
    }
    return numVars;
  }
  private Node tryExpandJqueryEachCall(
      NodeTraversal t, Node n, Node callbackFunction, List<Node> keyNodes, List<Node> valueNodes) {

    Node callTarget = n.getFirstChild();
    Node objectToLoopOver = callTarget.getNext();

    // New block to contain the expanded statements
    Node fncBlock = IR.block().srcref(callTarget);

    boolean isValidExpansion = true;

    // Expand the jQuery.expandedEach call
    Node key = objectToLoopOver.getFirstChild(), val = null;
    for (int i = 0; key != null; key = key.getNext(), i++) {
      if (key != null) {
        if (objectToLoopOver.isArrayLit()) {
          // Arrays have a value of their index number
          val = IR.number(i).srcref(key);
        } else {
          val = key.getFirstChild();
        }
      }

      // Keep track of the replaced nodes so we can reset the tree
      List<Node> newKeys = new ArrayList<>();
      List<Node> newValues = new ArrayList<>();
      List<Node> origGetElems = new ArrayList<>();
      List<Node> newGetProps = new ArrayList<>();

      // Replace all of the key nodes with the prop name
      for (int j = 0; j < keyNodes.size(); j++) {
        Node origNode = keyNodes.get(j);
        Node ancestor = origNode.getParent();

        Node newNode = IR.string(key.getString()).srcref(key);
        newKeys.add(newNode);
        ancestor.replaceChild(origNode, newNode);

        // Walk up the tree to see if the key is used in a GETELEM
        // assignment
        while (ancestor != null && !NodeUtil.isStatement(ancestor) && !ancestor.isGetElem()) {
          ancestor = ancestor.getParent();
        }

        // Convert GETELEM nodes to GETPROP nodes so that they can be
        // renamed or removed.
        if (ancestor != null && ancestor.isGetElem()) {

          Node propObject = ancestor;
          while (propObject.isGetProp() || propObject.isGetElem()) {
            propObject = propObject.getFirstChild();
          }

          Node ancestorClone = ancestor.cloneTree();
          // Run the peephole passes to handle cases such as
          // obj['lit' + key] = val;
          peepholePasses.process(null, ancestorClone.getChildAtIndex(1));
          Node prop = ancestorClone.getChildAtIndex(1);

          if (prop.isString()
              && NodeUtil.isValidPropertyName(LanguageMode.ECMASCRIPT3, prop.getString())) {
            Node target = ancestorClone.getFirstChild();
            Node newGetProp = IR.getprop(target.detachFromParent(), prop.detachFromParent());
            newGetProps.add(newGetProp);
            origGetElems.add(ancestor);
            ancestor.getParent().replaceChild(ancestor, newGetProp);
          } else {
            if (prop.isString()
                && !NodeUtil.isValidPropertyName(LanguageMode.ECMASCRIPT3, prop.getString())) {
              t.report(n, JQUERY_UNABLE_TO_EXPAND_INVALID_NAME_ERROR, prop.getString());
            }
            isValidExpansion = false;
          }
        }
      }

      if (isValidExpansion) {
        // Replace all of the value nodes with the prop value
        for (int j = 0; val != null && j < valueNodes.size(); j++) {
          Node origNode = valueNodes.get(j);
          Node newNode = val.cloneTree();
          newValues.add(newNode);
          origNode.getParent().replaceChild(origNode, newNode);
        }

        // Wrap the new tree in an anonymous function call
        Node fnc =
            IR.function(
                    IR.name("").srcref(key),
                    IR.paramList().srcref(key),
                    callbackFunction.getChildAtIndex(2).cloneTree())
                .srcref(key);
        Node call = IR.call(fnc).srcref(key);
        call.putBooleanProp(Node.FREE_CALL, true);
        fncBlock.addChildToBack(IR.exprResult(call).srcref(call));
      }

      // Reset the source tree
      for (int j = 0; j < newGetProps.size(); j++) {
        newGetProps.get(j).getParent().replaceChild(newGetProps.get(j), origGetElems.get(j));
      }
      for (int j = 0; j < newKeys.size(); j++) {
        newKeys.get(j).getParent().replaceChild(newKeys.get(j), keyNodes.get(j));
      }
      for (int j = 0; j < newValues.size(); j++) {
        newValues.get(j).getParent().replaceChild(newValues.get(j), valueNodes.get(j));
      }

      if (!isValidExpansion) {
        return null;
      }
    }
    return fncBlock;
  }
  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();
  }
  private void visitSuper(Node node, Node parent) {
    Node enclosing = parent;
    Node potentialCallee = node;
    if (!parent.isCall()) {
      enclosing = parent.getParent();
      potentialCallee = parent;
    }
    if (!enclosing.isCall() || enclosing.getFirstChild() != potentialCallee) {
      cannotConvertYet(node, "Only calls to super or to a method of super are supported.");
      return;
    }
    Node clazz = NodeUtil.getEnclosingClass(node);
    if (clazz == null) {
      compiler.report(JSError.make(node, NO_SUPERTYPE));
      return;
    }
    if (NodeUtil.getClassNameNode(clazz) == null) {
      // Unnamed classes of the form:
      //   f(class extends D { ... });
      // give the problem that there is no name to be used in the call to goog.base for the
      // translation of super calls.
      // This will throw an error when the class is processed.
      return;
    }

    Node enclosingMemberDef = NodeUtil.getEnclosingClassMember(node);
    if (enclosingMemberDef.isStaticMember()) {
      Node superName = clazz.getFirstChild().getNext();
      if (!superName.isQualifiedName()) {
        // This has already been reported, just don't need to continue processing the class.
        return;
      }
      Node callTarget;
      potentialCallee.detachFromParent();
      if (potentialCallee == node) {
        // of the form super()
        potentialCallee =
            IR.getprop(superName.cloneTree(), IR.string(enclosingMemberDef.getString()));
        enclosing.putBooleanProp(Node.FREE_CALL, false);
      } else {
        // of the form super.method()
        potentialCallee.replaceChild(node, superName.cloneTree());
      }
      callTarget = IR.getprop(potentialCallee, IR.string("call"));
      enclosing.addChildToFront(callTarget);
      enclosing.addChildAfter(IR.thisNode(), callTarget);
      enclosing.useSourceInfoIfMissingFromForTree(enclosing);
      compiler.reportCodeChange();
      return;
    }

    String methodName;
    Node callName = enclosing.removeFirstChild();
    if (callName.isSuper()) {
      methodName = enclosingMemberDef.getString();
    } else {
      methodName = callName.getLastChild().getString();
    }
    Node baseCall =
        baseCall(clazz, methodName, enclosing.removeChildren())
            .useSourceInfoIfMissingFromForTree(enclosing);
    enclosing.getParent().replaceChild(enclosing, baseCall);
    compiler.reportCodeChange();
  }
  private Node tryFoldAssign(Node n, Node left, Node right) {
    Preconditions.checkArgument(n.isAssign());

    if (!late) {
      return n;
    }

    // Tries to convert x = x + y -> x += y;
    if (!right.hasChildren() || right.getSecondChild() != right.getLastChild()) {
      // RHS must have two children.
      return n;
    }

    if (mayHaveSideEffects(left)) {
      return n;
    }

    Node newRight;
    if (areNodesEqualForInlining(left, right.getFirstChild())) {
      newRight = right.getLastChild();
    } else if (NodeUtil.isCommutative(right.getType())
        && areNodesEqualForInlining(left, right.getLastChild())) {
      newRight = right.getFirstChild();
    } else {
      return n;
    }

    Token newType = null;
    switch (right.getType()) {
      case ADD:
        newType = Token.ASSIGN_ADD;
        break;
      case BITAND:
        newType = Token.ASSIGN_BITAND;
        break;
      case BITOR:
        newType = Token.ASSIGN_BITOR;
        break;
      case BITXOR:
        newType = Token.ASSIGN_BITXOR;
        break;
      case DIV:
        newType = Token.ASSIGN_DIV;
        break;
      case LSH:
        newType = Token.ASSIGN_LSH;
        break;
      case MOD:
        newType = Token.ASSIGN_MOD;
        break;
      case MUL:
        newType = Token.ASSIGN_MUL;
        break;
      case RSH:
        newType = Token.ASSIGN_RSH;
        break;
      case SUB:
        newType = Token.ASSIGN_SUB;
        break;
      case URSH:
        newType = Token.ASSIGN_URSH;
        break;
      default:
        return n;
    }

    Node newNode = new Node(newType, left.detachFromParent(), newRight.detachFromParent());
    n.getParent().replaceChild(n, newNode);

    reportCodeChange();

    return newNode;
  }
  /**
   * 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();
  }
  /** Processes a rest parameter */
  private void visitRestParam(Node restParam, Node paramList) {
    Node functionBody = paramList.getLastSibling();

    restParam.setType(Token.NAME);
    restParam.setVarArgs(true);

    // Make sure rest parameters are typechecked
    JSTypeExpression type = null;
    JSDocInfo info = restParam.getJSDocInfo();
    String paramName = restParam.getString();
    if (info != null) {
      type = info.getType();
    } else {
      JSDocInfo functionInfo = paramList.getParent().getJSDocInfo();
      if (functionInfo != null) {
        type = functionInfo.getParameterType(paramName);
      }
    }
    if (type != null && type.getRoot().getType() != Token.ELLIPSIS) {
      compiler.report(JSError.make(restParam, BAD_REST_PARAMETER_ANNOTATION));
    }

    if (!functionBody.hasChildren()) {
      // If function has no body, we are done!
      compiler.reportCodeChange();
      return;
    }

    Node newBlock = IR.block().useSourceInfoFrom(functionBody);
    Node name = IR.name(paramName);
    Node let = IR.let(name, IR.name(REST_PARAMS)).useSourceInfoIfMissingFromForTree(functionBody);
    newBlock.addChildToFront(let);

    for (Node child : functionBody.children()) {
      newBlock.addChildToBack(child.detachFromParent());
    }

    if (type != null) {
      Node arrayType = IR.string("Array");
      Node typeNode = type.getRoot();
      Node memberType =
          typeNode.getType() == Token.ELLIPSIS
              ? typeNode.getFirstChild().cloneNode()
              : typeNode.cloneNode();
      arrayType.addChildToFront(
          new Node(Token.BLOCK, memberType).useSourceInfoIfMissingFrom(typeNode));
      JSDocInfoBuilder builder = new JSDocInfoBuilder(false);
      builder.recordType(
          new JSTypeExpression(new Node(Token.BANG, arrayType), restParam.getSourceFileName()));
      name.setJSDocInfo(builder.build());
    }

    int restIndex = paramList.getIndexOfChild(restParam);
    Node newArr = IR.var(IR.name(REST_PARAMS), IR.arraylit());
    functionBody.addChildToFront(newArr.useSourceInfoIfMissingFromForTree(restParam));
    Node init = IR.var(IR.name(REST_INDEX), IR.number(restIndex));
    Node cond = IR.lt(IR.name(REST_INDEX), IR.getprop(IR.name("arguments"), IR.string("length")));
    Node incr = IR.inc(IR.name(REST_INDEX), false);
    Node body =
        IR.block(
            IR.exprResult(
                IR.assign(
                    IR.getelem(
                        IR.name(REST_PARAMS), IR.sub(IR.name(REST_INDEX), IR.number(restIndex))),
                    IR.getelem(IR.name("arguments"), IR.name(REST_INDEX)))));
    functionBody.addChildAfter(
        IR.forNode(init, cond, incr, body).useSourceInfoIfMissingFromForTree(restParam), newArr);
    functionBody.addChildToBack(newBlock);
    compiler.reportCodeChange();

    // For now, we are running transpilation before type-checking, so we'll
    // need to make sure changes don't invalidate the JSDoc annotations.
    // Therefore we keep the parameter list the same length and only initialize
    // the values if they are set to undefined.
  }
    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);
      }
    }
  /**
   * Verifies that the compiler pass's JS output matches the expected output and (optionally) that
   * an expected warning is issued. Or, if an error is expected, this method just verifies that the
   * error is encountered.
   *
   * @param compiler A compiler that has been initialized via {@link Compiler#init}
   * @param expected Expected output, or null if an error is expected
   * @param error Expected error, or null if no error is expected
   * @param warning Expected warning, or null if no warning is expected
   * @param description The description of the expected warning, or null if no warning is expected
   *     or if the warning's description should not be examined
   */
  private void test(
      Compiler compiler,
      List<SourceFile> expected,
      DiagnosticType error,
      DiagnosticType warning,
      String description) {
    RecentChange recentChange = new RecentChange();
    compiler.addChangeHandler(recentChange);

    Node root = compiler.parseInputs();
    assertNotNull("Unexpected parse error(s): " + Joiner.on("\n").join(compiler.getErrors()), root);
    if (!expectParseWarningsThisTest) {
      assertEquals(
          "Unexpected parse warnings(s): " + Joiner.on("\n").join(compiler.getWarnings()),
          0,
          compiler.getWarnings().length);
    }

    if (astValidationEnabled) {
      (new AstValidator(compiler)).validateRoot(root);
    }
    Node externsRoot = root.getFirstChild();
    Node mainRoot = root.getLastChild();

    // Save the tree for later comparison.
    Node rootClone = root.cloneTree();
    Node externsRootClone = rootClone.getFirstChild();
    Node mainRootClone = rootClone.getLastChild();
    Map<Node, Node> mtoc = NodeUtil.mapMainToClone(mainRoot, mainRootClone);

    int numRepetitions = getNumRepetitions();
    ErrorManager[] errorManagers = new ErrorManager[numRepetitions];
    int aggregateWarningCount = 0;
    List<JSError> aggregateWarnings = Lists.newArrayList();
    boolean hasCodeChanged = false;

    assertFalse("Code should not change before processing", recentChange.hasCodeChanged());

    for (int i = 0; i < numRepetitions; ++i) {
      if (compiler.getErrorCount() == 0) {
        errorManagers[i] = new BlackHoleErrorManager(compiler);

        // Only run process closure primitives once, if asked.
        if (closurePassEnabled && i == 0) {
          recentChange.reset();
          new ProcessClosurePrimitives(compiler, null, CheckLevel.ERROR, false)
              .process(null, mainRoot);
          hasCodeChanged = hasCodeChanged || recentChange.hasCodeChanged();
        }

        // Only run the type checking pass once, if asked.
        // Running it twice can cause unpredictable behavior because duplicate
        // objects for the same type are created, and the type system
        // uses reference equality to compare many types.
        if (!runTypeCheckAfterProcessing && typeCheckEnabled && i == 0) {
          TypeCheck check = createTypeCheck(compiler, typeCheckLevel);
          check.processForTesting(externsRoot, mainRoot);
        }

        // Only run the normalize pass once, if asked.
        if (normalizeEnabled && i == 0) {
          normalizeActualCode(compiler, externsRoot, mainRoot);
        }

        if (enableInferConsts && i == 0) {
          new InferConsts(compiler).process(externsRoot, mainRoot);
        }

        if (computeSideEffects && i == 0) {
          PureFunctionIdentifier.Driver mark =
              new PureFunctionIdentifier.Driver(compiler, null, false);
          mark.process(externsRoot, mainRoot);
        }

        if (markNoSideEffects && i == 0) {
          MarkNoSideEffectCalls mark = new MarkNoSideEffectCalls(compiler);
          mark.process(externsRoot, mainRoot);
        }

        if (gatherExternPropertiesEnabled && i == 0) {
          (new GatherExternProperties(compiler)).process(externsRoot, mainRoot);
        }

        recentChange.reset();

        getProcessor(compiler).process(externsRoot, mainRoot);
        if (astValidationEnabled) {
          (new AstValidator(compiler)).validateRoot(root);
        }
        if (checkLineNumbers) {
          (new LineNumberCheck(compiler)).process(externsRoot, mainRoot);
        }

        if (runTypeCheckAfterProcessing && typeCheckEnabled && i == 0) {
          TypeCheck check = createTypeCheck(compiler, typeCheckLevel);
          check.processForTesting(externsRoot, mainRoot);
        }

        hasCodeChanged = hasCodeChanged || recentChange.hasCodeChanged();
        aggregateWarningCount += errorManagers[i].getWarningCount();
        Collections.addAll(aggregateWarnings, compiler.getWarnings());

        if (normalizeEnabled) {
          boolean verifyDeclaredConstants = true;
          new Normalize.VerifyConstants(compiler, verifyDeclaredConstants)
              .process(externsRoot, mainRoot);
        }
      }
    }

    if (error == null) {
      assertEquals(
          "Unexpected error(s): " + Joiner.on("\n").join(compiler.getErrors()),
          0,
          compiler.getErrorCount());

      // Verify the symbol table.
      ErrorManager symbolTableErrorManager = new BlackHoleErrorManager(compiler);
      Node expectedRoot = null;
      if (expected != null) {
        expectedRoot = parseExpectedJs(expected);
        expectedRoot.detachFromParent();
      }

      JSError[] stErrors = symbolTableErrorManager.getErrors();
      if (expectedSymbolTableError != null) {
        assertEquals("There should be one error.", 1, stErrors.length);
        assertEquals(expectedSymbolTableError, stErrors[0].getType());
      } else {
        assertEquals(
            "Unexpected symbol table error(s): " + Joiner.on("\n").join(stErrors),
            0,
            stErrors.length);
      }

      if (warning == null) {
        assertEquals(
            "Unexpected warning(s): " + Joiner.on("\n").join(aggregateWarnings),
            0,
            aggregateWarningCount);
      } else {
        assertEquals(
            "There should be one warning, repeated "
                + numRepetitions
                + " time(s). Warnings: "
                + aggregateWarnings,
            numRepetitions,
            aggregateWarningCount);
        for (int i = 0; i < numRepetitions; ++i) {
          JSError[] warnings = errorManagers[i].getWarnings();
          JSError actual = warnings[0];
          assertEquals(warning, actual.getType());

          // Make sure that source information is always provided.
          if (!allowSourcelessWarnings) {
            assertTrue(
                "Missing source file name in warning",
                actual.sourceName != null && !actual.sourceName.isEmpty());
            assertTrue("Missing line number in warning", -1 != actual.lineNumber);
            assertTrue("Missing char number in warning", -1 != actual.getCharno());
          }

          if (description != null) {
            assertEquals(description, actual.description);
          }
        }
      }

      // If we ran normalize on the AST, we must also run normalize on the
      // clone before checking for changes.
      if (normalizeEnabled) {
        normalizeActualCode(compiler, externsRootClone, mainRootClone);
      }

      boolean codeChange = !mainRootClone.isEquivalentTo(mainRoot);
      boolean externsChange = !externsRootClone.isEquivalentTo(externsRoot);

      // Generally, externs should not be changed by the compiler passes.
      if (externsChange && !allowExternsChanges) {
        String explanation = externsRootClone.checkTreeEquals(externsRoot);
        fail(
            "Unexpected changes to externs"
                + "\nExpected: "
                + compiler.toSource(externsRootClone)
                + "\nResult:   "
                + compiler.toSource(externsRoot)
                + "\n"
                + explanation);
      }

      if (!codeChange && !externsChange) {
        assertFalse(
            "compiler.reportCodeChange() was called " + "even though nothing changed",
            hasCodeChanged);
      } else {
        assertTrue(
            "compiler.reportCodeChange() should have been called."
                + "\nOriginal: "
                + mainRootClone.toStringTree()
                + "\nNew: "
                + mainRoot.toStringTree(),
            hasCodeChanged);
      }

      // Check correctness of the changed-scopes-only traversal
      NodeUtil.verifyScopeChanges(mtoc, mainRoot, false, compiler);

      if (expected != null) {
        if (compareAsTree) {
          String explanation;
          if (compareJsDoc) {
            explanation = expectedRoot.checkTreeEqualsIncludingJsDoc(mainRoot);
          } else {
            explanation = expectedRoot.checkTreeEquals(mainRoot);
          }
          assertNull(
              "\nExpected: "
                  + compiler.toSource(expectedRoot)
                  + "\nResult:   "
                  + compiler.toSource(mainRoot)
                  + "\n"
                  + explanation,
              explanation);
        } else if (expected != null) {
          String[] expectedSources = new String[expected.size()];
          for (int i = 0; i < expected.size(); ++i) {
            try {
              expectedSources[i] = expected.get(i).getCode();
            } catch (IOException e) {
              throw new RuntimeException("failed to get source code", e);
            }
          }
          assertEquals(Joiner.on("").join(expectedSources), compiler.toSource(mainRoot));
        }
      }

      // Verify normalization is not invalidated.
      Node normalizeCheckRootClone = root.cloneTree();
      Node normalizeCheckExternsRootClone = normalizeCheckRootClone.getFirstChild();
      Node normalizeCheckMainRootClone = normalizeCheckRootClone.getLastChild();
      new PrepareAst(compiler).process(normalizeCheckExternsRootClone, normalizeCheckMainRootClone);
      String explanation = normalizeCheckMainRootClone.checkTreeEquals(mainRoot);
      assertNull(
          "Node structure normalization invalidated."
              + "\nExpected: "
              + compiler.toSource(normalizeCheckMainRootClone)
              + "\nResult:   "
              + compiler.toSource(mainRoot)
              + "\n"
              + explanation,
          explanation);

      // TODO(johnlenz): enable this for most test cases.
      // Currently, this invalidates test for while-loops, for-loop
      // initializers, and other naming.  However, a set of code
      // (Closure primitive rewrites, etc) runs before the Normalize pass,
      // so this can't be force on everywhere.
      if (normalizeEnabled) {
        new Normalize(compiler, true)
            .process(normalizeCheckExternsRootClone, normalizeCheckMainRootClone);
        explanation = normalizeCheckMainRootClone.checkTreeEquals(mainRoot);
        assertNull(
            "Normalization invalidated."
                + "\nExpected: "
                + compiler.toSource(normalizeCheckMainRootClone)
                + "\nResult:   "
                + compiler.toSource(mainRoot)
                + "\n"
                + explanation,
            explanation);
      }
    } else {
      String errors = "";
      for (JSError actualError : compiler.getErrors()) {
        errors += actualError.description + "\n";
      }
      assertEquals("There should be one error. " + errors, 1, compiler.getErrorCount());
      assertEquals(errors, error, compiler.getErrors()[0].getType());

      if (warning != null) {
        String warnings = "";
        for (JSError actualError : compiler.getWarnings()) {
          warnings += actualError.description + "\n";
        }
        assertEquals("There should be one warning. " + warnings, 1, compiler.getWarningCount());
        assertEquals(warnings, warning, compiler.getWarnings()[0].getType());
      }
    }
  }
  /**
   * Expand jQuery.extend (and derivative) calls into direct object assignments Example:
   * jQuery.extend(obj1, {prop1: val1, prop2: val2}) -> obj1.prop1 = val1; obj1.prop2 = val2;
   */
  private void maybeExpandJqueryExtendCall(Node n) {
    Node callTarget = n.getFirstChild();
    Node objectToExtend = callTarget.getNext(); // first argument
    Node extendArg = objectToExtend.getNext(); // second argument
    boolean ensureObjectDefined = true;

    if (extendArg == null) {
      // Only one argument was specified, so extend jQuery namespace
      extendArg = objectToExtend;
      objectToExtend = callTarget.getFirstChild();
      ensureObjectDefined = false;
    } else if (objectToExtend.isGetProp()
        && (objectToExtend.getLastChild().getString().equals("prototype")
            || convention.isPrototypeAlias(objectToExtend))) {
      ensureObjectDefined = false;
    }

    // Check for an empty object literal
    if (!extendArg.hasChildren()) {
      return;
    }

    // Since we are expanding jQuery.extend calls into multiple statements,
    // encapsulate the new statements in a new block.
    Node fncBlock = IR.block().srcref(n);

    if (ensureObjectDefined) {
      Node assignVal = IR.or(objectToExtend.cloneTree(), IR.objectlit().srcref(n)).srcref(n);
      Node assign = IR.assign(objectToExtend.cloneTree(), assignVal).srcref(n);
      fncBlock.addChildrenToFront(IR.exprResult(assign).srcref(n));
    }

    while (extendArg.hasChildren()) {
      Node currentProp = extendArg.removeFirstChild();
      currentProp.setType(Token.STRING);

      Node propValue = currentProp.removeFirstChild();

      Node newProp;
      if (currentProp.isQuotedString()) {
        newProp = IR.getelem(objectToExtend.cloneTree(), currentProp).srcref(currentProp);
      } else {
        newProp = IR.getprop(objectToExtend.cloneTree(), currentProp).srcref(currentProp);
      }

      Node assignNode = IR.assign(newProp, propValue).srcref(currentProp);
      fncBlock.addChildToBack(IR.exprResult(assignNode).srcref(currentProp));
    }

    // Check to see if the return value is used. If not, replace the original
    // call with new block. Otherwise, wrap the statements in an
    // immediately-called anonymous function.
    if (n.getParent().isExprResult()) {
      Node parent = n.getParent();
      parent.getParent().replaceChild(parent, fncBlock);
    } else {
      Node targetVal;
      if ("jQuery.prototype".equals(objectToExtend.getQualifiedName())) {
        // When extending the jQuery prototype, return the jQuery namespace.
        // This is not commonly used.
        targetVal = objectToExtend.removeFirstChild();
      } else {
        targetVal = objectToExtend.detachFromParent();
      }
      fncBlock.addChildToBack(IR.returnNode(targetVal).srcref(targetVal));

      Node fnc = IR.function(IR.name("").srcref(n), IR.paramList().srcref(n), fncBlock).srcref(n);

      // add an explicit "call" statement so that we can maintain
      // the same reference for "this"
      Node newCallTarget = IR.getprop(fnc, IR.string("call").srcref(n)).srcref(n);
      n.replaceChild(callTarget, newCallTarget);
      n.putBooleanProp(Node.FREE_CALL, false);

      // remove any other pre-existing call arguments
      while (newCallTarget.getNext() != null) {
        n.removeChildAfter(newCallTarget);
      }
      n.addChildToBack(IR.thisNode().srcref(n));
    }
    compiler.reportCodeChange();
  }
 private static Node maybeDetach(Node node) {
   if (node != null && node.getParent() != null) {
     node.detachFromParent();
   }
   return node;
 }
  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();
  }