Index: third_party/closure_linter/closure_linter/closurizednamespacesinfo.py |
diff --git a/third_party/closure_linter/closure_linter/closurizednamespacesinfo.py b/third_party/closure_linter/closure_linter/closurizednamespacesinfo.py |
index 03bcf1caf34c67babd2dffb8bc52d816410bc8f1..18b5a2a0090b5f4131db417a8b68dca22ad585e9 100755 |
--- a/third_party/closure_linter/closure_linter/closurizednamespacesinfo.py |
+++ b/third_party/closure_linter/closure_linter/closurizednamespacesinfo.py |
@@ -38,6 +38,32 @@ DEFAULT_EXTRA_NAMESPACES = [ |
] |
+class UsedNamespace(object): |
+ """A type for information about a used namespace.""" |
+ |
+ def __init__(self, namespace, identifier, token, alias_definition): |
+ """Initializes the instance. |
+ |
+ Args: |
+ namespace: the namespace of an identifier used in the file |
+ identifier: the complete identifier |
+ token: the token that uses the namespace |
+ alias_definition: a boolean stating whether the namespace is only to used |
+ for an alias definition and should not be required. |
+ """ |
+ self.namespace = namespace |
+ self.identifier = identifier |
+ self.token = token |
+ self.alias_definition = alias_definition |
+ |
+ def GetLine(self): |
+ return self.token.line_number |
+ |
+ def __repr__(self): |
+ return 'UsedNamespace(%s)' % ', '.join( |
+ ['%s=%s' % (k, repr(v)) for k, v in self.__dict__.iteritems()]) |
+ |
+ |
class ClosurizedNamespacesInfo(object): |
"""Dependency information for closurized JavaScript files. |
@@ -86,9 +112,7 @@ class ClosurizedNamespacesInfo(object): |
# the line number where it's created. |
self._created_namespaces = [] |
- # A list of tuples where the first element is the namespace of an identifier |
- # used in the file, the second is the identifier itself and the third is the |
- # line number where it's used. |
+ # A list of UsedNamespace instances. |
self._used_namespaces = [] |
# A list of seemingly-unnecessary namespaces that are goog.required() and |
@@ -139,8 +163,7 @@ class ClosurizedNamespacesInfo(object): |
""" |
namespace = tokenutil.GetStringAfterToken(token) |
- base_namespace = namespace.split('.', 1)[0] |
- if base_namespace not in self._closurized_namespaces: |
+ if self.GetClosurizedNamespace(namespace) is None: |
return False |
if token in self._duplicate_provide_tokens: |
@@ -165,8 +188,7 @@ class ClosurizedNamespacesInfo(object): |
""" |
namespace = tokenutil.GetStringAfterToken(token) |
- base_namespace = namespace.split('.', 1)[0] |
- if base_namespace not in self._closurized_namespaces: |
+ if self.GetClosurizedNamespace(namespace) is None: |
return False |
if namespace in self._ignored_extra_namespaces: |
@@ -185,8 +207,9 @@ class ClosurizedNamespacesInfo(object): |
return True |
# TODO(user): There's probably a faster way to compute this. |
- for used_namespace, used_identifier, _ in self._used_namespaces: |
- if namespace == used_namespace or namespace == used_identifier: |
+ for ns in self._used_namespaces: |
+ if (not ns.alias_definition and ( |
+ namespace == ns.namespace or namespace == ns.identifier)): |
return False |
return True |
@@ -233,25 +256,74 @@ class ClosurizedNamespacesInfo(object): |
# Assume goog namespace is always available. |
external_dependencies.add('goog') |
+ # goog.module is treated as a builtin, too (for goog.module.get). |
+ external_dependencies.add('goog.module') |
created_identifiers = set() |
- for namespace, identifier, line_number in self._created_namespaces: |
+ for unused_namespace, identifier, unused_line_number in ( |
+ self._created_namespaces): |
created_identifiers.add(identifier) |
missing_requires = dict() |
- for namespace, identifier, line_number in self._used_namespaces: |
- if (not self._IsPrivateIdentifier(identifier) and |
+ illegal_alias_statements = dict() |
+ |
+ def ShouldRequireNamespace(namespace, identifier): |
+ """Checks if a namespace would normally be required.""" |
+ return ( |
+ not self._IsPrivateIdentifier(identifier) and |
namespace not in external_dependencies and |
namespace not in self._provided_namespaces and |
identifier not in external_dependencies and |
identifier not in created_identifiers and |
- namespace not in missing_requires): |
- missing_requires[namespace] = line_number |
+ namespace not in missing_requires) |
+ |
+ # First check all the used identifiers where we know that their namespace |
+ # needs to be provided (unless they are optional). |
+ for ns in self._used_namespaces: |
+ namespace = ns.namespace |
+ identifier = ns.identifier |
+ if (not ns.alias_definition and |
+ ShouldRequireNamespace(namespace, identifier)): |
+ missing_requires[namespace] = ns.GetLine() |
+ |
+ # Now that all required namespaces are known, we can check if the alias |
+ # definitions (that are likely being used for typeannotations that don't |
+ # need explicit goog.require statements) are already covered. If not |
+ # the user shouldn't use the alias. |
+ for ns in self._used_namespaces: |
+ if (not ns.alias_definition or |
+ not ShouldRequireNamespace(ns.namespace, ns.identifier)): |
+ continue |
+ if self._FindNamespace(ns.identifier, self._provided_namespaces, |
+ created_identifiers, external_dependencies, |
+ missing_requires): |
+ continue |
+ namespace = ns.identifier.rsplit('.', 1)[0] |
+ illegal_alias_statements[namespace] = ns.token |
- return missing_requires |
+ return missing_requires, illegal_alias_statements |
+ |
+ def _FindNamespace(self, identifier, *namespaces_list): |
+ """Finds the namespace of an identifier given a list of other namespaces. |
+ |
+ Args: |
+ identifier: An identifier whose parent needs to be defined. |
+ e.g. for goog.bar.foo we search something that provides |
+ goog.bar. |
+ *namespaces_list: var args of iterables of namespace identifiers |
+ Returns: |
+ The namespace that the given identifier is part of or None. |
+ """ |
+ identifier = identifier.rsplit('.', 1)[0] |
+ identifier_prefix = identifier + '.' |
+ for namespaces in namespaces_list: |
+ for namespace in namespaces: |
+ if namespace == identifier or namespace.startswith(identifier_prefix): |
+ return namespace |
+ return None |
def _IsPrivateIdentifier(self, identifier): |
- """Returns whether the given identifer is private.""" |
+ """Returns whether the given identifier is private.""" |
pieces = identifier.split('.') |
for piece in pieces: |
if piece.endswith('_'): |
@@ -311,10 +383,9 @@ class ClosurizedNamespacesInfo(object): |
# If there is a suppression for the require, add a usage for it so it |
# gets treated as a regular goog.require (i.e. still gets sorted). |
- jsdoc = state_tracker.GetDocComment() |
- if jsdoc and ('extraRequire' in jsdoc.suppressions): |
+ if self._HasSuppression(state_tracker, 'extraRequire'): |
self._suppressed_requires.append(namespace) |
- self._AddUsedNamespace(state_tracker, namespace, token.line_number) |
+ self._AddUsedNamespace(state_tracker, namespace, token) |
elif token.string == 'goog.provide': |
self._provide_tokens.append(token) |
@@ -326,8 +397,7 @@ class ClosurizedNamespacesInfo(object): |
# If there is a suppression for the provide, add a creation for it so it |
# gets treated as a regular goog.provide (i.e. still gets sorted). |
- jsdoc = state_tracker.GetDocComment() |
- if jsdoc and ('extraProvide' in jsdoc.suppressions): |
+ if self._HasSuppression(state_tracker, 'extraProvide'): |
self._AddCreatedNamespace(state_tracker, namespace, token.line_number) |
elif token.string == 'goog.scope': |
@@ -353,15 +423,24 @@ class ClosurizedNamespacesInfo(object): |
jsdoc = state_tracker.GetDocComment() |
if token.metadata and token.metadata.aliased_symbol: |
whole_identifier_string = token.metadata.aliased_symbol |
+ elif (token.string == 'goog.module.get' and |
+ not self._HasSuppression(state_tracker, 'extraRequire')): |
+ # Cannot use _AddUsedNamespace as this is not an identifier, but |
+ # already the entire namespace that's required. |
+ namespace = tokenutil.GetStringAfterToken(token) |
+ namespace = UsedNamespace(namespace, namespace, token, |
+ alias_definition=False) |
+ self._used_namespaces.append(namespace) |
if jsdoc and jsdoc.HasFlag('typedef'): |
self._AddCreatedNamespace(state_tracker, whole_identifier_string, |
token.line_number, |
namespace=self.GetClosurizedNamespace( |
whole_identifier_string)) |
else: |
- if not (token.metadata and token.metadata.is_alias_definition): |
- self._AddUsedNamespace(state_tracker, whole_identifier_string, |
- token.line_number) |
+ is_alias_definition = (token.metadata and |
+ token.metadata.is_alias_definition) |
+ self._AddUsedNamespace(state_tracker, whole_identifier_string, |
+ token, is_alias_definition) |
elif token.type == TokenType.SIMPLE_LVALUE: |
identifier = token.values['identifier'] |
@@ -381,20 +460,22 @@ class ClosurizedNamespacesInfo(object): |
if identifier: |
namespace = self.GetClosurizedNamespace(identifier) |
if state_tracker.InFunction(): |
- self._AddUsedNamespace(state_tracker, identifier, token.line_number) |
+ self._AddUsedNamespace(state_tracker, identifier, token) |
elif namespace and namespace != 'goog': |
self._AddCreatedNamespace(state_tracker, identifier, |
token.line_number, namespace=namespace) |
elif token.type == TokenType.DOC_FLAG: |
- flag_type = token.attached_object.flag_type |
- is_interface = state_tracker.GetDocComment().HasFlag('interface') |
- if flag_type == 'implements' or (flag_type == 'extends' and is_interface): |
- # Interfaces should be goog.require'd. |
- doc_start = tokenutil.Search(token, TokenType.DOC_START_BRACE) |
- interface = tokenutil.Search(doc_start, TokenType.COMMENT) |
- self._AddUsedNamespace(state_tracker, interface.string, |
- token.line_number) |
+ flag = token.attached_object |
+ flag_type = flag.flag_type |
+ if flag and flag.HasType() and flag.jstype: |
+ is_interface = state_tracker.GetDocComment().HasFlag('interface') |
+ if flag_type == 'implements' or (flag_type == 'extends' |
+ and is_interface): |
+ identifier = flag.jstype.alias or flag.jstype.identifier |
+ self._AddUsedNamespace(state_tracker, identifier, token) |
+ # Since we process doctypes only for implements and extends, the |
+ # type is a simple one and we don't need any iteration for subtypes. |
def _AddCreatedNamespace(self, state_tracker, identifier, line_number, |
namespace=None): |
@@ -413,13 +494,13 @@ class ClosurizedNamespacesInfo(object): |
if not namespace: |
namespace = identifier |
- jsdoc = state_tracker.GetDocComment() |
- if jsdoc and 'missingProvide' in jsdoc.suppressions: |
+ if self._HasSuppression(state_tracker, 'missingProvide'): |
return |
self._created_namespaces.append([namespace, identifier, line_number]) |
- def _AddUsedNamespace(self, state_tracker, identifier, line_number): |
+ def _AddUsedNamespace(self, state_tracker, identifier, token, |
+ is_alias_definition=False): |
"""Adds the namespace of an identifier to the list of used namespaces. |
If the identifier is annotated with a 'missingRequire' suppression, it is |
@@ -428,16 +509,32 @@ class ClosurizedNamespacesInfo(object): |
Args: |
state_tracker: The JavaScriptStateTracker instance. |
identifier: An identifier which has been used. |
- line_number: Line number where namespace is used. |
+ token: The token in which the namespace is used. |
+ is_alias_definition: If the used namespace is part of an alias_definition. |
+ Aliased symbols need their parent namespace to be available, if it is |
+ not yet required through another symbol, an error will be thrown. |
""" |
- jsdoc = state_tracker.GetDocComment() |
- if jsdoc and 'missingRequire' in jsdoc.suppressions: |
+ if self._HasSuppression(state_tracker, 'missingRequire'): |
return |
+ identifier = self._GetUsedIdentifier(identifier) |
namespace = self.GetClosurizedNamespace(identifier) |
# b/5362203 If its a variable in scope then its not a required namespace. |
if namespace and not state_tracker.IsVariableInScope(namespace): |
- self._used_namespaces.append([namespace, identifier, line_number]) |
+ namespace = UsedNamespace(namespace, identifier, token, |
+ is_alias_definition) |
+ self._used_namespaces.append(namespace) |
+ |
+ def _HasSuppression(self, state_tracker, suppression): |
+ jsdoc = state_tracker.GetDocComment() |
+ return jsdoc and suppression in jsdoc.suppressions |
+ |
+ def _GetUsedIdentifier(self, identifier): |
+ """Strips apply/call/inherit calls from the identifier.""" |
+ for suffix in ('.apply', '.call', '.inherit'): |
+ if identifier.endswith(suffix): |
+ return identifier[:-len(suffix)] |
+ return identifier |
def GetClosurizedNamespace(self, identifier): |
"""Given an identifier, returns the namespace that identifier is from. |
@@ -458,11 +555,6 @@ class ClosurizedNamespacesInfo(object): |
if not identifier.startswith(namespace + '.'): |
continue |
- last_part = parts[-1] |
- if not last_part: |
- # TODO(robbyw): Handle this: it's a multi-line identifier. |
- return None |
- |
# The namespace for a class is the shortest prefix ending in a class |
# name, which starts with a capital letter but is not a capitalized word. |
# |