/** 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);
    }
  }
  private void outOfBandTraversal(Var v) {
    if (startedFunctionTraverse.contains(v)) {
      return;
    }
    startedFunctionTraverse.add(v);

    Node fnNode = v.getParentNode();

    // Replace the block stack with a new one. This algorithm only works
    // because we know hoisted functions cannot be inside loops. It will have to
    // change if we ever do general function continuations.
    Preconditions.checkState(NodeUtil.isHoistedFunctionDeclaration(fnNode));

    Scope containingScope = v.getScope();

    // This is tricky to compute because of the weird traverseAtScope call for
    // CollapseProperties.
    List<BasicBlock> newBlockStack = null;
    if (containingScope.isGlobal()) {
      newBlockStack = new ArrayList<>();
      newBlockStack.add(blockStack.get(0));
    } else {
      for (int i = 0; i < blockStack.size(); i++) {
        if (blockStack.get(i).root == containingScope.getRootNode()) {
          newBlockStack = new ArrayList<>(blockStack.subList(0, i + 1));
        }
      }
    }
    Preconditions.checkNotNull(newBlockStack);

    List<BasicBlock> oldBlockStack = blockStack;
    blockStack = newBlockStack;

    NodeTraversal outOfBandTraversal = new NodeTraversal(compiler, this);
    outOfBandTraversal.traverseFunctionOutOfBand(fnNode, containingScope);

    blockStack = oldBlockStack;
    finishedFunctionTraverse.add(v);
  }
 private boolean isShadowed(String name, Scope scope) {
   Var nameVar = scope.getVar(name);
   return nameVar != null && nameVar.getScope() != this.startingScope;
 }
    @Override
    public void visit(NodeTraversal t, Node n, Node parent) {
      if (!NodeUtil.isReferenceName(n)) {
        return;
      }

      String name = n.getString();
      Scope referencedIn = t.getScope();
      Var var = referencedIn.getVar(name);
      if (var == null) {
        return;
      }

      if (!var.isLet() && !var.isConst()) {
        return;
      }

      // Traverse nodes up from let/const declaration:
      // If we hit a function or the root before a loop - Not a loop closure.
      // if we hit a loop first - maybe loop closure.
      Scope declaredIn = var.getScope();
      Node loopNode = null;
      for (Scope s = declaredIn; ; s = s.getParent()) {
        Node scopeRoot = s.getRootNode();
        if (NodeUtil.isLoopStructure(s.getRootNode())) {
          loopNode = scopeRoot;
          break;
        } else if (scopeRoot.getParent() != null
            && NodeUtil.isLoopStructure(scopeRoot.getParent())) {
          loopNode = scopeRoot.getParent();
          break;
        } else if (s.isFunctionBlockScope() || s.isGlobal()) {
          return;
        }
      }

      referenceMap.put(var, n);

      // Traverse scopes from reference scope to declaration scope.
      // If we hit a function - loop closure detected.
      for (Scope s = referencedIn; s != declaredIn; s = s.getParent()) {
        if (s.isFunctionBlockScope()) {
          Node function = s.getRootNode().getParent();
          if (functionHandledMap.containsEntry(function, name)) {
            return;
          }
          functionHandledMap.put(function, name);

          if (!loopObjectMap.containsKey(loopNode)) {
            loopObjectMap.put(
                loopNode,
                new LoopObject(LOOP_OBJECT_NAME + "$" + compiler.getUniqueNameIdSupplier().get()));
          }
          LoopObject object = loopObjectMap.get(loopNode);
          object.vars.add(var);

          functionLoopObjectsMap.put(function, object);
          return;
        }
      }
    }