12da489cd246702bee5938545b18a6f710ed214bcJamie Gennis#!/usr/bin/env python
22da489cd246702bee5938545b18a6f710ed214bcJamie Gennis#
32da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# Copyright 2008 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"""Base classes for writing checkers that operate on tokens."""
182da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
192da489cd246702bee5938545b18a6f710ed214bcJamie Gennis__author__ = ('robbyw@google.com (Robert Walker)',
202da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              'ajp@google.com (Andy Perelson)',
212da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              'jacobr@google.com (Jacob Richman)')
222da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
232da489cd246702bee5938545b18a6f710ed214bcJamie Gennisimport StringIO
242da489cd246702bee5938545b18a6f710ed214bcJamie Gennisimport traceback
252da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
262da489cd246702bee5938545b18a6f710ed214bcJamie Gennisimport gflags as flags
272da489cd246702bee5938545b18a6f710ed214bcJamie Gennisfrom closure_linter import ecmametadatapass
282da489cd246702bee5938545b18a6f710ed214bcJamie Gennisfrom closure_linter import errorrules
292da489cd246702bee5938545b18a6f710ed214bcJamie Gennisfrom closure_linter import errors
302da489cd246702bee5938545b18a6f710ed214bcJamie Gennisfrom closure_linter import javascripttokenizer
312da489cd246702bee5938545b18a6f710ed214bcJamie Gennisfrom closure_linter.common import error
322da489cd246702bee5938545b18a6f710ed214bcJamie Gennisfrom closure_linter.common import htmlutil
332da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
342da489cd246702bee5938545b18a6f710ed214bcJamie GennisFLAGS = flags.FLAGS
352da489cd246702bee5938545b18a6f710ed214bcJamie Gennisflags.DEFINE_boolean('debug_tokens', False,
362da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                     'Whether to print all tokens for debugging.')
372da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
382da489cd246702bee5938545b18a6f710ed214bcJamie Gennisflags.DEFINE_boolean('error_trace', False,
392da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                     'Whether to show error exceptions.')
402da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
412da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
422da489cd246702bee5938545b18a6f710ed214bcJamie Gennisclass LintRulesBase(object):
432da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  """Base class for all classes defining the lint rules for a language."""
442da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
452da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  def __init__(self):
462da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    self.__checker = None
472da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
482da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  def Initialize(self, checker, limited_doc_checks, is_html):
492da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """Initializes to prepare to check a file.
502da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
512da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    Args:
522da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      checker: Class to report errors to.
532da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      limited_doc_checks: Whether doc checking is relaxed for this file.
542da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      is_html: Whether the file is an HTML file with extracted contents.
552da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """
562da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    self.__checker = checker
572da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    self._limited_doc_checks = limited_doc_checks
582da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    self._is_html = is_html
592da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
602da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  def _HandleError(self, code, message, token, position=None,
612da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                   fix_data=None):
622da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """Call the HandleError function for the checker we are associated with."""
632da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    if errorrules.ShouldReportError(code):
642da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      self.__checker.HandleError(code, message, token, position, fix_data)
652da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
662da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  def _SetLimitedDocChecks(self, limited_doc_checks):
672da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """Sets whether doc checking is relaxed for this file.
682da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
692da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    Args:
702da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      limited_doc_checks: Whether doc checking is relaxed for this file.
712da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """
722da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    self._limited_doc_checks = limited_doc_checks
732da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
742da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  def CheckToken(self, token, parser_state):
752da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """Checks a token, given the current parser_state, for warnings and errors.
762da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
772da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    Args:
782da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      token: The current token under consideration.
792da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      parser_state: Object that indicates the parser state in the page.
802da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
812da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    Raises:
822da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      TypeError: If not overridden.
832da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """
842da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    raise TypeError('Abstract method CheckToken not implemented')
852da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
862da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  def Finalize(self, parser_state, tokenizer_mode):
872da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """Perform all checks that need to occur after all lines are processed.
882da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
892da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    Args:
902da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      parser_state: State of the parser after parsing all tokens
912da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      tokenizer_mode: Mode of the tokenizer after parsing the entire page
922da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
932da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    Raises:
942da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      TypeError: If not overridden.
952da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """
962da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    raise TypeError('Abstract method Finalize not implemented')
972da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
982da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
992da489cd246702bee5938545b18a6f710ed214bcJamie Gennisclass CheckerBase(object):
1002da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  """This class handles checking a LintRules object against a file."""
1012da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1022da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  def __init__(self, error_handler, lint_rules, state_tracker,
1032da489cd246702bee5938545b18a6f710ed214bcJamie Gennis               limited_doc_files=None, metadata_pass=None):
1042da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """Initialize a checker object.
1052da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1062da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    Args:
1072da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      error_handler: Object that handles errors.
1082da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      lint_rules: LintRules object defining lint errors given a token
1092da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        and state_tracker object.
1102da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      state_tracker: Object that tracks the current state in the token stream.
1112da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      limited_doc_files: List of filenames that are not required to have
1122da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        documentation comments.
1132da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      metadata_pass: Object that builds metadata about the token stream.
1142da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """
1152da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    self._error_handler = error_handler
1162da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    self._lint_rules = lint_rules
1172da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    self._state_tracker = state_tracker
1182da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    self._metadata_pass = metadata_pass
1192da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    self._limited_doc_files = limited_doc_files
1202da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1212da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    # TODO(user): Factor out. A checker does not need to know about the
1222da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    # tokenizer, only the token stream.
1232da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    self._tokenizer = javascripttokenizer.JavaScriptTokenizer()
1242da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1252da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    self._has_errors = False
1262da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1272da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  def HandleError(self, code, message, token, position=None,
1282da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                  fix_data=None):
1292da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """Prints out the given error message including a line number.
1302da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1312da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    Args:
1322da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      code: The error code.
1332da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      message: The error to print.
1342da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      token: The token where the error occurred, or None if it was a file-wide
1352da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          issue.
1362da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      position: The position of the error, defaults to None.
1372da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      fix_data: Metadata used for fixing the error.
1382da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """
1392da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    self._has_errors = True
1402da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    self._error_handler.HandleError(
1412da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        error.Error(code, message, token, position, fix_data))
1422da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1432da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  def HasErrors(self):
1442da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """Returns true if the style checker has found any errors.
1452da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1462da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    Returns:
1472da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      True if the style checker has found any errors.
1482da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """
1492da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    return self._has_errors
1502da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1512da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  def Check(self, filename, source=None):
1522da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """Checks the file, printing warnings and errors as they are found.
1532da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1542da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    Args:
1552da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      filename: The name of the file to check.
1562da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      source: Optional. The contents of the file.  Can be either a string or
1572da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          file-like object.  If omitted, contents will be read from disk from
1582da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          the given filename.
1592da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """
1602da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1612da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    if source is None:
1622da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      try:
1632da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        f = open(filename)
1642da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      except IOError:
1652da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        self._error_handler.HandleFile(filename, None)
1662da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        self.HandleError(errors.FILE_NOT_FOUND, 'File not found', None)
1672da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        self._error_handler.FinishFile()
1682da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        return
1692da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    else:
1702da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      if type(source) in [str, unicode]:
1712da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        f = StringIO.StringIO(source)
1722da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      else:
1732da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        f = source
1742da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1752da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    try:
1762da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      if filename.endswith('.html') or filename.endswith('.htm'):
1772da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        self.CheckLines(filename, htmlutil.GetScriptLines(f), True)
1782da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      else:
1792da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        self.CheckLines(filename, f, False)
1802da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    finally:
1812da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      f.close()
1822da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1832da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  def CheckLines(self, filename, lines_iter, is_html):
1842da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """Checks a file, given as an iterable of lines, for warnings and errors.
1852da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1862da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    Args:
1872da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      filename: The name of the file to check.
1882da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      lines_iter: An iterator that yields one line of the file at a time.
1892da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      is_html: Whether the file being checked is an HTML file with extracted
1902da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          contents.
1912da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1922da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    Returns:
1932da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      A boolean indicating whether the full file could be checked or if checking
1942da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      failed prematurely.
1952da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """
1962da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    limited_doc_checks = False
1972da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    if self._limited_doc_files:
1982da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      for limited_doc_filename in self._limited_doc_files:
1992da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        if filename.endswith(limited_doc_filename):
2002da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          limited_doc_checks = True
2012da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          break
2022da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
2032da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    lint_rules = self._lint_rules
2042da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    lint_rules.Initialize(self, limited_doc_checks, is_html)
2052da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
2062da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    token = self._tokenizer.TokenizeFile(lines_iter)
2072da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
2082da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    parse_error = None
2092da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    if self._metadata_pass:
2102da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      try:
2112da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        self._metadata_pass.Reset()
2122da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        self._metadata_pass.Process(token)
2132da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      except ecmametadatapass.ParseError, caught_parse_error:
2142da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        if FLAGS.error_trace:
2152da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          traceback.print_exc()
2162da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        parse_error = caught_parse_error
2172da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      except Exception:
2182da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        print 'Internal error in %s' % filename
2192da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        traceback.print_exc()
2202da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        return False
2212da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
2222da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    self._error_handler.HandleFile(filename, token)
2232da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
2242da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    return self._CheckTokens(token, parse_error=parse_error,
2252da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                             debug_tokens=FLAGS.debug_tokens)
2262da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
2272da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  def _CheckTokens(self, token, parse_error, debug_tokens):
2282da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """Checks a token stream for lint warnings/errors.
2292da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
2302da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    Args:
2312da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      token: The first token in the token stream to check.
2322da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      parse_error: A ParseError if any errors occurred.
2332da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      debug_tokens: Whether every token should be printed as it is encountered
2342da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          during the pass.
2352da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
2362da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    Returns:
2372da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      A boolean indicating whether the full token stream could be checked or if
2382da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      checking failed prematurely.
2392da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """
2402da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    result = self._ExecutePass(token, self._LintPass, parse_error, debug_tokens)
2412da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
2422da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    if not result:
2432da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      return False
2442da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
2452da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    self._lint_rules.Finalize(self._state_tracker, self._tokenizer.mode)
2462da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    self._error_handler.FinishFile()
2472da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    return True
2482da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
2492da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  def _LintPass(self, token):
2502da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """Checks an individual token for lint warnings/errors.
2512da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
2522da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    Used to encapsulate the logic needed to check an individual token so that it
2532da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    can be passed to _ExecutePass.
2542da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
2552da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    Args:
2562da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      token: The token to check.
2572da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """
2582da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    self._lint_rules.CheckToken(token, self._state_tracker)
2592da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
2602da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  def _ExecutePass(self, token, pass_function, parse_error=None,
2612da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                   debug_tokens=False):
2622da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """Calls the given function for every token in the given token stream.
2632da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
2642da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    As each token is passed to the given function, state is kept up to date and,
2652da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    depending on the error_trace flag, errors are either caught and reported, or
2662da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    allowed to bubble up so developers can see the full stack trace. If a parse
2672da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    error is specified, the pass will proceed as normal until the token causing
2682da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    the parse error is reached.
2692da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
2702da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    Args:
2712da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      token: The first token in the token stream.
2722da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      pass_function: The function to call for each token in the token stream.
2732da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      parse_error: A ParseError if any errors occurred.
2742da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      debug_tokens: Whether every token should be printed as it is encountered
2752da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          during the pass.
2762da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
2772da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    Returns:
2782da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      A boolean indicating whether the full token stream could be checked or if
2792da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      checking failed prematurely.
2802da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
2812da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    Raises:
2822da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      Exception: If any error occurred while calling the given function.
2832da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """
2842da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    self._state_tracker.Reset()
2852da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    while token:
2862da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      if debug_tokens:
2872da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        print token
2882da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
2892da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      if parse_error and parse_error.token == token:
2902da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        message = ('Error parsing file at token "%s". Unable to '
2912da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                   'check the rest of file.' % token.string)
2922da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        self.HandleError(errors.FILE_DOES_NOT_PARSE, message, token)
2932da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        self._error_handler.FinishFile()
2942da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        return
2952da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
2962da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      try:
2972da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        self._state_tracker.HandleToken(
2982da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            token, self._state_tracker.GetLastNonSpaceToken())
2992da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        pass_function(token)
3002da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        self._state_tracker.HandleAfterToken(token)
3012da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      except:
3022da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        if FLAGS.error_trace:
3032da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          raise
3042da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        else:
3052da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          self.HandleError(errors.FILE_DOES_NOT_PARSE,
3062da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                           ('Error parsing file at token "%s". Unable to '
3072da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                            'check the rest of file.' % token.string),
3082da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                           token)
3092da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          self._error_handler.FinishFile()
3102da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        return False
3112da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      token = token.next
3122da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    return True
313