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