private boolean matchesNodeShape(Node template, Node ast) {
    if (isTemplateParameterNode(template)) {
      // Match the entire expression but only if it is an expression.
      return !NodeUtil.isStatement(ast);
    } else if (isTemplateLocalNameNode(template)) {
      // Match any name. Maybe match locals here.
      if (!ast.isName()) {
        return false;
      }
    } else if (template.isCall()) {
      // Loosely match CALL nodes. isEquivalentToShallow checks free calls against non-free calls,
      // but the template should ignore that distinction.
      if (ast == null || !ast.isCall() || ast.getChildCount() != template.getChildCount()) {
        return false;
      }
      // But check any children.
    } else if (!template.isEquivalentToShallow(ast)) {
      return false;
    }

    // isEquivalentToShallow guarantees the child counts match
    Node templateChild = template.getFirstChild();
    Node astChild = ast.getFirstChild();
    while (templateChild != null) {
      if (!matchesNodeShape(templateChild, astChild)) {
        return false;
      }
      templateChild = templateChild.getNext();
      astChild = astChild.getNext();
    }
    return true;
  }
Esempio n. 2
0
 private void validateMaximumChildCount(Node n, int i) {
   boolean valid = false;
   if (i == 1) {
     valid = !n.hasMoreThanOneChild();
   } else if (i == -1) {
     valid = true; // Varying number of children.
   } else {
     valid = n.getChildCount() <= i;
   }
   if (!valid) {
     violation("Expected no more than " + i + " children, but was " + n.getChildCount(), n);
   }
 }
Esempio n. 3
0
  private void validateMinimumChildCount(Node n, int i) {
    boolean valid = false;
    if (i == 1) {
      valid = n.hasChildren();
    } else if (i == 2) {
      valid = n.hasMoreThanOneChild();
    } else {
      valid = n.getChildCount() >= i;
    }

    if (!valid) {
      violation("Expected at least " + i + " children, but was " + n.getChildCount(), n);
    }
  }
 private void handleFor(Node forNode) {
   if (forNode.getChildCount() == 4) {
     // We have for (init; cond; iter) { body }
     Node init = forNode.getFirstChild();
     Node cond = init.getNext();
     Node iter = cond.getNext();
     Node body = iter.getNext();
     // After initialization, we transfer to the FOR which is in charge of
     // checking the condition (for the first time).
     createEdge(init, Branch.UNCOND, forNode);
     // The edge that transfer control to the beginning of the loop body.
     createEdge(forNode, Branch.ON_TRUE, computeFallThrough(body));
     // The edge to end of the loop.
     createEdge(forNode, Branch.ON_FALSE, computeFollowNode(forNode));
     // The end of the body will have a unconditional branch to our iter
     // (handled by calling computeFollowNode of the last instruction of the
     // body. Our iter will jump to the forNode again to another condition
     // check.
     createEdge(iter, Branch.UNCOND, forNode);
     connectToPossibleExceptionHandler(init, init);
     connectToPossibleExceptionHandler(forNode, cond);
     connectToPossibleExceptionHandler(iter, iter);
   } else {
     // We have for (item in collection) { body }
     Node item = forNode.getFirstChild();
     Node collection = item.getNext();
     Node body = collection.getNext();
     // The edge that transfer control to the beginning of the loop body.
     createEdge(forNode, Branch.ON_TRUE, computeFallThrough(body));
     // The edge to end of the loop.
     createEdge(forNode, Branch.ON_FALSE, computeFollowNode(forNode));
     connectToPossibleExceptionHandler(forNode, collection);
   }
 }
  @Override
  public void visit(NodeTraversal t, Node n, Node parent) {
    if (n.isString() && !parent.isGetProp() && !parent.isRegExp()) {
      String s = n.getString();

      for (blacklist.reset(s); blacklist.find(); ) {
        if (parent.isTemplateLit()) {
          if (parent.getChildCount() > 1) {
            // Ignore template string with substitutions
            continue;
          } else {
            n = parent;
          }
        }
        if (insideGetCssNameCall(n)) {
          continue;
        }
        if (insideGetUniqueIdCall(n)) {
          continue;
        }
        if (insideAssignmentToIdConstant(n)) {
          continue;
        }
        compiler.report(t.makeError(n, level, MISSING_GETCSSNAME, blacklist.group()));
      }
    }
  }
  private void handleContinue(Node node) {
    String label = null;
    if (node.hasChildren()) {
      label = node.getFirstChild().getString();
    }
    Node cur;
    Node lastJump;
    // Similar to handBreak's logic with a few minor variation.
    Node parent = node.getParent();
    for (cur = node, lastJump = node;
        !isContinueTarget(cur, parent, label);
        cur = parent, parent = parent.getParent()) {
      if (cur.getType() == Token.TRY && NodeUtil.hasFinally(cur)) {
        if (lastJump == node) {
          createEdge(lastJump, Branch.UNCOND, cur.getLastChild());
        } else {
          finallyMap.put(lastJump, computeFallThrough(cur.getLastChild()));
        }
        lastJump = cur;
      }
      Preconditions.checkState(parent != null, "Cannot find continue target.");
    }
    Node iter = cur;
    if (cur.getChildCount() == 4) {
      iter = cur.getFirstChild().getNext().getNext();
    }

    if (lastJump == node) {
      createEdge(node, Branch.UNCOND, iter);
    } else {
      finallyMap.put(lastJump, iter);
    }
  }
Esempio n. 7
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;
  }
  /** Traverses a function. */
  private void traverseFunction(Node n, Node parent) {
    Preconditions.checkState(n.getChildCount() == 3);
    Preconditions.checkState(n.isFunction());

    final Node fnName = n.getFirstChild();
    boolean isFunctionExpression = (parent != null) && NodeUtil.isFunctionExpression(n);

    if (!isFunctionExpression) {
      // Functions declarations are in the scope containing the declaration.
      traverseBranch(fnName, n);
    }

    curNode = n;
    pushScope(n);

    if (isFunctionExpression) {
      // Function expression names are only accessible within the function
      // scope.
      traverseBranch(fnName, n);
    }

    final Node args = fnName.getNext();
    final Node body = args.getNext();

    // Args
    traverseBranch(args, n);

    // Body
    // ES6 "arrow" function may not have a block as a body.
    traverseBranch(body, n);

    popScope();
  }
 private void handleFunction(Node node) {
   // A block transfer control to its first child if it is not empty.
   Preconditions.checkState(node.getChildCount() >= 3);
   createEdge(node, Branch.UNCOND, computeFallThrough(node.getFirstChild().getNext().getNext()));
   Preconditions.checkState(exceptionHandler.peek() == node);
   exceptionHandler.pop();
 }
  /** Try to fold array-length. e.g [1, 2, 3].length ==> 3, [x, y].length ==> 2 */
  private Node tryFoldGetProp(Node n, Node left, Node right) {
    Preconditions.checkArgument(n.isGetProp());

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

    if (right.isString() && right.getString().equals("length")) {
      int knownLength = -1;
      switch (left.getType()) {
        case ARRAYLIT:
          if (mayHaveSideEffects(left)) {
            // Nope, can't fold this, without handling the side-effects.
            return n;
          }
          knownLength = left.getChildCount();
          break;
        case STRING:
          knownLength = left.getString().length();
          break;
        default:
          // Not a foldable case, forget it.
          return n;
      }

      Preconditions.checkState(knownLength != -1);
      Node lengthNode = IR.number(knownLength);
      n.getParent().replaceChild(n, lengthNode);
      reportCodeChange();

      return lengthNode;
    }

    return n;
  }
Esempio n. 11
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);
    }
  }
  /** @return whether the blockNode contains only a single "throw" child node. */
  private static boolean hasSingleThrow(Node blockNode) {
    if (blockNode.getChildCount() == 1 && blockNode.getFirstChild().getType() == Token.THROW) {
      // Functions consisting of a single "throw FOO" can be actually abstract,
      // so do not check their return type nullability.
      return true;
    }

    return false;
  }
Esempio n. 13
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());
   }
 }
  /** Scans and gather variables declarations under a Node */
  private void scanVars(Node n, Node parent) {
    switch (n.getType()) {
      case Token.VAR:
        // Declare all variables. e.g. var x = 1, y, z;
        for (Node child = n.getFirstChild(); child != null; ) {
          Node next = child.getNext();
          Preconditions.checkState(child.getType() == Token.NAME);

          String name = child.getString();
          declareVar(name, child, n, parent, null, n);
          child = next;
        }
        return;

      case Token.FUNCTION:
        if (NodeUtil.isFunctionExpression(n)) {
          return;
        }

        String fnName = n.getFirstChild().getString();
        if (fnName.isEmpty()) {
          // This is invalid, but allow it so the checks can catch it.
          return;
        }
        declareVar(fnName, n.getFirstChild(), n, parent, null, n);
        return; // should not examine function's children

      case Token.CATCH:
        Preconditions.checkState(n.getChildCount() == 2);
        Preconditions.checkState(n.getFirstChild().getType() == Token.NAME);
        // the first child is the catch var and the third child
        // is the code block

        final Node var = n.getFirstChild();
        final Node block = var.getNext();

        declareVar(var.getString(), var, n, parent, null, n);
        scanVars(block, n);
        return; // only one child to scan

      case Token.SCRIPT:
        sourceName = (String) n.getProp(Node.SOURCENAME_PROP);
        break;
    }

    // Variables can only occur in statement-level nodes, so
    // we only need to traverse children in a couple special cases.
    if (NodeUtil.isControlStructure(n) || NodeUtil.isStatementBlock(n)) {
      for (Node child = n.getFirstChild(); child != null; ) {
        Node next = child.getNext();
        scanVars(child, n);
        child = next;
      }
    }
  }
Esempio n. 15
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;
  }
Esempio n. 16
0
  /**
   * Gets the maximum number of arguments that this function requires, or Integer.MAX_VALUE if this
   * is a variable argument function.
   */
  public int getMaxArguments() {
    Node params = getParametersNode();
    if (params != null) {
      Node lastParam = params.getLastChild();
      if (lastParam == null || !lastParam.isVarArgs()) {
        return params.getChildCount();
      }
    }

    return Integer.MAX_VALUE;
  }
  /** 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.
  }
  /**
   * Traverses a function, which creates a new scope in javascript.
   *
   * <p>Note that CATCH blocks also create a new scope, but only for the catch variable.
   * Declarations within the block actually belong to the enclosing scope. Because we don't remove
   * catch variables, there's no need to treat CATCH blocks differently like we do functions.
   */
  private void traverseFunction(Node n, Scope parentScope) {
    Preconditions.checkState(n.getChildCount() == 3);
    Preconditions.checkState(n.getType() == Token.FUNCTION);

    final Node body = n.getLastChild();
    Preconditions.checkState(body.getNext() == null && body.getType() == Token.BLOCK);

    Scope fnScope = new SyntacticScopeCreator(compiler).createScope(n, parentScope);
    traverseNode(body, n, fnScope);

    collectMaybeUnreferencedVars(fnScope);
    allFunctionScopes.add(fnScope);
  }
Esempio n. 19
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);
     }
   }
 }
  /**
   * Gets an estimate of the cost in characters of making the function call: the sum of the
   * identifiers and the separators.
   *
   * @param referencesThis
   */
  private static int estimateCallCost(Node fnNode, boolean referencesThis) {
    Node argsNode = NodeUtil.getFnParameters(fnNode);
    int numArgs = argsNode.getChildCount();

    int callCost = NAME_COST_ESTIMATE + PAREN_COST;
    if (numArgs > 0) {
      callCost += (numArgs * NAME_COST_ESTIMATE) + ((numArgs - 1) * COMMA_COST);
    }

    if (referencesThis) {
      // TODO(johnlenz): Update this if we start supporting inlining
      // other functions that reference this.
      // The only functions that reference this that are currently inlined
      // are those that are called via ".call" with an explicit "this".
      callCost += 5 + 5; // ".call" + "this,"
    }

    return callCost;
  }
 @Override
 public FlowScope getPreciserScopeKnowingConditionOutcome(
     Node condition, FlowScope blindScope, boolean outcome) {
   if (condition.isCall() && condition.getChildCount() == 2) {
     Node callee = condition.getFirstChild();
     Node param = condition.getLastChild();
     if (callee.isGetProp() && param.isQualifiedName()) {
       JSType paramType = getTypeIfRefinable(param, blindScope);
       Node left = callee.getFirstChild();
       Node right = callee.getLastChild();
       if (left.isName() && "goog".equals(left.getString()) && right.isString()) {
         Function<TypeRestriction, JSType> restricter = restricters.get(right.getString());
         if (restricter != null) {
           return restrictParameter(param, paramType, blindScope, restricter, outcome);
         }
       }
     }
   }
   return nextPreciserScopeKnowingConditionOutcome(condition, blindScope, outcome);
 }
Esempio n. 22
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));
     }
   }
 }
Esempio n. 23
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 checkRequireCall(NodeTraversal t, Node callNode, Node parent) {
   Preconditions.checkState(callNode.isCall());
   switch (parent.getType()) {
     case Token.EXPR_RESULT:
       return;
     case Token.GETPROP:
       if (parent.getParent().isName()) {
         checkRequireCall(t, callNode, parent.getParent());
         return;
       }
       break;
     case Token.NAME:
     case Token.OBJECT_PATTERN:
       {
         Node declaration = parent.getParent();
         if (declaration.getChildCount() != 1) {
           t.report(declaration, ONE_REQUIRE_PER_DECLARATION);
         }
         return;
       }
   }
   t.report(callNode, REQUIRE_NOT_AT_TOP_LEVEL);
 }
  /**
   * Expressions such as [foo() * 10 * 20] generate parse trees where no node has two const children
   * ((foo() * 10) * 20), so performArithmeticOp() won't fold it -- tryFoldLeftChildOp() will.
   * Specifically, it folds associative expressions where: - The left child is also an associative
   * expression of the same time. - The right child is a constant NUMBER constant. - The left
   * child's right child is a NUMBER constant.
   */
  private Node tryFoldLeftChildOp(Node n, Node left, Node right) {
    Token opType = n.getType();
    Preconditions.checkState(
        (NodeUtil.isAssociative(opType) && NodeUtil.isCommutative(opType)) || n.isAdd());

    Preconditions.checkState(!n.isAdd() || !NodeUtil.mayBeString(n, shouldUseTypes));

    // Use getNumberValue to handle constants like "NaN" and "Infinity"
    // other values are converted to numbers elsewhere.
    Double rightValObj = NodeUtil.getNumberValue(right, shouldUseTypes);
    if (rightValObj != null && left.getType() == opType) {
      Preconditions.checkState(left.getChildCount() == 2);

      Node ll = left.getFirstChild();
      Node lr = ll.getNext();

      Node valueToCombine = ll;
      Node replacement = performArithmeticOp(opType, valueToCombine, right);
      if (replacement == null) {
        valueToCombine = lr;
        replacement = performArithmeticOp(opType, valueToCombine, right);
      }
      if (replacement != null) {
        // Remove the child that has been combined
        left.removeChild(valueToCombine);
        // Replace the left op with the remaining child.
        n.replaceChild(left, left.removeFirstChild());
        // New "-Infinity" node need location info explicitly
        // added.
        replacement.useSourceInfoIfMissingFromForTree(right);
        n.replaceChild(right, replacement);
        reportCodeChange();
      }
    }

    return n;
  }
Esempio n. 26
0
  @Override
  public void visit(NodeTraversal t, Node n, Node parent) {
    // VOID nodes appear when there are extra semicolons at the BLOCK level.
    // I've been unable to think of any cases where this indicates a bug,
    // and apparently some people like keeping these semicolons around,
    // so we'll allow it.
    if (n.isEmpty() || n.isComma()) {
      return;
    }

    if (parent == null) {
      return;
    }

    int pt = parent.getType();
    if (pt == Token.COMMA) {
      Node gramps = parent.getParent();
      if (gramps.isCall() && parent == gramps.getFirstChild()) {
        // Semantically, a direct call to eval is different from an indirect
        // call to an eval. See Ecma-262 S15.1.2.1. So it's ok for the first
        // expression to a comma to be a no-op if it's used to indirect
        // an eval.
        if (n == parent.getFirstChild()
            && parent.getChildCount() == 2
            && n.getNext().isName()
            && "eval".equals(n.getNext().getString())) {
          return;
        }
      }

      if (n == parent.getLastChild()) {
        for (Node an : parent.getAncestors()) {
          int ancestorType = an.getType();
          if (ancestorType == Token.COMMA) continue;
          if (ancestorType != Token.EXPR_RESULT && ancestorType != Token.BLOCK) return;
          else break;
        }
      }
    } else if (pt != Token.EXPR_RESULT && pt != Token.BLOCK) {
      if (pt == Token.FOR
          && parent.getChildCount() == 4
          && (n == parent.getFirstChild() || n == parent.getFirstChild().getNext().getNext())) {
        // Fall through and look for warnings for the 1st and 3rd child
        // of a for.
      } else {
        return; // it might be ok to not have a side-effect
      }
    }

    boolean isSimpleOp = NodeUtil.isSimpleOperatorType(n.getType());
    if (isSimpleOp || !NodeUtil.mayHaveSideEffects(n, t.getCompiler())) {
      if (n.isQualifiedName() && n.getJSDocInfo() != null) {
        // This no-op statement was there so that JSDoc information could
        // be attached to the name. This check should not complain about it.
        return;
      } else if (n.isExprResult()) {
        // we already reported the problem when we visited the child.
        return;
      }

      String msg = "This code lacks side-effects. Is there a bug?";
      if (n.isString()) {
        msg = "Is there a missing '+' on the previous line?";
      } else if (isSimpleOp) {
        msg =
            "The result of the '"
                + Token.name(n.getType()).toLowerCase()
                + "' operator is not being used.";
      }

      t.getCompiler().report(t.makeError(n, level, USELESS_CODE_ERROR, msg));
      // TODO(johnlenz): determine if it is necessary to
      // try to protect side-effect free statements as well.
      if (!NodeUtil.isStatement(n)) {
        problemNodes.add(n);
      }
    }
  }
  /**
   * Validates the class definition and if valid, destructively extracts the class definition from
   * the AST.
   */
  private ClassDefinition extractClassDefinition(Node callNode) {
    Node descriptor = NodeUtil.getArgumentForCallOrNew(callNode, 0);
    if (descriptor == null || !descriptor.isObjectLit()) {
      // report bad class definition
      compiler.report(JSError.make(callNode, POLYMER_DESCRIPTOR_NOT_VALID));
      return null;
    }

    int paramCount = callNode.getChildCount() - 1;
    if (paramCount != 1) {
      compiler.report(JSError.make(callNode, POLYMER_UNEXPECTED_PARAMS));
      return null;
    }

    Node elName = NodeUtil.getFirstPropMatchingKey(descriptor, "is");
    if (elName == null) {
      compiler.report(JSError.make(callNode, POLYMER_MISSING_IS));
      return null;
    }

    String elNameString = CaseFormat.LOWER_HYPHEN.to(CaseFormat.UPPER_CAMEL, elName.getString());
    elNameString += "Element";

    Node target;
    if (NodeUtil.isNameDeclaration(callNode.getParent().getParent())) {
      target = IR.name(callNode.getParent().getString());
    } else if (callNode.getParent().isAssign()) {
      target = callNode.getParent().getFirstChild().cloneTree();
    } else {
      target = IR.name(elNameString);
    }

    target.useSourceInfoIfMissingFrom(callNode);
    JSDocInfo classInfo = NodeUtil.getBestJSDocInfo(target);

    JSDocInfo ctorInfo = null;
    Node constructor = NodeUtil.getFirstPropMatchingKey(descriptor, "factoryImpl");
    if (constructor == null) {
      constructor = IR.function(IR.name(""), IR.paramList(), IR.block());
      constructor.useSourceInfoFromForTree(callNode);
    } else {
      ctorInfo = NodeUtil.getBestJSDocInfo(constructor);
    }

    Node baseClass = NodeUtil.getFirstPropMatchingKey(descriptor, "extends");
    String nativeBaseElement = baseClass == null ? null : baseClass.getString();

    Node behaviorArray = NodeUtil.getFirstPropMatchingKey(descriptor, "behaviors");
    List<BehaviorDefinition> behaviors = extractBehaviors(behaviorArray);
    List<MemberDefinition> allProperties = new LinkedList<>();
    for (BehaviorDefinition behavior : behaviors) {
      overwriteMembersIfPresent(allProperties, behavior.props);
    }
    overwriteMembersIfPresent(allProperties, extractProperties(descriptor));

    ClassDefinition def =
        new ClassDefinition(
            target,
            descriptor,
            classInfo,
            new MemberDefinition(ctorInfo, null, constructor),
            nativeBaseElement,
            allProperties,
            behaviors);
    return def;
  }
  /**
   * Tries to optimize all the arguments array access in this scope by assigning a name to each
   * element.
   *
   * @param scope scope of the function
   * @return true if any modification has been done to the AST
   */
  private boolean tryReplaceArguments(Scope scope) {

    Node parametersList = scope.getRootNode().getSecondChild();
    Preconditions.checkState(parametersList.isParamList());

    // Keep track of rather this function modified the AST and needs to be
    // reported back to the compiler later.
    boolean changed = false;

    // Number of parameter that can be accessed without using the arguments
    // array.
    int numNamedParameter = parametersList.getChildCount();

    // We want to guess what the highest index that has been access from the
    // arguments array. We will guess that it does not use anything index higher
    // than the named parameter list first until we see other wise.
    int highestIndex = numNamedParameter - 1;

    // Iterate through all the references to arguments array in the function to
    // determine the real highestIndex.
    for (Node ref : currentArgumentsAccess) {

      Node getElem = ref.getParent();

      // Bail on anything but argument[c] access where c is a constant.
      // TODO(user): We might not need to bail out all the time, there might
      // be more cases that we can cover.
      if (!getElem.isGetElem() || ref != getElem.getFirstChild()) {
        return false;
      }

      Node index = ref.getNext();

      // We have something like arguments[x] where x is not a constant. That
      // means at least one of the access is not known.
      if (!index.isNumber() || index.getDouble() < 0) {
        // TODO(user): Its possible not to give up just yet. The type
        // inference did a 'semi value propagation'. If we know that string
        // is never a subclass of the type of the index. We'd know that
        // it is never 'callee'.
        return false; // Give up.
      }

      Node getElemParent = getElem.getParent();
      // When we have argument[0](), replacing it with a() is semantically
      // different if argument[0] is a function call that refers to 'this'
      if (getElemParent.isCall() && getElemParent.getFirstChild() == getElem) {
        // TODO(user): We can consider using .call() if aliasing that
        // argument allows shorter alias for other arguments.
        return false;
      }

      // Replace the highest index if we see an access that has a higher index
      // than all the one we saw before.
      int value = (int) index.getDouble();
      if (value > highestIndex) {
        highestIndex = value;
      }
    }

    // Number of extra arguments we need.
    // For example: function() { arguments[3] } access index 3 so
    // it will need 4 extra named arguments to changed into:
    // function(a,b,c,d) { d }.
    int numExtraArgs = highestIndex - numNamedParameter + 1;

    // Temporary holds the new names as string for quick access later.
    String[] argNames = new String[numExtraArgs];

    // Insert the formal parameter to the method's signature.
    // Example: function() --> function(r0, r1, r2)
    for (int i = 0; i < numExtraArgs; i++) {
      String name = getNewName();
      argNames[i] = name;
      parametersList.addChildToBack(IR.name(name).useSourceInfoIfMissingFrom(parametersList));
      changed = true;
    }

    // This loop performs the replacement of arguments[x] -> a if x is known.
    for (Node ref : currentArgumentsAccess) {
      Node index = ref.getNext();

      // Skip if it is unknown.
      if (!index.isNumber()) {
        continue;
      }
      int value = (int) index.getDouble();

      // Unnamed parameter.
      if (value >= numNamedParameter) {
        ref.getGrandparent()
            .replaceChild(ref.getParent(), IR.name(argNames[value - numNamedParameter]));
      } else {

        // Here, for no apparent reason, the user is accessing a named parameter
        // with arguments[idx]. We can replace it with the actual name for them.
        Node name = parametersList.getFirstChild();

        // This is a linear search for the actual name from the signature.
        // It is not necessary to make this fast because chances are the user
        // will not deliberately write code like this.
        for (int i = 0; i < value; i++) {
          name = name.getNext();
        }
        ref.getGrandparent().replaceChild(ref.getParent(), IR.name(name.getString()));
      }
      changed = true;
    }

    return changed;
  }
  /**
   * Validates the class definition and if valid, destructively extracts the class definition from
   * the AST.
   */
  private ClassDefinition extractClassDefinition(Node targetName, Node callNode) {

    JSDocInfo classInfo = NodeUtil.getBestJSDocInfo(targetName);

    // name = goog.defineClass(superClass, {...}, [modifier, ...])
    Node superClass = NodeUtil.getArgumentForCallOrNew(callNode, 0);
    if (superClass == null || (!superClass.isNull() && !superClass.isQualifiedName())) {
      compiler.report(JSError.make(callNode, GOOG_CLASS_SUPER_CLASS_NOT_VALID));
      return null;
    }

    if (NodeUtil.isNullOrUndefined(superClass) || superClass.matchesQualifiedName("Object")) {
      superClass = null;
    }

    Node description = NodeUtil.getArgumentForCallOrNew(callNode, 1);
    if (description == null || !description.isObjectLit() || !validateObjLit(description)) {
      // report bad class definition
      compiler.report(JSError.make(callNode, GOOG_CLASS_DESCRIPTOR_NOT_VALID));
      return null;
    }

    int paramCount = callNode.getChildCount() - 1;
    if (paramCount > 2) {
      compiler.report(JSError.make(callNode, GOOG_CLASS_UNEXPECTED_PARAMS));
      return null;
    }

    Node constructor = extractProperty(description, "constructor");
    if (classInfo != null && classInfo.isInterface()) {
      if (constructor != null) {
        compiler.report(JSError.make(description, GOOG_CLASS_CONSTRUCTOR_ON_INTERFACE));
        return null;
      }
    } else if (constructor == null) {
      // report missing constructor
      compiler.report(JSError.make(description, GOOG_CLASS_CONSTRUCTOR_MISSING));
      return null;
    }
    if (constructor == null) {
      constructor =
          IR.function(
              IR.name("").srcref(callNode),
              IR.paramList().srcref(callNode),
              IR.block().srcref(callNode));
      constructor.srcref(callNode);
    }

    JSDocInfo info = NodeUtil.getBestJSDocInfo(constructor);

    Node classModifier = null;
    Node statics = null;
    Node staticsProp = extractProperty(description, "statics");
    if (staticsProp != null) {
      if (staticsProp.isObjectLit() && validateObjLit(staticsProp)) {
        statics = staticsProp;
      } else if (staticsProp.isFunction()) {
        classModifier = staticsProp;
      } else {
        compiler.report(JSError.make(staticsProp, GOOG_CLASS_STATICS_NOT_VALID));
        return null;
      }
    }

    if (statics == null) {
      statics = IR.objectlit();
    }

    // Ok, now rip apart the definition into its component pieces.
    // Remove the "special" property key nodes.
    maybeDetach(constructor.getParent());
    maybeDetach(statics.getParent());
    if (classModifier != null) {
      maybeDetach(classModifier.getParent());
    }
    ClassDefinition def =
        new ClassDefinition(
            targetName,
            classInfo,
            maybeDetach(superClass),
            new MemberDefinition(info, null, maybeDetach(constructor)),
            objectLitToList(maybeDetach(statics)),
            objectLitToList(description),
            maybeDetach(classModifier));
    return def;
  }
 private static boolean isAlwaysInlinable(Node fn) {
   Preconditions.checkArgument(fn.isFunction());
   Node body = NodeUtil.getFunctionBody(fn);
   int numOfStmsInBody = body.getChildCount();
   return numOfStmsInBody == 0 || numOfStmsInBody == 1 && body.getFirstChild().isReturn();
 }