/**
   * 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
 /**
  * Processes array literals or calls containing spreads. Eg.: [1, 2, ...x, 4, 5] => [1,
  * 2].concat(x, [4, 5]); Eg.: f(...arr) => f.apply(null, arr) Eg.: new F(...args) => new
  * Function.prototype.bind.apply(F, [].concat(args))
  */
 private void visitArrayLitOrCallWithSpread(Node node, Node parent) {
   Preconditions.checkArgument(node.isCall() || node.isArrayLit() || node.isNew());
   List<Node> groups = new ArrayList<>();
   Node currGroup = null;
   Node callee = node.isArrayLit() ? null : node.removeFirstChild();
   Node currElement = node.removeFirstChild();
   while (currElement != null) {
     if (currElement.isSpread()) {
       if (currGroup != null) {
         groups.add(currGroup);
         currGroup = null;
       }
       groups.add(currElement.removeFirstChild());
     } else {
       if (currGroup == null) {
         currGroup = IR.arraylit();
       }
       currGroup.addChildToBack(currElement);
     }
     currElement = node.removeFirstChild();
   }
   if (currGroup != null) {
     groups.add(currGroup);
   }
   Node result = null;
   Node joinedGroups =
       IR.call(
           IR.getprop(IR.arraylit(), IR.string("concat")),
           groups.toArray(new Node[groups.size()]));
   if (node.isArrayLit()) {
     result = joinedGroups;
   } else if (node.isCall()) {
     if (NodeUtil.mayHaveSideEffects(callee) && callee.isGetProp()) {
       Node statement = node;
       while (!NodeUtil.isStatement(statement)) {
         statement = statement.getParent();
       }
       Node freshVar = IR.name(FRESH_SPREAD_VAR + freshSpreadVarCounter++);
       Node n = IR.var(freshVar.cloneTree());
       n.useSourceInfoIfMissingFromForTree(statement);
       statement.getParent().addChildBefore(n, statement);
       callee.addChildToFront(IR.assign(freshVar.cloneTree(), callee.removeFirstChild()));
       result = IR.call(IR.getprop(callee, IR.string("apply")), freshVar, joinedGroups);
     } else {
       Node context = callee.isGetProp() ? callee.getFirstChild().cloneTree() : IR.nullNode();
       result = IR.call(IR.getprop(callee, IR.string("apply")), context, joinedGroups);
     }
   } else {
     Node bindApply =
         NodeUtil.newQualifiedNameNode(
             compiler.getCodingConvention(), "Function.prototype.bind.apply");
     result = IR.newNode(bindApply, callee, joinedGroups);
   }
   result.useSourceInfoIfMissingFromForTree(node);
   parent.replaceChild(node, result);
   compiler.reportCodeChange();
 }
  /** Try to fold an ADD node with constant operands */
  private Node tryFoldAddConstantString(Node n, Node left, Node right) {
    if (left.isString() || right.isString() || left.isArrayLit() || right.isArrayLit()) {
      // Add strings.
      String leftString = NodeUtil.getStringValue(left);
      String rightString = NodeUtil.getStringValue(right);
      if (leftString != null && rightString != null) {
        Node newStringNode = IR.string(leftString + rightString);
        n.getParent().replaceChild(n, newStringNode);
        reportCodeChange();
        return newStringNode;
      }
    }

    return n;
  }
    /** Strip property type annotations and add suppress checkTypes and globalThis on functions. */
    private void suppressBehavior(Node behaviorValue) {
      if (behaviorValue == null) {
        compiler.report(JSError.make(behaviorValue, POLYMER_UNQUALIFIED_BEHAVIOR));
        return;
      }

      if (behaviorValue.isArrayLit()) {
        for (Node child : behaviorValue.children()) {
          suppressBehavior(child);
        }
      } else if (behaviorValue.isObjectLit()) {
        stripPropertyTypes(behaviorValue);
        addBehaviorSuppressions(behaviorValue);
      }
    }
  /** Try to fold array-element. e.g [1, 2, 3][10]; */
  private Node tryFoldGetElem(Node n, Node left, Node right) {
    Preconditions.checkArgument(n.isGetElem());

    if (left.isObjectLit()) {
      return tryFoldObjectPropAccess(n, left, right);
    }

    if (left.isArrayLit()) {
      return tryFoldArrayAccess(n, left, right);
    }

    if (left.isString()) {
      return tryFoldStringArrayAccess(n, left, right);
    }
    return n;
  }
  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;
  }
  /**
   * Extracts all Behaviors from an array recursively. The array must be an array literal whose
   * value is known at compile-time. Entries in the array can be object literals or array literals
   * (of other behaviors). Behavior names must be global, fully qualified names.
   *
   * @see https://github.com/Polymer/polymer/blob/0.8-preview/PRIMER.md#behaviors
   * @return A list of all {@code BehaviorDefinitions} in the array.
   */
  private List<BehaviorDefinition> extractBehaviors(Node behaviorArray) {
    if (behaviorArray == null) {
      return ImmutableList.of();
    }

    if (!behaviorArray.isArrayLit()) {
      compiler.report(JSError.make(behaviorArray, POLYMER_INVALID_BEHAVIOR_ARRAY));
      return ImmutableList.of();
    }

    ImmutableList.Builder<BehaviorDefinition> behaviors = ImmutableList.builder();
    for (Node behaviorName : behaviorArray.children()) {
      if (behaviorName.isObjectLit()) {
        this.switchDollarSignPropsToBrackets(behaviorName);
        this.quoteListenerAndHostAttributeKeys(behaviorName);
        behaviors.add(
            new BehaviorDefinition(
                extractProperties(behaviorName),
                getBehaviorFunctionsToCopy(behaviorName),
                getNonPropertyMembersToCopy(behaviorName),
                !NodeUtil.isInFunction(behaviorName)));
        continue;
      }

      Name behaviorGlobalName = globalNames.getSlot(behaviorName.getQualifiedName());
      boolean isGlobalDeclaration = true;
      if (behaviorGlobalName == null) {
        compiler.report(JSError.make(behaviorName, POLYMER_UNQUALIFIED_BEHAVIOR));
        continue;
      }

      Ref behaviorDeclaration = behaviorGlobalName.getDeclaration();

      // Use any set as a backup declaration, even if it's local.
      if (behaviorDeclaration == null) {
        List<Ref> behaviorRefs = behaviorGlobalName.getRefs();
        for (Ref ref : behaviorRefs) {
          if (ref.isSet()) {
            isGlobalDeclaration = false;
            behaviorDeclaration = ref;
            break;
          }
        }
      }

      if (behaviorDeclaration == null) {
        compiler.report(JSError.make(behaviorName, POLYMER_UNQUALIFIED_BEHAVIOR));
        continue;
      }

      Node behaviorDeclarationNode = behaviorDeclaration.getNode();
      JSDocInfo behaviorInfo = NodeUtil.getBestJSDocInfo(behaviorDeclarationNode);
      if (behaviorInfo == null || !behaviorInfo.isPolymerBehavior()) {
        compiler.report(JSError.make(behaviorDeclarationNode, POLYMER_UNANNOTATED_BEHAVIOR));
      }

      Node behaviorValue = NodeUtil.getRValueOfLValue(behaviorDeclarationNode);

      if (behaviorValue == null) {
        compiler.report(JSError.make(behaviorName, POLYMER_UNQUALIFIED_BEHAVIOR));
      } else if (behaviorValue.isArrayLit()) {
        // Individual behaviors can also be arrays of behaviors. Parse them recursively.
        behaviors.addAll(extractBehaviors(behaviorValue));
      } else if (behaviorValue.isObjectLit()) {
        this.switchDollarSignPropsToBrackets(behaviorValue);
        this.quoteListenerAndHostAttributeKeys(behaviorValue);
        behaviors.add(
            new BehaviorDefinition(
                extractProperties(behaviorValue),
                getBehaviorFunctionsToCopy(behaviorValue),
                getNonPropertyMembersToCopy(behaviorValue),
                isGlobalDeclaration));
      } else {
        compiler.report(JSError.make(behaviorName, POLYMER_UNQUALIFIED_BEHAVIOR));
      }
    }

    return behaviors.build();
  }