| Index: third_party/closure_linter/closure_linter/ecmametadatapass.py
|
| diff --git a/third_party/closure_linter/closure_linter/ecmametadatapass.py b/third_party/closure_linter/closure_linter/ecmametadatapass.py
|
| index 2c797b3c399a02c4a7676e4ff9ab1fc6010c5b2d..a8657d2f4defa91eaad2d071a94e16687f68f846 100755
|
| --- a/third_party/closure_linter/closure_linter/ecmametadatapass.py
|
| +++ b/third_party/closure_linter/closure_linter/ecmametadatapass.py
|
| @@ -115,18 +115,30 @@ class EcmaContext(object):
|
| BLOCK_TYPES = frozenset([
|
| ROOT, BLOCK, CASE_BLOCK, FOR_GROUP_BLOCK, IMPLIED_BLOCK])
|
|
|
| - def __init__(self, type, start_token, parent):
|
| + def __init__(self, context_type, start_token, parent=None):
|
| """Initializes the context object.
|
|
|
| Args:
|
| + context_type: The context type.
|
| + start_token: The token where this context starts.
|
| + parent: The parent context.
|
| +
|
| + Attributes:
|
| type: The context type.
|
| start_token: The token where this context starts.
|
| + end_token: The token where this context ends.
|
| parent: The parent context.
|
| + children: The child contexts of this context, in order.
|
| """
|
| - self.type = type
|
| + self.type = context_type
|
| self.start_token = start_token
|
| self.end_token = None
|
| - self.parent = parent
|
| +
|
| + self.parent = None
|
| + self.children = []
|
| +
|
| + if parent:
|
| + parent.AddChild(self)
|
|
|
| def __repr__(self):
|
| """Returns a string representation of the context object."""
|
| @@ -137,6 +149,32 @@ class EcmaContext(object):
|
| context = context.parent
|
| return 'Context(%s)' % ' > '.join(stack)
|
|
|
| + def AddChild(self, child):
|
| + """Adds a child to this context and sets child's parent to this context.
|
| +
|
| + Args:
|
| + child: A child EcmaContext. The child's parent will be set to this
|
| + context.
|
| + """
|
| +
|
| + child.parent = self
|
| +
|
| + self.children.append(child)
|
| + self.children.sort(EcmaContext._CompareContexts)
|
| +
|
| + def GetRoot(self):
|
| + """Get the root context that contains this context, if any."""
|
| + context = self
|
| + while context:
|
| + if context.type is EcmaContext.ROOT:
|
| + return context
|
| + context = context.parent
|
| +
|
| + @staticmethod
|
| + def _CompareContexts(context1, context2):
|
| + """Sorts contexts 1 and 2 by start token document position."""
|
| + return tokenutil.Compare(context1.start_token, context2.start_token)
|
| +
|
|
|
| class EcmaMetaData(object):
|
| """Token metadata for EcmaScript languages.
|
| @@ -146,6 +184,11 @@ class EcmaMetaData(object):
|
| context: The context this token appears in.
|
| operator_type: The operator type, will be one of the *_OPERATOR constants
|
| defined below.
|
| + aliased_symbol: The full symbol being identified, as a string (e.g. an
|
| + 'XhrIo' alias for 'goog.net.XhrIo'). Only applicable to identifier
|
| + tokens. This is set in aliaspass.py and is a best guess.
|
| + is_alias_definition: True if the symbol is part of an alias definition.
|
| + If so, these symbols won't be counted towards goog.requires/provides.
|
| """
|
|
|
| UNARY_OPERATOR = 'unary'
|
| @@ -164,6 +207,8 @@ class EcmaMetaData(object):
|
| self.is_implied_semicolon = False
|
| self.is_implied_block = False
|
| self.is_implied_block_close = False
|
| + self.aliased_symbol = None
|
| + self.is_alias_definition = False
|
|
|
| def __repr__(self):
|
| """Returns a string representation of the context object."""
|
| @@ -172,6 +217,8 @@ class EcmaMetaData(object):
|
| parts.append('optype: %r' % self.operator_type)
|
| if self.is_implied_semicolon:
|
| parts.append('implied;')
|
| + if self.aliased_symbol:
|
| + parts.append('alias for: %s' % self.aliased_symbol)
|
| return 'MetaData(%s)' % ', '.join(parts)
|
|
|
| def IsUnaryOperator(self):
|
| @@ -196,21 +243,21 @@ class EcmaMetaDataPass(object):
|
| self._AddContext(EcmaContext.ROOT)
|
| self._last_code = None
|
|
|
| - def _CreateContext(self, type):
|
| + def _CreateContext(self, context_type):
|
| """Overridable by subclasses to create the appropriate context type."""
|
| - return EcmaContext(type, self._token, self._context)
|
| + return EcmaContext(context_type, self._token, self._context)
|
|
|
| def _CreateMetaData(self):
|
| """Overridable by subclasses to create the appropriate metadata type."""
|
| return EcmaMetaData()
|
|
|
| - def _AddContext(self, type):
|
| + def _AddContext(self, context_type):
|
| """Adds a context of the given type to the context stack.
|
|
|
| Args:
|
| - type: The type of context to create
|
| + context_type: The type of context to create
|
| """
|
| - self._context = self._CreateContext(type)
|
| + self._context = self._CreateContext(context_type)
|
|
|
| def _PopContext(self):
|
| """Moves up one level in the context stack.
|
| @@ -233,7 +280,7 @@ class EcmaMetaDataPass(object):
|
| """Pops the context stack until a context of the given type is popped.
|
|
|
| Args:
|
| - stop_types: The types of context to pop to - stops at the first match.
|
| + *stop_types: The types of context to pop to - stops at the first match.
|
|
|
| Returns:
|
| The context object of the given type that was popped.
|
| @@ -364,10 +411,14 @@ class EcmaMetaDataPass(object):
|
| self._AddContext(EcmaContext.SWITCH)
|
|
|
| elif (token_type == TokenType.KEYWORD and
|
| - token.string in ('case', 'default')):
|
| + token.string in ('case', 'default') and
|
| + self._context.type != EcmaContext.OBJECT_LITERAL):
|
| # Pop up to but not including the switch block.
|
| while self._context.parent.type != EcmaContext.SWITCH:
|
| self._PopContext()
|
| + if self._context.parent is None:
|
| + raise ParseError(token, 'Encountered case/default statement '
|
| + 'without switch statement')
|
|
|
| elif token.IsOperator('?'):
|
| self._AddContext(EcmaContext.TERNARY_TRUE)
|
| @@ -386,9 +437,9 @@ class EcmaMetaDataPass(object):
|
| # ternary_false > ternary_true > statement > root
|
| elif (self._context.type == EcmaContext.TERNARY_FALSE and
|
| self._context.parent.type == EcmaContext.TERNARY_TRUE):
|
| - self._PopContext() # Leave current ternary false context.
|
| - self._PopContext() # Leave current parent ternary true
|
| - self._AddContext(EcmaContext.TERNARY_FALSE)
|
| + self._PopContext() # Leave current ternary false context.
|
| + self._PopContext() # Leave current parent ternary true
|
| + self._AddContext(EcmaContext.TERNARY_FALSE)
|
|
|
| elif self._context.parent.type == EcmaContext.SWITCH:
|
| self._AddContext(EcmaContext.CASE_BLOCK)
|
| @@ -451,14 +502,21 @@ class EcmaMetaDataPass(object):
|
| is_continued_dot = token.string == '.'
|
| next_code_is_operator = next_code and next_code.type == TokenType.OPERATOR
|
| next_code_is_dot = next_code and next_code.string == '.'
|
| - is_end_of_block = (token.type == TokenType.END_BLOCK and
|
| + is_end_of_block = (
|
| + token.type == TokenType.END_BLOCK and
|
| token.metadata.context.type != EcmaContext.OBJECT_LITERAL)
|
| is_multiline_string = token.type == TokenType.STRING_TEXT
|
| + is_continued_var_decl = (token.IsKeyword('var') and
|
| + next_code and
|
| + (next_code.type in [TokenType.IDENTIFIER,
|
| + TokenType.SIMPLE_LVALUE]) and
|
| + token.line_number < next_code.line_number)
|
| next_code_is_block = next_code and next_code.type == TokenType.START_BLOCK
|
| if (is_last_code_in_line and
|
| self._StatementCouldEndInContext() and
|
| not is_multiline_string and
|
| not is_end_of_block and
|
| + not is_continued_var_decl and
|
| not is_continued_identifier and
|
| not is_continued_operator and
|
| not is_continued_dot and
|
| @@ -470,7 +528,7 @@ class EcmaMetaDataPass(object):
|
| self._EndStatement()
|
|
|
| def _StatementCouldEndInContext(self):
|
| - """Returns whether the current statement (if any) may end in this context."""
|
| + """Returns if the current statement (if any) may end in this context."""
|
| # In the basic statement or variable declaration context, statement can
|
| # always end in this context.
|
| if self._context.type in (EcmaContext.STATEMENT, EcmaContext.VAR):
|
|
|