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 |
deleted file mode 100755 |
index 2036aa2824e64eaf32669a1d4a37a3f1ffb9c93e..0000000000000000000000000000000000000000 |
--- a/third_party/closure_linter/closure_linter/ecmametadatapass.py |
+++ /dev/null |
@@ -1,575 +0,0 @@ |
-#!/usr/bin/env python |
-# |
-# Copyright 2010 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. |
- |
-"""Metadata pass for annotating tokens in EcmaScript files.""" |
- |
-__author__ = ('robbyw@google.com (Robert Walker)') |
- |
-from closure_linter import javascripttokens |
-from closure_linter import tokenutil |
- |
- |
-TokenType = javascripttokens.JavaScriptTokenType |
- |
- |
-class ParseError(Exception): |
- """Exception indicating a parse error at the given token. |
- |
- Attributes: |
- token: The token where the parse error occurred. |
- """ |
- |
- def __init__(self, token, message=None): |
- """Initialize a parse error at the given token with an optional message. |
- |
- Args: |
- token: The token where the parse error occurred. |
- message: A message describing the parse error. |
- """ |
- Exception.__init__(self, message) |
- self.token = token |
- |
- |
-class EcmaContext(object): |
- """Context object for EcmaScript languages. |
- |
- 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. |
- """ |
- |
- # The root context. |
- ROOT = 'root' |
- |
- # A block of code. |
- BLOCK = 'block' |
- |
- # A pseudo-block of code for a given case or default section. |
- CASE_BLOCK = 'case_block' |
- |
- # Block of statements in a for loop's parentheses. |
- FOR_GROUP_BLOCK = 'for_block' |
- |
- # An implied block of code for 1 line if, while, and for statements |
- IMPLIED_BLOCK = 'implied_block' |
- |
- # An index in to an array or object. |
- INDEX = 'index' |
- |
- # An array literal in []. |
- ARRAY_LITERAL = 'array_literal' |
- |
- # An object literal in {}. |
- OBJECT_LITERAL = 'object_literal' |
- |
- # An individual element in an array or object literal. |
- LITERAL_ELEMENT = 'literal_element' |
- |
- # The portion of a ternary statement between ? and : |
- TERNARY_TRUE = 'ternary_true' |
- |
- # The portion of a ternary statment after : |
- TERNARY_FALSE = 'ternary_false' |
- |
- # The entire switch statment. This will contain a GROUP with the variable |
- # and a BLOCK with the code. |
- |
- # Since that BLOCK is not a normal block, it can not contain statements except |
- # for case and default. |
- SWITCH = 'switch' |
- |
- # A normal comment. |
- COMMENT = 'comment' |
- |
- # A JsDoc comment. |
- DOC = 'doc' |
- |
- # An individual statement. |
- STATEMENT = 'statement' |
- |
- # Code within parentheses. |
- GROUP = 'group' |
- |
- # Parameter names in a function declaration. |
- PARAMETERS = 'parameters' |
- |
- # A set of variable declarations appearing after the 'var' keyword. |
- VAR = 'var' |
- |
- # Context types that are blocks. |
- BLOCK_TYPES = frozenset([ |
- ROOT, BLOCK, CASE_BLOCK, FOR_GROUP_BLOCK, IMPLIED_BLOCK]) |
- |
- 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 = context_type |
- self.start_token = start_token |
- self.end_token = None |
- |
- self.parent = None |
- self.children = [] |
- |
- if parent: |
- parent.AddChild(self) |
- |
- def __repr__(self): |
- """Returns a string representation of the context object.""" |
- stack = [] |
- context = self |
- while context: |
- stack.append(context.type) |
- 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. |
- |
- Attributes: |
- last_code: The last code token to appear before this one. |
- 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' |
- |
- UNARY_POST_OPERATOR = 'unary_post' |
- |
- BINARY_OPERATOR = 'binary' |
- |
- TERNARY_OPERATOR = 'ternary' |
- |
- def __init__(self): |
- """Initializes a token metadata object.""" |
- self.last_code = None |
- self.context = None |
- self.operator_type = None |
- 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.""" |
- parts = ['%r' % self.context] |
- if self.operator_type: |
- 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): |
- return self.operator_type in (EcmaMetaData.UNARY_OPERATOR, |
- EcmaMetaData.UNARY_POST_OPERATOR) |
- |
- def IsUnaryPostOperator(self): |
- return self.operator_type == EcmaMetaData.UNARY_POST_OPERATOR |
- |
- |
-class EcmaMetaDataPass(object): |
- """A pass that iterates over all tokens and builds metadata about them.""" |
- |
- def __init__(self): |
- """Initialize the meta data pass object.""" |
- self.Reset() |
- |
- def Reset(self): |
- """Resets the metadata pass to prepare for the next file.""" |
- self._token = None |
- self._context = None |
- self._AddContext(EcmaContext.ROOT) |
- self._last_code = None |
- |
- def _CreateContext(self, context_type): |
- """Overridable by subclasses to create the appropriate context type.""" |
- 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, context_type): |
- """Adds a context of the given type to the context stack. |
- |
- Args: |
- context_type: The type of context to create |
- """ |
- self._context = self._CreateContext(context_type) |
- |
- def _PopContext(self): |
- """Moves up one level in the context stack. |
- |
- Returns: |
- The former context. |
- |
- Raises: |
- ParseError: If the root context is popped. |
- """ |
- top_context = self._context |
- top_context.end_token = self._token |
- self._context = top_context.parent |
- if self._context: |
- return top_context |
- else: |
- raise ParseError(self._token) |
- |
- def _PopContextType(self, *stop_types): |
- """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. |
- |
- Returns: |
- The context object of the given type that was popped. |
- """ |
- last = None |
- while not last or last.type not in stop_types: |
- last = self._PopContext() |
- return last |
- |
- def _EndStatement(self): |
- """Process the end of a statement.""" |
- self._PopContextType(EcmaContext.STATEMENT) |
- if self._context.type == EcmaContext.IMPLIED_BLOCK: |
- self._token.metadata.is_implied_block_close = True |
- self._PopContext() |
- |
- def _ProcessContext(self): |
- """Process the context at the current token. |
- |
- Returns: |
- The context that should be assigned to the current token, or None if |
- the current context after this method should be used. |
- |
- Raises: |
- ParseError: When the token appears in an invalid context. |
- """ |
- token = self._token |
- token_type = token.type |
- |
- if self._context.type in EcmaContext.BLOCK_TYPES: |
- # Whenever we're in a block, we add a statement context. We make an |
- # exception for switch statements since they can only contain case: and |
- # default: and therefore don't directly contain statements. |
- # The block we add here may be immediately removed in some cases, but |
- # that causes no harm. |
- parent = self._context.parent |
- if not parent or parent.type != EcmaContext.SWITCH: |
- self._AddContext(EcmaContext.STATEMENT) |
- |
- elif self._context.type == EcmaContext.ARRAY_LITERAL: |
- self._AddContext(EcmaContext.LITERAL_ELEMENT) |
- |
- if token_type == TokenType.START_PAREN: |
- if self._last_code and self._last_code.IsKeyword('for'): |
- # for loops contain multiple statements in the group unlike while, |
- # switch, if, etc. |
- self._AddContext(EcmaContext.FOR_GROUP_BLOCK) |
- else: |
- self._AddContext(EcmaContext.GROUP) |
- |
- elif token_type == TokenType.END_PAREN: |
- result = self._PopContextType(EcmaContext.GROUP, |
- EcmaContext.FOR_GROUP_BLOCK) |
- keyword_token = result.start_token.metadata.last_code |
- # keyword_token will not exist if the open paren is the first line of the |
- # file, for example if all code is wrapped in an immediately executed |
- # annonymous function. |
- if keyword_token and keyword_token.string in ('if', 'for', 'while'): |
- next_code = tokenutil.SearchExcept(token, TokenType.NON_CODE_TYPES) |
- if next_code.type != TokenType.START_BLOCK: |
- # Check for do-while. |
- is_do_while = False |
- pre_keyword_token = keyword_token.metadata.last_code |
- if (pre_keyword_token and |
- pre_keyword_token.type == TokenType.END_BLOCK): |
- start_block_token = pre_keyword_token.metadata.context.start_token |
- is_do_while = start_block_token.metadata.last_code.string == 'do' |
- |
- # If it's not do-while, it's an implied block. |
- if not is_do_while: |
- self._AddContext(EcmaContext.IMPLIED_BLOCK) |
- token.metadata.is_implied_block = True |
- |
- return result |
- |
- # else (not else if) with no open brace after it should be considered the |
- # start of an implied block, similar to the case with if, for, and while |
- # above. |
- elif (token_type == TokenType.KEYWORD and |
- token.string == 'else'): |
- next_code = tokenutil.SearchExcept(token, TokenType.NON_CODE_TYPES) |
- if (next_code.type != TokenType.START_BLOCK and |
- (next_code.type != TokenType.KEYWORD or next_code.string != 'if')): |
- self._AddContext(EcmaContext.IMPLIED_BLOCK) |
- token.metadata.is_implied_block = True |
- |
- elif token_type == TokenType.START_PARAMETERS: |
- self._AddContext(EcmaContext.PARAMETERS) |
- |
- elif token_type == TokenType.END_PARAMETERS: |
- return self._PopContextType(EcmaContext.PARAMETERS) |
- |
- elif token_type == TokenType.START_BRACKET: |
- if (self._last_code and |
- self._last_code.type in TokenType.EXPRESSION_ENDER_TYPES): |
- self._AddContext(EcmaContext.INDEX) |
- else: |
- self._AddContext(EcmaContext.ARRAY_LITERAL) |
- |
- elif token_type == TokenType.END_BRACKET: |
- return self._PopContextType(EcmaContext.INDEX, EcmaContext.ARRAY_LITERAL) |
- |
- elif token_type == TokenType.START_BLOCK: |
- if (self._last_code.type in (TokenType.END_PAREN, |
- TokenType.END_PARAMETERS) or |
- self._last_code.IsKeyword('else') or |
- self._last_code.IsKeyword('do') or |
- self._last_code.IsKeyword('try') or |
- self._last_code.IsKeyword('finally') or |
- (self._last_code.IsOperator(':') and |
- self._last_code.metadata.context.type == EcmaContext.CASE_BLOCK)): |
- # else, do, try, and finally all might have no () before {. |
- # Also, handle the bizzare syntax case 10: {...}. |
- self._AddContext(EcmaContext.BLOCK) |
- else: |
- self._AddContext(EcmaContext.OBJECT_LITERAL) |
- |
- elif token_type == TokenType.END_BLOCK: |
- context = self._PopContextType(EcmaContext.BLOCK, |
- EcmaContext.OBJECT_LITERAL) |
- if self._context.type == EcmaContext.SWITCH: |
- # The end of the block also means the end of the switch statement it |
- # applies to. |
- return self._PopContext() |
- return context |
- |
- elif token.IsKeyword('switch'): |
- self._AddContext(EcmaContext.SWITCH) |
- |
- elif (token_type == TokenType.KEYWORD and |
- 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) |
- |
- elif token.IsOperator(':'): |
- if self._context.type == EcmaContext.OBJECT_LITERAL: |
- self._AddContext(EcmaContext.LITERAL_ELEMENT) |
- |
- elif self._context.type == EcmaContext.TERNARY_TRUE: |
- self._PopContext() |
- self._AddContext(EcmaContext.TERNARY_FALSE) |
- |
- # Handle nested ternary statements like: |
- # foo = bar ? baz ? 1 : 2 : 3 |
- # When we encounter the second ":" the context is |
- # 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) |
- |
- elif self._context.parent.type == EcmaContext.SWITCH: |
- self._AddContext(EcmaContext.CASE_BLOCK) |
- |
- elif token.IsKeyword('var'): |
- self._AddContext(EcmaContext.VAR) |
- |
- elif token.IsOperator(','): |
- while self._context.type not in (EcmaContext.VAR, |
- EcmaContext.ARRAY_LITERAL, |
- EcmaContext.OBJECT_LITERAL, |
- EcmaContext.STATEMENT, |
- EcmaContext.PARAMETERS, |
- EcmaContext.GROUP): |
- self._PopContext() |
- |
- elif token_type == TokenType.SEMICOLON: |
- self._EndStatement() |
- |
- def Process(self, first_token): |
- """Processes the token stream starting with the given token.""" |
- self._token = first_token |
- while self._token: |
- self._ProcessToken() |
- |
- if self._token.IsCode(): |
- self._last_code = self._token |
- |
- self._token = self._token.next |
- |
- try: |
- self._PopContextType(self, EcmaContext.ROOT) |
- except ParseError: |
- # Ignore the "popped to root" error. |
- pass |
- |
- def _ProcessToken(self): |
- """Process the given token.""" |
- token = self._token |
- token.metadata = self._CreateMetaData() |
- context = (self._ProcessContext() or self._context) |
- token.metadata.context = context |
- token.metadata.last_code = self._last_code |
- |
- # Determine the operator type of the token, if applicable. |
- if token.type == TokenType.OPERATOR: |
- token.metadata.operator_type = self._GetOperatorType(token) |
- |
- # Determine if there is an implied semicolon after the token. |
- if token.type != TokenType.SEMICOLON: |
- next_code = tokenutil.SearchExcept(token, TokenType.NON_CODE_TYPES) |
- # A statement like if (x) does not need a semicolon after it |
- is_implied_block = self._context == EcmaContext.IMPLIED_BLOCK |
- is_last_code_in_line = token.IsCode() and ( |
- not next_code or next_code.line_number != token.line_number) |
- is_continued_operator = (token.type == TokenType.OPERATOR and |
- not token.metadata.IsUnaryPostOperator()) |
- is_continued_dot = token.string == '.' |
- next_code_is_operator = next_code and next_code.type == TokenType.OPERATOR |
- 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 or |
- token.type == TokenType.TEMPLATE_STRING_START) |
- 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_operator and |
- not is_continued_dot and |
- not next_code_is_operator and |
- not is_implied_block and |
- not next_code_is_block): |
- token.metadata.is_implied_semicolon = True |
- self._EndStatement() |
- |
- def _StatementCouldEndInContext(self): |
- """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): |
- return True |
- |
- # End of a ternary false branch inside a statement can also be the |
- # end of the statement, for example: |
- # var x = foo ? foo.bar() : null |
- # In this case the statement ends after the null, when the context stack |
- # looks like ternary_false > var > statement > root. |
- if (self._context.type == EcmaContext.TERNARY_FALSE and |
- self._context.parent.type in (EcmaContext.STATEMENT, EcmaContext.VAR)): |
- return True |
- |
- # In all other contexts like object and array literals, ternary true, etc. |
- # the statement can't yet end. |
- return False |
- |
- def _GetOperatorType(self, token): |
- """Returns the operator type of the given operator token. |
- |
- Args: |
- token: The token to get arity for. |
- |
- Returns: |
- The type of the operator. One of the *_OPERATOR constants defined in |
- EcmaMetaData. |
- """ |
- if token.string == '?': |
- return EcmaMetaData.TERNARY_OPERATOR |
- |
- if token.string in TokenType.UNARY_OPERATORS: |
- return EcmaMetaData.UNARY_OPERATOR |
- |
- last_code = token.metadata.last_code |
- if not last_code or last_code.type == TokenType.END_BLOCK: |
- return EcmaMetaData.UNARY_OPERATOR |
- |
- if (token.string in TokenType.UNARY_POST_OPERATORS and |
- last_code.type in TokenType.EXPRESSION_ENDER_TYPES): |
- return EcmaMetaData.UNARY_POST_OPERATOR |
- |
- if (token.string in TokenType.UNARY_OK_OPERATORS and |
- last_code.type not in TokenType.EXPRESSION_ENDER_TYPES and |
- last_code.string not in TokenType.UNARY_POST_OPERATORS): |
- return EcmaMetaData.UNARY_OPERATOR |
- |
- return EcmaMetaData.BINARY_OPERATOR |