12da489cd246702bee5938545b18a6f710ed214bcJamie Gennis#!/usr/bin/env python
22da489cd246702bee5938545b18a6f710ed214bcJamie Gennis#
32da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# Copyright 2008 The Closure Linter Authors. All Rights Reserved.
42da489cd246702bee5938545b18a6f710ed214bcJamie Gennis#
52da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# Licensed under the Apache License, Version 2.0 (the "License");
62da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# you may not use this file except in compliance with the License.
72da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# You may obtain a copy of the License at
82da489cd246702bee5938545b18a6f710ed214bcJamie Gennis#
92da489cd246702bee5938545b18a6f710ed214bcJamie Gennis#      http://www.apache.org/licenses/LICENSE-2.0
102da489cd246702bee5938545b18a6f710ed214bcJamie Gennis#
112da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# Unless required by applicable law or agreed to in writing, software
122da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# distributed under the License is distributed on an "AS-IS" BASIS,
132da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
142da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# See the License for the specific language governing permissions and
152da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# limitations under the License.
162da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
172da489cd246702bee5938545b18a6f710ed214bcJamie Gennis"""Logic for computing dependency information for closurized JavaScript files.
182da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
192da489cd246702bee5938545b18a6f710ed214bcJamie GennisClosurized JavaScript files express dependencies using goog.require and
202da489cd246702bee5938545b18a6f710ed214bcJamie Gennisgoog.provide statements. In order for the linter to detect when a statement is
212da489cd246702bee5938545b18a6f710ed214bcJamie Gennismissing or unnecessary, all identifiers in the JavaScript file must first be
222da489cd246702bee5938545b18a6f710ed214bcJamie Gennisprocessed to determine if they constitute the creation or usage of a dependency.
232da489cd246702bee5938545b18a6f710ed214bcJamie Gennis"""
242da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
252da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
262da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
272da489cd246702bee5938545b18a6f710ed214bcJamie Gennisfrom closure_linter import javascripttokens
282da489cd246702bee5938545b18a6f710ed214bcJamie Gennisfrom closure_linter import tokenutil
292da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
302da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# pylint: disable-msg=C6409
312da489cd246702bee5938545b18a6f710ed214bcJamie GennisTokenType = javascripttokens.JavaScriptTokenType
322da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
332da489cd246702bee5938545b18a6f710ed214bcJamie GennisDEFAULT_EXTRA_NAMESPACES = [
342da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  'goog.testing.asserts',
352da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  'goog.testing.jsunit',
362da489cd246702bee5938545b18a6f710ed214bcJamie Gennis]
372da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
382da489cd246702bee5938545b18a6f710ed214bcJamie Gennisclass ClosurizedNamespacesInfo(object):
392da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  """Dependency information for closurized JavaScript files.
402da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
412da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  Processes token streams for dependency creation or usage and provides logic
422da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  for determining if a given require or provide statement is unnecessary or if
432da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  there are missing require or provide statements.
442da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  """
452da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
462da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  def __init__(self, closurized_namespaces, ignored_extra_namespaces):
472da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """Initializes an instance the ClosurizedNamespacesInfo class.
482da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
492da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    Args:
502da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      closurized_namespaces: A list of namespace prefixes that should be
512da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          processed for dependency information. Non-matching namespaces are
522da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          ignored.
532da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      ignored_extra_namespaces: A list of namespaces that should not be reported
542da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          as extra regardless of whether they are actually used.
552da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """
562da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    self._closurized_namespaces = closurized_namespaces
572da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    self._ignored_extra_namespaces = (ignored_extra_namespaces +
582da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                                      DEFAULT_EXTRA_NAMESPACES)
592da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    self.Reset()
602da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
612da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  def Reset(self):
622da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """Resets the internal state to prepare for processing a new file."""
632da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
642da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    # A list of goog.provide tokens in the order they appeared in the file.
652da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    self._provide_tokens = []
662da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
672da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    # A list of goog.require tokens in the order they appeared in the file.
682da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    self._require_tokens = []
692da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
702da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    # Namespaces that are already goog.provided.
712da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    self._provided_namespaces = []
722da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
732da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    # Namespaces that are already goog.required.
742da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    self._required_namespaces = []
752da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
762da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    # Note that created_namespaces and used_namespaces contain both namespaces
772da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    # and identifiers because there are many existing cases where a method or
782da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    # constant is provided directly instead of its namespace. Ideally, these
792da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    # two lists would only have to contain namespaces.
802da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
812da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    # A list of tuples where the first element is the namespace of an identifier
822da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    # created in the file and the second is the identifier itself.
832da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    self._created_namespaces = []
842da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
852da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    # A list of tuples where the first element is the namespace of an identifier
862da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    # used in the file and the second is the identifier itself.
872da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    self._used_namespaces = []
882da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
892da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    # A list of seemingly-unnecessary namespaces that are goog.required() and
902da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    # annotated with @suppress {extraRequire}.
912da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    self._suppressed_requires = []
922da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
932da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    # A list of goog.provide tokens which are duplicates.
942da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    self._duplicate_provide_tokens = []
952da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
962da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    # A list of goog.require tokens which are duplicates.
972da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    self._duplicate_require_tokens = []
982da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
992da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    # Whether this file is in a goog.scope. Someday, we may add support
1002da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    # for checking scopified namespaces, but for now let's just fail
1012da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    # in a more reasonable way.
1022da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    self._scopified_file = False
1032da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1042da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    # TODO(user): Handle the case where there are 2 different requires
1052da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    # that can satisfy the same dependency, but only one is necessary.
1062da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1072da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  def GetProvidedNamespaces(self):
1082da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """Returns the namespaces which are already provided by this file.
1092da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1102da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    Returns:
1112da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      A list of strings where each string is a 'namespace' corresponding to an
1122da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      existing goog.provide statement in the file being checked.
1132da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """
1142da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    return list(self._provided_namespaces)
1152da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1162da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  def GetRequiredNamespaces(self):
1172da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """Returns the namespaces which are already required by this file.
1182da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1192da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    Returns:
1202da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      A list of strings where each string is a 'namespace' corresponding to an
1212da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      existing goog.require statement in the file being checked.
1222da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """
1232da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    return list(self._required_namespaces)
1242da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1252da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  def IsExtraProvide(self, token):
1262da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """Returns whether the given goog.provide token is unnecessary.
1272da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1282da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    Args:
1292da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      token: A goog.provide token.
1302da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1312da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    Returns:
1322da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      True if the given token corresponds to an unnecessary goog.provide
1332da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      statement, otherwise False.
1342da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """
1352da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    if self._scopified_file:
1362da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      return False
1372da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1382da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    namespace = tokenutil.Search(token, TokenType.STRING_TEXT).string
1392da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1402da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    base_namespace = namespace.split('.', 1)[0]
1412da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    if base_namespace not in self._closurized_namespaces:
1422da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      return False
1432da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1442da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    if token in self._duplicate_provide_tokens:
1452da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      return True
1462da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1472da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    # TODO(user): There's probably a faster way to compute this.
1482da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    for created_namespace, created_identifier in self._created_namespaces:
1492da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      if namespace == created_namespace or namespace == created_identifier:
1502da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        return False
1512da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1522da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    return True
1532da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1542da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  def IsExtraRequire(self, token):
1552da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """Returns whether the given goog.require token is unnecessary.
1562da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1572da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    Args:
1582da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      token: A goog.require token.
1592da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1602da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    Returns:
1612da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      True if the given token corresponds to an unnecessary goog.require
1622da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      statement, otherwise False.
1632da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """
1642da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    if self._scopified_file:
1652da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      return False
1662da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1672da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    namespace = tokenutil.Search(token, TokenType.STRING_TEXT).string
1682da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1692da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    base_namespace = namespace.split('.', 1)[0]
1702da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    if base_namespace not in self._closurized_namespaces:
1712da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      return False
1722da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1732da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    if namespace in self._ignored_extra_namespaces:
1742da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      return False
1752da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1762da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    if token in self._duplicate_require_tokens:
1772da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      return True
1782da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1792da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    if namespace in self._suppressed_requires:
1802da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      return False
1812da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1822da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    # If the namespace contains a component that is initial caps, then that
1832da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    # must be the last component of the namespace.
1842da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    parts = namespace.split('.')
1852da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    if len(parts) > 1 and parts[-2][0].isupper():
1862da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      return True
1872da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1882da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    # TODO(user): There's probably a faster way to compute this.
1892da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    for used_namespace, used_identifier in self._used_namespaces:
1902da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      if namespace == used_namespace or namespace == used_identifier:
1912da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        return False
1922da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1932da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    return True
1942da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1952da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  def GetMissingProvides(self):
1962da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """Returns the set of missing provided namespaces for the current file.
1972da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1982da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    Returns:
1992da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      Returns a set of strings where each string is a namespace that should be
2002da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      provided by this file, but is not.
2012da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """
2022da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    if self._scopified_file:
2032da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      return set()
2042da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
2052da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    missing_provides = set()
2062da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    for namespace, identifier in self._created_namespaces:
2072da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      if (not self._IsPrivateIdentifier(identifier) and
2082da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          namespace not in self._provided_namespaces and
2092da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          identifier not in self._provided_namespaces and
2102da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          namespace not in self._required_namespaces):
2112da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        missing_provides.add(namespace)
2122da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
2132da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    return missing_provides
2142da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
2152da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  def GetMissingRequires(self):
2162da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """Returns the set of missing required namespaces for the current file.
2172da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
2182da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    For each non-private identifier used in the file, find either a
2192da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    goog.require, goog.provide or a created identifier that satisfies it.
2202da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    goog.require statements can satisfy the identifier by requiring either the
2212da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    namespace of the identifier or the identifier itself. goog.provide
2222da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    statements can satisfy the identifier by providing the namespace of the
2232da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    identifier. A created identifier can only satisfy the used identifier if
2242da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    it matches it exactly (necessary since things can be defined on a
2252da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    namespace in more than one file). Note that provided namespaces should be
2262da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    a subset of created namespaces, but we check both because in some cases we
2272da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    can't always detect the creation of the namespace.
2282da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
2292da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    Returns:
2302da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      Returns a set of strings where each string is a namespace that should be
2312da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      required by this file, but is not.
2322da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """
2332da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    if self._scopified_file:
2342da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      return set()
2352da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
2362da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    external_dependencies = set(self._required_namespaces)
2372da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
2382da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    # Assume goog namespace is always available.
2392da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    external_dependencies.add('goog')
2402da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
2412da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    created_identifiers = set()
2422da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    for namespace, identifier in self._created_namespaces:
2432da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      created_identifiers.add(identifier)
2442da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
2452da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    missing_requires = set()
2462da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    for namespace, identifier in self._used_namespaces:
2472da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      if (not self._IsPrivateIdentifier(identifier) and
2482da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          namespace not in external_dependencies and
2492da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          namespace not in self._provided_namespaces and
2502da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          identifier not in external_dependencies and
2512da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          identifier not in created_identifiers):
2522da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        missing_requires.add(namespace)
2532da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
2542da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    return missing_requires
2552da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
2562da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  def _IsPrivateIdentifier(self, identifier):
2572da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """Returns whether the given identifer is private."""
2582da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    pieces = identifier.split('.')
2592da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    for piece in pieces:
2602da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      if piece.endswith('_'):
2612da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        return True
2622da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    return False
2632da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
2642da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  def IsFirstProvide(self, token):
2652da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """Returns whether token is the first provide token."""
2662da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    return self._provide_tokens and token == self._provide_tokens[0]
2672da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
2682da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  def IsFirstRequire(self, token):
2692da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """Returns whether token is the first require token."""
2702da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    return self._require_tokens and token == self._require_tokens[0]
2712da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
2722da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  def IsLastProvide(self, token):
2732da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """Returns whether token is the last provide token."""
2742da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    return self._provide_tokens and token == self._provide_tokens[-1]
2752da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
2762da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  def IsLastRequire(self, token):
2772da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """Returns whether token is the last require token."""
2782da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    return self._require_tokens and token == self._require_tokens[-1]
2792da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
2802da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  def ProcessToken(self, token, state_tracker):
2812da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """Processes the given token for dependency information.
2822da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
2832da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    Args:
2842da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      token: The token to process.
2852da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      state_tracker: The JavaScript state tracker.
2862da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """
2872da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
2882da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    # Note that this method is in the critical path for the linter and has been
2892da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    # optimized for performance in the following ways:
2902da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    # - Tokens are checked by type first to minimize the number of function
2912da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    #   calls necessary to determine if action needs to be taken for the token.
2922da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    # - The most common tokens types are checked for first.
2932da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    # - The number of function calls has been minimized (thus the length of this
2942da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    #   function.
2952da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
2962da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    if token.type == TokenType.IDENTIFIER:
2972da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      # TODO(user): Consider saving the whole identifier in metadata.
2982da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      whole_identifier_string = self._GetWholeIdentifierString(token)
2992da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      if whole_identifier_string is None:
3002da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        # We only want to process the identifier one time. If the whole string
3012da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        # identifier is None, that means this token was part of a multi-token
3022da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        # identifier, but it was not the first token of the identifier.
3032da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        return
3042da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
3052da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      # In the odd case that a goog.require is encountered inside a function,
3062da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      # just ignore it (e.g. dynamic loading in test runners).
3072da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      if token.string == 'goog.require' and not state_tracker.InFunction():
3082da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        self._require_tokens.append(token)
3092da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        namespace = tokenutil.Search(token, TokenType.STRING_TEXT).string
3102da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        if namespace in self._required_namespaces:
3112da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          self._duplicate_require_tokens.append(token)
3122da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        else:
3132da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          self._required_namespaces.append(namespace)
3142da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
3152da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        # If there is a suppression for the require, add a usage for it so it
3162da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        # gets treated as a regular goog.require (i.e. still gets sorted).
3172da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        jsdoc = state_tracker.GetDocComment()
3182da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        if jsdoc and ('extraRequire' in jsdoc.suppressions):
3192da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          self._suppressed_requires.append(namespace)
3202da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          self._AddUsedNamespace(state_tracker, namespace)
3212da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
3222da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      elif token.string == 'goog.provide':
3232da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        self._provide_tokens.append(token)
3242da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        namespace = tokenutil.Search(token, TokenType.STRING_TEXT).string
3252da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        if namespace in self._provided_namespaces:
3262da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          self._duplicate_provide_tokens.append(token)
3272da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        else:
3282da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          self._provided_namespaces.append(namespace)
3292da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
3302da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        # If there is a suppression for the provide, add a creation for it so it
3312da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        # gets treated as a regular goog.provide (i.e. still gets sorted).
3322da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        jsdoc = state_tracker.GetDocComment()
3332da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        if jsdoc and ('extraProvide' in jsdoc.suppressions):
3342da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          self._AddCreatedNamespace(state_tracker, namespace)
3352da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
3362da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      elif token.string == 'goog.scope':
3372da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        self._scopified_file = True
3382da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
3392da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      else:
3402da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        jsdoc = state_tracker.GetDocComment()
3412da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        if jsdoc and jsdoc.HasFlag('typedef'):
3422da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          self._AddCreatedNamespace(state_tracker, whole_identifier_string,
3432da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                                    self.GetClosurizedNamespace(
3442da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                                        whole_identifier_string))
3452da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        else:
3462da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          self._AddUsedNamespace(state_tracker, whole_identifier_string)
3472da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
3482da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    elif token.type == TokenType.SIMPLE_LVALUE:
3492da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      identifier = token.values['identifier']
3502da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      namespace = self.GetClosurizedNamespace(identifier)
3512da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      if state_tracker.InFunction():
3522da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        self._AddUsedNamespace(state_tracker, identifier)
3532da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      elif namespace and namespace != 'goog':
3542da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        self._AddCreatedNamespace(state_tracker, identifier, namespace)
3552da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
3562da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    elif token.type == TokenType.DOC_FLAG:
3572da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      flag_type = token.attached_object.flag_type
3582da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      is_interface = state_tracker.GetDocComment().HasFlag('interface')
3592da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      if flag_type == 'implements' or (flag_type == 'extends' and is_interface):
3602da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        # Interfaces should be goog.require'd.
3612da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        doc_start = tokenutil.Search(token, TokenType.DOC_START_BRACE)
3622da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        interface = tokenutil.Search(doc_start, TokenType.COMMENT)
3632da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        self._AddUsedNamespace(state_tracker, interface.string)
3642da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
3652da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
3662da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  def _GetWholeIdentifierString(self, token):
3672da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """Returns the whole identifier string for the given token.
3682da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
3692da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    Checks the tokens after the current one to see if the token is one in a
3702da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    sequence of tokens which are actually just one identifier (i.e. a line was
3712da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    wrapped in the middle of an identifier).
3722da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
3732da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    Args:
3742da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      token: The token to check.
3752da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
3762da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    Returns:
3772da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      The whole identifier string or None if this token is not the first token
3782da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      in a multi-token identifier.
3792da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """
3802da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    result = ''
3812da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
3822da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    # Search backward to determine if this token is the first token of the
3832da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    # identifier. If it is not the first token, return None to signal that this
3842da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    # token should be ignored.
3852da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    prev_token = token.previous
3862da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    while prev_token:
3872da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      if (prev_token.IsType(TokenType.IDENTIFIER) or
3882da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          prev_token.IsType(TokenType.NORMAL) and prev_token.string == '.'):
3892da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        return None
3902da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      elif (not prev_token.IsType(TokenType.WHITESPACE) and
3912da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            not prev_token.IsAnyType(TokenType.COMMENT_TYPES)):
3922da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        break
3932da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      prev_token = prev_token.previous
3942da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
3952da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    # Search forward to find other parts of this identifier separated by white
3962da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    # space.
3972da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    next_token = token
3982da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    while next_token:
3992da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      if (next_token.IsType(TokenType.IDENTIFIER) or
4002da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          next_token.IsType(TokenType.NORMAL) and next_token.string == '.'):
4012da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        result += next_token.string
4022da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      elif (not next_token.IsType(TokenType.WHITESPACE) and
4032da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            not next_token.IsAnyType(TokenType.COMMENT_TYPES)):
4042da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        break
4052da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      next_token = next_token.next
4062da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
4072da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    return result
4082da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
4092da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  def _AddCreatedNamespace(self, state_tracker, identifier, namespace=None):
4102da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """Adds the namespace of an identifier to the list of created namespaces.
4112da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
4122da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    If the identifier is annotated with a 'missingProvide' suppression, it is
4132da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    not added.
4142da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
4152da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    Args:
4162da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      state_tracker: The JavaScriptStateTracker instance.
4172da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      identifier: The identifier to add.
4182da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      namespace: The namespace of the identifier or None if the identifier is
4192da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          also the namespace.
4202da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """
4212da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    if not namespace:
4222da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      namespace = identifier
4232da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
4242da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    jsdoc = state_tracker.GetDocComment()
4252da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    if jsdoc and 'missingProvide' in jsdoc.suppressions:
4262da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      return
4272da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
4282da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    self._created_namespaces.append([namespace, identifier])
4292da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
4302da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  def _AddUsedNamespace(self, state_tracker, identifier):
4312da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """Adds the namespace of an identifier to the list of used namespaces.
4322da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
4332da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    If the identifier is annotated with a 'missingRequire' suppression, it is
4342da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    not added.
4352da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
4362da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    Args:
4372da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      state_tracker: The JavaScriptStateTracker instance.
4382da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      identifier: An identifier which has been used.
4392da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """
4402da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    jsdoc = state_tracker.GetDocComment()
4412da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    if jsdoc and 'missingRequire' in jsdoc.suppressions:
4422da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      return
4432da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
4442da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    namespace = self.GetClosurizedNamespace(identifier)
4452da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    if namespace:
4462da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      self._used_namespaces.append([namespace, identifier])
4472da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
4482da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  def GetClosurizedNamespace(self, identifier):
4492da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """Given an identifier, returns the namespace that identifier is from.
4502da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
4512da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    Args:
4522da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      identifier: The identifier to extract a namespace from.
4532da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
4542da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    Returns:
4552da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      The namespace the given identifier resides in, or None if one could not
4562da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      be found.
4572da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """
4582da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    if identifier.startswith('goog.global'):
4592da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      # Ignore goog.global, since it is, by definition, global.
4602da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      return None
4612da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
4622da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    parts = identifier.split('.')
4632da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    for namespace in self._closurized_namespaces:
4642da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      if not identifier.startswith(namespace + '.'):
4652da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        continue
4662da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
4672da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      last_part = parts[-1]
4682da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      if not last_part:
4692da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        # TODO(robbyw): Handle this: it's a multi-line identifier.
4702da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        return None
4712da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
4722da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      # The namespace for a class is the shortest prefix ending in a class
4732da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      # name, which starts with a capital letter but is not a capitalized word.
4742da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      #
4752da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      # We ultimately do not want to allow requiring or providing of inner
4762da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      # classes/enums.  Instead, a file should provide only the top-level class
4772da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      # and users should require only that.
4782da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      namespace = []
4792da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      for part in parts:
4802da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        if part == 'prototype' or part.isupper():
4812da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          return '.'.join(namespace)
4822da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        namespace.append(part)
4832da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        if part[0].isupper():
4842da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          return '.'.join(namespace)
4852da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
4862da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      # At this point, we know there's no class or enum, so the namespace is
4872da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      # just the identifier with the last part removed. With the exception of
4882da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      # apply, inherits, and call, which should also be stripped.
4892da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      if parts[-1] in ('apply', 'inherits', 'call'):
4902da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        parts.pop()
4912da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      parts.pop()
4922da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
4932da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      # If the last part ends with an underscore, it is a private variable,
4942da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      # method, or enum. The namespace is whatever is before it.
4952da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      if parts and parts[-1].endswith('_'):
4962da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        parts.pop()
4972da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
4982da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      return '.'.join(parts)
4992da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
5002da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    return None
501