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

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

Issue 2592193002: Remove closure_linter from Chrome (Closed)
Patch Set: Created 4 years 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
deleted file mode 100755
index 18b5a2a0090b5f4131db417a8b68dca22ad585e9..0000000000000000000000000000000000000000
--- a/third_party/closure_linter/closure_linter/closurizednamespacesinfo.py
+++ /dev/null
@@ -1,586 +0,0 @@
-#!/usr/bin/env python
-#
-# Copyright 2008 The Closure Linter Authors. All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS-IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-"""Logic for computing dependency information for closurized JavaScript files.
-
-Closurized JavaScript files express dependencies using goog.require and
-goog.provide statements. In order for the linter to detect when a statement is
-missing or unnecessary, all identifiers in the JavaScript file must first be
-processed to determine if they constitute the creation or usage of a dependency.
-"""
-
-
-
-import re
-
-from closure_linter import javascripttokens
-from closure_linter import tokenutil
-
-# pylint: disable=g-bad-name
-TokenType = javascripttokens.JavaScriptTokenType
-
-DEFAULT_EXTRA_NAMESPACES = [
- 'goog.testing.asserts',
- 'goog.testing.jsunit',
-]
-
-
-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.
-
- Processes token streams for dependency creation or usage and provides logic
- for determining if a given require or provide statement is unnecessary or if
- there are missing require or provide statements.
- """
-
- def __init__(self, closurized_namespaces, ignored_extra_namespaces):
- """Initializes an instance the ClosurizedNamespacesInfo class.
-
- Args:
- closurized_namespaces: A list of namespace prefixes that should be
- processed for dependency information. Non-matching namespaces are
- ignored.
- ignored_extra_namespaces: A list of namespaces that should not be reported
- as extra regardless of whether they are actually used.
- """
- self._closurized_namespaces = closurized_namespaces
- self._ignored_extra_namespaces = (ignored_extra_namespaces +
- DEFAULT_EXTRA_NAMESPACES)
- self.Reset()
-
- def Reset(self):
- """Resets the internal state to prepare for processing a new file."""
-
- # A list of goog.provide tokens in the order they appeared in the file.
- self._provide_tokens = []
-
- # A list of goog.require tokens in the order they appeared in the file.
- self._require_tokens = []
-
- # Namespaces that are already goog.provided.
- self._provided_namespaces = []
-
- # Namespaces that are already goog.required.
- self._required_namespaces = []
-
- # Note that created_namespaces and used_namespaces contain both namespaces
- # and identifiers because there are many existing cases where a method or
- # constant is provided directly instead of its namespace. Ideally, these
- # two lists would only have to contain namespaces.
-
- # A list of tuples where the first element is the namespace of an identifier
- # created in the file, the second is the identifier itself and the third is
- # the line number where it's created.
- self._created_namespaces = []
-
- # A list of UsedNamespace instances.
- self._used_namespaces = []
-
- # A list of seemingly-unnecessary namespaces that are goog.required() and
- # annotated with @suppress {extraRequire}.
- self._suppressed_requires = []
-
- # A list of goog.provide tokens which are duplicates.
- self._duplicate_provide_tokens = []
-
- # A list of goog.require tokens which are duplicates.
- self._duplicate_require_tokens = []
-
- # Whether this file is in a goog.scope. Someday, we may add support
- # for checking scopified namespaces, but for now let's just fail
- # in a more reasonable way.
- self._scopified_file = False
-
- # TODO(user): Handle the case where there are 2 different requires
- # that can satisfy the same dependency, but only one is necessary.
-
- def GetProvidedNamespaces(self):
- """Returns the namespaces which are already provided by this file.
-
- Returns:
- A list of strings where each string is a 'namespace' corresponding to an
- existing goog.provide statement in the file being checked.
- """
- return set(self._provided_namespaces)
-
- def GetRequiredNamespaces(self):
- """Returns the namespaces which are already required by this file.
-
- Returns:
- A list of strings where each string is a 'namespace' corresponding to an
- existing goog.require statement in the file being checked.
- """
- return set(self._required_namespaces)
-
- def IsExtraProvide(self, token):
- """Returns whether the given goog.provide token is unnecessary.
-
- Args:
- token: A goog.provide token.
-
- Returns:
- True if the given token corresponds to an unnecessary goog.provide
- statement, otherwise False.
- """
- namespace = tokenutil.GetStringAfterToken(token)
-
- if self.GetClosurizedNamespace(namespace) is None:
- return False
-
- if token in self._duplicate_provide_tokens:
- return True
-
- # TODO(user): There's probably a faster way to compute this.
- for created_namespace, created_identifier, _ in self._created_namespaces:
- if namespace == created_namespace or namespace == created_identifier:
- return False
-
- return True
-
- def IsExtraRequire(self, token):
- """Returns whether the given goog.require token is unnecessary.
-
- Args:
- token: A goog.require token.
-
- Returns:
- True if the given token corresponds to an unnecessary goog.require
- statement, otherwise False.
- """
- namespace = tokenutil.GetStringAfterToken(token)
-
- if self.GetClosurizedNamespace(namespace) is None:
- return False
-
- if namespace in self._ignored_extra_namespaces:
- return False
-
- if token in self._duplicate_require_tokens:
- return True
-
- if namespace in self._suppressed_requires:
- return False
-
- # If the namespace contains a component that is initial caps, then that
- # must be the last component of the namespace.
- parts = namespace.split('.')
- if len(parts) > 1 and parts[-2][0].isupper():
- return True
-
- # TODO(user): There's probably a faster way to compute this.
- for ns in self._used_namespaces:
- if (not ns.alias_definition and (
- namespace == ns.namespace or namespace == ns.identifier)):
- return False
-
- return True
-
- def GetMissingProvides(self):
- """Returns the dict of missing provided namespaces for the current file.
-
- Returns:
- Returns a dictionary of key as string and value as integer where each
- string(key) is a namespace that should be provided by this file, but is
- not and integer(value) is first line number where it's defined.
- """
- missing_provides = dict()
- for namespace, identifier, line_number in self._created_namespaces:
- if (not self._IsPrivateIdentifier(identifier) and
- namespace not in self._provided_namespaces and
- identifier not in self._provided_namespaces and
- namespace not in self._required_namespaces and
- namespace not in missing_provides):
- missing_provides[namespace] = line_number
-
- return missing_provides
-
- def GetMissingRequires(self):
- """Returns the dict of missing required namespaces for the current file.
-
- For each non-private identifier used in the file, find either a
- goog.require, goog.provide or a created identifier that satisfies it.
- goog.require statements can satisfy the identifier by requiring either the
- namespace of the identifier or the identifier itself. goog.provide
- statements can satisfy the identifier by providing the namespace of the
- identifier. A created identifier can only satisfy the used identifier if
- it matches it exactly (necessary since things can be defined on a
- namespace in more than one file). Note that provided namespaces should be
- a subset of created namespaces, but we check both because in some cases we
- can't always detect the creation of the namespace.
-
- Returns:
- Returns a dictionary of key as string and value integer where each
- string(key) is a namespace that should be required by this file, but is
- not and integer(value) is first line number where it's used.
- """
- external_dependencies = set(self._required_namespaces)
-
- # 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 unused_namespace, identifier, unused_line_number in (
- self._created_namespaces):
- created_identifiers.add(identifier)
-
- missing_requires = dict()
- 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)
-
- # 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, 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 identifier is private."""
- pieces = identifier.split('.')
- for piece in pieces:
- if piece.endswith('_'):
- return True
- return False
-
- def IsFirstProvide(self, token):
- """Returns whether token is the first provide token."""
- return self._provide_tokens and token == self._provide_tokens[0]
-
- def IsFirstRequire(self, token):
- """Returns whether token is the first require token."""
- return self._require_tokens and token == self._require_tokens[0]
-
- def IsLastProvide(self, token):
- """Returns whether token is the last provide token."""
- return self._provide_tokens and token == self._provide_tokens[-1]
-
- def IsLastRequire(self, token):
- """Returns whether token is the last require token."""
- return self._require_tokens and token == self._require_tokens[-1]
-
- def ProcessToken(self, token, state_tracker):
- """Processes the given token for dependency information.
-
- Args:
- token: The token to process.
- state_tracker: The JavaScript state tracker.
- """
-
- # Note that this method is in the critical path for the linter and has been
- # optimized for performance in the following ways:
- # - Tokens are checked by type first to minimize the number of function
- # calls necessary to determine if action needs to be taken for the token.
- # - The most common tokens types are checked for first.
- # - The number of function calls has been minimized (thus the length of this
- # function.
-
- if token.type == TokenType.IDENTIFIER:
- # TODO(user): Consider saving the whole identifier in metadata.
- whole_identifier_string = tokenutil.GetIdentifierForToken(token)
- if whole_identifier_string is None:
- # We only want to process the identifier one time. If the whole string
- # identifier is None, that means this token was part of a multi-token
- # identifier, but it was not the first token of the identifier.
- return
-
- # In the odd case that a goog.require is encountered inside a function,
- # just ignore it (e.g. dynamic loading in test runners).
- if token.string == 'goog.require' and not state_tracker.InFunction():
- self._require_tokens.append(token)
- namespace = tokenutil.GetStringAfterToken(token)
- if namespace in self._required_namespaces:
- self._duplicate_require_tokens.append(token)
- else:
- self._required_namespaces.append(namespace)
-
- # 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).
- if self._HasSuppression(state_tracker, 'extraRequire'):
- self._suppressed_requires.append(namespace)
- self._AddUsedNamespace(state_tracker, namespace, token)
-
- elif token.string == 'goog.provide':
- self._provide_tokens.append(token)
- namespace = tokenutil.GetStringAfterToken(token)
- if namespace in self._provided_namespaces:
- self._duplicate_provide_tokens.append(token)
- else:
- self._provided_namespaces.append(namespace)
-
- # 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).
- if self._HasSuppression(state_tracker, 'extraProvide'):
- self._AddCreatedNamespace(state_tracker, namespace, token.line_number)
-
- elif token.string == 'goog.scope':
- self._scopified_file = True
-
- elif token.string == 'goog.setTestOnly':
-
- # Since the message is optional, we don't want to scan to later lines.
- for t in tokenutil.GetAllTokensInSameLine(token):
- if t.type == TokenType.STRING_TEXT:
- message = t.string
-
- if re.match(r'^\w+(\.\w+)+$', message):
- # This looks like a namespace. If it's a Closurized namespace,
- # consider it created.
- base_namespace = message.split('.', 1)[0]
- if base_namespace in self._closurized_namespaces:
- self._AddCreatedNamespace(state_tracker, message,
- token.line_number)
-
- break
- else:
- 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:
- 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']
- start_token = tokenutil.GetIdentifierStart(token)
- if start_token and start_token != token:
- # Multi-line identifier being assigned. Get the whole identifier.
- identifier = tokenutil.GetIdentifierForToken(start_token)
- else:
- start_token = token
- # If an alias is defined on the start_token, use it instead.
- if (start_token and
- start_token.metadata and
- start_token.metadata.aliased_symbol and
- not start_token.metadata.is_alias_definition):
- identifier = start_token.metadata.aliased_symbol
-
- if identifier:
- namespace = self.GetClosurizedNamespace(identifier)
- if state_tracker.InFunction():
- 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 = 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):
- """Adds the namespace of an identifier to the list of created namespaces.
-
- If the identifier is annotated with a 'missingProvide' suppression, it is
- not added.
-
- Args:
- state_tracker: The JavaScriptStateTracker instance.
- identifier: The identifier to add.
- line_number: Line number where namespace is created.
- namespace: The namespace of the identifier or None if the identifier is
- also the namespace.
- """
- if not namespace:
- namespace = identifier
-
- if self._HasSuppression(state_tracker, 'missingProvide'):
- return
-
- self._created_namespaces.append([namespace, 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
- not added.
-
- Args:
- state_tracker: The JavaScriptStateTracker instance.
- identifier: An identifier which has been 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.
- """
- 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):
- 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.
-
- Args:
- identifier: The identifier to extract a namespace from.
-
- Returns:
- The namespace the given identifier resides in, or None if one could not
- be found.
- """
- if identifier.startswith('goog.global'):
- # Ignore goog.global, since it is, by definition, global.
- return None
-
- parts = identifier.split('.')
- for namespace in self._closurized_namespaces:
- if not identifier.startswith(namespace + '.'):
- continue
-
- # 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.
- #
- # We ultimately do not want to allow requiring or providing of inner
- # classes/enums. Instead, a file should provide only the top-level class
- # and users should require only that.
- namespace = []
- for part in parts:
- if part == 'prototype' or part.isupper():
- return '.'.join(namespace)
- namespace.append(part)
- if part[0].isupper():
- return '.'.join(namespace)
-
- # At this point, we know there's no class or enum, so the namespace is
- # just the identifier with the last part removed. With the exception of
- # apply, inherits, and call, which should also be stripped.
- if parts[-1] in ('apply', 'inherits', 'call'):
- parts.pop()
- parts.pop()
-
- # If the last part ends with an underscore, it is a private variable,
- # method, or enum. The namespace is whatever is before it.
- if parts and parts[-1].endswith('_'):
- parts.pop()
-
- return '.'.join(parts)
-
- return None

Powered by Google App Engine
This is Rietveld 408576698