/**
  * @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));
     }
   }
 }
  /**
   * Flattens all references to a collapsible property of a global name except its initial
   * definition.
   *
   * @param n A global property name (e.g. "a.b" or "a.b.c.d")
   * @param alias The flattened name (e.g. "a$b" or "a$b$c$d")
   */
  private void flattenReferencesTo(Name n, String alias) {
    String originalName = n.getFullName();
    for (Ref r : n.getRefs()) {
      if (r == n.getDeclaration()) {
        // Declarations are handled separately.
        continue;
      }
      Node rParent = r.node.getParent();
      // There are two cases when we shouldn't flatten a reference:
      // 1) Object literal keys, because duplicate keys show up as refs.
      // 2) References inside a complex assign. (a = x.y = 0). These are
      //    called TWIN references, because they show up twice in the
      //    reference list. Only collapse the set, not the alias.
      if (!NodeUtil.isObjectLitKey(r.node) && (r.getTwin() == null || r.isSet())) {
        flattenNameRef(alias, r.node, rParent, originalName);
      }
    }

    // Flatten all occurrences of a name as a prefix of its subnames. For
    // example, if {@code n} corresponds to the name "a.b", then "a.b" will be
    // replaced with "a$b" in all occurrences of "a.b.c", "a.b.c.d", etc.
    if (n.props != null) {
      for (Name p : n.props) {
        flattenPrefixes(alias, p, 1);
      }
    }
  }
 /**
  * Flattens a particular prefix of a single name reference.
  *
  * @param alias A flattened prefix name (e.g. "a$b")
  * @param n The node corresponding to a subproperty name (e.g. "a.b.c.d")
  * @param depth The difference in depth between the property name and the prefix name (e.g. 2)
  * @param originalName String version of the property name.
  */
 private void flattenNameRefAtDepth(String alias, Node n, int depth, String originalName) {
   // This method has to work for both GETPROP chains and, in rare cases,
   // OBJLIT keys, possibly nested. That's why we check for children before
   // proceeding. In the OBJLIT case, we don't need to do anything.
   int nType = n.getType();
   boolean isQName = nType == Token.NAME || nType == Token.GETPROP;
   boolean isObjKey = NodeUtil.isObjectLitKey(n);
   Preconditions.checkState(isObjKey || isQName);
   if (isQName) {
     for (int i = 1; i < depth && n.hasChildren(); i++) {
       n = n.getFirstChild();
     }
     if (n.isGetProp() && n.getFirstChild().isGetProp()) {
       flattenNameRef(alias, n.getFirstChild(), n, originalName);
     }
   }
 }
  @Override
  public void visit(NodeTraversal t, Node n, Node parent) {
    JSDocInfo docInfo = n.getJSDocInfo();
    if (docInfo != null && docInfo.isExport()) {

      if (parent.isAssign() && (n.isFunction() || n.isClass())) {
        JSDocInfo parentInfo = parent.getJSDocInfo();
        if (parentInfo != null && parentInfo.isExport()) {
          // ScopedAliases produces export annotations on both the function/class
          // node and assign node, we only want to visit the assign node.
          return;
        }
      }

      String export = null;
      GenerateNodeContext context = null;

      switch (n.getType()) {
        case Token.FUNCTION:
        case Token.CLASS:
          if (parent.isScript()) {
            export = NodeUtil.getName(n);
            context = new GenerateNodeContext(n, Mode.EXPORT);
          }
          break;

        case Token.MEMBER_FUNCTION_DEF:
          export = n.getString();
          context = new GenerateNodeContext(n, Mode.EXPORT);
          break;

        case Token.ASSIGN:
          Node grandparent = parent.getParent();
          if (parent.isExprResult() && !n.getLastChild().isAssign()) {
            if (grandparent != null
                && grandparent.isScript()
                && n.getFirstChild().isQualifiedName()) {
              export = n.getFirstChild().getQualifiedName();
              context = new GenerateNodeContext(n, Mode.EXPORT);
            } else if (allowLocalExports && n.getFirstChild().isGetProp()) {
              Node target = n.getFirstChild();
              export = target.getLastChild().getString();
              context = new GenerateNodeContext(n, Mode.EXTERN);
            }
          }
          break;

        case Token.VAR:
        case Token.LET:
        case Token.CONST:
          if (parent.isScript()) {
            if (n.getFirstChild().hasChildren() && !n.getFirstChild().getFirstChild().isAssign()) {
              export = n.getFirstChild().getString();
              context = new GenerateNodeContext(n, Mode.EXPORT);
            }
          }
          break;

        case Token.GETPROP:
          if (allowLocalExports && parent.isExprResult()) {
            export = n.getLastChild().getString();
            context = new GenerateNodeContext(n, Mode.EXTERN);
          }
          break;

        case Token.STRING_KEY:
        case Token.GETTER_DEF:
        case Token.SETTER_DEF:
          if (allowLocalExports) {
            export = n.getString();
            context = new GenerateNodeContext(n, Mode.EXTERN);
          }
          break;
      }

      if (export != null) {
        exports.put(export, context);
      } else {
        // Don't produce extra warnings for functions values of object literals
        if (!n.isFunction() || !NodeUtil.isObjectLitKey(parent)) {
          if (allowLocalExports) {
            compiler.report(t.makeError(n, EXPORT_ANNOTATION_NOT_ALLOWED));
          } else {
            compiler.report(t.makeError(n, NON_GLOBAL_ERROR));
          }
        }
      }
    }
  }