@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;
      }
    }
  /** 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.
  }
  /**
   * Attempt to inline an global alias of a global name. This requires that the name is well
   * defined: assigned unconditionally, assigned exactly once. It is assumed that, the name for
   * which it is an alias must already meet these same requirements.
   *
   * @param alias The alias to inline
   * @return Whether the alias was inlined.
   */
  private boolean inlineGlobalAliasIfPossible(Name name, Ref alias, GlobalNamespace namespace) {
    // Ensure that the alias is assigned to global name at that the
    // declaration.
    Node aliasParent = alias.node.getParent();
    if (aliasParent.isAssign() && NodeUtil.isExecutedExactlyOnce(aliasParent)
        // We special-case for constructors here, to inline constructor aliases
        // more aggressively in global scope.
        // We do this because constructor properties are always collapsed,
        // so we want to inline the aliases also to avoid breakages.
        || aliasParent.isName() && name.isConstructor()) {
      Node lvalue = aliasParent.isName() ? aliasParent : aliasParent.getFirstChild();
      if (!lvalue.isQualifiedName()) {
        return false;
      }
      name = namespace.getSlot(lvalue.getQualifiedName());
      if (name != null && name.isInlinableGlobalAlias()) {
        Set<AstChange> newNodes = new LinkedHashSet<>();

        List<Ref> refs = new ArrayList<>(name.getRefs());
        for (Ref ref : refs) {
          switch (ref.type) {
            case SET_FROM_GLOBAL:
              continue;
            case DIRECT_GET:
            case ALIASING_GET:
              Node newNode = alias.node.cloneTree();
              Node node = ref.node;
              node.getParent().replaceChild(node, newNode);
              newNodes.add(new AstChange(ref.module, ref.scope, newNode));
              name.removeRef(ref);
              break;
            default:
              throw new IllegalStateException();
          }
        }

        rewriteAliasProps(name, alias.node, 0, newNodes);

        // just set the original alias to null.
        aliasParent.replaceChild(alias.node, IR.nullNode());
        compiler.reportCodeChange();

        // Inlining the variable may have introduced new references
        // to descendants of {@code name}. So those need to be collected now.
        namespace.scanNewNodes(newNodes);

        return true;
      }
    }
    return false;
  }
Esempio n. 4
0
  private void validateParametersEs6(Node n) {
    boolean defaultParams = false;
    for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
      if (c.isRest()) {
        if (c.getNext() != null) {
          violation("Rest parameters must come after all other parameters.", c);
        }
        validateRest(c);
      } else if (c.isDefaultValue()) {
        defaultParams = true;
        validateDefaultValue(Token.PARAM_LIST, c);
      } else {
        if (defaultParams) {
          violation(
              "Cannot have a parameter without a default value,"
                  + " after one with a default value.",
              c);
        }

        if (c.isName()) {
          validateName(c);
        } else if (c.isArrayPattern()) {
          validateArrayPattern(Token.PARAM_LIST, c);
        } else {
          validateObjectPattern(Token.PARAM_LIST, c);
        }
      }
    }
  }
  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;
  }
  /** For each node, update the block stack and reference collection as appropriate. */
  @Override
  public void visit(NodeTraversal t, Node n, Node parent) {
    if (n.isName()
        || n.isRest()
        || (n.isStringKey() && parent.isObjectPattern() && !n.hasChildren())) {
      Var v;
      if (n.getString().equals("arguments")) {
        v = t.getScope().getArgumentsVar();
      } else {
        v = t.getScope().getVar(n.getString());
      }

      if (v != null) {
        if (varFilter.apply(v)) {
          addReference(v, new Reference(n, t, peek(blockStack)));
        }

        if (v.getParentNode() != null
            && NodeUtil.isHoistedFunctionDeclaration(v.getParentNode())
            &&
            // If we're only traversing a narrow scope, do not try to climb outside.
            (narrowScope == null || narrowScope.getDepth() <= v.getScope().getDepth())) {
          outOfBandTraversal(v);
        }
      }
    }

    if (isBlockBoundary(n, parent)) {
      pop(blockStack);
    }
  }
    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;
      }
    }
 /**
  * @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));
     }
   }
 }
  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);
    }
  }
  private void visitForOf(Node node, Node parent) {
    Node variable = node.removeFirstChild();
    Node iterable = node.removeFirstChild();
    Node body = node.removeFirstChild();

    Node iterName = IR.name(ITER_BASE + compiler.getUniqueNameIdSupplier().get());
    Node getNext = IR.call(IR.getprop(iterName.cloneTree(), IR.string("next")));
    String variableName =
        variable.isName()
            ? variable.getQualifiedName()
            : variable.getFirstChild().getQualifiedName(); // var or let
    Node iterResult = IR.name(ITER_RESULT + variableName);

    Node makeIter =
        IR.call(NodeUtil.newQualifiedNameNode(compiler.getCodingConvention(), MAKE_ITER), iterable);

    Node init = IR.var(iterName.cloneTree(), makeIter);
    Node initIterResult = iterResult.cloneTree();
    initIterResult.addChildToFront(getNext.cloneTree());
    init.addChildToBack(initIterResult);

    Node cond = IR.not(IR.getprop(iterResult.cloneTree(), IR.string("done")));
    Node incr = IR.assign(iterResult.cloneTree(), getNext.cloneTree());
    body.addChildToFront(
        IR.var(IR.name(variableName), IR.getprop(iterResult.cloneTree(), IR.string("value"))));

    Node newFor = IR.forNode(init, cond, incr, body);
    newFor.useSourceInfoIfMissingFromForTree(node);
    parent.replaceChild(node, newFor);
    compiler.reportCodeChange();
  }
  /** @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;
  }
  /** @see #testVarMotionWithCode(String, Token ...) */
  private void testVarMotionWithCode(String code, List<Token> expectedTokens) {
    List<Node> ancestors = new ArrayList<>();

    // Add an empty node to the beginning of the code and start there.
    Node root = (new Compiler()).parseTestCode(";" + code);
    for (Node n = root; n != null; n = n.getFirstChild()) {
      ancestors.add(0, n);
    }

    FunctionlessLocalScope searchIt = new FunctionlessLocalScope(ancestors.toArray(new Node[0]));

    boolean found = false;
    while (searchIt.hasNext()) {
      Node n = searchIt.next();
      if (n.isName() && searchIt.currentParent().isVar() && n.getString().equals("X")) {
        found = true;
        break;
      }
    }

    assertTrue("Variable X not found! " + root.toStringTree(), found);

    List<Node> currentAncestors = searchIt.currentAncestors();
    assert (currentAncestors.size() >= 3);
    Iterator<Node> moveIt =
        LocalVarMotion.forVar(
            currentAncestors.get(0), currentAncestors.get(1), currentAncestors.get(2));
    List<Token> actualTokens = new ArrayList<>();
    while (moveIt.hasNext()) {
      actualTokens.add(moveIt.next().getType());
    }

    assertEquals(expectedTokens, actualTokens);
  }
    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;
      }
    }
 @Override
 public void visit(NodeTraversal t, Node n, Node parent) {
   super.visit(t, n, parent);
   if (n.isName()) {
     checkNameUsage(n, parent);
   }
 }
  /** Try to fold {@code left instanceof right} into {@code true} or {@code false}. */
  private Node tryFoldInstanceof(Node n, Node left, Node right) {
    Preconditions.checkArgument(n.isInstanceOf());

    // TODO(johnlenz) Use type information if available to fold
    // instanceof.
    if (NodeUtil.isLiteralValue(left, true) && !mayHaveSideEffects(right)) {

      Node replacementNode = null;

      if (NodeUtil.isImmutableValue(left)) {
        // Non-object types are never instances.
        replacementNode = IR.falseNode();
      } else if (right.isName() && "Object".equals(right.getString())) {
        replacementNode = IR.trueNode();
      }

      if (replacementNode != null) {
        n.getParent().replaceChild(n, replacementNode);
        reportCodeChange();
        return replacementNode;
      }
    }

    return n;
  }
 /** http://www.ecma-international.org/ecma-262/6.0/#sec-abstract-relational-comparison */
 private static TernaryValue tryAbstractRelationalComparison(
     Node left, Node right, boolean useTypes, boolean willNegate) {
   // First, try to evaluate based on the general type.
   ValueType leftValueType = NodeUtil.getKnownValueType(left);
   ValueType rightValueType = NodeUtil.getKnownValueType(right);
   if (leftValueType != ValueType.UNDETERMINED && rightValueType != ValueType.UNDETERMINED) {
     if (leftValueType == ValueType.STRING && rightValueType == ValueType.STRING) {
       String lv = NodeUtil.getStringValue(left);
       String rv = NodeUtil.getStringValue(right);
       if (lv != null && rv != null) {
         // In JS, browsers parse \v differently. So do not compare strings if one contains \v.
         if (lv.indexOf('\u000B') != -1 || rv.indexOf('\u000B') != -1) {
           return TernaryValue.UNKNOWN;
         } else {
           return TernaryValue.forBoolean(lv.compareTo(rv) < 0);
         }
       } else if (left.isTypeOf()
           && right.isTypeOf()
           && left.getFirstChild().isName()
           && right.getFirstChild().isName()
           && left.getFirstChild().getString().equals(right.getFirstChild().getString())) {
         // Special case: `typeof a < typeof a` is always false.
         return TernaryValue.FALSE;
       }
     }
   }
   // Then, try to evaluate based on the value of the node. Try comparing as numbers.
   Double lv = NodeUtil.getNumberValue(left, useTypes);
   Double rv = NodeUtil.getNumberValue(right, useTypes);
   if (lv == null || rv == null) {
     // Special case: `x < x` is always false.
     //
     // TODO(moz): If we knew the named value wouldn't be NaN, it would be nice to handle
     // LE and GE. We should use type information if available here.
     if (!willNegate && left.isName() && right.isName()) {
       if (left.getString().equals(right.getString())) {
         return TernaryValue.FALSE;
       }
     }
     return TernaryValue.UNKNOWN;
   }
   if (Double.isNaN(lv) || Double.isNaN(rv)) {
     return TernaryValue.forBoolean(willNegate);
   } else {
     return TernaryValue.forBoolean(lv.doubleValue() < rv.doubleValue());
   }
 }
 /** Checks that the arguments.callee is not used. */
 private void checkGetProp(NodeTraversal t, Node n) {
   Node target = n.getFirstChild();
   Node prop = n.getLastChild();
   if (prop.getString().equals("callee")) {
     if (target.isName() && target.getString().equals("arguments")) {
       t.report(n, ARGUMENTS_CALLEE_FORBIDDEN);
     }
   } else if (prop.getString().equals("caller")) {
     if (target.isName() && target.getString().equals("arguments")) {
       t.report(n, ARGUMENTS_CALLER_FORBIDDEN);
     } else if (isFunctionType(target)) {
       t.report(n, FUNCTION_CALLER_FORBIDDEN);
     }
   } else if (prop.getString().equals("arguments") && isFunctionType(target)) {
     t.report(n, FUNCTION_ARGUMENTS_PROP_FORBIDDEN);
   }
 }
 @Override
 public void visit(NodeTraversal t, Node n, Node parent) {
   if ((n.isName()) && isDeclaration(n)) {
     checkDeclaration(t, n);
   } else if (n.isGetProp()) {
     checkGetProp(t, n);
   }
 }
    /** Find functions that can be inlined. */
    private void checkNameUsage(Node n, Node parent) {
      Preconditions.checkState(n.isName());

      if (isCandidateUsage(n)) {
        return;
      }

      // Other refs to a function name remove its candidacy for inlining
      String name = n.getString();
      FunctionState fs = fns.get(name);
      if (fs == null) {
        return;
      }

      // Unlike normal call/new parameters, references passed to
      // JSCompiler_ObjectPropertyString are not aliases of a value, but
      // a reference to the name itself, as such the value of the name is
      // unknown and can not be inlined.
      if (parent.isNew()) {
        Node target = parent.getFirstChild();
        if (target.isName()
            && target
                .getString()
                .equals(ObjectPropertyStringPreprocess.EXTERN_OBJECT_PROPERTY_STRING)) {
          // This method is going to be replaced so don't inline it anywhere.
          fs.setInline(false);
        }
      }

      // If the name is being assigned to it can not be inlined.
      if (parent.isAssign() && parent.getFirstChild() == n) {
        // e.g. bar = something; <== we can't inline "bar"
        // so mark the function as uninlinable.
        // TODO(johnlenz): Should we just remove it from fns here?
        fs.setInline(false);
      } else {
        // e.g. var fn = bar; <== we can't inline "bar"
        // As this reference can't be inlined mark the function as
        // unremovable.
        fs.setRemove(false);
      }
    }
  /** @see #findCalledFunctions(Node) */
  private static void findCalledFunctions(Node node, Set<String> changed) {
    Preconditions.checkArgument(changed != null);
    // For each referenced function, add a new reference
    if (node.isName() && isCandidateUsage(node)) {
      changed.add(node.getString());
    }

    for (Node c = node.getFirstChild(); c != null; c = c.getNext()) {
      findCalledFunctions(c, changed);
    }
  }
 private void visitImportNode(Node importNode) {
   Node defaultImport = importNode.getFirstChild();
   if (defaultImport.isName()) {
     visitRequire(defaultImport.getString(), importNode);
   }
   Node namedImports = defaultImport.getNext();
   if (namedImports.getType() == Token.IMPORT_SPECS) {
     for (Node importSpec : namedImports.children()) {
       visitRequire(importSpec.getLastChild().getString(), importNode);
     }
   }
 }
 @Override
 public void visit(NodeTraversal t, Node n, Node parent) {
   if (n.isThis()) {
     Node name = IR.name(THIS_VAR).srcref(n);
     parent.replaceChild(n, name);
     changedThis = true;
   } else if (n.isName() && n.getString().equals("arguments")) {
     Node name = IR.name(ARGUMENTS_VAR).srcref(n);
     parent.replaceChild(n, name);
     changedArguments = true;
   }
 }
Esempio n. 23
0
 @Override
 public void visit(NodeTraversal t, Node n, Node parent) {
   if (n.isCall()) {
     Node target = n.getFirstChild();
     // TODO(johnlenz): add this to the coding convention
     // so we can remove goog.reflect.sinkValue as well.
     if (target.isName() && target.getString().equals(PROTECTOR_FN)) {
       Node expr = n.getLastChild();
       n.detachChildren();
       parent.replaceChild(n, expr);
     }
   }
 }
Esempio n. 24
0
  private void validateDefaultValue(int type, Node n) {
    validateAssignmentExpression(n);
    Node lhs = n.getFirstChild();

    // LHS can only be a name or destructuring pattern.
    if (lhs.isName()) {
      validateName(lhs);
    } else if (lhs.isArrayPattern()) {
      validateArrayPattern(type, lhs);
    } else {
      validateObjectPattern(type, lhs);
    }
  }
  private void visitNewNode(NodeTraversal t, Node newNode) {
    Node qNameNode = newNode.getFirstChild();

    // Single names are likely external, but if this is running in single-file mode, they
    // will not be in the externs, so add a weak usage.
    if (mode == Mode.SINGLE_FILE && qNameNode.isName()) {
      weakUsages.put(qNameNode.getString(), qNameNode);
      return;
    }

    // If the ctor is something other than a qualified name, ignore it.
    if (!qNameNode.isQualifiedName()) {
      return;
    }

    // Grab the root ctor namespace.
    Node root = NodeUtil.getRootOfQualifiedName(qNameNode);

    // We only consider programmer-defined constructors that are
    // global variables, or are defined on global variables.
    if (!root.isName()) {
      return;
    }

    String name = root.getString();
    Var var = t.getScope().getVar(name);
    if (var != null && (var.isExtern() || var.getSourceFile() == newNode.getStaticSourceFile())) {
      return;
    }
    usages.put(qNameNode.getQualifiedName(), newNode);

    // for "new foo.bar.Baz.Qux" add weak usages for "foo.bar.Baz", "foo.bar", and "foo"
    // because those might be goog.provide'd from a different file than foo.bar.Baz.Qux,
    // so it doesn't make sense to require the user to goog.require all of them.
    for (; qNameNode != null; qNameNode = qNameNode.getFirstChild()) {
      weakUsages.put(qNameNode.getQualifiedName(), qNameNode);
    }
  }
  private void visitForOf(Node node, Node parent) {
    Node variable = node.removeFirstChild();
    Node iterable = node.removeFirstChild();
    Node body = node.removeFirstChild();

    Node iterName = IR.name(ITER_BASE + compiler.getUniqueNameIdSupplier().get());
    Node getNext = IR.call(IR.getprop(iterName.cloneTree(), IR.string("next")));
    String variableName;
    int declType;
    if (variable.isName()) {
      declType = Token.NAME;
      variableName = variable.getQualifiedName();
    } else {
      Preconditions.checkState(
          NodeUtil.isNameDeclaration(variable), "Expected var, let, or const. Got %s", variable);
      declType = variable.getType();
      variableName = variable.getFirstChild().getQualifiedName();
    }
    Node iterResult = IR.name(ITER_RESULT + variableName);

    Node makeIter = IR.call(NodeUtil.newQName(compiler, MAKE_ITER), iterable);
    compiler.needsEs6Runtime = true;

    Node init = IR.var(iterName.cloneTree(), makeIter);
    Node initIterResult = iterResult.cloneTree();
    initIterResult.addChildToFront(getNext.cloneTree());
    init.addChildToBack(initIterResult);

    Node cond = IR.not(IR.getprop(iterResult.cloneTree(), IR.string("done")));
    Node incr = IR.assign(iterResult.cloneTree(), getNext.cloneTree());

    Node declarationOrAssign;
    if (declType == Token.NAME) {
      declarationOrAssign =
          IR.exprResult(
              IR.assign(
                  IR.name(variableName), IR.getprop(iterResult.cloneTree(), IR.string("value"))));
    } else {
      declarationOrAssign = new Node(declType, IR.name(variableName));
      declarationOrAssign
          .getFirstChild()
          .addChildToBack(IR.getprop(iterResult.cloneTree(), IR.string("value")));
    }
    body.addChildToFront(declarationOrAssign);

    Node newFor = IR.forNode(init, cond, incr, body);
    newFor.useSourceInfoIfMissingFromForTree(node);
    parent.replaceChild(node, newFor);
    compiler.reportCodeChange();
  }
  private void visitCallNode(NodeTraversal t, Node call, Node parent) {
    String required = codingConvention.extractClassNameIfRequire(call, parent);
    if (required != null) {
      visitRequire(required, call);
      return;
    }
    String provided = codingConvention.extractClassNameIfProvide(call, parent);
    if (provided != null) {
      providedNames.add(provided);
      return;
    }

    if (codingConvention.isClassFactoryCall(call)) {
      if (parent.isName()) {
        providedNames.add(parent.getString());
      } else if (parent.isAssign()) {
        providedNames.add(parent.getFirstChild().getQualifiedName());
      }
    }

    Node callee = call.getFirstChild();
    if (callee.isName()) {
      weakUsages.put(callee.getString(), callee);
    } else if (callee.isQualifiedName()) {
      Node root = NodeUtil.getRootOfQualifiedName(callee);
      if (root.isName()) {
        Var var = t.getScope().getVar(root.getString());
        if (var == null || (!var.isExtern() && !var.isLocal())) {
          String name = getOutermostClassName(callee.getQualifiedName());
          if (name == null) {
            name = callee.getQualifiedName();
          }
          usages.put(name, call);
        }
      }
    }
  }
  /**
   * Declares a variable.
   *
   * @param n The node corresponding to the variable name.
   */
  private void declareVar(Node n) {
    Preconditions.checkState(n.isName());

    CompilerInput input = compiler.getInput(inputId);
    String name = n.getString();
    if (scope.isDeclared(name, false) || (scope.isLocal() && name.equals(ARGUMENTS))) {
      redeclarationHandler.onRedeclaration(scope, name, n, input);
    } else {
      if (isTyped) {
        ((TypedScope) scope).declare(name, n, null, input);
      } else {
        scope.declare(name, n, input);
      }
    }
  }
    FindCallbackArgumentReferences(
        Node functionRoot,
        List<Node> keyReferences,
        List<Node> valueReferences,
        boolean useArrayMode) {
      Preconditions.checkState(functionRoot.isFunction());

      String keyString = null, valueString = null;
      Node callbackParams = NodeUtil.getFunctionParameters(functionRoot);
      Node param = callbackParams.getFirstChild();
      if (param != null) {
        Preconditions.checkState(param.isName());
        keyString = param.getString();

        param = param.getNext();
        if (param != null) {
          Preconditions.checkState(param.isName());
          valueString = param.getString();
        }
      }

      this.keyName = keyString;
      this.valueName = valueString;

      // For arrays, the keyString is the index number of the element.
      // We're interested in the value of the element instead
      if (useArrayMode) {
        this.keyReferences = valueReferences;
        this.valueReferences = keyReferences;
      } else {
        this.keyReferences = keyReferences;
        this.valueReferences = valueReferences;
      }

      this.startingScope = null;
    }
  private void visitClassNode(NodeTraversal t, Node classNode) {
    String name = NodeUtil.getName(classNode);
    if (name != null) {
      providedNames.add(name);
    }

    Node extendClass = classNode.getSecondChild();

    // If the superclass is something other than a qualified name, ignore it.
    if (!extendClass.isQualifiedName()) {
      return;
    }

    // Single names are likely external, but if this is running in single-file mode, they
    // will not be in the externs, so add a weak usage.
    if (mode == Mode.SINGLE_FILE && extendClass.isName()) {
      weakUsages.put(extendClass.getString(), extendClass);
      return;
    }

    Node root = NodeUtil.getRootOfQualifiedName(extendClass);

    // It should always be a name. Extending this.something or
    // super.something is unlikely.
    // We only consider programmer-defined superclasses that are
    // global variables, or are defined on global variables.
    if (root.isName()) {
      String rootName = root.getString();
      Var var = t.getScope().getVar(rootName);
      if (var != null && (var.isLocal() || var.isExtern())) {
        // "require" not needed for these
      } else {
        usages.put(extendClass.getQualifiedName(), extendClass);
      }
    }
  }