| Index: third_party/closure_linter/closure_linter/aliaspass.py
|
| diff --git a/third_party/closure_linter/closure_linter/aliaspass.py b/third_party/closure_linter/closure_linter/aliaspass.py
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..8854380321ef080c459abb70a3ed37d8b509594f
|
| --- /dev/null
|
| +++ b/third_party/closure_linter/closure_linter/aliaspass.py
|
| @@ -0,0 +1,222 @@
|
| +#!/usr/bin/env python
|
| +#
|
| +# Copyright 2012 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.
|
| +
|
| +"""Pass that scans for goog.scope aliases and lint/usage errors."""
|
| +
|
| +# Allow non-Google copyright
|
| +# pylint: disable=g-bad-file-header
|
| +
|
| +__author__ = ('nnaze@google.com (Nathan Naze)')
|
| +
|
| +import itertools
|
| +
|
| +from closure_linter import ecmametadatapass
|
| +from closure_linter import errors
|
| +from closure_linter import javascripttokens
|
| +from closure_linter import scopeutil
|
| +from closure_linter import tokenutil
|
| +from closure_linter.common import error
|
| +
|
| +
|
| +# TODO(nnaze): Create a Pass interface and move this class, EcmaMetaDataPass,
|
| +# and related classes onto it.
|
| +
|
| +
|
| +def _GetAliasForIdentifier(identifier, alias_map):
|
| + """Returns the aliased_symbol name for an identifier.
|
| +
|
| + Example usage:
|
| + >>> alias_map = {'MyClass': 'goog.foo.MyClass'}
|
| + >>> _GetAliasForIdentifier('MyClass.prototype.action', alias_map)
|
| + 'goog.foo.MyClass.prototype.action'
|
| +
|
| + >>> _GetAliasForIdentifier('MyClass.prototype.action', {})
|
| + None
|
| +
|
| + Args:
|
| + identifier: The identifier.
|
| + alias_map: A dictionary mapping a symbol to an alias.
|
| +
|
| + Returns:
|
| + The aliased symbol name or None if not found.
|
| + """
|
| + ns = identifier.split('.', 1)[0]
|
| + aliased_symbol = alias_map.get(ns)
|
| + if aliased_symbol:
|
| + return aliased_symbol + identifier[len(ns):]
|
| +
|
| +
|
| +class AliasPass(object):
|
| + """Pass to identify goog.scope() usages.
|
| +
|
| + Identifies goog.scope() usages and finds lint/usage errors. Notes any
|
| + aliases of symbols in Closurized namespaces (that is, reassignments
|
| + such as "var MyClass = goog.foo.MyClass;") and annotates identifiers
|
| + when they're using an alias (so they may be expanded to the full symbol
|
| + later -- that "MyClass.prototype.action" refers to
|
| + "goog.foo.MyClass.prototype.action" when expanded.).
|
| + """
|
| +
|
| + def __init__(self, closurized_namespaces=None, error_handler=None):
|
| + """Creates a new pass.
|
| +
|
| + Args:
|
| + closurized_namespaces: A set of Closurized namespaces (e.g. 'goog').
|
| + error_handler: An error handler to report lint errors to.
|
| + """
|
| +
|
| + self._error_handler = error_handler
|
| +
|
| + # If we have namespaces, freeze the set.
|
| + if closurized_namespaces:
|
| + closurized_namespaces = frozenset(closurized_namespaces)
|
| +
|
| + self._closurized_namespaces = closurized_namespaces
|
| +
|
| + def Process(self, start_token):
|
| + """Runs the pass on a token stream.
|
| +
|
| + Args:
|
| + start_token: The first token in the stream.
|
| + """
|
| +
|
| + if start_token is None:
|
| + return
|
| +
|
| + # TODO(nnaze): Add more goog.scope usage checks.
|
| + self._CheckGoogScopeCalls(start_token)
|
| +
|
| + # If we have closurized namespaces, identify aliased identifiers.
|
| + if self._closurized_namespaces:
|
| + context = start_token.metadata.context
|
| + root_context = context.GetRoot()
|
| + self._ProcessRootContext(root_context)
|
| +
|
| + def _CheckGoogScopeCalls(self, start_token):
|
| + """Check goog.scope calls for lint/usage errors."""
|
| +
|
| + def IsScopeToken(token):
|
| + return (token.type is javascripttokens.JavaScriptTokenType.IDENTIFIER and
|
| + token.string == 'goog.scope')
|
| +
|
| + # Find all the goog.scope tokens in the file
|
| + scope_tokens = [t for t in start_token if IsScopeToken(t)]
|
| +
|
| + for token in scope_tokens:
|
| + scope_context = token.metadata.context
|
| +
|
| + if not (scope_context.type == ecmametadatapass.EcmaContext.STATEMENT and
|
| + scope_context.parent.type == ecmametadatapass.EcmaContext.ROOT):
|
| + self._MaybeReportError(
|
| + error.Error(errors.INVALID_USE_OF_GOOG_SCOPE,
|
| + 'goog.scope call not in global scope', token))
|
| +
|
| + # There should be only one goog.scope reference. Register errors for
|
| + # every instance after the first.
|
| + for token in scope_tokens[1:]:
|
| + self._MaybeReportError(
|
| + error.Error(errors.EXTRA_GOOG_SCOPE_USAGE,
|
| + 'More than one goog.scope call in file.', token))
|
| +
|
| + def _MaybeReportError(self, err):
|
| + """Report an error to the handler (if registered)."""
|
| + if self._error_handler:
|
| + self._error_handler.HandleError(err)
|
| +
|
| + @classmethod
|
| + def _YieldAllContexts(cls, context):
|
| + """Yields all contexts that are contained by the given context."""
|
| + yield context
|
| + for child_context in context.children:
|
| + for descendent_child in cls._YieldAllContexts(child_context):
|
| + yield descendent_child
|
| +
|
| + @staticmethod
|
| + def _IsTokenInParentBlock(token, parent_block):
|
| + """Determines whether the given token is contained by the given block.
|
| +
|
| + Args:
|
| + token: A token
|
| + parent_block: An EcmaContext.
|
| +
|
| + Returns:
|
| + Whether the token is in a context that is or is a child of the given
|
| + parent_block context.
|
| + """
|
| + context = token.metadata.context
|
| +
|
| + while context:
|
| + if context is parent_block:
|
| + return True
|
| + context = context.parent
|
| +
|
| + return False
|
| +
|
| + def _ProcessRootContext(self, root_context):
|
| + """Processes all goog.scope blocks under the root context."""
|
| +
|
| + assert root_context.type is ecmametadatapass.EcmaContext.ROOT
|
| +
|
| + # Identify all goog.scope blocks.
|
| + goog_scope_blocks = itertools.ifilter(
|
| + scopeutil.IsGoogScopeBlock,
|
| + self._YieldAllContexts(root_context))
|
| +
|
| + # Process each block to find aliases.
|
| + for scope_block in goog_scope_blocks:
|
| + self._ProcessGoogScopeBlock(scope_block)
|
| +
|
| + def _ProcessGoogScopeBlock(self, scope_block):
|
| + """Scans a goog.scope block to find aliases and mark alias tokens."""
|
| +
|
| + alias_map = dict()
|
| +
|
| + # Iterate over every token in the scope_block. Each token points to one
|
| + # context, but multiple tokens may point to the same context. We only want
|
| + # to check each context once, so keep track of those we've seen.
|
| + seen_contexts = set()
|
| + token = scope_block.start_token
|
| + while token and self._IsTokenInParentBlock(token, scope_block):
|
| +
|
| + token_context = token.metadata.context
|
| +
|
| + # Check to see if this token is an alias.
|
| + if token_context not in seen_contexts:
|
| + seen_contexts.add(token_context)
|
| +
|
| + # If this is a alias statement in the goog.scope block.
|
| + if (token_context.type == ecmametadatapass.EcmaContext.VAR and
|
| + token_context.parent.parent is scope_block):
|
| + match = scopeutil.MatchAlias(token_context)
|
| +
|
| + # If this is an alias, remember it in the map.
|
| + if match:
|
| + alias, symbol = match
|
| + symbol = _GetAliasForIdentifier(symbol, alias_map) or symbol
|
| + if scopeutil.IsInClosurizedNamespace(symbol,
|
| + self._closurized_namespaces):
|
| + alias_map[alias] = symbol
|
| +
|
| + # If this token is an identifier that matches an alias,
|
| + # mark the token as an alias to the original symbol.
|
| + if (token.type is javascripttokens.JavaScriptTokenType.SIMPLE_LVALUE or
|
| + token.type is javascripttokens.JavaScriptTokenType.IDENTIFIER):
|
| + identifier = tokenutil.GetIdentifierForToken(token)
|
| + if identifier:
|
| + aliased_symbol = _GetAliasForIdentifier(identifier, alias_map)
|
| + if aliased_symbol:
|
| + token.metadata.aliased_symbol = aliased_symbol
|
| +
|
| + token = token.next # Get next token
|
|
|