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