12da489cd246702bee5938545b18a6f710ed214bcJamie Gennis#!/usr/bin/env python 22da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# 32da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# Copyright 2010 The Closure Linter Authors. All Rights Reserved. 42da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# 52da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# Licensed under the Apache License, Version 2.0 (the "License"); 62da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# you may not use this file except in compliance with the License. 72da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# You may obtain a copy of the License at 82da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# 92da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# http://www.apache.org/licenses/LICENSE-2.0 102da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# 112da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# Unless required by applicable law or agreed to in writing, software 122da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# distributed under the License is distributed on an "AS-IS" BASIS, 132da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 142da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# See the License for the specific language governing permissions and 152da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# limitations under the License. 162da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 172da489cd246702bee5938545b18a6f710ed214bcJamie Gennis"""Methods for checking EcmaScript files for indentation issues.""" 182da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 192da489cd246702bee5938545b18a6f710ed214bcJamie Gennis__author__ = ('robbyw@google.com (Robert Walker)') 202da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 212da489cd246702bee5938545b18a6f710ed214bcJamie Gennisfrom closure_linter import ecmametadatapass 222da489cd246702bee5938545b18a6f710ed214bcJamie Gennisfrom closure_linter import errors 232da489cd246702bee5938545b18a6f710ed214bcJamie Gennisfrom closure_linter import javascripttokens 242da489cd246702bee5938545b18a6f710ed214bcJamie Gennisfrom closure_linter import tokenutil 252da489cd246702bee5938545b18a6f710ed214bcJamie Gennisfrom closure_linter.common import error 262da489cd246702bee5938545b18a6f710ed214bcJamie Gennisfrom closure_linter.common import position 272da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 282da489cd246702bee5938545b18a6f710ed214bcJamie Gennisimport gflags as flags 292da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 302da489cd246702bee5938545b18a6f710ed214bcJamie Gennisflags.DEFINE_boolean('debug_indentation', False, 312da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 'Whether to print debugging information for indentation.') 322da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 332da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 342da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# Shorthand 352da489cd246702bee5938545b18a6f710ed214bcJamie GennisContext = ecmametadatapass.EcmaContext 362da489cd246702bee5938545b18a6f710ed214bcJamie GennisError = error.Error 372da489cd246702bee5938545b18a6f710ed214bcJamie GennisPosition = position.Position 382da489cd246702bee5938545b18a6f710ed214bcJamie GennisType = javascripttokens.JavaScriptTokenType 392da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 402da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 412da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# The general approach: 422da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# 432da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# 1. Build a stack of tokens that can affect indentation. 442da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# For each token, we determine if it is a block or continuation token. 452da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# Some tokens need to be temporarily overwritten in case they are removed 462da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# before the end of the line. 472da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# Much of the work here is determining which tokens to keep on the stack 482da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# at each point. Operators, for example, should be removed once their 492da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# expression or line is gone, while parentheses must stay until the matching 502da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# end parentheses is found. 512da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# 522da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# 2. Given that stack, determine the allowable indentations. 532da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# Due to flexible indentation rules in JavaScript, there may be many 542da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# allowable indentations for each stack. We follows the general 552da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# "no false positives" approach of GJsLint and build the most permissive 562da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# set possible. 572da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 582da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 592da489cd246702bee5938545b18a6f710ed214bcJamie Gennisclass TokenInfo(object): 602da489cd246702bee5938545b18a6f710ed214bcJamie Gennis """Stores information about a token. 612da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 622da489cd246702bee5938545b18a6f710ed214bcJamie Gennis Attributes: 632da489cd246702bee5938545b18a6f710ed214bcJamie Gennis token: The token 642da489cd246702bee5938545b18a6f710ed214bcJamie Gennis is_block: Whether the token represents a block indentation. 652da489cd246702bee5938545b18a6f710ed214bcJamie Gennis is_transient: Whether the token should be automatically removed without 662da489cd246702bee5938545b18a6f710ed214bcJamie Gennis finding a matching end token. 672da489cd246702bee5938545b18a6f710ed214bcJamie Gennis overridden_by: TokenInfo for a token that overrides the indentation that 682da489cd246702bee5938545b18a6f710ed214bcJamie Gennis this token would require. 692da489cd246702bee5938545b18a6f710ed214bcJamie Gennis is_permanent_override: Whether the override on this token should persist 702da489cd246702bee5938545b18a6f710ed214bcJamie Gennis even after the overriding token is removed from the stack. For example: 712da489cd246702bee5938545b18a6f710ed214bcJamie Gennis x([ 722da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 1], 732da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 2); 742da489cd246702bee5938545b18a6f710ed214bcJamie Gennis needs this to be set so the last line is not required to be a continuation 752da489cd246702bee5938545b18a6f710ed214bcJamie Gennis indent. 762da489cd246702bee5938545b18a6f710ed214bcJamie Gennis line_number: The effective line number of this token. Will either be the 772da489cd246702bee5938545b18a6f710ed214bcJamie Gennis actual line number or the one before it in the case of a mis-wrapped 782da489cd246702bee5938545b18a6f710ed214bcJamie Gennis operator. 792da489cd246702bee5938545b18a6f710ed214bcJamie Gennis """ 802da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 812da489cd246702bee5938545b18a6f710ed214bcJamie Gennis def __init__(self, token, is_block=False): 822da489cd246702bee5938545b18a6f710ed214bcJamie Gennis """Initializes a TokenInfo object. 832da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 842da489cd246702bee5938545b18a6f710ed214bcJamie Gennis Args: 852da489cd246702bee5938545b18a6f710ed214bcJamie Gennis token: The token 862da489cd246702bee5938545b18a6f710ed214bcJamie Gennis is_block: Whether the token represents a block indentation. 872da489cd246702bee5938545b18a6f710ed214bcJamie Gennis """ 882da489cd246702bee5938545b18a6f710ed214bcJamie Gennis self.token = token 892da489cd246702bee5938545b18a6f710ed214bcJamie Gennis self.overridden_by = None 902da489cd246702bee5938545b18a6f710ed214bcJamie Gennis self.is_permanent_override = False 912da489cd246702bee5938545b18a6f710ed214bcJamie Gennis self.is_block = is_block 922da489cd246702bee5938545b18a6f710ed214bcJamie Gennis self.is_transient = not is_block and not token.type in ( 932da489cd246702bee5938545b18a6f710ed214bcJamie Gennis Type.START_PAREN, Type.START_PARAMETERS) 942da489cd246702bee5938545b18a6f710ed214bcJamie Gennis self.line_number = token.line_number 952da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 962da489cd246702bee5938545b18a6f710ed214bcJamie Gennis def __repr__(self): 972da489cd246702bee5938545b18a6f710ed214bcJamie Gennis result = '\n %s' % self.token 982da489cd246702bee5938545b18a6f710ed214bcJamie Gennis if self.overridden_by: 992da489cd246702bee5938545b18a6f710ed214bcJamie Gennis result = '%s OVERRIDDEN [by "%s"]' % ( 1002da489cd246702bee5938545b18a6f710ed214bcJamie Gennis result, self.overridden_by.token.string) 1012da489cd246702bee5938545b18a6f710ed214bcJamie Gennis result += ' {is_block: %s, is_transient: %s}' % ( 1022da489cd246702bee5938545b18a6f710ed214bcJamie Gennis self.is_block, self.is_transient) 1032da489cd246702bee5938545b18a6f710ed214bcJamie Gennis return result 1042da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 1052da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 1062da489cd246702bee5938545b18a6f710ed214bcJamie Gennisclass IndentationRules(object): 1072da489cd246702bee5938545b18a6f710ed214bcJamie Gennis """EmcaScript indentation rules. 1082da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 1092da489cd246702bee5938545b18a6f710ed214bcJamie Gennis Can be used to find common indentation errors in JavaScript, ActionScript and 1102da489cd246702bee5938545b18a6f710ed214bcJamie Gennis other Ecma like scripting languages. 1112da489cd246702bee5938545b18a6f710ed214bcJamie Gennis """ 1122da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 1132da489cd246702bee5938545b18a6f710ed214bcJamie Gennis def __init__(self): 1142da489cd246702bee5938545b18a6f710ed214bcJamie Gennis """Initializes the IndentationRules checker.""" 1152da489cd246702bee5938545b18a6f710ed214bcJamie Gennis self._stack = [] 1162da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 1172da489cd246702bee5938545b18a6f710ed214bcJamie Gennis # Map from line number to number of characters it is off in indentation. 1182da489cd246702bee5938545b18a6f710ed214bcJamie Gennis self._start_index_offset = {} 1192da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 1202da489cd246702bee5938545b18a6f710ed214bcJamie Gennis def Finalize(self): 1212da489cd246702bee5938545b18a6f710ed214bcJamie Gennis if self._stack: 1222da489cd246702bee5938545b18a6f710ed214bcJamie Gennis old_stack = self._stack 1232da489cd246702bee5938545b18a6f710ed214bcJamie Gennis self._stack = [] 1242da489cd246702bee5938545b18a6f710ed214bcJamie Gennis raise Exception("INTERNAL ERROR: indentation stack is not empty: %r" % 1252da489cd246702bee5938545b18a6f710ed214bcJamie Gennis old_stack) 1262da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 1272da489cd246702bee5938545b18a6f710ed214bcJamie Gennis def CheckToken(self, token, state): 1282da489cd246702bee5938545b18a6f710ed214bcJamie Gennis """Checks a token for indentation errors. 1292da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 1302da489cd246702bee5938545b18a6f710ed214bcJamie Gennis Args: 1312da489cd246702bee5938545b18a6f710ed214bcJamie Gennis token: The current token under consideration 1322da489cd246702bee5938545b18a6f710ed214bcJamie Gennis state: Additional information about the current tree state 1332da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 1342da489cd246702bee5938545b18a6f710ed214bcJamie Gennis Returns: 1352da489cd246702bee5938545b18a6f710ed214bcJamie Gennis An error array [error code, error string, error token] if the token is 1362da489cd246702bee5938545b18a6f710ed214bcJamie Gennis improperly indented, or None if indentation is correct. 1372da489cd246702bee5938545b18a6f710ed214bcJamie Gennis """ 1382da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 1392da489cd246702bee5938545b18a6f710ed214bcJamie Gennis token_type = token.type 1402da489cd246702bee5938545b18a6f710ed214bcJamie Gennis indentation_errors = [] 1412da489cd246702bee5938545b18a6f710ed214bcJamie Gennis stack = self._stack 1422da489cd246702bee5938545b18a6f710ed214bcJamie Gennis is_first = self._IsFirstNonWhitespaceTokenInLine(token) 1432da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 1442da489cd246702bee5938545b18a6f710ed214bcJamie Gennis # Add tokens that could decrease indentation before checking. 1452da489cd246702bee5938545b18a6f710ed214bcJamie Gennis if token_type == Type.END_PAREN: 1462da489cd246702bee5938545b18a6f710ed214bcJamie Gennis self._PopTo(Type.START_PAREN) 1472da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 1482da489cd246702bee5938545b18a6f710ed214bcJamie Gennis elif token_type == Type.END_PARAMETERS: 1492da489cd246702bee5938545b18a6f710ed214bcJamie Gennis self._PopTo(Type.START_PARAMETERS) 1502da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 1512da489cd246702bee5938545b18a6f710ed214bcJamie Gennis elif token_type == Type.END_BRACKET: 1522da489cd246702bee5938545b18a6f710ed214bcJamie Gennis self._PopTo(Type.START_BRACKET) 1532da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 1542da489cd246702bee5938545b18a6f710ed214bcJamie Gennis elif token_type == Type.END_BLOCK: 1552da489cd246702bee5938545b18a6f710ed214bcJamie Gennis start_token = self._PopTo(Type.START_BLOCK) 1562da489cd246702bee5938545b18a6f710ed214bcJamie Gennis # Check for required goog.scope comment. 1572da489cd246702bee5938545b18a6f710ed214bcJamie Gennis if start_token: 1582da489cd246702bee5938545b18a6f710ed214bcJamie Gennis goog_scope = self._GoogScopeOrNone(start_token.token) 1592da489cd246702bee5938545b18a6f710ed214bcJamie Gennis if goog_scope is not None: 1602da489cd246702bee5938545b18a6f710ed214bcJamie Gennis if not token.line.endswith('; // goog.scope\n'): 1612da489cd246702bee5938545b18a6f710ed214bcJamie Gennis if (token.line.find('//') > -1 and 1622da489cd246702bee5938545b18a6f710ed214bcJamie Gennis token.line.find('goog.scope') > 1632da489cd246702bee5938545b18a6f710ed214bcJamie Gennis token.line.find('//')): 1642da489cd246702bee5938545b18a6f710ed214bcJamie Gennis indentation_errors.append([ 1652da489cd246702bee5938545b18a6f710ed214bcJamie Gennis errors.MALFORMED_END_OF_SCOPE_COMMENT, 1662da489cd246702bee5938545b18a6f710ed214bcJamie Gennis ('Malformed end of goog.scope comment. Please use the ' 1672da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 'exact following syntax to close the scope:\n' 1682da489cd246702bee5938545b18a6f710ed214bcJamie Gennis '}); // goog.scope'), 1692da489cd246702bee5938545b18a6f710ed214bcJamie Gennis token, 1702da489cd246702bee5938545b18a6f710ed214bcJamie Gennis Position(token.start_index, token.length)]) 1712da489cd246702bee5938545b18a6f710ed214bcJamie Gennis else: 1722da489cd246702bee5938545b18a6f710ed214bcJamie Gennis indentation_errors.append([ 1732da489cd246702bee5938545b18a6f710ed214bcJamie Gennis errors.MISSING_END_OF_SCOPE_COMMENT, 1742da489cd246702bee5938545b18a6f710ed214bcJamie Gennis ('Missing comment for end of goog.scope which opened at line ' 1752da489cd246702bee5938545b18a6f710ed214bcJamie Gennis '%d. End the scope with:\n' 1762da489cd246702bee5938545b18a6f710ed214bcJamie Gennis '}); // goog.scope' % 1772da489cd246702bee5938545b18a6f710ed214bcJamie Gennis (start_token.line_number)), 1782da489cd246702bee5938545b18a6f710ed214bcJamie Gennis token, 1792da489cd246702bee5938545b18a6f710ed214bcJamie Gennis Position(token.start_index, token.length)]) 1802da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 1812da489cd246702bee5938545b18a6f710ed214bcJamie Gennis elif token_type == Type.KEYWORD and token.string in ('case', 'default'): 1822da489cd246702bee5938545b18a6f710ed214bcJamie Gennis self._Add(self._PopTo(Type.START_BLOCK)) 1832da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 1842da489cd246702bee5938545b18a6f710ed214bcJamie Gennis elif is_first and token.string == '.': 1852da489cd246702bee5938545b18a6f710ed214bcJamie Gennis # This token should have been on the previous line, so treat it as if it 1862da489cd246702bee5938545b18a6f710ed214bcJamie Gennis # was there. 1872da489cd246702bee5938545b18a6f710ed214bcJamie Gennis info = TokenInfo(token) 1882da489cd246702bee5938545b18a6f710ed214bcJamie Gennis info.line_number = token.line_number - 1 1892da489cd246702bee5938545b18a6f710ed214bcJamie Gennis self._Add(info) 1902da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 1912da489cd246702bee5938545b18a6f710ed214bcJamie Gennis elif token_type == Type.SEMICOLON: 1922da489cd246702bee5938545b18a6f710ed214bcJamie Gennis self._PopTransient() 1932da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 1942da489cd246702bee5938545b18a6f710ed214bcJamie Gennis not_binary_operator = (token_type != Type.OPERATOR or 1952da489cd246702bee5938545b18a6f710ed214bcJamie Gennis token.metadata.IsUnaryOperator()) 1962da489cd246702bee5938545b18a6f710ed214bcJamie Gennis not_dot = token.string != '.' 1972da489cd246702bee5938545b18a6f710ed214bcJamie Gennis if is_first and not_binary_operator and not_dot and token.type not in ( 1982da489cd246702bee5938545b18a6f710ed214bcJamie Gennis Type.COMMENT, Type.DOC_PREFIX, Type.STRING_TEXT): 1992da489cd246702bee5938545b18a6f710ed214bcJamie Gennis if flags.FLAGS.debug_indentation: 2002da489cd246702bee5938545b18a6f710ed214bcJamie Gennis print 'Line #%d: stack %r' % (token.line_number, stack) 2012da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 2022da489cd246702bee5938545b18a6f710ed214bcJamie Gennis # Ignore lines that start in JsDoc since we don't check them properly yet. 2032da489cd246702bee5938545b18a6f710ed214bcJamie Gennis # TODO(robbyw): Support checking JsDoc indentation. 2042da489cd246702bee5938545b18a6f710ed214bcJamie Gennis # Ignore lines that start as multi-line strings since indentation is N/A. 2052da489cd246702bee5938545b18a6f710ed214bcJamie Gennis # Ignore lines that start with operators since we report that already. 2062da489cd246702bee5938545b18a6f710ed214bcJamie Gennis # Ignore lines with tabs since we report that already. 2072da489cd246702bee5938545b18a6f710ed214bcJamie Gennis expected = self._GetAllowableIndentations() 2082da489cd246702bee5938545b18a6f710ed214bcJamie Gennis actual = self._GetActualIndentation(token) 2092da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 2102da489cd246702bee5938545b18a6f710ed214bcJamie Gennis # Special case comments describing else, case, and default. Allow them 2112da489cd246702bee5938545b18a6f710ed214bcJamie Gennis # to outdent to the parent block. 2122da489cd246702bee5938545b18a6f710ed214bcJamie Gennis if token_type in Type.COMMENT_TYPES: 2132da489cd246702bee5938545b18a6f710ed214bcJamie Gennis next_code = tokenutil.SearchExcept(token, Type.NON_CODE_TYPES) 2142da489cd246702bee5938545b18a6f710ed214bcJamie Gennis if next_code and next_code.type == Type.END_BLOCK: 2152da489cd246702bee5938545b18a6f710ed214bcJamie Gennis next_code = tokenutil.SearchExcept(next_code, Type.NON_CODE_TYPES) 2162da489cd246702bee5938545b18a6f710ed214bcJamie Gennis if next_code and next_code.string in ('else', 'case', 'default'): 2172da489cd246702bee5938545b18a6f710ed214bcJamie Gennis # TODO(robbyw): This almost certainly introduces false negatives. 2182da489cd246702bee5938545b18a6f710ed214bcJamie Gennis expected |= self._AddToEach(expected, -2) 2192da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 2202da489cd246702bee5938545b18a6f710ed214bcJamie Gennis if actual >= 0 and actual not in expected: 2212da489cd246702bee5938545b18a6f710ed214bcJamie Gennis expected = sorted(expected) 2222da489cd246702bee5938545b18a6f710ed214bcJamie Gennis indentation_errors.append([ 2232da489cd246702bee5938545b18a6f710ed214bcJamie Gennis errors.WRONG_INDENTATION, 2242da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 'Wrong indentation: expected any of {%s} but got %d' % ( 2252da489cd246702bee5938545b18a6f710ed214bcJamie Gennis ', '.join( 2262da489cd246702bee5938545b18a6f710ed214bcJamie Gennis ['%d' % x for x in expected]), actual), 2272da489cd246702bee5938545b18a6f710ed214bcJamie Gennis token, 2282da489cd246702bee5938545b18a6f710ed214bcJamie Gennis Position(actual, expected[0])]) 2292da489cd246702bee5938545b18a6f710ed214bcJamie Gennis self._start_index_offset[token.line_number] = expected[0] - actual 2302da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 2312da489cd246702bee5938545b18a6f710ed214bcJamie Gennis # Add tokens that could increase indentation. 2322da489cd246702bee5938545b18a6f710ed214bcJamie Gennis if token_type == Type.START_BRACKET: 2332da489cd246702bee5938545b18a6f710ed214bcJamie Gennis self._Add(TokenInfo(token=token, 2342da489cd246702bee5938545b18a6f710ed214bcJamie Gennis is_block=token.metadata.context.type == Context.ARRAY_LITERAL)) 2352da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 2362da489cd246702bee5938545b18a6f710ed214bcJamie Gennis elif token_type == Type.START_BLOCK or token.metadata.is_implied_block: 2372da489cd246702bee5938545b18a6f710ed214bcJamie Gennis self._Add(TokenInfo(token=token, is_block=True)) 2382da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 2392da489cd246702bee5938545b18a6f710ed214bcJamie Gennis elif token_type in (Type.START_PAREN, Type.START_PARAMETERS): 2402da489cd246702bee5938545b18a6f710ed214bcJamie Gennis self._Add(TokenInfo(token=token, is_block=False)) 2412da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 2422da489cd246702bee5938545b18a6f710ed214bcJamie Gennis elif token_type == Type.KEYWORD and token.string == 'return': 2432da489cd246702bee5938545b18a6f710ed214bcJamie Gennis self._Add(TokenInfo(token)) 2442da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 2452da489cd246702bee5938545b18a6f710ed214bcJamie Gennis elif not token.IsLastInLine() and ( 2462da489cd246702bee5938545b18a6f710ed214bcJamie Gennis token.IsAssignment() or token.IsOperator('?')): 2472da489cd246702bee5938545b18a6f710ed214bcJamie Gennis self._Add(TokenInfo(token=token)) 2482da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 2492da489cd246702bee5938545b18a6f710ed214bcJamie Gennis # Handle implied block closes. 2502da489cd246702bee5938545b18a6f710ed214bcJamie Gennis if token.metadata.is_implied_block_close: 2512da489cd246702bee5938545b18a6f710ed214bcJamie Gennis self._PopToImpliedBlock() 2522da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 2532da489cd246702bee5938545b18a6f710ed214bcJamie Gennis # Add some tokens only if they appear at the end of the line. 2542da489cd246702bee5938545b18a6f710ed214bcJamie Gennis is_last = self._IsLastCodeInLine(token) 2552da489cd246702bee5938545b18a6f710ed214bcJamie Gennis if is_last: 2562da489cd246702bee5938545b18a6f710ed214bcJamie Gennis if token_type == Type.OPERATOR: 2572da489cd246702bee5938545b18a6f710ed214bcJamie Gennis if token.string == ':': 2582da489cd246702bee5938545b18a6f710ed214bcJamie Gennis if (stack and stack[-1].token.string == '?'): 2592da489cd246702bee5938545b18a6f710ed214bcJamie Gennis # When a ternary : is on a different line than its '?', it doesn't 2602da489cd246702bee5938545b18a6f710ed214bcJamie Gennis # add indentation. 2612da489cd246702bee5938545b18a6f710ed214bcJamie Gennis if (token.line_number == stack[-1].token.line_number): 2622da489cd246702bee5938545b18a6f710ed214bcJamie Gennis self._Add(TokenInfo(token)) 2632da489cd246702bee5938545b18a6f710ed214bcJamie Gennis elif token.metadata.context.type == Context.CASE_BLOCK: 2642da489cd246702bee5938545b18a6f710ed214bcJamie Gennis # Pop transient tokens from say, line continuations, e.g., 2652da489cd246702bee5938545b18a6f710ed214bcJamie Gennis # case x. 2662da489cd246702bee5938545b18a6f710ed214bcJamie Gennis # y: 2672da489cd246702bee5938545b18a6f710ed214bcJamie Gennis # Want to pop the transient 4 space continuation indent. 2682da489cd246702bee5938545b18a6f710ed214bcJamie Gennis self._PopTransient() 2692da489cd246702bee5938545b18a6f710ed214bcJamie Gennis # Starting the body of the case statement, which is a type of 2702da489cd246702bee5938545b18a6f710ed214bcJamie Gennis # block. 2712da489cd246702bee5938545b18a6f710ed214bcJamie Gennis self._Add(TokenInfo(token=token, is_block=True)) 2722da489cd246702bee5938545b18a6f710ed214bcJamie Gennis elif token.metadata.context.type == Context.LITERAL_ELEMENT: 2732da489cd246702bee5938545b18a6f710ed214bcJamie Gennis # When in an object literal, acts as operator indicating line 2742da489cd246702bee5938545b18a6f710ed214bcJamie Gennis # continuations. 2752da489cd246702bee5938545b18a6f710ed214bcJamie Gennis self._Add(TokenInfo(token)) 2762da489cd246702bee5938545b18a6f710ed214bcJamie Gennis pass 2772da489cd246702bee5938545b18a6f710ed214bcJamie Gennis else: 2782da489cd246702bee5938545b18a6f710ed214bcJamie Gennis # ':' might also be a statement label, no effect on indentation in 2792da489cd246702bee5938545b18a6f710ed214bcJamie Gennis # this case. 2802da489cd246702bee5938545b18a6f710ed214bcJamie Gennis pass 2812da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 2822da489cd246702bee5938545b18a6f710ed214bcJamie Gennis elif token.string != ',': 2832da489cd246702bee5938545b18a6f710ed214bcJamie Gennis self._Add(TokenInfo(token)) 2842da489cd246702bee5938545b18a6f710ed214bcJamie Gennis else: 2852da489cd246702bee5938545b18a6f710ed214bcJamie Gennis # The token is a comma. 2862da489cd246702bee5938545b18a6f710ed214bcJamie Gennis if token.metadata.context.type == Context.VAR: 2872da489cd246702bee5938545b18a6f710ed214bcJamie Gennis self._Add(TokenInfo(token)) 2882da489cd246702bee5938545b18a6f710ed214bcJamie Gennis elif token.metadata.context.type != Context.PARAMETERS: 2892da489cd246702bee5938545b18a6f710ed214bcJamie Gennis self._PopTransient() 2902da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 2912da489cd246702bee5938545b18a6f710ed214bcJamie Gennis elif (token.string.endswith('.') 2922da489cd246702bee5938545b18a6f710ed214bcJamie Gennis and token_type in (Type.IDENTIFIER, Type.NORMAL)): 2932da489cd246702bee5938545b18a6f710ed214bcJamie Gennis self._Add(TokenInfo(token)) 2942da489cd246702bee5938545b18a6f710ed214bcJamie Gennis elif token_type == Type.PARAMETERS and token.string.endswith(','): 2952da489cd246702bee5938545b18a6f710ed214bcJamie Gennis # Parameter lists. 2962da489cd246702bee5938545b18a6f710ed214bcJamie Gennis self._Add(TokenInfo(token)) 2972da489cd246702bee5938545b18a6f710ed214bcJamie Gennis elif token.metadata.is_implied_semicolon: 2982da489cd246702bee5938545b18a6f710ed214bcJamie Gennis self._PopTransient() 2992da489cd246702bee5938545b18a6f710ed214bcJamie Gennis elif token.IsAssignment(): 3002da489cd246702bee5938545b18a6f710ed214bcJamie Gennis self._Add(TokenInfo(token)) 3012da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 3022da489cd246702bee5938545b18a6f710ed214bcJamie Gennis return indentation_errors 3032da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 3042da489cd246702bee5938545b18a6f710ed214bcJamie Gennis def _AddToEach(self, original, amount): 3052da489cd246702bee5938545b18a6f710ed214bcJamie Gennis """Returns a new set with the given amount added to each element. 3062da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 3072da489cd246702bee5938545b18a6f710ed214bcJamie Gennis Args: 3082da489cd246702bee5938545b18a6f710ed214bcJamie Gennis original: The original set of numbers 3092da489cd246702bee5938545b18a6f710ed214bcJamie Gennis amount: The amount to add to each element 3102da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 3112da489cd246702bee5938545b18a6f710ed214bcJamie Gennis Returns: 3122da489cd246702bee5938545b18a6f710ed214bcJamie Gennis A new set containing each element of the original set added to the amount. 3132da489cd246702bee5938545b18a6f710ed214bcJamie Gennis """ 3142da489cd246702bee5938545b18a6f710ed214bcJamie Gennis return set([x + amount for x in original]) 3152da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 3162da489cd246702bee5938545b18a6f710ed214bcJamie Gennis _HARD_STOP_TYPES = (Type.START_PAREN, Type.START_PARAMETERS, 3172da489cd246702bee5938545b18a6f710ed214bcJamie Gennis Type.START_BRACKET) 3182da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 3192da489cd246702bee5938545b18a6f710ed214bcJamie Gennis _HARD_STOP_STRINGS = ('return', '?') 3202da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 3212da489cd246702bee5938545b18a6f710ed214bcJamie Gennis def _IsHardStop(self, token): 3222da489cd246702bee5938545b18a6f710ed214bcJamie Gennis """Determines if the given token can have a hard stop after it. 3232da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 3242da489cd246702bee5938545b18a6f710ed214bcJamie Gennis Hard stops are indentations defined by the position of another token as in 3252da489cd246702bee5938545b18a6f710ed214bcJamie Gennis indentation lined up with return, (, [, and ?. 3262da489cd246702bee5938545b18a6f710ed214bcJamie Gennis """ 3272da489cd246702bee5938545b18a6f710ed214bcJamie Gennis return (token.type in self._HARD_STOP_TYPES or 3282da489cd246702bee5938545b18a6f710ed214bcJamie Gennis token.string in self._HARD_STOP_STRINGS or 3292da489cd246702bee5938545b18a6f710ed214bcJamie Gennis token.IsAssignment()) 3302da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 3312da489cd246702bee5938545b18a6f710ed214bcJamie Gennis def _GetAllowableIndentations(self): 3322da489cd246702bee5938545b18a6f710ed214bcJamie Gennis """Computes the set of allowable indentations. 3332da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 3342da489cd246702bee5938545b18a6f710ed214bcJamie Gennis Returns: 3352da489cd246702bee5938545b18a6f710ed214bcJamie Gennis The set of allowable indentations, given the current stack. 3362da489cd246702bee5938545b18a6f710ed214bcJamie Gennis """ 3372da489cd246702bee5938545b18a6f710ed214bcJamie Gennis expected = set([0]) 3382da489cd246702bee5938545b18a6f710ed214bcJamie Gennis hard_stops = set([]) 3392da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 3402da489cd246702bee5938545b18a6f710ed214bcJamie Gennis # Whether the tokens are still in the same continuation, meaning additional 3412da489cd246702bee5938545b18a6f710ed214bcJamie Gennis # indentation is optional. As an example: 3422da489cd246702bee5938545b18a6f710ed214bcJamie Gennis # x = 5 + 3432da489cd246702bee5938545b18a6f710ed214bcJamie Gennis # 6 + 3442da489cd246702bee5938545b18a6f710ed214bcJamie Gennis # 7; 3452da489cd246702bee5938545b18a6f710ed214bcJamie Gennis # The second '+' does not add any required indentation. 3462da489cd246702bee5938545b18a6f710ed214bcJamie Gennis in_same_continuation = False 3472da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 3482da489cd246702bee5938545b18a6f710ed214bcJamie Gennis for token_info in self._stack: 3492da489cd246702bee5938545b18a6f710ed214bcJamie Gennis token = token_info.token 3502da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 3512da489cd246702bee5938545b18a6f710ed214bcJamie Gennis # Handle normal additive indentation tokens. 3522da489cd246702bee5938545b18a6f710ed214bcJamie Gennis if not token_info.overridden_by and token.string != 'return': 3532da489cd246702bee5938545b18a6f710ed214bcJamie Gennis if token_info.is_block: 3542da489cd246702bee5938545b18a6f710ed214bcJamie Gennis expected = self._AddToEach(expected, 2) 3552da489cd246702bee5938545b18a6f710ed214bcJamie Gennis hard_stops = self._AddToEach(hard_stops, 2) 3562da489cd246702bee5938545b18a6f710ed214bcJamie Gennis in_same_continuation = False 3572da489cd246702bee5938545b18a6f710ed214bcJamie Gennis elif in_same_continuation: 3582da489cd246702bee5938545b18a6f710ed214bcJamie Gennis expected |= self._AddToEach(expected, 4) 3592da489cd246702bee5938545b18a6f710ed214bcJamie Gennis hard_stops |= self._AddToEach(hard_stops, 4) 3602da489cd246702bee5938545b18a6f710ed214bcJamie Gennis else: 3612da489cd246702bee5938545b18a6f710ed214bcJamie Gennis expected = self._AddToEach(expected, 4) 3622da489cd246702bee5938545b18a6f710ed214bcJamie Gennis hard_stops |= self._AddToEach(hard_stops, 4) 3632da489cd246702bee5938545b18a6f710ed214bcJamie Gennis in_same_continuation = True 3642da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 3652da489cd246702bee5938545b18a6f710ed214bcJamie Gennis # Handle hard stops after (, [, return, =, and ? 3662da489cd246702bee5938545b18a6f710ed214bcJamie Gennis if self._IsHardStop(token): 3672da489cd246702bee5938545b18a6f710ed214bcJamie Gennis override_is_hard_stop = (token_info.overridden_by and 3682da489cd246702bee5938545b18a6f710ed214bcJamie Gennis self._IsHardStop(token_info.overridden_by.token)) 3692da489cd246702bee5938545b18a6f710ed214bcJamie Gennis if not override_is_hard_stop: 3702da489cd246702bee5938545b18a6f710ed214bcJamie Gennis start_index = token.start_index 3712da489cd246702bee5938545b18a6f710ed214bcJamie Gennis if token.line_number in self._start_index_offset: 3722da489cd246702bee5938545b18a6f710ed214bcJamie Gennis start_index += self._start_index_offset[token.line_number] 3732da489cd246702bee5938545b18a6f710ed214bcJamie Gennis if (token.type in (Type.START_PAREN, Type.START_PARAMETERS) and 3742da489cd246702bee5938545b18a6f710ed214bcJamie Gennis not token_info.overridden_by): 3752da489cd246702bee5938545b18a6f710ed214bcJamie Gennis hard_stops.add(start_index + 1) 3762da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 3772da489cd246702bee5938545b18a6f710ed214bcJamie Gennis elif token.string == 'return' and not token_info.overridden_by: 3782da489cd246702bee5938545b18a6f710ed214bcJamie Gennis hard_stops.add(start_index + 7) 3792da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 3802da489cd246702bee5938545b18a6f710ed214bcJamie Gennis elif (token.type == Type.START_BRACKET): 3812da489cd246702bee5938545b18a6f710ed214bcJamie Gennis hard_stops.add(start_index + 1) 3822da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 3832da489cd246702bee5938545b18a6f710ed214bcJamie Gennis elif token.IsAssignment(): 3842da489cd246702bee5938545b18a6f710ed214bcJamie Gennis hard_stops.add(start_index + len(token.string) + 1) 3852da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 3862da489cd246702bee5938545b18a6f710ed214bcJamie Gennis elif token.IsOperator('?') and not token_info.overridden_by: 3872da489cd246702bee5938545b18a6f710ed214bcJamie Gennis hard_stops.add(start_index + 2) 3882da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 3892da489cd246702bee5938545b18a6f710ed214bcJamie Gennis return (expected | hard_stops) or set([0]) 3902da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 3912da489cd246702bee5938545b18a6f710ed214bcJamie Gennis def _GetActualIndentation(self, token): 3922da489cd246702bee5938545b18a6f710ed214bcJamie Gennis """Gets the actual indentation of the line containing the given token. 3932da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 3942da489cd246702bee5938545b18a6f710ed214bcJamie Gennis Args: 3952da489cd246702bee5938545b18a6f710ed214bcJamie Gennis token: Any token on the line. 3962da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 3972da489cd246702bee5938545b18a6f710ed214bcJamie Gennis Returns: 3982da489cd246702bee5938545b18a6f710ed214bcJamie Gennis The actual indentation of the line containing the given token. Returns 3992da489cd246702bee5938545b18a6f710ed214bcJamie Gennis -1 if this line should be ignored due to the presence of tabs. 4002da489cd246702bee5938545b18a6f710ed214bcJamie Gennis """ 4012da489cd246702bee5938545b18a6f710ed214bcJamie Gennis # Move to the first token in the line 4022da489cd246702bee5938545b18a6f710ed214bcJamie Gennis token = tokenutil.GetFirstTokenInSameLine(token) 4032da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 4042da489cd246702bee5938545b18a6f710ed214bcJamie Gennis # If it is whitespace, it is the indentation. 4052da489cd246702bee5938545b18a6f710ed214bcJamie Gennis if token.type == Type.WHITESPACE: 4062da489cd246702bee5938545b18a6f710ed214bcJamie Gennis if token.string.find('\t') >= 0: 4072da489cd246702bee5938545b18a6f710ed214bcJamie Gennis return -1 4082da489cd246702bee5938545b18a6f710ed214bcJamie Gennis else: 4092da489cd246702bee5938545b18a6f710ed214bcJamie Gennis return len(token.string) 4102da489cd246702bee5938545b18a6f710ed214bcJamie Gennis elif token.type == Type.PARAMETERS: 4112da489cd246702bee5938545b18a6f710ed214bcJamie Gennis return len(token.string) - len(token.string.lstrip()) 4122da489cd246702bee5938545b18a6f710ed214bcJamie Gennis else: 4132da489cd246702bee5938545b18a6f710ed214bcJamie Gennis return 0 4142da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 4152da489cd246702bee5938545b18a6f710ed214bcJamie Gennis def _IsFirstNonWhitespaceTokenInLine(self, token): 4162da489cd246702bee5938545b18a6f710ed214bcJamie Gennis """Determines if the given token is the first non-space token on its line. 4172da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 4182da489cd246702bee5938545b18a6f710ed214bcJamie Gennis Args: 4192da489cd246702bee5938545b18a6f710ed214bcJamie Gennis token: The token. 4202da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 4212da489cd246702bee5938545b18a6f710ed214bcJamie Gennis Returns: 4222da489cd246702bee5938545b18a6f710ed214bcJamie Gennis True if the token is the first non-whitespace token on its line. 4232da489cd246702bee5938545b18a6f710ed214bcJamie Gennis """ 4242da489cd246702bee5938545b18a6f710ed214bcJamie Gennis if token.type in (Type.WHITESPACE, Type.BLANK_LINE): 4252da489cd246702bee5938545b18a6f710ed214bcJamie Gennis return False 4262da489cd246702bee5938545b18a6f710ed214bcJamie Gennis if token.IsFirstInLine(): 4272da489cd246702bee5938545b18a6f710ed214bcJamie Gennis return True 4282da489cd246702bee5938545b18a6f710ed214bcJamie Gennis return (token.previous and token.previous.IsFirstInLine() and 4292da489cd246702bee5938545b18a6f710ed214bcJamie Gennis token.previous.type == Type.WHITESPACE) 4302da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 4312da489cd246702bee5938545b18a6f710ed214bcJamie Gennis def _IsLastCodeInLine(self, token): 4322da489cd246702bee5938545b18a6f710ed214bcJamie Gennis """Determines if the given token is the last code token on its line. 4332da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 4342da489cd246702bee5938545b18a6f710ed214bcJamie Gennis Args: 4352da489cd246702bee5938545b18a6f710ed214bcJamie Gennis token: The token. 4362da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 4372da489cd246702bee5938545b18a6f710ed214bcJamie Gennis Returns: 4382da489cd246702bee5938545b18a6f710ed214bcJamie Gennis True if the token is the last code token on its line. 4392da489cd246702bee5938545b18a6f710ed214bcJamie Gennis """ 4402da489cd246702bee5938545b18a6f710ed214bcJamie Gennis if token.type in Type.NON_CODE_TYPES: 4412da489cd246702bee5938545b18a6f710ed214bcJamie Gennis return False 4422da489cd246702bee5938545b18a6f710ed214bcJamie Gennis start_token = token 4432da489cd246702bee5938545b18a6f710ed214bcJamie Gennis while True: 4442da489cd246702bee5938545b18a6f710ed214bcJamie Gennis token = token.next 4452da489cd246702bee5938545b18a6f710ed214bcJamie Gennis if not token or token.line_number != start_token.line_number: 4462da489cd246702bee5938545b18a6f710ed214bcJamie Gennis return True 4472da489cd246702bee5938545b18a6f710ed214bcJamie Gennis if token.type not in Type.NON_CODE_TYPES: 4482da489cd246702bee5938545b18a6f710ed214bcJamie Gennis return False 4492da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 4502da489cd246702bee5938545b18a6f710ed214bcJamie Gennis def _GoogScopeOrNone(self, token): 4512da489cd246702bee5938545b18a6f710ed214bcJamie Gennis """Determines if the given START_BLOCK is part of a goog.scope statement. 4522da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 4532da489cd246702bee5938545b18a6f710ed214bcJamie Gennis Args: 4542da489cd246702bee5938545b18a6f710ed214bcJamie Gennis token: A token of type START_BLOCK. 4552da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 4562da489cd246702bee5938545b18a6f710ed214bcJamie Gennis Returns: 4572da489cd246702bee5938545b18a6f710ed214bcJamie Gennis The goog.scope function call token, or None if such call doesn't exist. 4582da489cd246702bee5938545b18a6f710ed214bcJamie Gennis """ 4592da489cd246702bee5938545b18a6f710ed214bcJamie Gennis # Search for a goog.scope statement, which will be 5 tokens before the 4602da489cd246702bee5938545b18a6f710ed214bcJamie Gennis # block. Illustration of the tokens found prior to the start block: 4612da489cd246702bee5938545b18a6f710ed214bcJamie Gennis # goog.scope(function() { 4622da489cd246702bee5938545b18a6f710ed214bcJamie Gennis # 5 4 3 21 ^ 4632da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 4642da489cd246702bee5938545b18a6f710ed214bcJamie Gennis maybe_goog_scope = token 4652da489cd246702bee5938545b18a6f710ed214bcJamie Gennis for unused_i in xrange(5): 4662da489cd246702bee5938545b18a6f710ed214bcJamie Gennis maybe_goog_scope = (maybe_goog_scope.previous if maybe_goog_scope and 4672da489cd246702bee5938545b18a6f710ed214bcJamie Gennis maybe_goog_scope.previous else None) 4682da489cd246702bee5938545b18a6f710ed214bcJamie Gennis if maybe_goog_scope and maybe_goog_scope.string == 'goog.scope': 4692da489cd246702bee5938545b18a6f710ed214bcJamie Gennis return maybe_goog_scope 4702da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 4712da489cd246702bee5938545b18a6f710ed214bcJamie Gennis def _Add(self, token_info): 4722da489cd246702bee5938545b18a6f710ed214bcJamie Gennis """Adds the given token info to the stack. 4732da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 4742da489cd246702bee5938545b18a6f710ed214bcJamie Gennis Args: 4752da489cd246702bee5938545b18a6f710ed214bcJamie Gennis token_info: The token information to add. 4762da489cd246702bee5938545b18a6f710ed214bcJamie Gennis """ 4772da489cd246702bee5938545b18a6f710ed214bcJamie Gennis if self._stack and self._stack[-1].token == token_info.token: 4782da489cd246702bee5938545b18a6f710ed214bcJamie Gennis # Don't add the same token twice. 4792da489cd246702bee5938545b18a6f710ed214bcJamie Gennis return 4802da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 4812da489cd246702bee5938545b18a6f710ed214bcJamie Gennis if token_info.is_block or token_info.token.type == Type.START_PAREN: 4822da489cd246702bee5938545b18a6f710ed214bcJamie Gennis token_info.overridden_by = self._GoogScopeOrNone(token_info.token) 4832da489cd246702bee5938545b18a6f710ed214bcJamie Gennis index = 1 4842da489cd246702bee5938545b18a6f710ed214bcJamie Gennis while index <= len(self._stack): 4852da489cd246702bee5938545b18a6f710ed214bcJamie Gennis stack_info = self._stack[-index] 4862da489cd246702bee5938545b18a6f710ed214bcJamie Gennis stack_token = stack_info.token 4872da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 4882da489cd246702bee5938545b18a6f710ed214bcJamie Gennis if stack_info.line_number == token_info.line_number: 4892da489cd246702bee5938545b18a6f710ed214bcJamie Gennis # In general, tokens only override each other when they are on 4902da489cd246702bee5938545b18a6f710ed214bcJamie Gennis # the same line. 4912da489cd246702bee5938545b18a6f710ed214bcJamie Gennis stack_info.overridden_by = token_info 4922da489cd246702bee5938545b18a6f710ed214bcJamie Gennis if (token_info.token.type == Type.START_BLOCK and 4932da489cd246702bee5938545b18a6f710ed214bcJamie Gennis (stack_token.IsAssignment() or 4942da489cd246702bee5938545b18a6f710ed214bcJamie Gennis stack_token.type in (Type.IDENTIFIER, Type.START_PAREN))): 4952da489cd246702bee5938545b18a6f710ed214bcJamie Gennis # Multi-line blocks have lasting overrides, as in: 4962da489cd246702bee5938545b18a6f710ed214bcJamie Gennis # callFn({ 4972da489cd246702bee5938545b18a6f710ed214bcJamie Gennis # a: 10 4982da489cd246702bee5938545b18a6f710ed214bcJamie Gennis # }, 4992da489cd246702bee5938545b18a6f710ed214bcJamie Gennis # 30); 5002da489cd246702bee5938545b18a6f710ed214bcJamie Gennis close_block = token_info.token.metadata.context.end_token 5012da489cd246702bee5938545b18a6f710ed214bcJamie Gennis stack_info.is_permanent_override = \ 5022da489cd246702bee5938545b18a6f710ed214bcJamie Gennis close_block.line_number != token_info.token.line_number 5032da489cd246702bee5938545b18a6f710ed214bcJamie Gennis elif (token_info.token.type == Type.START_BLOCK and 5042da489cd246702bee5938545b18a6f710ed214bcJamie Gennis token_info.token.metadata.context.type == Context.BLOCK and 5052da489cd246702bee5938545b18a6f710ed214bcJamie Gennis (stack_token.IsAssignment() or 5062da489cd246702bee5938545b18a6f710ed214bcJamie Gennis stack_token.type == Type.IDENTIFIER)): 5072da489cd246702bee5938545b18a6f710ed214bcJamie Gennis # When starting a function block, the override can transcend lines. 5082da489cd246702bee5938545b18a6f710ed214bcJamie Gennis # For example 5092da489cd246702bee5938545b18a6f710ed214bcJamie Gennis # long.long.name = function( 5102da489cd246702bee5938545b18a6f710ed214bcJamie Gennis # a) { 5112da489cd246702bee5938545b18a6f710ed214bcJamie Gennis # In this case the { and the = are on different lines. But the 5122da489cd246702bee5938545b18a6f710ed214bcJamie Gennis # override should still apply. 5132da489cd246702bee5938545b18a6f710ed214bcJamie Gennis stack_info.overridden_by = token_info 5142da489cd246702bee5938545b18a6f710ed214bcJamie Gennis stack_info.is_permanent_override = True 5152da489cd246702bee5938545b18a6f710ed214bcJamie Gennis else: 5162da489cd246702bee5938545b18a6f710ed214bcJamie Gennis break 5172da489cd246702bee5938545b18a6f710ed214bcJamie Gennis index += 1 5182da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 5192da489cd246702bee5938545b18a6f710ed214bcJamie Gennis self._stack.append(token_info) 5202da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 5212da489cd246702bee5938545b18a6f710ed214bcJamie Gennis def _Pop(self): 5222da489cd246702bee5938545b18a6f710ed214bcJamie Gennis """Pops the top token from the stack. 5232da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 5242da489cd246702bee5938545b18a6f710ed214bcJamie Gennis Returns: 5252da489cd246702bee5938545b18a6f710ed214bcJamie Gennis The popped token info. 5262da489cd246702bee5938545b18a6f710ed214bcJamie Gennis """ 5272da489cd246702bee5938545b18a6f710ed214bcJamie Gennis token_info = self._stack.pop() 5282da489cd246702bee5938545b18a6f710ed214bcJamie Gennis if token_info.token.type not in (Type.START_BLOCK, Type.START_BRACKET): 5292da489cd246702bee5938545b18a6f710ed214bcJamie Gennis # Remove any temporary overrides. 5302da489cd246702bee5938545b18a6f710ed214bcJamie Gennis self._RemoveOverrides(token_info) 5312da489cd246702bee5938545b18a6f710ed214bcJamie Gennis else: 5322da489cd246702bee5938545b18a6f710ed214bcJamie Gennis # For braces and brackets, which can be object and array literals, remove 5332da489cd246702bee5938545b18a6f710ed214bcJamie Gennis # overrides when the literal is closed on the same line. 5342da489cd246702bee5938545b18a6f710ed214bcJamie Gennis token_check = token_info.token 5352da489cd246702bee5938545b18a6f710ed214bcJamie Gennis same_type = token_check.type 5362da489cd246702bee5938545b18a6f710ed214bcJamie Gennis goal_type = None 5372da489cd246702bee5938545b18a6f710ed214bcJamie Gennis if token_info.token.type == Type.START_BRACKET: 5382da489cd246702bee5938545b18a6f710ed214bcJamie Gennis goal_type = Type.END_BRACKET 5392da489cd246702bee5938545b18a6f710ed214bcJamie Gennis else: 5402da489cd246702bee5938545b18a6f710ed214bcJamie Gennis goal_type = Type.END_BLOCK 5412da489cd246702bee5938545b18a6f710ed214bcJamie Gennis line_number = token_info.token.line_number 5422da489cd246702bee5938545b18a6f710ed214bcJamie Gennis count = 0 5432da489cd246702bee5938545b18a6f710ed214bcJamie Gennis while token_check and token_check.line_number == line_number: 5442da489cd246702bee5938545b18a6f710ed214bcJamie Gennis if token_check.type == goal_type: 5452da489cd246702bee5938545b18a6f710ed214bcJamie Gennis count -= 1 5462da489cd246702bee5938545b18a6f710ed214bcJamie Gennis if not count: 5472da489cd246702bee5938545b18a6f710ed214bcJamie Gennis self._RemoveOverrides(token_info) 5482da489cd246702bee5938545b18a6f710ed214bcJamie Gennis break 5492da489cd246702bee5938545b18a6f710ed214bcJamie Gennis if token_check.type == same_type: 5502da489cd246702bee5938545b18a6f710ed214bcJamie Gennis count += 1 5512da489cd246702bee5938545b18a6f710ed214bcJamie Gennis token_check = token_check.next 5522da489cd246702bee5938545b18a6f710ed214bcJamie Gennis return token_info 5532da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 5542da489cd246702bee5938545b18a6f710ed214bcJamie Gennis def _PopToImpliedBlock(self): 5552da489cd246702bee5938545b18a6f710ed214bcJamie Gennis """Pops the stack until an implied block token is found.""" 5562da489cd246702bee5938545b18a6f710ed214bcJamie Gennis while not self._Pop().token.metadata.is_implied_block: 5572da489cd246702bee5938545b18a6f710ed214bcJamie Gennis pass 5582da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 5592da489cd246702bee5938545b18a6f710ed214bcJamie Gennis def _PopTo(self, stop_type): 5602da489cd246702bee5938545b18a6f710ed214bcJamie Gennis """Pops the stack until a token of the given type is popped. 5612da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 5622da489cd246702bee5938545b18a6f710ed214bcJamie Gennis Args: 5632da489cd246702bee5938545b18a6f710ed214bcJamie Gennis stop_type: The type of token to pop to. 5642da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 5652da489cd246702bee5938545b18a6f710ed214bcJamie Gennis Returns: 5662da489cd246702bee5938545b18a6f710ed214bcJamie Gennis The token info of the given type that was popped. 5672da489cd246702bee5938545b18a6f710ed214bcJamie Gennis """ 5682da489cd246702bee5938545b18a6f710ed214bcJamie Gennis last = None 5692da489cd246702bee5938545b18a6f710ed214bcJamie Gennis while True: 5702da489cd246702bee5938545b18a6f710ed214bcJamie Gennis last = self._Pop() 5712da489cd246702bee5938545b18a6f710ed214bcJamie Gennis if last.token.type == stop_type: 5722da489cd246702bee5938545b18a6f710ed214bcJamie Gennis break 5732da489cd246702bee5938545b18a6f710ed214bcJamie Gennis return last 5742da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 5752da489cd246702bee5938545b18a6f710ed214bcJamie Gennis def _RemoveOverrides(self, token_info): 5762da489cd246702bee5938545b18a6f710ed214bcJamie Gennis """Marks any token that was overridden by this token as active again. 5772da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 5782da489cd246702bee5938545b18a6f710ed214bcJamie Gennis Args: 5792da489cd246702bee5938545b18a6f710ed214bcJamie Gennis token_info: The token that is being removed from the stack. 5802da489cd246702bee5938545b18a6f710ed214bcJamie Gennis """ 5812da489cd246702bee5938545b18a6f710ed214bcJamie Gennis for stack_token in self._stack: 5822da489cd246702bee5938545b18a6f710ed214bcJamie Gennis if (stack_token.overridden_by == token_info and 5832da489cd246702bee5938545b18a6f710ed214bcJamie Gennis not stack_token.is_permanent_override): 5842da489cd246702bee5938545b18a6f710ed214bcJamie Gennis stack_token.overridden_by = None 5852da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 5862da489cd246702bee5938545b18a6f710ed214bcJamie Gennis def _PopTransient(self): 5872da489cd246702bee5938545b18a6f710ed214bcJamie Gennis """Pops all transient tokens - i.e. not blocks, literals, or parens.""" 5882da489cd246702bee5938545b18a6f710ed214bcJamie Gennis while self._stack and self._stack[-1].is_transient: 5892da489cd246702bee5938545b18a6f710ed214bcJamie Gennis self._Pop() 590