/**
   * Expand a jQuery.expandedEach call
   *
   * <p>Expanded jQuery.expandedEach calls will replace the GETELEM nodes of a property assignment
   * with GETPROP nodes to allow for renaming.
   */
  private void maybeExpandJqueryEachCall(NodeTraversal t, Node n) {
    Node objectToLoopOver = n.getChildAtIndex(1);

    if (objectToLoopOver == null) {
      return;
    }

    Node callbackFunction = objectToLoopOver.getNext();
    if (callbackFunction == null || !callbackFunction.isFunction()) {
      return;
    }

    // Run the peephole optimizations on the first argument to handle
    // cases like ("a " + "b").split(" ")
    peepholePasses.process(null, n.getChildAtIndex(1));

    // Create a reference tree
    Node nClone = n.cloneTree();

    objectToLoopOver = nClone.getChildAtIndex(1);

    // Check to see if the first argument is something we recognize and can
    // expand.
    if (!objectToLoopOver.isObjectLit()
        && !(objectToLoopOver.isArrayLit() && isArrayLitValidForExpansion(objectToLoopOver))) {
      t.report(n, JQUERY_UNABLE_TO_EXPAND_INVALID_LIT_ERROR, (String) null);
      return;
    }

    // Find all references to the callback function arguments
    List<Node> keyNodeReferences = new ArrayList<>();
    List<Node> valueNodeReferences = new ArrayList<>();

    new NodeTraversal(
            compiler,
            new FindCallbackArgumentReferences(
                callbackFunction,
                keyNodeReferences,
                valueNodeReferences,
                objectToLoopOver.isArrayLit()))
        .traverseInnerNode(
            NodeUtil.getFunctionBody(callbackFunction), callbackFunction, t.getScope());

    if (keyNodeReferences.isEmpty()) {
      // We didn't do anything useful ...
      t.report(n, JQUERY_USELESS_EACH_EXPANSION, (String) null);
      return;
    }

    Node fncBlock =
        tryExpandJqueryEachCall(
            t, nClone, callbackFunction, keyNodeReferences, valueNodeReferences);

    if (fncBlock != null && fncBlock.hasChildren()) {
      replaceOriginalJqueryEachCall(n, fncBlock);
    } else {
      // We didn't do anything useful ...
      t.report(n, JQUERY_USELESS_EACH_EXPANSION, (String) null);
    }
  }
Example #2
0
  private void validateImport(Node n) {
    validateEs6Feature("import statement", n);
    validateNodeType(Token.IMPORT, n);
    validateChildCount(n);

    if (n.getFirstChild().isName()) {
      validateName(n.getFirstChild());
    } else {
      validateNodeType(Token.EMPTY, n.getFirstChild());
    }

    Node secondChild = n.getChildAtIndex(1);
    switch (secondChild.getType()) {
      case Token.IMPORT_SPECS:
        validateImportSpecifiers(secondChild);
        break;
      case Token.IMPORT_STAR:
        validateNonEmptyString(secondChild);
        break;
      default:
        validateNodeType(Token.EMPTY, secondChild);
    }

    validateString(n.getChildAtIndex(2));
  }
Example #3
0
  /**
   * Finds set of all defs in function given (defSites). Adds parameters of function to this set:
   * defSites. Finds all variables that are referenced in the function given (alf.names).
   * windowProps are all global properties available. Closures = alf.names - defSites - windowProps
   *
   * @param n root node of the function who's closures are to be found
   * @return set of closure names
   */
  private Set<String> findClosures(Node n) {
    Set<String> closureVars = null;

    SimpleDefinitionFinder defFinder = new SimpleDefinitionFinder(compiler);
    defFinder.process(externs, n.getLastChild());
    Collection<DefinitionSite> defSites = defFinder.getDefinitionSites();
    Set<String> localDefs = new HashSet<String>();

    for (DefinitionSite site : defSites) {
      if (site.node.getType() == Token.GETPROP) continue;
      String def = site.node.getString();
      if (def.length() > 0) {
        localDefs.add(def);
      }
    }

    // adding params to function as defs
    Node origParamNode = n.getChildAtIndex(1);
    for (int i = 0; i < origParamNode.getChildCount(); i++) {
      String temp = origParamNode.getChildAtIndex(i).getString();
      localDefs.add(temp);
    }

    // System.out.println("\nPrinting LOCAL def sites:" + defs);

    /*SimpleDefinitionFinder defFinder1 = new SimpleDefinitionFinder(compiler);
    defFinder1.process(externs, root);
    Collection<DefinitionSite> defSites1 = defFinder1.getDefinitionSites();
    Set<String> defs1 = new HashSet<String>();

    for(DefinitionSite site1: defSites1){
    	if (site1.node.getType() == Token.GETPROP) continue;
    	String def = site1.node.getString();
    	if (def.length() > 0){
    		defs1.add(def);
    	}
    }
    System.out.println("\nPrinting Global def sites:" + defs1);*/

    AllNamesFinder alf = new AllNamesFinder(compiler);
    NodeTraversal.traverse(compiler, n.getLastChild(), alf);

    // System.out.println("all names: " + alf.names);

    closureVars = alf.names;

    closureVars.removeAll(localDefs);
    closureVars.removeAll(Props.windowProps);

    closureVars.remove(
        "this"); // since 'this' is later modified to $$_self we don't need to consider this as
    // closure

    return closureVars;
  }
Example #4
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
          }
        }
    }
  }
Example #5
0
 private void validateFunctionStatement(Node n) {
   validateNodeType(Token.FUNCTION, n);
   validateChildCount(n);
   validateName(n.getFirstChild());
   validateParameters(n.getChildAtIndex(1));
   validateBlock(n.getLastChild());
 }
Example #6
0
 private void validateForOf(Node n) {
   validateNodeType(Token.FOR_OF, n);
   validateChildCount(n);
   validateVarOrAssignmentTarget(n.getFirstChild());
   validateExpression(n.getChildAtIndex(1));
   validateBlock(n.getLastChild());
 }
Example #7
0
  private void validateTry(Node n) {
    validateNodeType(Token.TRY, n);
    validateChildCountIn(n, 2, 3);
    validateBlock(n.getFirstChild());

    boolean seenCatchOrFinally = false;

    // Validate catch
    Node catches = n.getChildAtIndex(1);
    validateNodeType(Token.BLOCK, catches);
    validateMaximumChildCount(catches, 1);
    if (catches.hasChildren()) {
      validateCatch(catches.getFirstChild());
      seenCatchOrFinally = true;
    }

    // Validate finally
    if (n.getChildCount() == 3) {
      validateBlock(n.getLastChild());
      seenCatchOrFinally = true;
    }

    if (!seenCatchOrFinally) {
      violation("Missing catch or finally for try statement.", n);
    }
  }
 String nextString() {
   if (paramNames != null) {
     return paramNames.next();
   }
   index++;
   return params.getChildAtIndex(index).getString();
 }
Example #9
0
  private Node createAnonymWithParamCall(
      Set<String> closures, String anonymName, Node clonedorigParamNode, boolean thisChanged) {
    Node newAnonymNode = new Node(Token.FUNCTION);
    Node blockNode = new Node(Token.BLOCK);
    newAnonymNode.addChildrenToBack(Node.newString(Token.NAME, ""));

    newAnonymNode.addChildrenToBack(clonedorigParamNode);
    newAnonymNode.addChildrenToBack(blockNode);

    // to block add call with params as closure varibles
    Node returnNode = new Node(Token.RETURN);
    Node callNode = new Node(Token.CALL);
    callNode.addChildrenToBack(Node.newString(Token.NAME, anonymName));

    // first add all original params to this call
    for (int i = 0; i < clonedorigParamNode.getChildCount(); i++) {
      Node temp = clonedorigParamNode.getChildAtIndex(i).cloneTree();
      callNode.addChildrenToBack(temp);
    }

    addParamsToMethod(closures, callNode, thisChanged, "this");

    returnNode.addChildrenToBack(callNode);
    blockNode.addChildrenToBack(returnNode);

    return newAnonymNode;
  }
Example #10
0
 private void validateFor(Node n) {
   validateNodeType(Token.FOR, n);
   if (NodeUtil.isForIn(n)) {
     // FOR-IN
     validateChildCount(n, 3);
     validateVarOrAssignmentTarget(n.getFirstChild());
     validateExpression(n.getChildAtIndex(1));
   } else {
     // FOR
     validateChildCount(n, 4);
     validateVarOrOptionalExpression(n.getFirstChild());
     validateOptionalExpression(n.getChildAtIndex(1));
     validateOptionalExpression(n.getChildAtIndex(2));
   }
   validateBlock(n.getLastChild());
 }
Example #11
0
 private void validateIf(Node n) {
   validateNodeType(Token.IF, n);
   validateChildCountIn(n, 2, 3);
   validateExpression(n.getFirstChild());
   validateBlock(n.getChildAtIndex(1));
   if (n.getChildCount() == 3) {
     validateBlock(n.getLastChild());
   }
 }
  /** Processes trailing default and rest parameters. */
  private void visitParamList(Node paramList, Node function) {
    Node insertSpot = null;
    Node block = function.getLastChild();
    for (int i = 0; i < paramList.getChildCount(); i++) {
      Node param = paramList.getChildAtIndex(i);
      if (param.isDefaultValue()) {
        Node nameOrPattern = param.removeFirstChild();
        Node defaultValue = param.removeFirstChild();
        Node newParam =
            nameOrPattern.isName()
                ? nameOrPattern
                : IR.name(DESTRUCTURING_TEMP_VAR + (destructuringVarCounter++));

        Node lhs = nameOrPattern.cloneTree();
        Node rhs = defaultValueHook(newParam.cloneTree(), defaultValue);
        Node newStatement =
            nameOrPattern.isName() ? IR.exprResult(IR.assign(lhs, rhs)) : IR.var(lhs, rhs);
        newStatement.useSourceInfoIfMissingFromForTree(param);
        block.addChildAfter(newStatement, insertSpot);
        insertSpot = newStatement;

        paramList.replaceChild(param, newParam);
        newParam.setOptionalArg(true);

        compiler.reportCodeChange();
      } else if (param.isRest()) { // rest parameter
        param.setType(Token.NAME);
        param.setVarArgs(true);
        // Transpile to: param = [].slice.call(arguments, i);
        Node newArr =
            IR.exprResult(
                IR.assign(
                    IR.name(param.getString()),
                    IR.call(
                        IR.getprop(
                            IR.getprop(IR.arraylit(), IR.string("slice")), IR.string("call")),
                        IR.name("arguments"),
                        IR.number(i))));
        block.addChildAfter(newArr.useSourceInfoIfMissingFromForTree(param), insertSpot);
        compiler.reportCodeChange();
      } else if (param.isDestructuringPattern()) {
        String tempVarName = DESTRUCTURING_TEMP_VAR + (destructuringVarCounter++);
        paramList.replaceChild(param, IR.name(tempVarName));
        Node newDecl = IR.var(param, IR.name(tempVarName));
        block.addChildAfter(newDecl, insertSpot);
        insertSpot = newDecl;
      }
    }
    // 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.
  }
Example #13
0
 private void validateExport(Node n) {
   validateNodeType(Token.EXPORT, n);
   if (n.getBooleanProp(Node.EXPORT_ALL_FROM)) { // export * from "mod"
     validateChildCount(n, 2);
     validateNodeType(Token.EMPTY, n.getFirstChild());
     validateString(n.getChildAtIndex(1));
   } else if (n.getBooleanProp(Node.EXPORT_DEFAULT)) { // export default foo = 2
     validateChildCount(n, 1);
     validateExpression(n.getFirstChild());
   } else {
     validateChildCountIn(n, 1, 2);
     if (n.getFirstChild().getType() == Token.EXPORT_SPECS) {
       validateExportSpecifiers(n.getFirstChild());
     } else {
       validateStatement(n.getFirstChild());
     }
     if (n.getChildCount() == 2) {
       validateString(n.getChildAtIndex(1));
     }
   }
 }
Example #14
0
 private void validateObjectLitSetKey(Node n) {
   validateNodeType(Token.SETTER_DEF, n);
   validateChildCount(n);
   validateObjectLiteralKeyName(n);
   Node function = n.getFirstChild();
   validateFunctionExpression(function);
   // objlit set functions must be nameless, and must have 1 parameter.
   if (!function.getFirstChild().getString().isEmpty()) {
     violation("Expected unnamed function expression.", n);
   }
   Node functionParams = function.getChildAtIndex(1);
   if (!functionParams.hasOneChild()) {
     violation("set methods must have exactly one parameter.", n);
   }
 }
Example #15
0
 private void validateTemplateLit(Node n) {
   validateEs6Feature("template literal", n);
   validateNodeType(Token.TEMPLATELIT, n);
   if (!n.hasChildren()) {
     return;
   }
   for (int i = 0; i < n.getChildCount(); i++) {
     Node child = n.getChildAtIndex(i);
     // If the first child is not a STRING, this is a tagged template.
     if (i == 0 && !child.isString()) {
       validateExpression(child);
     } else if (child.isString()) {
       validateString(child);
     } else {
       validateTemplateLitSub(child);
     }
   }
 }
Example #16
0
  private void validateFunctionExpression(Node n) {
    validateNodeType(Token.FUNCTION, n);
    validateChildCount(n);

    validateParameters(n.getChildAtIndex(1));

    if (n.isArrowFunction()) {
      validateEs6Feature("arrow functions", n);
      validateEmptyName(n.getFirstChild());
      if (n.getLastChild().getType() == Token.BLOCK) {
        validateBlock(n.getLastChild());
      } else {
        validateExpression(n.getLastChild());
      }
    } else {
      validateOptionalName(n.getFirstChild());
      validateBlock(n.getLastChild());
    }
  }
    @Override
    public void visit(NodeTraversal t, Node n, Node parent) {
      if (isBehavior(n)) {
        if (!n.isVar() && !n.isAssign()) {
          compiler.report(JSError.make(n, POLYMER_UNQUALIFIED_BEHAVIOR));
          return;
        }

        // Add @nocollapse.
        JSDocInfoBuilder newDocs = JSDocInfoBuilder.maybeCopyFrom(n.getJSDocInfo());
        newDocs.recordNoCollapse();
        n.setJSDocInfo(newDocs.build());

        Node behaviorValue = n.getChildAtIndex(1);
        if (n.isVar()) {
          behaviorValue = n.getFirstChild().getFirstChild();
        }
        suppressBehavior(behaviorValue);
      }
    }
Example #18
0
 /** Processes trailing default and rest parameters. */
 private void visitParamList(Node paramList, Node function) {
   Node insertSpot = null;
   Node block = function.getLastChild();
   for (int i = 0; i < paramList.getChildCount(); i++) {
     Node param = paramList.getChildAtIndex(i);
     if (param.hasChildren()) { // default parameter
       param.setOptionalArg(true);
       Node defaultValue = param.removeFirstChild();
       // Transpile to: param === undefined && (param = defaultValue);
       Node name = IR.name(param.getString());
       Node undefined = IR.name("undefined");
       Node stm =
           IR.exprResult(
               IR.and(IR.sheq(name, undefined), IR.assign(name.cloneNode(), defaultValue)));
       block.addChildAfter(stm.useSourceInfoIfMissingFromForTree(param), insertSpot);
       insertSpot = stm;
       compiler.reportCodeChange();
     } else if (param.isRest()) { // rest parameter
       param.setType(Token.NAME);
       param.setVarArgs(true);
       // Transpile to: param = [].slice.call(arguments, i);
       Node newArr =
           IR.exprResult(
               IR.assign(
                   IR.name(param.getString()),
                   IR.call(
                       IR.getprop(
                           IR.getprop(IR.arraylit(), IR.string("slice")), IR.string("call")),
                       IR.name("arguments"),
                       IR.number(i))));
       block.addChildAfter(newArr.useSourceInfoIfMissingFromForTree(param), insertSpot);
       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);
      }
    }
 Node getNode() {
   if (paramNames != null) {
     return null;
   }
   return params.getChildAtIndex(index);
 }
  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 rewritePolymerClass(Node exprRoot, final ClassDefinition cls, NodeTraversal t) {
    // Add {@code @lends} to the object literal.
    Node call = exprRoot.getFirstChild();
    if (call.isAssign()) {
      call = call.getChildAtIndex(1);
    } else if (call.isName()) {
      call = call.getFirstChild();
    }

    Node objLit = cls.descriptor;
    if (hasShorthandAssignment(objLit)) {
      compiler.report(JSError.make(objLit, POLYMER_SHORTHAND_NOT_SUPPORTED));
      return;
    }

    JSDocInfoBuilder objLitDoc = new JSDocInfoBuilder(true);
    objLitDoc.recordLends(cls.target.getQualifiedName() + ".prototype");
    objLit.setJSDocInfo(objLitDoc.build());

    this.addTypesToFunctions(objLit, cls.target.getQualifiedName());
    this.switchDollarSignPropsToBrackets(objLit);
    this.quoteListenerAndHostAttributeKeys(objLit);

    // For simplicity add everything into a block, before adding it to the AST.
    Node block = IR.block();

    if (cls.nativeBaseElement != null) {
      this.appendPolymerElementExterns(cls);
    }
    JSDocInfoBuilder constructorDoc = this.getConstructorDoc(cls);

    // Remove the original constructor JS docs from the objlit.
    Node ctorKey = cls.constructor.value.getParent();
    if (ctorKey != null) {
      ctorKey.removeProp(Node.JSDOC_INFO_PROP);
    }

    if (cls.target.isGetProp()) {
      // foo.bar = Polymer({...});
      Node assign = IR.assign(cls.target.cloneTree(), cls.constructor.value.cloneTree());
      assign.setJSDocInfo(constructorDoc.build());
      Node exprResult = IR.exprResult(assign);
      block.addChildToBack(exprResult);
    } else {
      // var foo = Polymer({...}); OR Polymer({...});
      Node var = IR.var(cls.target.cloneTree(), cls.constructor.value.cloneTree());
      var.setJSDocInfo(constructorDoc.build());
      block.addChildToBack(var);
    }

    appendPropertiesToBlock(cls, block, cls.target.getQualifiedName() + ".prototype.");
    appendBehaviorMembersToBlock(cls, block);
    List<MemberDefinition> readOnlyProps = parseReadOnlyProperties(cls, block);
    addInterfaceExterns(cls, readOnlyProps);
    removePropertyDocs(objLit);

    block.useSourceInfoIfMissingFromForTree(exprRoot);
    Node stmts = block.removeChildren();
    Node parent = exprRoot.getParent();

    // If the call to Polymer() is not in the global scope and the assignment target is not
    // namespaced (which likely means it's exported to the global scope), put the type declaration
    // into the global scope at the start of the current script.
    //
    // This avoids unknown type warnings which are a result of the compiler's poor understanding of
    // types declared inside IIFEs or any non-global scope. We should revisit this decision after
    // moving to the new type inference system which should be able to infer these types better.
    if (!t.getScope().isGlobal() && !cls.target.isGetProp()) {
      Node scriptNode = NodeUtil.getEnclosingScript(exprRoot);
      scriptNode.addChildrenToFront(stmts);
    } else {
      Node beforeRoot = parent.getChildBefore(exprRoot);
      if (beforeRoot == null) {
        parent.addChildrenToFront(stmts);
      } else {
        parent.addChildrenAfter(stmts, beforeRoot);
      }
    }

    if (exprRoot.isVar()) {
      Node assignExpr = varToAssign(exprRoot);
      parent.replaceChild(exprRoot, assignExpr);
    }

    compiler.reportCodeChange();
  }