/**
   * If we haven't found a return value yet, try to look at the "return" statements in the function.
   */
  FunctionTypeBuilder inferReturnStatementsAsLastResort(@Nullable Node functionBlock) {
    if (functionBlock == null || compiler.getInput(sourceName).isExtern()) {
      return this;
    }
    Preconditions.checkArgument(functionBlock.getType() == Token.BLOCK);
    if (returnType == null) {
      boolean hasNonEmptyReturns = false;
      List<Node> worklist = Lists.newArrayList(functionBlock);
      while (!worklist.isEmpty()) {
        Node current = worklist.remove(worklist.size() - 1);
        int cType = current.getType();
        if (cType == Token.RETURN && current.getFirstChild() != null || cType == Token.THROW) {
          hasNonEmptyReturns = true;
          break;
        } else if (NodeUtil.isStatementBlock(current) || NodeUtil.isControlStructure(current)) {
          for (Node child = current.getFirstChild(); child != null; child = child.getNext()) {
            worklist.add(child);
          }
        }
      }

      if (!hasNonEmptyReturns) {
        returnType = typeRegistry.getNativeType(VOID_TYPE);
        returnTypeInferred = true;
      }
    }
    return this;
  }
  public void testLinenoCharnoObjectLiteral() throws Exception {
    Node n = parse("\n\n var a = {a:0\n,b :1};").getFirstChild().getFirstChild().getFirstChild();

    assertEquals(Token.OBJECTLIT, n.getType());
    assertEquals(3, n.getLineno());
    assertEquals(9, n.getCharno());

    Node key = n.getFirstChild();

    assertEquals(Token.STRING_KEY, key.getType());
    assertEquals(3, key.getLineno());
    assertEquals(10, key.getCharno());

    Node value = key.getFirstChild();

    assertEquals(Token.NUMBER, value.getType());
    assertEquals(3, value.getLineno());
    assertEquals(12, value.getCharno());

    key = key.getNext();

    assertEquals(Token.STRING_KEY, key.getType());
    assertEquals(4, key.getLineno());
    assertEquals(1, key.getCharno());

    value = key.getFirstChild();

    assertEquals(Token.NUMBER, value.getType());
    assertEquals(4, value.getLineno());
    assertEquals(4, value.getCharno());
  }
 /**
  * Gets whether a node is a CALL node whose return value should be stripped. A call's return
  * value should be stripped if the function getting called is a static method in a class that
  * gets stripped. For example, if "goog.debug.Logger" is a strip name, then this function
  * returns true for a call such as "goog.debug.Logger.getLogger(...)". It may also simply be a
  * function that is getting stripped. For example, if "getLogger" is a strip name, but not
  * "goog.debug.Logger", this will still return true.
  *
  * @param n A node (typically a CALL node)
  * @return Whether the call's return value should be stripped
  */
 boolean isCallWhoseReturnValueShouldBeStripped(@Nullable Node n) {
   return n != null
       && (n.getType() == Token.CALL || n.getType() == Token.NEW)
       && n.hasChildren()
       && (qualifiedNameBeginsWithStripType(n.getFirstChild())
           || nameEndsWithFieldNameToStrip(n.getFirstChild()));
 }
 public void testJSDocAttachment18() {
   Node fn =
       parse("function f() { " + "  var x = /** @type {string} */ (y);" + "};").getFirstChild();
   assertEquals(Token.FUNCTION, fn.getType());
   Node cast = fn.getLastChild().getFirstChild().getFirstChild().getFirstChild();
   assertEquals(Token.CAST, cast.getType());
 }
    /** If this is an assign to a variable or its property, return it. Otherwise, return null. */
    static Assign maybeCreateAssign(Node assignNode) {
      Preconditions.checkState(NodeUtil.isAssignmentOp(assignNode));

      // Skip one level of GETPROPs or GETELEMs.
      //
      // Don't skip more than one level, because then we get into
      // situations where assigns to properties of properties will always
      // trigger side-effects, and the variable they're on cannot be removed.
      boolean isPropAssign = false;
      Node current = assignNode.getFirstChild();
      if (NodeUtil.isGet(current)) {
        current = current.getFirstChild();
        isPropAssign = true;

        if (current.getType() == Token.GETPROP
            && current.getLastChild().getString().equals("prototype")) {
          // Prototype properties sets should be considered like normal
          // property sets.
          current = current.getFirstChild();
        }
      }

      if (current.getType() == Token.NAME) {
        return new Assign(assignNode, current, isPropAssign);
      }
      return null;
    }
    /** Replace the current assign with its right hand side. */
    void remove() {
      Node parent = assignNode.getParent();
      if (mayHaveSecondarySideEffects) {
        Node replacement = assignNode.getLastChild().detachFromParent();

        // Aggregate any expressions in GETELEMs.
        for (Node current = assignNode.getFirstChild();
            current.getType() != Token.NAME;
            current = current.getFirstChild()) {
          if (current.getType() == Token.GETELEM) {
            replacement =
                new Node(Token.COMMA, current.getLastChild().detachFromParent(), replacement);
            replacement.copyInformationFrom(current);
          }
        }

        parent.replaceChild(assignNode, replacement);
      } else {
        Node gramps = parent.getParent();
        if (parent.getType() == Token.EXPR_RESULT) {
          gramps.removeChild(parent);
        } else {
          parent.replaceChild(assignNode, assignNode.getLastChild().detachFromParent());
        }
      }
    }
  private void scanRoot(Node n, Scope parent) {
    if (n.getType() == Token.FUNCTION) {
      sourceName = (String) n.getProp(Node.SOURCENAME_PROP);

      final Node fnNameNode = n.getFirstChild();
      final Node args = fnNameNode.getNext();
      final Node body = args.getNext();

      // Bleed the function name into the scope, if it hasn't
      // been declared in the outer scope.
      String fnName = fnNameNode.getString();
      if (!fnName.isEmpty() && NodeUtil.isFunctionExpression(n)) {
        declareVar(fnName, fnNameNode, n, null, null, n);
      }

      // Args: Declare function variables
      Preconditions.checkState(args.getType() == Token.LP);
      for (Node a = args.getFirstChild(); a != null; a = a.getNext()) {
        Preconditions.checkState(a.getType() == Token.NAME);
        declareVar(a.getString(), a, args, n, null, n);
      }

      // Body
      scanVars(body, n);
    } else {
      // It's the global block
      Preconditions.checkState(scope.getParent() == null);
      scanVars(n, null);
    }
  }
    public void onRedeclaration(
        Scope s, String name, Node n, Node parent, Node gramps, Node nodeWithLineNumber) {
      // Don't allow multiple variables to be declared at the top level scope
      if (scope.isGlobal()) {
        Scope.Var origVar = scope.getVar(name);
        Node origParent = origVar.getParentNode();
        if (origParent.getType() == Token.CATCH && parent.getType() == Token.CATCH) {
          // Okay, both are 'catch(x)' variables.
          return;
        }

        boolean allowDupe = false;
        JSDocInfo info = n.getJSDocInfo();
        if (info == null) {
          info = parent.getJSDocInfo();
        }
        allowDupe = info != null && info.getSuppressions().contains("duplicate");

        if (!allowDupe) {
          compiler.report(
              JSError.make(
                  sourceName,
                  nodeWithLineNumber,
                  VAR_MULTIPLY_DECLARED_ERROR,
                  name,
                  (origVar.input != null ? origVar.input.getName() : "??")));
        }
      } else if (name.equals(ARGUMENTS) && !NodeUtil.isVarDeclaration(n)) {
        // Disallow shadowing "arguments" as we can't handle with our current
        // scope modeling.
        compiler.report(JSError.make(sourceName, nodeWithLineNumber, VAR_ARGUMENTS_SHADOWED_ERROR));
      }
    }
 @Override
 public void visit(NodeTraversal t, Node n, Node parent) {
   if (n.getType() == Token.GETPROP) {
     handleGetProp(t, n);
   } else if (n.getType() == Token.OBJECTLIT) {
     handleObjectLit(t, n);
   }
 }
 /**
  * Gets whether a name ends with a field name that should be stripped. For example, this
  * function would return true when passed "this.logger" or "a.b.c.myLogger" if "logger" is a
  * strip name.
  *
  * @param n A node (typically a GETPROP node)
  * @return Whether the name ends with a field name that should be stripped
  */
 boolean nameEndsWithFieldNameToStrip(@Nullable Node n) {
   if (n != null && n.getType() == Token.GETPROP) {
     Node propNode = n.getLastChild();
     return propNode != null
         && propNode.getType() == Token.STRING
         && isStripName(propNode.getString());
   }
   return false;
 }
  public void testObjectLiteralDoc1() {
    Node n = parse("var x = {/** @type {number} */ 1: 2};");

    Node objectLit = n.getFirstChild().getFirstChild().getFirstChild();
    assertEquals(Token.OBJECTLIT, objectLit.getType());

    Node number = objectLit.getFirstChild();
    assertEquals(Token.STRING_KEY, number.getType());
    assertNotNull(number.getJSDocInfo());
  }
  /** 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;
      }
    }
  }
  /**
   * Locates the array literal specified by the <code>array</code> node, in the source using the
   * source location information provided by the node, and inserts <code>str</code> as a string
   * literal (quoted) in the source at the end of the array.
   *
   * @param array Node with source position information
   * @param str the string to insert into the source at the end of the specified array
   */
  public void appendToArrayLit(Node array, String str) {
    final String sourceMethod = "appendToArrayLit"; // $NON-NLS-1$
    if (isTraceLogging) {
      log.entering(JSSource.class.getName(), sourceMethod, new Object[] {array, str});
    }
    // Get the node for the last element in the array
    Node lastChild = array.getLastChild();

    // If token is not a string, then punt
    if (lastChild.getType() != Token.STRING && lastChild.getType() != Token.NAME) {
      if (log.isLoggable(Level.WARNING)) {
        log.warning(
            "Last element of array at "
                + mid
                + "("
                + array.getLineno()
                + ","
                + array.getCharno()
                + ") is type "
                + array.getType()); // $NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
      }
      if (isTraceLogging) {
        log.exiting(JSSource.class.getName(), sourceMethod);
      }
      return;
    }
    int len =
        lastChild.getString().length()
            + (lastChild.getType() == Token.STRING ? 2 : 0); // add 2 for string quotes

    // Search the source for the closing array bracket ']', skipping over
    // whitespace and comments.  In order to be valid javascript, the next
    // token must be the closing bracket.
    int lineno = lastChild.getLineno();
    int charno = lastChild.getCharno() + len;
    PositionLocator pos = new PositionLocator(lineno, charno);
    if (pos.findNextJSToken() == ']') {
      insert(",\"" + str + "\"", pos.getLineno(), pos.getCharno()); // $NON-NLS-1$ //$NON-NLS-2$
    } else {
      if (log.isLoggable(Level.WARNING)) {
        log.warning(
            "Closing array bracket not found in "
                + mid
                + "("
                + lineno
                + ","
                + charno
                + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
      }
    }
    if (isTraceLogging) {
      log.exiting(JSSource.class.getName(), sourceMethod);
    }
  }
  public void testJSDocAttachment10() {
    Node varNode = parse("/** x\n */var a;").getFirstChild();

    // VAR
    assertEquals(Token.VAR, varNode.getType());

    // NAME
    Node nameNode = varNode.getFirstChild();
    assertEquals(Token.NAME, nameNode.getType());
    assertNull(nameNode.getJSDocInfo());
  }
  public void testLinenoCharnoGetProp2() throws Exception {
    Node getprop = parse("\n foo.\nbar").getFirstChild().getFirstChild();

    assertEquals(Token.GETPROP, getprop.getType());
    assertEquals(2, getprop.getLineno());
    assertEquals(1, getprop.getCharno());

    Node name = getprop.getFirstChild().getNext();
    assertEquals(Token.STRING, name.getType());
    assertEquals(3, name.getLineno());
    assertEquals(0, name.getCharno());
  }
Exemple #16
0
 private void validateSwitchMember(Node n) {
   switch (n.getType()) {
     case Token.CASE:
       validateCase(n);
       return;
     case Token.DEFAULT_CASE:
       validateDefaultCase(n);
       return;
     default:
       violation("Expected switch member but was " + Token.name(n.getType()), n);
   }
 }
    public void visit(NodeTraversal t, Node n, Node parent) {
      if (n.getType() != Token.FUNCTION) {
        return;
      }

      int id = functionNames.getFunctionId(n);
      if (id < 0) {
        // Function node was added during compilation; don't instrument.
        return;
      }

      if (reportFunctionName.length() != 0) {
        Node body = n.getFirstChild().getNext().getNext();
        Node call =
            new Node(
                Token.CALL, Node.newString(Token.NAME, reportFunctionName), Node.newNumber(id));
        Node expr = new Node(Token.EXPR_RESULT, call);
        body.addChildToFront(expr);
        compiler.reportCodeChange();
      }

      if (reportFunctionExitName.length() != 0) {
        Node body = n.getFirstChild().getNext().getNext();
        (new InstrumentReturns(id)).process(body);
      }

      if (definedFunctionName.length() != 0) {
        Node call =
            new Node(
                Token.CALL, Node.newString(Token.NAME, definedFunctionName), Node.newNumber(id));
        Node expr = NodeUtil.newExpr(call);

        Node addingRoot = null;
        if (NodeUtil.isFunctionDeclaration(n)) {
          JSModule module = t.getModule();
          addingRoot = compiler.getNodeForCodeInsertion(module);
          addingRoot.addChildToFront(expr);
        } else {
          Node beforeChild = n;
          for (Node ancestor : n.getAncestors()) {
            int type = ancestor.getType();
            if (type == Token.BLOCK || type == Token.SCRIPT) {
              addingRoot = ancestor;
              break;
            }
            beforeChild = ancestor;
          }
          addingRoot.addChildBefore(expr, beforeChild);
        }
        compiler.reportCodeChange();
      }
    }
 // Looks at the type AST without evaluating it
 private boolean isUnionWithUndefined(Node n) {
   if (n == null || n.getType() != Token.PIPE) {
     return false;
   }
   for (Node child : n.children()) {
     if (child.getType() == Token.VOID
         || child.getType() == Token.STRING
             && (child.getString().equals("void") || child.getString().equals("undefined"))) {
       return true;
     }
   }
   return false;
 }
  /**
   * 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);
  }
 private void fillInFunTypeBuilder(
     Node jsdocNode,
     RawNominalType ownerType,
     DeclaredTypeRegistry registry,
     ImmutableList<String> typeParameters,
     FunctionTypeBuilder builder)
     throws UnknownTypeException {
   Node child = jsdocNode.getFirstChild();
   if (child.getType() == Token.THIS) {
     if (ownerType == null) {
       builder.addReceiverType(getThisOrNewType(child.getFirstChild(), registry, typeParameters));
     }
     child = child.getNext();
   } else if (child.getType() == Token.NEW) {
     Node newTypeNode = child.getFirstChild();
     JSType t = getThisOrNewType(newTypeNode, registry, typeParameters);
     if (!t.isSubtypeOf(JSType.TOP_OBJECT) && (!t.hasTypeVariable() || t.hasScalar())) {
       warnings.add(JSError.make(newTypeNode, NEW_EXPECTS_OBJECT_OR_TYPEVAR, t.toString()));
     }
     builder.addNominalType(t);
     child = child.getNext();
   }
   if (child.getType() == Token.PARAM_LIST) {
     for (Node arg = child.getFirstChild(); arg != null; arg = arg.getNext()) {
       try {
         switch (arg.getType()) {
           case Token.EQUALS:
             builder.addOptFormal(
                 getTypeFromCommentHelper(arg.getFirstChild(), registry, typeParameters));
             break;
           case Token.ELLIPSIS:
             Node restNode = arg.getFirstChild();
             builder.addRestFormals(
                 restNode == null
                     ? JSType.UNKNOWN
                     : getTypeFromCommentHelper(restNode, registry, typeParameters));
             break;
           default:
             builder.addReqFormal(getTypeFromCommentHelper(arg, registry, typeParameters));
             break;
         }
       } catch (FunctionTypeBuilder.WrongParameterOrderException e) {
         warnings.add(JSError.make(jsdocNode, WRONG_PARAMETER_ORDER));
         builder.addPlaceholderFormal();
       }
     }
     child = child.getNext();
   }
   builder.addRetType(getTypeFromCommentHelper(child, registry, typeParameters));
 }
  public void testJSDocAttachment1() {
    Node varNode = parse("/** @type number */var a;").getFirstChild();

    // VAR
    assertEquals(Token.VAR, varNode.getType());
    JSDocInfo info = varNode.getJSDocInfo();
    assertNotNull(info);
    assertTypeEquals(NUMBER_TYPE, info.getType());

    // NAME
    Node nameNode = varNode.getFirstChild();
    assertEquals(Token.NAME, nameNode.getType());
    assertNull(nameNode.getJSDocInfo());
  }
 /**
  * Gets all the control flow edges from some node with the first token to
  * some node with the second token.
  */
 private static List<DiGraphEdge<Node, Branch>> getAllEdges(
     ControlFlowGraph<Node> cfg, int startToken, int endToken) {
   List<DiGraphEdge<Node, Branch>> edges = getAllEdges(cfg);
   Iterator<DiGraphEdge<Node, Branch>> it = edges.iterator();
   while (it.hasNext()) {
     DiGraphEdge<Node, Branch> edge = it.next();
     Node startNode = edge.getSource().getValue();
     Node endNode = edge.getDestination().getValue();
     if (startNode == null || endNode == null ||
         startNode.getType() != startToken || endNode.getType() != endToken) {
       it.remove();
     }
   }
   return edges;
 }
  /**
   * Connects cfgNode to the proper CATCH block if target subtree might throw an exception. If there
   * are FINALLY blocks reached before a CATCH, it will make the corresponding entry in finallyMap.
   */
  private void connectToPossibleExceptionHandler(Node cfgNode, Node target) {
    if (mayThrowException(target) && !exceptionHandler.isEmpty()) {
      Node lastJump = cfgNode;
      for (Node handler : exceptionHandler) {
        if (NodeUtil.isFunction(handler)) {
          return;
        }
        Preconditions.checkState(handler.getType() == Token.TRY);
        Node catchBlock = NodeUtil.getCatchBlock(handler);

        if (!NodeUtil.hasCatchHandler(catchBlock)) { // No catch but a FINALLY.
          if (lastJump == cfgNode) {
            createEdge(cfgNode, Branch.ON_EX, handler.getLastChild());
          } else {
            finallyMap.put(lastJump, handler.getLastChild());
          }
        } else { // Has a catch.
          if (lastJump == cfgNode) {
            createEdge(cfgNode, Branch.ON_EX, catchBlock);
            return;
          } else {
            finallyMap.put(lastJump, catchBlock);
          }
        }
        lastJump = handler;
      }
    }
  }
  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);
    }
  }
  /** @return true if this node marks the start of a new basic block */
  private static boolean isBlockBoundary(Node n, Node parent) {
    if (parent != null) {
      switch (parent.getType()) {
        case Token.DO:
        case Token.FOR:
        case Token.FOR_OF:
        case Token.TRY:
        case Token.WHILE:
        case Token.WITH:
          // NOTE: TRY has up to 3 child blocks:
          // TRY
          //   BLOCK
          //   BLOCK
          //     CATCH
          //   BLOCK
          // Note that there is an explicit CATCH token but no explicit
          // FINALLY token. For simplicity, we consider each BLOCK
          // a separate basic BLOCK.
          return true;
        case Token.AND:
        case Token.HOOK:
        case Token.IF:
        case Token.OR:
          // The first child of a conditional is not a boundary,
          // but all the rest of the children are.
          return n != parent.getFirstChild();
      }
    }

    return n.isCase();
  }
  /** Creates an instance for a function that might be a constructor. */
  FunctionType(
      JSTypeRegistry registry,
      String name,
      Node source,
      ArrowType arrowType,
      JSType typeOfThis,
      TemplateTypeMap templateTypeMap,
      boolean isConstructor,
      boolean nativeType) {
    super(
        registry,
        name,
        registry.getNativeObjectType(JSTypeNative.FUNCTION_INSTANCE_TYPE),
        nativeType,
        templateTypeMap);
    setPrettyPrint(true);

    Preconditions.checkArgument(source == null || Token.FUNCTION == source.getType());
    Preconditions.checkNotNull(arrowType);
    this.source = source;
    if (isConstructor) {
      this.kind = Kind.CONSTRUCTOR;
      this.propAccess = PropAccess.ANY;
      this.typeOfThis =
          typeOfThis != null ? typeOfThis : new InstanceObjectType(registry, this, nativeType);
    } else {
      this.kind = Kind.ORDINARY;
      this.typeOfThis =
          typeOfThis != null ? typeOfThis : registry.getNativeObjectType(JSTypeNative.UNKNOWN_TYPE);
    }
    this.call = arrowType;
  }
    @Override
    public void visit(NodeTraversal t, Node n, Node parent) {
      switch (n.getType()) {
          // Function calls
        case Token.CALL:
          Node child = n.getFirstChild();
          String name = null;
          // NOTE: The normalization pass insures that local names do not
          // collide with global names.
          if (child.isName()) {
            name = child.getString();
          } else if (child.isFunction()) {
            name = anonFunctionMap.get(child);
          } else if (NodeUtil.isFunctionObjectCall(n)) {
            Preconditions.checkState(NodeUtil.isGet(child));
            Node fnIdentifingNode = child.getFirstChild();
            if (fnIdentifingNode.isName()) {
              name = fnIdentifingNode.getString();
            } else if (fnIdentifingNode.isFunction()) {
              name = anonFunctionMap.get(fnIdentifingNode);
            }
          }

          if (name != null) {
            FunctionState fs = functionMap.get(name);

            // Only visit call-sites for functions that can be inlined.
            if (fs != null) {
              callback.visitCallSite(t, n, fs);
            }
          }
          break;
      }
    }
    public void findNamedFunctions(NodeTraversal t, Node n, Node parent) {
      if (!NodeUtil.isStatement(n)) {
        // There aren't any interesting functions here.
        return;
      }

      switch (n.getType()) {
          // Functions expressions in the form of:
          //   var fooFn = function(x) { return ... }
        case Token.VAR:
          Preconditions.checkState(n.hasOneChild());
          Node nameNode = n.getFirstChild();
          if (nameNode.isName()
              && nameNode.hasChildren()
              && nameNode.getFirstChild().isFunction()) {
            maybeAddFunction(new FunctionVar(n), t.getModule());
          }
          break;

          // Named functions
          // function Foo(x) { return ... }
        case Token.FUNCTION:
          Preconditions.checkState(NodeUtil.isStatementBlock(parent) || parent.isLabel());
          if (!NodeUtil.isFunctionExpression(n)) {
            Function fn = new NamedFunction(n);
            maybeAddFunction(fn, t.getModule());
          }
          break;
      }
    }
  @Override
  Node optimizeSubtree(Node subtree) {
    switch (subtree.getType()) {
      case CALL:
        return tryFoldCall(subtree);
      case NEW:
        return tryFoldCtorCall(subtree);

      case TYPEOF:
        return tryFoldTypeof(subtree);

      case NOT:
      case POS:
      case NEG:
      case BITNOT:
        tryReduceOperandsForOp(subtree);
        return tryFoldUnaryOperator(subtree);

      case VOID:
        return tryReduceVoid(subtree);

      default:
        tryReduceOperandsForOp(subtree);
        return tryFoldBinaryOperator(subtree);
    }
  }
  /** 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;
  }