<commit_before>/*
 * Copyright 2006 The Closure Compiler Authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.google.javascript.jscomp;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static com.google.javascript.rhino.jstype.JSTypeNative.GLOBAL_THIS;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Joiner;
import com.google.common.base.MoreObjects;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.javascript.jscomp.CodingConvention.SubclassRelationship;
import com.google.javascript.rhino.JSDocInfo;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.StaticSourceFile;
import com.google.javascript.rhino.StaticSymbolTable;
import com.google.javascript.rhino.TokenStream;
import com.google.javascript.rhino.jstype.JSType;
import com.google.javascript.rhino.jstype.StaticTypedRef;
import com.google.javascript.rhino.jstype.StaticTypedScope;
import com.google.javascript.rhino.jstype.StaticTypedSlot;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import javax.annotation.Nullable;

/**
 * Builds a global namespace of all the objects and their properties in the global scope. Also
 * builds an index of all the references to those names.
 *
 * @author <EMAIL> (<NAME>)
 */
class GlobalNamespace
    implements StaticTypedScope, StaticSymbolTable<GlobalNamespace.Name, GlobalNamespace.Ref> {

  private final AbstractCompiler compiler;
  private final Node root;
  private final Node externsRoot;
  private SourceKind sourceKind;
  private Scope externsScope;
  private boolean generated = false;

  enum SourceKind {
    EXTERN,
    TYPE_SUMMARY,
    CODE;

    static SourceKind fromScriptNode(Node n) {
      if (!n.isFromExterns()) {
        return CODE;
      } else if (NodeUtil.isFromTypeSummary(n)) {
        return TYPE_SUMMARY;
      } else {
        return EXTERN;
      }
    }
  }

  /**
   * Each reference has an index in post-order. Notice that some nodes are represented by 2 Ref
   * objects, so this index is not necessarily unique.
   */
  private int currentPreOrderIndex = 0;

  /** Global namespace tree */
  private final List<Name> globalNames = new ArrayList<>();

  /** Maps names (e.g. "a.b.c") to nodes in the global namespace tree */
  private final Map<String, Name> nameMap = new HashMap<>();

  /**
   * Creates an instance that may emit warnings when building the namespace.
   *
   * @param compiler The AbstractCompiler, for reporting code changes
   * @param root The root of the rest of the code to build a namespace for.
   */
  GlobalNamespace(AbstractCompiler compiler, Node root) {
    this(compiler, null, root);
  }

  /**
   * Creates an instance that may emit warnings when building the namespace.
   *
   * @param compiler The AbstractCompiler, for reporting code changes
   * @param externsRoot The root of the externs to build a namespace for. If this is null, externs
   *     and properties defined on extern types will not be included in the global namespace. If
   *     non-null, it allows user-defined function on extern types to be included in the global
   *     namespace. E.g. String.foo.
   * @param root The root of the rest of the code to build a namespace for.
   */
  GlobalNamespace(AbstractCompiler compiler, Node externsRoot, Node root) {
    this.compiler = compiler;
    this.externsRoot = externsRoot;
    this.root = root;
  }

  boolean hasExternsRoot() {
    return externsRoot != null;
  }

  @Override
  public Node getRootNode() {
    return root.getParent();
  }

  @Override
  public StaticTypedScope getParentScope() {
    return null;
  }

  @Override
  public Name getSlot(String name) {
    return getOwnSlot(name);
  }

  @Override
  public Name getOwnSlot(String name) {
    ensureGenerated();
    return nameMap.get(name);
  }

  @Override
  public JSType getTypeOfThis() {
    return compiler.getTypeRegistry().getNativeObjectType(GLOBAL_THIS);
  }

  @Override
  public Iterable<Ref> getReferences(Name slot) {
    ensureGenerated();
    return Collections.unmodifiableCollection(slot.getRefs());
  }

  @Override
  public StaticTypedScope getScope(Name slot) {
    return this;
  }

  @Override
  public Iterable<Name> getAllSymbols() {
    ensureGenerated();
    return Collections.unmodifiableCollection(getNameIndex().values());
  }

  private void ensureGenerated() {
    if (!generated) {
      process();
    }
  }

  /**
   * Gets a list of the roots of the forest of the global names, where the roots are the top-level
   * names.
   */
  List<Name> getNameForest() {
    ensureGenerated();
    return globalNames;
  }

  /**
   * Gets an index of all the global names, indexed by full qualified name (as in "a", "a.b.c",
   * etc.).
   */
  Map<String, Name> getNameIndex() {
    ensureGenerated();
    return nameMap;
  }

  /**
   * A simple data class that contains the information necessary to inspect a node for changes to
   * the global namespace.
   */
  static class AstChange {
    final JSModule module;
    final Scope scope;
    final Node node;

    AstChange(JSModule module, Scope scope, Node node) {
      this.module = module;
      this.scope = scope;
      this.node = node;
    }

    @Override
    public boolean equals(Object obj) {
      if (obj instanceof AstChange) {
        AstChange other = (AstChange) obj;
        return Objects.equals(this.module, other.module)
            && Objects.equals(this.scope, other.scope)
            && Objects.equals(this.node, other.node);
      }
      return false;
    }

    @Override
    public int hashCode() {
      return Objects.hash(this.module, this.scope, this.node);
    }
  }

  /**
   * If the client adds new nodes to the AST, scan these new nodes to see if they've added any
   * references to the global namespace.
   *
   * @param newNodes New nodes to check.
   */
  void scanNewNodes(Set<AstChange> newNodes) {
    BuildGlobalNamespace builder = new BuildGlobalNamespace();

    for (AstChange info : newNodes) {
      if (!info.node.isQualifiedName() && !NodeUtil.mayBeObjectLitKey(info.node)) {
        continue;
      }
      scanFromNode(builder, info.module, info.scope, info.node);
    }
  }

  private void scanFromNode(BuildGlobalNamespace builder, JSModule module, Scope scope, Node n) {
    // Check affected parent nodes first.
    Node parent = n.getParent();
    if ((n.isName() || n.isGetProp()) && parent.isGetProp()) {
      // e.g. when replacing "my.alias.prop" with "foo.bar.prop"
      // we want also want to visit "foo.bar.prop", since that's a new global qname we are now
      // referencing.
      scanFromNode(builder, module, scope, n.getParent());
    } else if (n.getPrevious() != null && n.getPrevious().isObjectPattern()) {
      // e.g. if we change `const {x} = bar` to `const {x} = foo`, add a new reference to `foo.x`
      // attached to the STRING_KEY `x`
      Node pattern = n.getPrevious();
      for (Node key : pattern.children()) {
        if (key.isStringKey()) {
          scanFromNode(builder, module, scope, key);
        }
      }
    }
    builder.collect(module, scope, n);
  }

  /** Builds the namespace lazily. */
  private void process() {
    if (hasExternsRoot()) {
      sourceKind = SourceKind.EXTERN;
      NodeTraversal.traverse(compiler, externsRoot, new BuildGlobalNamespace());
    }
    sourceKind = SourceKind.CODE;

    NodeTraversal.traverse(compiler, root, new BuildGlobalNamespace());
    generated = true;
    externsScope = null;
  }

  /**
   * Determines whether a name reference in a particular scope is a global name reference.
   *
   * @param name A variable or property name (e.g. "a" or "a.b.c.d")
   * @param s The scope in which the name is referenced
   * @return Whether the name reference is a global name reference
   */
  private boolean isGlobalNameReference(String name, Scope s) {
    String topVarName = getTopVarName(name);
    return isGlobalVarReference(topVarName, s);
  }

  /**
   * Gets the top variable name from a possibly namespaced name.
   *
   * @param name A variable or qualified property name (e.g. "a" or "a.b.c.d")
   * @return The top variable name (e.g. "a")
   */
  private static String getTopVarName(String name) {
    int firstDotIndex = name.indexOf('.');
    return firstDotIndex == -1 ? name : name.substring(0, firstDotIndex);
  }

  /**
   * Determines whether a variable name reference in a particular scope is a global variable
   * reference.
   *
   * @param name A variable name (e.g. "a")
   * @param s The scope in which the name is referenced
   * @return Whether the name reference is a global variable reference
   */
  private boolean isGlobalVarReference(String name, Scope s) {
    Var v = s.getVar(name);
    if (v == null && externsScope != null) {
      v = externsScope.getVar(name);
    }
    return v != null && !v.isLocal();
  }

  // -------------------------------------------------------------------------

  /** Builds a tree representation of the global namespace. Omits prototypes. */
  private class BuildGlobalNamespace extends NodeTraversal.AbstractPreOrderCallback {
    /** Collect the references in pre-order. */
    @Override
    public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
      if (hasExternsRoot()) {
        if (n == externsRoot) {
          // If we are traversing the externs, then we save a pointer to the scope
          // generated by them, so that we can do lookups in it later.
          externsScope = t.getScope();
        } else if (n.isScript()) {
          // When checking type-summary files, we want to consider them like normal code
          // for some things (like alias inlining) but like externs for other things.
          sourceKind = SourceKind.fromScriptNode(n);
        }
      }

      collect(t.getModule(), t.getScope(), n);

      return true;
    }

    public void collect(JSModule module, Scope scope, Node n) {
      Node parent = n.getParent();

      String name;
      boolean isSet = false;
      Name.Type type = Name.Type.OTHER;

      switch (n.getToken()) {
        case GETTER_DEF:
        case SETTER_DEF:
        case STRING_KEY:
        case MEMBER_FUNCTION_DEF:
          // This may be a key in an object literal declaration.
          name = null;
          if (parent.isObjectLit()) {
            name = getNameForObjLitKey(n);
            isSet = true;
          } else if (parent.isClassMembers()) {
            name = getNameForClassMembers(n);
            isSet = true;
          } else if (parent.isObjectPattern()) {
            name = getNameForObjectPatternKey(n);
            // not a set
          }
          if (name == null) {
            return;
          }
          switch (n.getToken()) {
            case MEMBER_FUNCTION_DEF:
              type = getValueType(n.getFirstChild());
              if (n.getParent().isClassMembers() && !n.isStaticMember()) {
                // `class C { x() {} }` does /not/ create a global qualified name C.x, so return.
                return;
              }
              break;
            case STRING_KEY:
              type = getValueType(n.getFirstChild());
              break;
            case GETTER_DEF:
            case SETTER_DEF:
              type = Name.Type.GET_SET;
              break;
            default:
              throw new IllegalStateException("unexpected:" + n);
          }
          break;
        case NAME:
          // This may be a variable get or set.
          switch (parent.getToken()) {
            case VAR:
            case LET:
            case CONST:
              isSet = true;
              Node rvalue = n.getFirstChild();
              type = (rvalue == null) ? Name.Type.OTHER : getValueType(rvalue);
              break;
            case ASSIGN:
              if (parent.getFirstChild() == n) {
                isSet = true;
                type = getValueType(n.getNext());
              }
              break;
            case GETPROP:
              // This name is nested in a getprop. Return and only create a Ref for the outermost
              // getprop in the chain.
              return;
            case FUNCTION:
              Node grandparent = parent.getParent();
              if (grandparent == null || NodeUtil.isFunctionExpression(parent)) {
                return;
              }
              isSet = true;
              type = Name.Type.FUNCTION;
              break;
            case CATCH:
            case INC:
            case DEC:
              isSet = true;
              type = Name.Type.OTHER;
              break;
            case CLASS:
              // The first child is the class name, and the second child is the superclass name.
              if (parent.getFirstChild() == n) {
                isSet = true;
                type = Name.Type.CLASS;
              }
              break;
            case STRING_KEY:
            case ARRAY_PATTERN:
            case DEFAULT_VALUE:
            case COMPUTED_PROP:
            case REST:
              // This may be a set.
              if (NodeUtil.isLhsByDestructuring(n)) {
                isSet = true;
                type = Name.Type.OTHER;
              }
              break;
            default:
              if (NodeUtil.isAssignmentOp(parent) && parent.getFirstChild() == n) {
                isSet = true;
                type = Name.Type.OTHER;
              }
          }
          name = n.getString();
          break;
        case GETPROP:
          // This may be a namespaced name get or set.
          if (parent != null) {
            switch (parent.getToken()) {
              case ASSIGN:
                if (parent.getFirstChild() == n) {
                  isSet = true;
                  type = getValueType(n.getNext());
                }
                break;
              case INC:
              case DEC:
                isSet = true;
                type = Name.Type.OTHER;
                break;
              case GETPROP:
                // This is nested in another getprop. Return and only create a Ref for the outermost
                // getprop in the chain.
                return;
              default:
                if (NodeUtil.isAssignmentOp(parent) && parent.getFirstChild() == n) {
                  isSet = true;
                  type = Name.Type.OTHER;
                }
            }
          }
          if (!n.isQualifiedName()) {
            return;
          }
          name = n.getQualifiedName();
          break;
        case CALL:
          if (isObjectHasOwnPropertyCall(n)) {
            String qname = n.getFirstFirstChild().getQualifiedName();
            Name globalName = getOrCreateName(qname);
            globalName.usedHasOwnProperty = true;
          }
          return;
        default:
          return;
      }

      // We are only interested in global names.
      if (!isGlobalNameReference(name, scope)) {
        return;
      }

      if (isSet) {
        // Use the closest hoist scope to select handleSetFromGlobal or handleSetFromLocal
        // because they use the term 'global' in an ES5, pre-block-scoping sense.
        Scope hoistScope = scope.getClosestHoistScope();
        if (hoistScope.isGlobal()) {
          handleSetFromGlobal(module, scope, n, parent, name, type);
        } else {
          handleSetFromLocal(module, scope, n, parent, name);
        }
      } else {
        handleGet(module, scope, n, parent, name);
      }
    }

    /**
     * Gets the fully qualified name corresponding to an object literal key, as long as it and its
     * prefix property names are valid JavaScript identifiers. The object literal may be nested
     * inside of other object literals.
     *
     * <p>For example, if called with node {@code n} representing "z" in any of the following
     * expressions, the result would be "w.x.y.z": <code> var w = {x: {y: {z: 0}}}; </code> <code>
     *  w.x = {y: {z: 0}}; </code> <code> w.x.y = {'a': 0, 'z': 0}; </code>
     *
     * @param n A child of an OBJLIT node
     * @return The global name, or null if {@code n} doesn't correspond to the key of an object
     *     literal that can be named
     */
    String getNameForObjLitKey(Node n) {
      Node parent = n.getParent();
      checkState(parent.isObjectLit());

      Node grandparent = parent.getParent();
      if (grandparent == null) {
        return null;
      }

      Node greatGrandparent = grandparent.getParent();
      String name;
      switch (grandparent.getToken()) {
        case NAME:
          // VAR
          //   NAME (grandparent)
          //     OBJLIT (parent)
          //       STRING (n)
          if (greatGrandparent == null || !NodeUtil.isNameDeclaration(greatGrandparent)) {
            return null;
          }
          name = grandparent.getString();
          break;
        case ASSIGN:
          // ASSIGN (grandparent)
          //   NAME|GETPROP
          //   OBJLIT (parent)
          //     STRING (n)
          Node lvalue = grandparent.getFirstChild();
          name = lvalue.getQualifiedName();
          break;
        case STRING_KEY:
          // OBJLIT
          //   STRING (grandparent)
          //     OBJLIT (parent)
          //       STRING (n)
          if (greatGrandparent != null && greatGrandparent.isObjectLit()) {
            name = getNameForObjLitKey(grandparent);
          } else {
            return null;
          }
          break;
        default:
          return null;
      }
      if (name != null) {
        String key = n.getString();
        if (TokenStream.isJSIdentifier(key)) {
          return name + '.' + key;
        }
      }
      return null;
    }

    /**
     * Gets the fully qualified name corresponding to an class member function, as long as it and
     * its prefix property names are valid JavaScript identifiers.
     *
     * <p>For example, if called with node {@code n} representing "y" in any of the following
     * expressions, the result would be "x.y": <code> class x{y(){}}; </code> <code>
     *  var x = class{y(){}}; </code> <code> var x; x = class{y(){}}; </code>
     *
     * @param n A child of an CLASS_MEMBERS node
     * @return The global name, or null if {@code n} doesn't correspond to a class member function
     *     that can be named
     */
    String getNameForClassMembers(Node n) {
      Node parent = n.getParent();
      checkState(parent.isClassMembers());
      String className = NodeUtil.getName(parent.getParent());
      return className == null ? null : className + '.' + n.getString();
    }

    /**
     * Gets the fully qualified name corresponding to an object pattern key, as long as it is not in
     * a nested pattern and is destructuring an qualified name.
     *
     * @param stringKey A child of an OBJECT_PATTERN node
     * @return The global name, or null if {@code n} doesn't correspond to the key of an object
     *     literal that can be named
     */
    String getNameForObjectPatternKey(Node stringKey) {
      Node parent = stringKey.getParent();<commit_msg>Collapse some unnecessary switch-cases in `GlobalNamespace` and improves correctness with respect to static class getters and setters.<commit_after>/*
 * Copyright 2006 The Closure Compiler Authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.google.javascript.jscomp;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static com.google.javascript.rhino.jstype.JSTypeNative.GLOBAL_THIS;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Joiner;
import com.google.common.base.MoreObjects;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.javascript.jscomp.CodingConvention.SubclassRelationship;
import com.google.javascript.rhino.JSDocInfo;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.StaticSourceFile;
import com.google.javascript.rhino.StaticSymbolTable;
import com.google.javascript.rhino.jstype.JSType;
import com.google.javascript.rhino.jstype.StaticTypedRef;
import com.google.javascript.rhino.jstype.StaticTypedScope;
import com.google.javascript.rhino.jstype.StaticTypedSlot;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import javax.annotation.Nullable;

/**
 * Builds a global namespace of all the objects and their properties in the global scope. Also
 * builds an index of all the references to those names.
 *
 * @author <EMAIL> (<NAME>)
 */
class GlobalNamespace
    implements StaticTypedScope, StaticSymbolTable<GlobalNamespace.Name, GlobalNamespace.Ref> {

  private final AbstractCompiler compiler;
  private final Node root;
  private final Node externsRoot;
  private SourceKind sourceKind;
  private Scope externsScope;
  private boolean generated = false;

  enum SourceKind {
    EXTERN,
    TYPE_SUMMARY,
    CODE;

    static SourceKind fromScriptNode(Node n) {
      if (!n.isFromExterns()) {
        return CODE;
      } else if (NodeUtil.isFromTypeSummary(n)) {
        return TYPE_SUMMARY;
      } else {
        return EXTERN;
      }
    }
  }

  /**
   * Each reference has an index in post-order. Notice that some nodes are represented by 2 Ref
   * objects, so this index is not necessarily unique.
   */
  private int currentPreOrderIndex = 0;

  /** Global namespace tree */
  private final List<Name> globalNames = new ArrayList<>();

  /** Maps names (e.g. "a.b.c") to nodes in the global namespace tree */
  private final Map<String, Name> nameMap = new HashMap<>();

  /**
   * Creates an instance that may emit warnings when building the namespace.
   *
   * @param compiler The AbstractCompiler, for reporting code changes
   * @param root The root of the rest of the code to build a namespace for.
   */
  GlobalNamespace(AbstractCompiler compiler, Node root) {
    this(compiler, null, root);
  }

  /**
   * Creates an instance that may emit warnings when building the namespace.
   *
   * @param compiler The AbstractCompiler, for reporting code changes
   * @param externsRoot The root of the externs to build a namespace for. If this is null, externs
   *     and properties defined on extern types will not be included in the global namespace. If
   *     non-null, it allows user-defined function on extern types to be included in the global
   *     namespace. E.g. String.foo.
   * @param root The root of the rest of the code to build a namespace for.
   */
  GlobalNamespace(AbstractCompiler compiler, Node externsRoot, Node root) {
    this.compiler = compiler;
    this.externsRoot = externsRoot;
    this.root = root;
  }

  boolean hasExternsRoot() {
    return externsRoot != null;
  }

  @Override
  public Node getRootNode() {
    return root.getParent();
  }

  @Override
  public StaticTypedScope getParentScope() {
    return null;
  }

  @Override
  public Name getSlot(String name) {
    return getOwnSlot(name);
  }

  @Override
  public Name getOwnSlot(String name) {
    ensureGenerated();
    return nameMap.get(name);
  }

  @Override
  public JSType getTypeOfThis() {
    return compiler.getTypeRegistry().getNativeObjectType(GLOBAL_THIS);
  }

  @Override
  public Iterable<Ref> getReferences(Name slot) {
    ensureGenerated();
    return Collections.unmodifiableCollection(slot.getRefs());
  }

  @Override
  public StaticTypedScope getScope(Name slot) {
    return this;
  }

  @Override
  public Iterable<Name> getAllSymbols() {
    ensureGenerated();
    return Collections.unmodifiableCollection(getNameIndex().values());
  }

  private void ensureGenerated() {
    if (!generated) {
      process();
    }
  }

  /**
   * Gets a list of the roots of the forest of the global names, where the roots are the top-level
   * names.
   */
  List<Name> getNameForest() {
    ensureGenerated();
    return globalNames;
  }

  /**
   * Gets an index of all the global names, indexed by full qualified name (as in "a", "a.b.c",
   * etc.).
   */
  Map<String, Name> getNameIndex() {
    ensureGenerated();
    return nameMap;
  }

  /**
   * A simple data class that contains the information necessary to inspect a node for changes to
   * the global namespace.
   */
  static class AstChange {
    final JSModule module;
    final Scope scope;
    final Node node;

    AstChange(JSModule module, Scope scope, Node node) {
      this.module = module;
      this.scope = scope;
      this.node = node;
    }

    @Override
    public boolean equals(Object obj) {
      if (obj instanceof AstChange) {
        AstChange other = (AstChange) obj;
        return Objects.equals(this.module, other.module)
            && Objects.equals(this.scope, other.scope)
            && Objects.equals(this.node, other.node);
      }
      return false;
    }

    @Override
    public int hashCode() {
      return Objects.hash(this.module, this.scope, this.node);
    }
  }

  /**
   * If the client adds new nodes to the AST, scan these new nodes to see if they've added any
   * references to the global namespace.
   *
   * @param newNodes New nodes to check.
   */
  void scanNewNodes(Set<AstChange> newNodes) {
    BuildGlobalNamespace builder = new BuildGlobalNamespace();

    for (AstChange info : newNodes) {
      if (!info.node.isQualifiedName() && !NodeUtil.mayBeObjectLitKey(info.node)) {
        continue;
      }
      scanFromNode(builder, info.module, info.scope, info.node);
    }
  }

  private void scanFromNode(BuildGlobalNamespace builder, JSModule module, Scope scope, Node n) {
    // Check affected parent nodes first.
    Node parent = n.getParent();
    if ((n.isName() || n.isGetProp()) && parent.isGetProp()) {
      // e.g. when replacing "my.alias.prop" with "foo.bar.prop"
      // we want also want to visit "foo.bar.prop", since that's a new global qname we are now
      // referencing.
      scanFromNode(builder, module, scope, n.getParent());
    } else if (n.getPrevious() != null && n.getPrevious().isObjectPattern()) {
      // e.g. if we change `const {x} = bar` to `const {x} = foo`, add a new reference to `foo.x`
      // attached to the STRING_KEY `x`
      Node pattern = n.getPrevious();
      for (Node key : pattern.children()) {
        if (key.isStringKey()) {
          scanFromNode(builder, module, scope, key);
        }
      }
    }
    builder.collect(module, scope, n);
  }

  /** Builds the namespace lazily. */
  private void process() {
    if (hasExternsRoot()) {
      sourceKind = SourceKind.EXTERN;
      NodeTraversal.traverse(compiler, externsRoot, new BuildGlobalNamespace());
    }
    sourceKind = SourceKind.CODE;

    NodeTraversal.traverse(compiler, root, new BuildGlobalNamespace());
    generated = true;
    externsScope = null;
  }

  /**
   * Determines whether a name reference in a particular scope is a global name reference.
   *
   * @param name A variable or property name (e.g. "a" or "a.b.c.d")
   * @param s The scope in which the name is referenced
   * @return Whether the name reference is a global name reference
   */
  private boolean isGlobalNameReference(String name, Scope s) {
    String topVarName = getTopVarName(name);
    return isGlobalVarReference(topVarName, s);
  }

  /**
   * Gets the top variable name from a possibly namespaced name.
   *
   * @param name A variable or qualified property name (e.g. "a" or "a.b.c.d")
   * @return The top variable name (e.g. "a")
   */
  private static String getTopVarName(String name) {
    int firstDotIndex = name.indexOf('.');
    return firstDotIndex == -1 ? name : name.substring(0, firstDotIndex);
  }

  /**
   * Determines whether a variable name reference in a particular scope is a global variable
   * reference.
   *
   * @param name A variable name (e.g. "a")
   * @param s The scope in which the name is referenced
   * @return Whether the name reference is a global variable reference
   */
  private boolean isGlobalVarReference(String name, Scope s) {
    Var v = s.getVar(name);
    if (v == null && externsScope != null) {
      v = externsScope.getVar(name);
    }
    return v != null && !v.isLocal();
  }

  // -------------------------------------------------------------------------

  /** Builds a tree representation of the global namespace. Omits prototypes. */
  private class BuildGlobalNamespace extends NodeTraversal.AbstractPreOrderCallback {
    /** Collect the references in pre-order. */
    @Override
    public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
      if (hasExternsRoot()) {
        if (n == externsRoot) {
          // If we are traversing the externs, then we save a pointer to the scope
          // generated by them, so that we can do lookups in it later.
          externsScope = t.getScope();
        } else if (n.isScript()) {
          // When checking type-summary files, we want to consider them like normal code
          // for some things (like alias inlining) but like externs for other things.
          sourceKind = SourceKind.fromScriptNode(n);
        }
      }

      collect(t.getModule(), t.getScope(), n);

      return true;
    }

    private void collect(JSModule module, Scope scope, Node n) {
      Node parent = n.getParent();

      String name;
      boolean isSet = false;
      Name.Type type = Name.Type.OTHER;

      switch (n.getToken()) {
        case GETTER_DEF:
        case SETTER_DEF:
        case MEMBER_FUNCTION_DEF:
          if (parent.isClassMembers() && !n.isStaticMember()) {
            return; // Within a class, only static members define global names.
          }
          name = NodeUtil.getBestLValueName(n);
          isSet = true;
          type = n.isMemberFunctionDef() ? Name.Type.FUNCTION : Name.Type.GET_SET;
          break;
        case STRING_KEY:
          name = null;
          if (parent.isObjectLit()) {
            name = NodeUtil.getBestLValueName(n);
            isSet = true;
          } else if (parent.isObjectPattern()) {
            name = getNameForObjectPatternKey(n);
            // not a set
          }
          type = getValueType(n.getFirstChild());
          break;
        case NAME:
          // This may be a variable get or set.
          switch (parent.getToken()) {
            case VAR:
            case LET:
            case CONST:
              isSet = true;
              Node rvalue = n.getFirstChild();
              type = (rvalue == null) ? Name.Type.OTHER : getValueType(rvalue);
              break;
            case ASSIGN:
              if (parent.getFirstChild() == n) {
                isSet = true;
                type = getValueType(n.getNext());
              }
              break;
            case GETPROP:
              // This name is nested in a getprop. Return and only create a Ref for the outermost
              // getprop in the chain.
              return;
            case FUNCTION:
              Node grandparent = parent.getParent();
              if (grandparent == null || NodeUtil.isFunctionExpression(parent)) {
                return;
              }
              isSet = true;
              type = Name.Type.FUNCTION;
              break;
            case CATCH:
            case INC:
            case DEC:
              isSet = true;
              type = Name.Type.OTHER;
              break;
            case CLASS:
              // The first child is the class name, and the second child is the superclass name.
              if (parent.getFirstChild() == n) {
                isSet = true;
                type = Name.Type.CLASS;
              }
              break;
            case STRING_KEY:
            case ARRAY_PATTERN:
            case DEFAULT_VALUE:
            case COMPUTED_PROP:
            case REST:
              // This may be a set.
              if (NodeUtil.isLhsByDestructuring(n)) {
                isSet = true;
                type = Name.Type.OTHER;
              }
              break;
            default:
              if (NodeUtil.isAssignmentOp(parent) && parent.getFirstChild() == n) {
                isSet = true;
                type = Name.Type.OTHER;
              }
          }
          name = n.getString();
          break;
        case GETPROP:
          // TODO(b/117673791): Merge this case with NAME case to fix.
          // TODO(b/120303257): Merging this case with the NAME case makes this a breaking bug.
          // This may be a namespaced name get or set.
          if (parent != null) {
            switch (parent.getToken()) {
              case ASSIGN:
                if (parent.getFirstChild() == n) {
                  isSet = true;
                  type = getValueType(n.getNext());
                }
                break;
              case INC:
              case DEC:
                isSet = true;
                type = Name.Type.OTHER;
                break;
              case GETPROP:
                // This is nested in another getprop. Return and only create a Ref for the outermost
                // getprop in the chain.
                return;
              default:
                if (NodeUtil.isAssignmentOp(parent) && parent.getFirstChild() == n) {
                  isSet = true;
                  type = Name.Type.OTHER;
                }
            }
          }
          if (!n.isQualifiedName()) {
            return;
          }
          name = n.getQualifiedName();
          break;
        case CALL:
          if (isObjectHasOwnPropertyCall(n)) {
            String qname = n.getFirstFirstChild().getQualifiedName();
            Name globalName = getOrCreateName(qname);
            globalName.usedHasOwnProperty = true;
          }
          return;
        default:
          return;
      }

      if (name == null) {
        return;
      }

      // We are only interested in global names.
      if (!isGlobalNameReference(name, scope)) {
        return;
      }

      if (isSet) {
        // Use the closest hoist scope to select handleSetFromGlobal or handleSetFromLocal
        // because they use the term 'global' in an ES5, pre-block-scoping sense.
        Scope hoistScope = scope.getClosestHoistScope();
        if (hoistScope.isGlobal()) {
          handleSetFromGlobal(module, scope, n, parent, name, type);
        } else {
          handleSetFromLocal(module, scope, n, parent, name);
        }
      } else {
        handleGet(module, scope, n, parent, name);
      }
    }

    /**
     * Gets the fully qualified name corresponding to an object pattern key, as long as it is not in
     * a nested pattern and is destructuring an qualified name.
     *
     * @param stringKey A child of an OBJECT_PATTERN node
     * @return The global name, or null if {@code n} doesn't correspond to the key of an object
     *     literal that can be named
     */
    String getNameForObjectPatternKey(Node stringKey) {
      Node parent = stringKey.getParent();
{'fraction_non_alphanumeric': 0.10015344988217241, 'fraction_numerical': 0.0013152847043349592, 'mean_word_length': 2.1512822726880234, 'pattern_counts': {'":': 2, '<': 41, '<?xml version=': 0, '>': 41, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4735687', 'n_tokens_mistral': 10281, 'n_tokens_neox': 9989, 'n_words': 3286}