| 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.
|
| #
|
|
|