private boolean isFusableControlStatement(Node n) {
   switch (n.getToken()) {
     case IF:
     case THROW:
     case SWITCH:
     case EXPR_RESULT:
       return true;
     case RETURN:
       // We don't want to add a new return value.
       return n.hasChildren();
     case FOR:
       if (NodeUtil.isForIn(n)) {
         // Avoid cases where we have for(var x = foo() in a) { ....
         return !mayHaveSideEffects(n.getFirstChild());
       } else {
         // Avoid cases where we have for(var x;_;_) { ....
         return !n.getFirstChild().isVar();
       }
     case LABEL:
       return isFusableControlStatement(n.getLastChild());
     case BLOCK:
       return !n.isSyntheticBlock() && isFusableControlStatement(n.getFirstChild());
     default:
       break;
   }
   return false;
 }
  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());
  }
  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);
    }
  }
  /**
   * 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;
  }
  private void scanRoot(Node n) {
    if (n.isFunction()) {
      if (inputId == null) {
        inputId = NodeUtil.getInputId(n);
        // TODO(johnlenz): inputId maybe null if the FUNCTION node is detached
        // from the AST.
        // Is it meaningful to build a scope for detached FUNCTION node?
      }

      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(fnNameNode);
      }

      // Args: Declare function variables
      Preconditions.checkState(args.isParamList());
      for (Node a = args.getFirstChild(); a != null; a = a.getNext()) {
        Preconditions.checkState(a.isName());
        declareVar(a);
      }

      // Body
      scanVars(body);
    } else {
      // It's the global block
      Preconditions.checkState(scope.getParent() == null);
      scanVars(n);
    }
  }
Example #6
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);
    }
  }
 public void testFileOverviewJSDoc1() {
   Node n = parse("/** @fileoverview Hi mom! */ function Foo() {}");
   assertEquals(Token.FUNCTION, n.getFirstChild().getType());
   assertTrue(n.getJSDocInfo() != null);
   assertNull(n.getFirstChild().getJSDocInfo());
   assertEquals("Hi mom!", n.getJSDocInfo().getFileOverview());
 }
    /** 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;
    }
  /** @return Whether the name is used in a way that might be a candidate for inlining. */
  static boolean isCandidateUsage(Node name) {
    Node parent = name.getParent();
    Preconditions.checkState(name.isName());
    if (parent.isVar() || parent.isFunction()) {
      // This is a declaration.  Duplicate declarations are handle during
      // function candidate gathering.
      return true;
    }

    if (parent.isCall() && parent.getFirstChild() == name) {
      // This is a normal reference to the function.
      return true;
    }

    // Check for a ".call" to the named function:
    //   CALL
    //     GETPROP/GETELEM
    //       NAME
    //       STRING == "call"
    //     This-Value
    //     Function-parameter-1
    //     ...
    if (NodeUtil.isGet(parent)
        && name == parent.getFirstChild()
        && name.getNext().isString()
        && name.getNext().getString().equals("call")) {
      Node gramps = name.getAncestor(2);
      if (gramps.isCall() && gramps.getFirstChild() == parent) {
        // Yep, a ".call".
        return true;
      }
    }
    return false;
  }
  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);
    }
  }
 /**
  * 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 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;
      }
    }
  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;
  }
 /**
  * @param name The Name whose properties references should be updated.
  * @param value The value to use when rewriting.
  * @param depth The chain depth.
  * @param newNodes Expression nodes that have been updated.
  */
 private static void rewriteAliasProps(Name name, Node value, int depth, Set<AstChange> newNodes) {
   if (name.props == null) {
     return;
   }
   Preconditions.checkState(!value.matchesQualifiedName(name.getFullName()));
   for (Name prop : name.props) {
     rewriteAliasProps(prop, value, depth + 1, newNodes);
     List<Ref> refs = new ArrayList<>(prop.getRefs());
     for (Ref ref : refs) {
       Node target = ref.node;
       for (int i = 0; i <= depth; i++) {
         if (target.isGetProp()) {
           target = target.getFirstChild();
         } else if (NodeUtil.isObjectLitKey(target)) {
           // Object literal key definitions are a little trickier, as we
           // need to find the assignment target
           Node gparent = target.getParent().getParent();
           if (gparent.isAssign()) {
             target = gparent.getFirstChild();
           } else {
             Preconditions.checkState(NodeUtil.isObjectLitKey(gparent));
             target = gparent;
           }
         } else {
           throw new IllegalStateException("unexpected: " + target);
         }
       }
       Preconditions.checkState(target.isGetProp() || target.isName());
       target.getParent().replaceChild(target, value.cloneTree());
       prop.removeRef(ref);
       // Rescan the expression root.
       newNodes.add(new AstChange(ref.module, ref.scope, ref.node));
     }
   }
 }
Example #15
0
 private void normalizeObjectLiteralAnnotations(Node objlit) {
   Preconditions.checkState(objlit.isObjectLit());
   for (Node key = objlit.getFirstChild(); key != null; key = key.getNext()) {
     Node value = key.getFirstChild();
     normalizeObjectLiteralKeyAnnotations(objlit, key, value);
   }
 }
  /**
   * Prepare an template AST to use when performing matches.
   *
   * @param templateFunctionNode The template declaration function to extract the template AST from.
   * @return The first node of the template AST sequence to use when matching.
   */
  private Node initTemplate(Node templateFunctionNode) {
    Node prepped = templateFunctionNode.cloneTree();
    prepTemplatePlaceholders(prepped);

    Node body = prepped.getLastChild();
    Node startNode;
    if (body.hasOneChild() && body.getFirstChild().isExprResult()) {
      // When matching an expression, don't require it to be a complete
      // statement.
      startNode = body.getFirstFirstChild();
    } else {
      startNode = body.getFirstChild();
    }

    for (int i = 0; i < templateLocals.size(); i++) {
      // reserve space in the locals array.
      this.localVarMatches.add(null);
    }
    for (int i = 0; i < templateParams.size(); i++) {
      // reserve space in the params array.
      this.paramNodeMatches.add(null);
    }

    return startNode;
  }
  private void addExportMethod(
      Map<String, GenerateNodeContext> exports, String export, GenerateNodeContext context) {
    CodingConvention convention = compiler.getCodingConvention();

    // Emit the proper CALL expression.
    // This is an optimization to avoid exporting everything as a symbol
    // because exporting a property is significantly simpler/faster.
    // Only export the property if the parent is being exported or
    // if the parent is "prototype" and the grandparent is being exported.
    String parent = null;
    String grandparent = null;

    Node node = context.getNode().getFirstChild();
    if (node.isGetProp()) {
      Node parentNode = node.getFirstChild();
      parent = parentNode.getQualifiedName();
      if (parentNode.isGetProp()
          && parentNode.getLastChild().getString().equals(PROTOTYPE_PROPERTY)) {
        grandparent = parentNode.getFirstChild().getQualifiedName();
      }
    }

    boolean useExportSymbol = true;
    if (grandparent != null && exports.containsKey(grandparent)) {
      // grandparent is only set for properties exported off a prototype obj.
      useExportSymbol = false;
    } else if (parent != null && exports.containsKey(parent)) {
      useExportSymbol = false;
    }

    Node call;
    if (useExportSymbol) {
      // exportSymbol(publicPath, object);
      call =
          IR.call(
              NodeUtil.newQualifiedNameNode(
                  convention, exportSymbolFunction, context.getNode(), export),
              IR.string(export),
              NodeUtil.newQualifiedNameNode(convention, export, context.getNode(), export));
    } else {
      // exportProperty(object, publicName, symbol);
      String property = getPropertyName(node);
      call =
          IR.call(
              NodeUtil.newQualifiedNameNode(
                  convention, exportPropertyFunction, context.getNode(), exportPropertyFunction),
              NodeUtil.newQualifiedNameNode(
                  convention, parent, context.getNode(), exportPropertyFunction),
              IR.string(property),
              NodeUtil.newQualifiedNameNode(
                  convention, export, context.getNode(), exportPropertyFunction));
    }

    Node expression = IR.exprResult(call);
    annotate(expression);

    addStatement(context, expression);

    compiler.reportCodeChange();
  }
    static ClassDeclarationMetadata create(Node classNode, Node parent) {
      Node classNameNode = classNode.getFirstChild();
      Node superClassNameNode = classNameNode.getNext();

      // If this is a class statement, or a class expression in a simple
      // assignment or var statement, convert it. In any other case, the
      // code is too dynamic, so return null.
      if (NodeUtil.isStatement(classNode)) {
        return new ClassDeclarationMetadata(
            classNode, classNameNode.getString(), false, classNameNode, superClassNameNode);
      } else if (parent.isAssign() && parent.getParent().isExprResult()) {
        // Add members after the EXPR_RESULT node:
        // example.C = class {}; example.C.prototype.foo = function() {};
        String fullClassName = parent.getFirstChild().getQualifiedName();
        if (fullClassName == null) {
          return null;
        }
        return new ClassDeclarationMetadata(
            parent.getParent(), fullClassName, true, classNameNode, superClassNameNode);
      } else if (parent.isName()) {
        // Add members after the 'var' statement.
        // var C = class {}; C.prototype.foo = function() {};
        return new ClassDeclarationMetadata(
            parent.getParent(), parent.getString(), true, classNameNode, superClassNameNode);
      } else {
        // Cannot handle this class declaration.
        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());
        }
      }
    }
Example #20
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));
  }
  /**
   * Duplicates the PolymerElement externs with a different element base class if needed. For
   * example, if the base class is HTMLInputElement, then a class PolymerInputElement will be added.
   * If the element does not extend a native HTML element, this method is a no-op.
   */
  private void appendPolymerElementExterns(final ClassDefinition cls) {
    if (!nativeExternsAdded.add(cls.nativeBaseElement)) {
      return;
    }

    Node block = IR.block();

    Node baseExterns = polymerElementExterns.cloneTree();
    String polymerElementType = getPolymerElementType(cls);
    baseExterns.getFirstChild().setString(polymerElementType);

    String elementType = tagNameMap.get(cls.nativeBaseElement);
    JSTypeExpression elementBaseType =
        new JSTypeExpression(new Node(Token.BANG, IR.string(elementType)), VIRTUAL_FILE);
    JSDocInfoBuilder baseDocs = JSDocInfoBuilder.copyFrom(baseExterns.getJSDocInfo());
    baseDocs.changeBaseType(elementBaseType);
    baseExterns.setJSDocInfo(baseDocs.build());
    block.addChildToBack(baseExterns);

    for (Node baseProp : polymerElementProps) {
      Node newProp = baseProp.cloneTree();
      Node newPropRootName =
          NodeUtil.getRootOfQualifiedName(newProp.getFirstChild().getFirstChild());
      newPropRootName.setString(polymerElementType);
      block.addChildToBack(newProp);
    }

    Node parent = polymerElementExterns.getParent();
    Node stmts = block.removeChildren();
    parent.addChildrenAfter(stmts, polymerElementExterns);

    compiler.reportCodeChange();
  }
 /**
  * @return Whether the node is an expression result of an assignment to a property of
  *     PolymerElement.
  */
 private boolean isPolymerElementPropExpr(Node value) {
   return value != null
       && value.isExprResult()
       && value.getFirstChild().getFirstChild().isGetProp()
       && NodeUtil.getRootOfQualifiedName(value.getFirstChild().getFirstChild())
           .matchesQualifiedName(POLYMER_ELEMENT_NAME);
 }
    @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;
      }
    }
 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);
   }
 }
 /** Checks that variables, functions, and arguments are not deleted. */
 private static void checkDelete(NodeTraversal t, Node n) {
   if (n.getFirstChild().isName()) {
     Var v = t.getScope().getVar(n.getFirstChild().getString());
     if (v != null) {
       t.report(n, DELETE_VARIABLE);
     }
   }
 }
 public void testFileOverviewJSDoc2() {
   Node n = parse("/** @fileoverview Hi mom! */ " + "/** @constructor */ function Foo() {}");
   assertTrue(n.getJSDocInfo() != null);
   assertEquals("Hi mom!", n.getJSDocInfo().getFileOverview());
   assertTrue(n.getFirstChild().getJSDocInfo() != null);
   assertFalse(n.getFirstChild().getJSDocInfo().hasFileOverview());
   assertTrue(n.getFirstChild().getJSDocInfo().isConstructor());
 }
 @Override
 public void visit(NodeTraversal t, Node n, Node parent) {
   if (!n.isAssign() || n.getFirstChild() == className) {
     return;
   }
   if (className.matchesQualifiedName(n.getFirstChild())) {
     compiler.report(JSError.make(n, CLASS_REASSIGNMENT));
   }
 }
  /**
   * Determines whether a function can be inlined at a particular call site. There are several
   * criteria that the function and reference must hold in order for the functions to be inlined: 1)
   * If a call's arguments have side effects, the corresponding argument in the function must only
   * be referenced once. For instance, this will not be inlined:
   *
   * <pre>
   *     function foo(a) { return a + a }
   *     x = foo(i++);
   * </pre>
   */
  private CanInlineResult canInlineReferenceDirectly(Node callNode, Node fnNode) {
    if (!isDirectCallNodeReplacementPossible(fnNode)) {
      return CanInlineResult.NO;
    }

    Node block = fnNode.getLastChild();

    // CALL NODE: [ NAME, ARG1, ARG2, ... ]
    Node cArg = callNode.getFirstChild().getNext();

    // Functions called via 'call' and 'apply' have a this-object as
    // the first parameter, but this is not part of the called function's
    // parameter list.
    if (callNode.getFirstChild().getType() != Token.NAME) {
      if (NodeUtil.isFunctionObjectCall(callNode)) {
        // TODO(johnlenz): Support replace this with a value.
        Preconditions.checkNotNull(cArg);
        Preconditions.checkState(cArg.getType() == Token.THIS);
        cArg = cArg.getNext();
      } else {
        // ".apply" call should be filtered before this.
        Preconditions.checkState(!NodeUtil.isFunctionObjectApply(callNode));
      }
    }

    // FUNCTION NODE -> LP NODE: [ ARG1, ARG2, ... ]
    Node fnParam = NodeUtil.getFnParameters(fnNode).getFirstChild();
    while (cArg != null || fnParam != null) {
      // For each named parameter check if a mutable argument use more than one.
      if (fnParam != null) {
        if (cArg != null) {
          // Check for arguments that are evaluated more than once.
          // Note: Unlike block inlining, there it is not possible that a
          // parameter reference will be in a loop.
          if (NodeUtil.mayEffectMutableState(cArg)
              && NodeUtil.getNameReferenceCount(block, fnParam.getString()) > 1) {
            return CanInlineResult.NO;
          }
        }

        // Move to the next name.
        fnParam = fnParam.getNext();
      }

      // For every call argument check for side-effects, even if there
      // isn't a named parameter to match.
      if (cArg != null) {
        if (NodeUtil.mayHaveSideEffects(cArg)) {
          return CanInlineResult.NO;
        }
        cArg = cArg.getNext();
      }
    }

    return CanInlineResult.YES;
  }
 boolean isLvalue() {
   Node parent = getParent();
   int parentType = parent.getType();
   return (parentType == Token.VAR && nameNode.getFirstChild() != null)
       || parentType == Token.INC
       || parentType == Token.DEC
       || (NodeUtil.isAssignmentOp(parent)
           && parent.getFirstChild() == nameNode)
       || isLhsOfForInExpression(nameNode);
 }
  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());
  }