Index: third_party/closure_linter/closure_linter/indentation.py |
diff --git a/third_party/closure_linter/closure_linter/indentation.py b/third_party/closure_linter/closure_linter/indentation.py |
deleted file mode 100755 |
index d48ad2b862c43acfb4fbd1c7c5b5b329ecfd4327..0000000000000000000000000000000000000000 |
--- a/third_party/closure_linter/closure_linter/indentation.py |
+++ /dev/null |
@@ -1,617 +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. |
- |
-"""Methods for checking EcmaScript files for indentation issues.""" |
- |
-__author__ = ('robbyw@google.com (Robert Walker)') |
- |
-import gflags as flags |
- |
-from closure_linter import ecmametadatapass |
-from closure_linter import errors |
-from closure_linter import javascripttokens |
-from closure_linter import tokenutil |
-from closure_linter.common import error |
-from closure_linter.common import position |
- |
- |
-flags.DEFINE_boolean('debug_indentation', False, |
- 'Whether to print debugging information for indentation.') |
- |
- |
-# Shorthand |
-Context = ecmametadatapass.EcmaContext |
-Error = error.Error |
-Position = position.Position |
-Type = javascripttokens.JavaScriptTokenType |
- |
- |
-# The general approach: |
-# |
-# 1. Build a stack of tokens that can affect indentation. |
-# For each token, we determine if it is a block or continuation token. |
-# Some tokens need to be temporarily overwritten in case they are removed |
-# before the end of the line. |
-# Much of the work here is determining which tokens to keep on the stack |
-# at each point. Operators, for example, should be removed once their |
-# expression or line is gone, while parentheses must stay until the matching |
-# end parentheses is found. |
-# |
-# 2. Given that stack, determine the allowable indentations. |
-# Due to flexible indentation rules in JavaScript, there may be many |
-# allowable indentations for each stack. We follows the general |
-# "no false positives" approach of GJsLint and build the most permissive |
-# set possible. |
- |
- |
-class TokenInfo(object): |
- """Stores information about a token. |
- |
- Attributes: |
- token: The token |
- is_block: Whether the token represents a block indentation. |
- is_transient: Whether the token should be automatically removed without |
- finding a matching end token. |
- overridden_by: TokenInfo for a token that overrides the indentation that |
- this token would require. |
- is_permanent_override: Whether the override on this token should persist |
- even after the overriding token is removed from the stack. For example: |
- x([ |
- 1], |
- 2); |
- needs this to be set so the last line is not required to be a continuation |
- indent. |
- line_number: The effective line number of this token. Will either be the |
- actual line number or the one before it in the case of a mis-wrapped |
- operator. |
- """ |
- |
- def __init__(self, token, is_block=False): |
- """Initializes a TokenInfo object. |
- |
- Args: |
- token: The token |
- is_block: Whether the token represents a block indentation. |
- """ |
- self.token = token |
- self.overridden_by = None |
- self.is_permanent_override = False |
- self.is_block = is_block |
- self.is_transient = not is_block and token.type not in ( |
- Type.START_PAREN, Type.START_PARAMETERS) |
- self.line_number = token.line_number |
- |
- def __repr__(self): |
- result = '\n %s' % self.token |
- if self.overridden_by: |
- result = '%s OVERRIDDEN [by "%s"]' % ( |
- result, self.overridden_by.token.string) |
- result += ' {is_block: %s, is_transient: %s}' % ( |
- self.is_block, self.is_transient) |
- return result |
- |
- |
-class IndentationRules(object): |
- """EmcaScript indentation rules. |
- |
- Can be used to find common indentation errors in JavaScript, ActionScript and |
- other Ecma like scripting languages. |
- """ |
- |
- def __init__(self): |
- """Initializes the IndentationRules checker.""" |
- self._stack = [] |
- |
- # Map from line number to number of characters it is off in indentation. |
- self._start_index_offset = {} |
- |
- def Finalize(self): |
- if self._stack: |
- old_stack = self._stack |
- self._stack = [] |
- raise Exception('INTERNAL ERROR: indentation stack is not empty: %r' % |
- old_stack) |
- |
- def CheckToken(self, token, state): |
- """Checks a token for indentation errors. |
- |
- Args: |
- token: The current token under consideration |
- state: Additional information about the current tree state |
- |
- Returns: |
- An error array [error code, error string, error token] if the token is |
- improperly indented, or None if indentation is correct. |
- """ |
- |
- token_type = token.type |
- indentation_errors = [] |
- stack = self._stack |
- is_first = self._IsFirstNonWhitespaceTokenInLine(token) |
- |
- # Add tokens that could decrease indentation before checking. |
- if token_type == Type.END_PAREN: |
- self._PopTo(Type.START_PAREN) |
- |
- elif token_type == Type.END_PARAMETERS: |
- self._PopTo(Type.START_PARAMETERS) |
- |
- elif token_type == Type.END_BRACKET: |
- self._PopTo(Type.START_BRACKET) |
- |
- elif token_type == Type.END_BLOCK: |
- start_token = self._PopTo(Type.START_BLOCK) |
- # Check for required goog.scope comment. |
- if start_token: |
- goog_scope = tokenutil.GoogScopeOrNoneFromStartBlock(start_token.token) |
- if goog_scope is not None: |
- if not token.line.endswith('; // goog.scope\n'): |
- if (token.line.find('//') > -1 and |
- token.line.find('goog.scope') > |
- token.line.find('//')): |
- indentation_errors.append([ |
- errors.MALFORMED_END_OF_SCOPE_COMMENT, |
- ('Malformed end of goog.scope comment. Please use the ' |
- 'exact following syntax to close the scope:\n' |
- '}); // goog.scope'), |
- token, |
- Position(token.start_index, token.length)]) |
- else: |
- indentation_errors.append([ |
- errors.MISSING_END_OF_SCOPE_COMMENT, |
- ('Missing comment for end of goog.scope which opened at line ' |
- '%d. End the scope with:\n' |
- '}); // goog.scope' % |
- (start_token.line_number)), |
- token, |
- Position(token.start_index, token.length)]) |
- |
- elif token_type == Type.KEYWORD and token.string in ('case', 'default'): |
- self._Add(self._PopTo(Type.START_BLOCK)) |
- |
- elif token_type == Type.SEMICOLON: |
- self._PopTransient() |
- |
- if (is_first and |
- token_type not in (Type.COMMENT, Type.DOC_PREFIX, Type.STRING_TEXT)): |
- if flags.FLAGS.debug_indentation: |
- print 'Line #%d: stack %r' % (token.line_number, stack) |
- |
- # Ignore lines that start in JsDoc since we don't check them properly yet. |
- # TODO(robbyw): Support checking JsDoc indentation. |
- # Ignore lines that start as multi-line strings since indentation is N/A. |
- # Ignore lines that start with operators since we report that already. |
- # Ignore lines with tabs since we report that already. |
- expected = self._GetAllowableIndentations() |
- actual = self._GetActualIndentation(token) |
- |
- # Special case comments describing else, case, and default. Allow them |
- # to outdent to the parent block. |
- if token_type in Type.COMMENT_TYPES: |
- next_code = tokenutil.SearchExcept(token, Type.NON_CODE_TYPES) |
- if next_code and next_code.type == Type.END_BLOCK: |
- next_code = tokenutil.SearchExcept(next_code, Type.NON_CODE_TYPES) |
- if next_code and next_code.string in ('else', 'case', 'default'): |
- # TODO(robbyw): This almost certainly introduces false negatives. |
- expected |= self._AddToEach(expected, -2) |
- |
- if actual >= 0 and actual not in expected: |
- expected = sorted(expected) |
- indentation_errors.append([ |
- errors.WRONG_INDENTATION, |
- 'Wrong indentation: expected any of {%s} but got %d' % ( |
- ', '.join('%d' % x for x in expected if x < 80), actual), |
- token, |
- Position(actual, expected[0])]) |
- self._start_index_offset[token.line_number] = expected[0] - actual |
- |
- # Add tokens that could increase indentation. |
- if token_type == Type.START_BRACKET: |
- self._Add(TokenInfo( |
- token=token, |
- is_block=token.metadata.context.type == Context.ARRAY_LITERAL)) |
- |
- elif token_type == Type.START_BLOCK or token.metadata.is_implied_block: |
- self._Add(TokenInfo(token=token, is_block=True)) |
- |
- elif token_type in (Type.START_PAREN, Type.START_PARAMETERS): |
- self._Add(TokenInfo(token=token, is_block=False)) |
- |
- elif token_type == Type.KEYWORD and token.string == 'return': |
- self._Add(TokenInfo(token)) |
- |
- elif not token.IsLastInLine() and ( |
- token.IsAssignment() or token.IsOperator('?')): |
- self._Add(TokenInfo(token=token)) |
- |
- # Handle implied block closes. |
- if token.metadata.is_implied_block_close: |
- self._PopToImpliedBlock() |
- |
- # Add some tokens only if they appear at the end of the line. |
- is_last = self._IsLastCodeInLine(token) |
- if is_last: |
- next_code_token = tokenutil.GetNextCodeToken(token) |
- # Increase required indentation if this is an overlong wrapped statement |
- # ending in an operator. |
- if token_type == Type.OPERATOR: |
- if token.string == ':': |
- if stack and stack[-1].token.string == '?': |
- # When a ternary : is on a different line than its '?', it doesn't |
- # add indentation. |
- if token.line_number == stack[-1].token.line_number: |
- self._Add(TokenInfo(token)) |
- elif token.metadata.context.type == Context.CASE_BLOCK: |
- # Pop transient tokens from say, line continuations, e.g., |
- # case x. |
- # y: |
- # Want to pop the transient 4 space continuation indent. |
- self._PopTransient() |
- # Starting the body of the case statement, which is a type of |
- # block. |
- self._Add(TokenInfo(token=token, is_block=True)) |
- elif token.metadata.context.type == Context.LITERAL_ELEMENT: |
- # When in an object literal, acts as operator indicating line |
- # continuations. |
- self._Add(TokenInfo(token)) |
- else: |
- # ':' might also be a statement label, no effect on indentation in |
- # this case. |
- pass |
- |
- elif token.string != ',': |
- self._Add(TokenInfo(token)) |
- else: |
- # The token is a comma. |
- if token.metadata.context.type == Context.VAR: |
- self._Add(TokenInfo(token)) |
- elif token.metadata.context.type != Context.PARAMETERS: |
- self._PopTransient() |
- # Increase required indentation if this is the end of a statement that's |
- # continued with an operator on the next line (e.g. the '.'). |
- elif (next_code_token and next_code_token.type == Type.OPERATOR and |
- not next_code_token.metadata.IsUnaryOperator()): |
- self._Add(TokenInfo(token)) |
- elif token_type == Type.PARAMETERS and token.string.endswith(','): |
- # Parameter lists. |
- self._Add(TokenInfo(token)) |
- elif token.IsKeyword('var'): |
- self._Add(TokenInfo(token)) |
- elif token.metadata.is_implied_semicolon: |
- self._PopTransient() |
- elif token.IsAssignment(): |
- self._Add(TokenInfo(token)) |
- |
- return indentation_errors |
- |
- def _AddToEach(self, original, amount): |
- """Returns a new set with the given amount added to each element. |
- |
- Args: |
- original: The original set of numbers |
- amount: The amount to add to each element |
- |
- Returns: |
- A new set containing each element of the original set added to the amount. |
- """ |
- return set([x + amount for x in original]) |
- |
- _HARD_STOP_TYPES = (Type.START_PAREN, Type.START_PARAMETERS, |
- Type.START_BRACKET) |
- |
- _HARD_STOP_STRINGS = ('return', '?') |
- |
- def _IsHardStop(self, token): |
- """Determines if the given token can have a hard stop after it. |
- |
- Args: |
- token: token to examine |
- |
- Returns: |
- Whether the token can have a hard stop after it. |
- |
- Hard stops are indentations defined by the position of another token as in |
- indentation lined up with return, (, [, and ?. |
- """ |
- return (token.type in self._HARD_STOP_TYPES or |
- token.string in self._HARD_STOP_STRINGS or |
- token.IsAssignment()) |
- |
- def _GetAllowableIndentations(self): |
- """Computes the set of allowable indentations. |
- |
- Returns: |
- The set of allowable indentations, given the current stack. |
- """ |
- expected = set([0]) |
- hard_stops = set([]) |
- |
- # Whether the tokens are still in the same continuation, meaning additional |
- # indentation is optional. As an example: |
- # x = 5 + |
- # 6 + |
- # 7; |
- # The second '+' does not add any required indentation. |
- in_same_continuation = False |
- |
- for token_info in self._stack: |
- token = token_info.token |
- |
- # Handle normal additive indentation tokens. |
- if not token_info.overridden_by and token.string != 'return': |
- if token_info.is_block: |
- expected = self._AddToEach(expected, 2) |
- hard_stops = self._AddToEach(hard_stops, 2) |
- in_same_continuation = False |
- elif in_same_continuation: |
- expected |= self._AddToEach(expected, 4) |
- hard_stops |= self._AddToEach(hard_stops, 4) |
- else: |
- expected = self._AddToEach(expected, 4) |
- hard_stops |= self._AddToEach(hard_stops, 4) |
- in_same_continuation = True |
- |
- # Handle hard stops after (, [, return, =, and ? |
- if self._IsHardStop(token): |
- override_is_hard_stop = (token_info.overridden_by and |
- self._IsHardStop( |
- token_info.overridden_by.token)) |
- if token.type == Type.START_PAREN and token.previous: |
- # For someFunction(...) we allow to indent at the beginning of the |
- # identifier +4 |
- prev = token.previous |
- if (prev.type == Type.IDENTIFIER and |
- prev.line_number == token.line_number): |
- hard_stops.add(prev.start_index + 4) |
- if not override_is_hard_stop: |
- start_index = token.start_index |
- if token.line_number in self._start_index_offset: |
- start_index += self._start_index_offset[token.line_number] |
- if (token.type in (Type.START_PAREN, Type.START_PARAMETERS) and |
- not token_info.overridden_by): |
- hard_stops.add(start_index + 1) |
- |
- elif token.string == 'return' and not token_info.overridden_by: |
- hard_stops.add(start_index + 7) |
- |
- elif token.type == Type.START_BRACKET: |
- hard_stops.add(start_index + 1) |
- |
- elif token.IsAssignment(): |
- hard_stops.add(start_index + len(token.string) + 1) |
- |
- elif token.IsOperator('?') and not token_info.overridden_by: |
- hard_stops.add(start_index + 2) |
- |
- return (expected | hard_stops) or set([0]) |
- |
- def _GetActualIndentation(self, token): |
- """Gets the actual indentation of the line containing the given token. |
- |
- Args: |
- token: Any token on the line. |
- |
- Returns: |
- The actual indentation of the line containing the given token. Returns |
- -1 if this line should be ignored due to the presence of tabs. |
- """ |
- # Move to the first token in the line |
- token = tokenutil.GetFirstTokenInSameLine(token) |
- |
- # If it is whitespace, it is the indentation. |
- if token.type == Type.WHITESPACE: |
- if token.string.find('\t') >= 0: |
- return -1 |
- else: |
- return len(token.string) |
- elif token.type == Type.PARAMETERS: |
- return len(token.string) - len(token.string.lstrip()) |
- else: |
- return 0 |
- |
- def _IsFirstNonWhitespaceTokenInLine(self, token): |
- """Determines if the given token is the first non-space token on its line. |
- |
- Args: |
- token: The token. |
- |
- Returns: |
- True if the token is the first non-whitespace token on its line. |
- """ |
- if token.type in (Type.WHITESPACE, Type.BLANK_LINE): |
- return False |
- if token.IsFirstInLine(): |
- return True |
- return (token.previous and token.previous.IsFirstInLine() and |
- token.previous.type == Type.WHITESPACE) |
- |
- def _IsLastCodeInLine(self, token): |
- """Determines if the given token is the last code token on its line. |
- |
- Args: |
- token: The token. |
- |
- Returns: |
- True if the token is the last code token on its line. |
- """ |
- if token.type in Type.NON_CODE_TYPES: |
- return False |
- start_token = token |
- while True: |
- token = token.next |
- if not token or token.line_number != start_token.line_number: |
- return True |
- if token.type not in Type.NON_CODE_TYPES: |
- return False |
- |
- def _AllFunctionPropertyAssignTokens(self, start_token, end_token): |
- """Checks if tokens are (likely) a valid function property assignment. |
- |
- Args: |
- start_token: Start of the token range. |
- end_token: End of the token range. |
- |
- Returns: |
- True if all tokens between start_token and end_token are legal tokens |
- within a function declaration and assignment into a property. |
- """ |
- for token in tokenutil.GetTokenRange(start_token, end_token): |
- fn_decl_tokens = (Type.FUNCTION_DECLARATION, |
- Type.PARAMETERS, |
- Type.START_PARAMETERS, |
- Type.END_PARAMETERS, |
- Type.END_PAREN) |
- if (token.type not in fn_decl_tokens and |
- token.IsCode() and |
- not tokenutil.IsIdentifierOrDot(token) and |
- not token.IsAssignment() and |
- not (token.type == Type.OPERATOR and token.string == ',')): |
- return False |
- return True |
- |
- def _Add(self, token_info): |
- """Adds the given token info to the stack. |
- |
- Args: |
- token_info: The token information to add. |
- """ |
- if self._stack and self._stack[-1].token == token_info.token: |
- # Don't add the same token twice. |
- return |
- |
- if token_info.is_block or token_info.token.type == Type.START_PAREN: |
- scope_token = tokenutil.GoogScopeOrNoneFromStartBlock(token_info.token) |
- token_info.overridden_by = TokenInfo(scope_token) if scope_token else None |
- |
- if (token_info.token.type == Type.START_BLOCK and |
- token_info.token.metadata.context.type == Context.BLOCK): |
- # Handle function() {} assignments: their block contents get special |
- # treatment and are allowed to just indent by two whitespace. |
- # For example |
- # long.long.name = function( |
- # a) { |
- # In this case the { and the = are on different lines. But the |
- # override should still apply for all previous stack tokens that are |
- # part of an assignment of a block. |
- |
- has_assignment = any(x for x in self._stack if x.token.IsAssignment()) |
- if has_assignment: |
- last_token = token_info.token.previous |
- for stack_info in reversed(self._stack): |
- if (last_token and |
- not self._AllFunctionPropertyAssignTokens(stack_info.token, |
- last_token)): |
- break |
- stack_info.overridden_by = token_info |
- stack_info.is_permanent_override = True |
- last_token = stack_info.token |
- |
- index = len(self._stack) - 1 |
- while index >= 0: |
- stack_info = self._stack[index] |
- stack_token = stack_info.token |
- |
- if stack_info.line_number == token_info.line_number: |
- # In general, tokens only override each other when they are on |
- # the same line. |
- stack_info.overridden_by = token_info |
- if (token_info.token.type == Type.START_BLOCK and |
- (stack_token.IsAssignment() or |
- stack_token.type in (Type.IDENTIFIER, Type.START_PAREN))): |
- # Multi-line blocks have lasting overrides, as in: |
- # callFn({ |
- # a: 10 |
- # }, |
- # 30); |
- # b/11450054. If a string is not closed properly then close_block |
- # could be null. |
- close_block = token_info.token.metadata.context.end_token |
- stack_info.is_permanent_override = close_block and ( |
- close_block.line_number != token_info.token.line_number) |
- else: |
- break |
- index -= 1 |
- |
- self._stack.append(token_info) |
- |
- def _Pop(self): |
- """Pops the top token from the stack. |
- |
- Returns: |
- The popped token info. |
- """ |
- token_info = self._stack.pop() |
- if token_info.token.type not in (Type.START_BLOCK, Type.START_BRACKET): |
- # Remove any temporary overrides. |
- self._RemoveOverrides(token_info) |
- else: |
- # For braces and brackets, which can be object and array literals, remove |
- # overrides when the literal is closed on the same line. |
- token_check = token_info.token |
- same_type = token_check.type |
- goal_type = None |
- if token_info.token.type == Type.START_BRACKET: |
- goal_type = Type.END_BRACKET |
- else: |
- goal_type = Type.END_BLOCK |
- line_number = token_info.token.line_number |
- count = 0 |
- while token_check and token_check.line_number == line_number: |
- if token_check.type == goal_type: |
- count -= 1 |
- if not count: |
- self._RemoveOverrides(token_info) |
- break |
- if token_check.type == same_type: |
- count += 1 |
- token_check = token_check.next |
- return token_info |
- |
- def _PopToImpliedBlock(self): |
- """Pops the stack until an implied block token is found.""" |
- while not self._Pop().token.metadata.is_implied_block: |
- pass |
- |
- def _PopTo(self, stop_type): |
- """Pops the stack until a token of the given type is popped. |
- |
- Args: |
- stop_type: The type of token to pop to. |
- |
- Returns: |
- The token info of the given type that was popped. |
- """ |
- last = None |
- while True: |
- last = self._Pop() |
- if last.token.type == stop_type: |
- break |
- return last |
- |
- def _RemoveOverrides(self, token_info): |
- """Marks any token that was overridden by this token as active again. |
- |
- Args: |
- token_info: The token that is being removed from the stack. |
- """ |
- for stack_token in self._stack: |
- if (stack_token.overridden_by == token_info and |
- not stack_token.is_permanent_override): |
- stack_token.overridden_by = None |
- |
- def _PopTransient(self): |
- """Pops all transient tokens - i.e. not blocks, literals, or parens.""" |
- while self._stack and self._stack[-1].is_transient: |
- self._Pop() |