Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(116)

Unified Diff: third_party/closure_linter/closure_linter/closurizednamespacesinfo.py

Issue 2328693002: Updated linter with upstream release (2.3.19) (Closed)
Patch Set: Created 4 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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.
#

Powered by Google App Engine
This is Rietveld 408576698