Home | History | Annotate | Download | only in style
      1 # Copyright (C) 2009 Google Inc. All rights reserved.
      2 # Copyright (C) 2010 Chris Jerdonek (chris.jerdonek (at] gmail.com)
      3 # Copyright (C) 2010 ProFUSION embedded systems
      4 #
      5 # Redistribution and use in source and binary forms, with or without
      6 # modification, are permitted provided that the following conditions are
      7 # met:
      8 #
      9 #     * Redistributions of source code must retain the above copyright
     10 # notice, this list of conditions and the following disclaimer.
     11 #     * Redistributions in binary form must reproduce the above
     12 # copyright notice, this list of conditions and the following disclaimer
     13 # in the documentation and/or other materials provided with the
     14 # distribution.
     15 #     * Neither the name of Google Inc. nor the names of its
     16 # contributors may be used to endorse or promote products derived from
     17 # this software without specific prior written permission.
     18 #
     19 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     20 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     21 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     22 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
     23 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     24 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
     25 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     26 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     27 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     28 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     29 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     30 
     31 """Front end of some style-checker modules."""
     32 
     33 import logging
     34 import os.path
     35 import sys
     36 
     37 from checkers.common import categories as CommonCategories
     38 from checkers.common import CarriageReturnChecker
     39 from checkers.changelog import ChangeLogChecker
     40 from checkers.cpp import CppChecker
     41 from checkers.python import PythonChecker
     42 from checkers.test_expectations import TestExpectationsChecker
     43 from checkers.text import TextChecker
     44 from checkers.xml import XMLChecker
     45 from error_handlers import DefaultStyleErrorHandler
     46 from filter import FilterConfiguration
     47 from optparser import ArgumentParser
     48 from optparser import DefaultCommandOptionValues
     49 from webkitpy.style_references import configure_logging as _configure_logging
     50 
     51 _log = logging.getLogger("webkitpy.style.checker")
     52 
     53 # These are default option values for the command-line option parser.
     54 _DEFAULT_MIN_CONFIDENCE = 1
     55 _DEFAULT_OUTPUT_FORMAT = 'emacs'
     56 
     57 
     58 # FIXME: For style categories we will never want to have, remove them.
     59 #        For categories for which we want to have similar functionality,
     60 #        modify the implementation and enable them.
     61 #
     62 # Throughout this module, we use "filter rule" rather than "filter"
     63 # for an individual boolean filter flag like "+foo".  This allows us to
     64 # reserve "filter" for what one gets by collectively applying all of
     65 # the filter rules.
     66 #
     67 # The base filter rules are the filter rules that begin the list of
     68 # filter rules used to check style.  For example, these rules precede
     69 # any user-specified filter rules.  Since by default all categories are
     70 # checked, this list should normally include only rules that begin
     71 # with a "-" sign.
     72 _BASE_FILTER_RULES = [
     73     '-build/endif_comment',
     74     '-build/include_what_you_use',  # <string> for std::string
     75     '-build/storage_class',  # const static
     76     '-legal/copyright',
     77     '-readability/multiline_comment',
     78     '-readability/braces',  # int foo() {};
     79     '-readability/fn_size',
     80     '-readability/casting',
     81     '-readability/function',
     82     '-runtime/arrays',  # variable length array
     83     '-runtime/casting',
     84     '-runtime/sizeof',
     85     '-runtime/explicit',  # explicit
     86     '-runtime/virtual',  # virtual dtor
     87     '-runtime/printf',
     88     '-runtime/threadsafe_fn',
     89     '-runtime/rtti',
     90     '-whitespace/blank_line',
     91     '-whitespace/end_of_line',
     92     '-whitespace/labels',
     93     # List Python pep8 categories last.
     94     #
     95     # Because much of WebKit's Python code base does not abide by the
     96     # PEP8 79 character limit, we ignore the 79-character-limit category
     97     # pep8/E501 for now.
     98     #
     99     # FIXME: Consider bringing WebKit's Python code base into conformance
    100     #        with the 79 character limit, or some higher limit that is
    101     #        agreeable to the WebKit project.
    102     '-pep8/E501',
    103     ]
    104 
    105 
    106 # The path-specific filter rules.
    107 #
    108 # This list is order sensitive.  Only the first path substring match
    109 # is used.  See the FilterConfiguration documentation in filter.py
    110 # for more information on this list.
    111 #
    112 # Each string appearing in this nested list should have at least
    113 # one associated unit test assertion.  These assertions are located,
    114 # for example, in the test_path_rules_specifier() unit test method of
    115 # checker_unittest.py.
    116 _PATH_RULES_SPECIFIER = [
    117     # Files in these directories are consumers of the WebKit
    118     # API and therefore do not follow the same header including
    119     # discipline as WebCore.
    120 
    121     ([# TestNetscapePlugIn has no config.h and uses funny names like
    122       # NPP_SetWindow.
    123       "Tools/DumpRenderTree/TestNetscapePlugIn/",
    124       # The API test harnesses have no config.h and use funny macros like
    125       # TEST_CLASS_NAME.
    126       "Tools/WebKitAPITest/",
    127       "Tools/TestWebKitAPI/",
    128       "Source/WebKit/qt/tests/qdeclarativewebview"],
    129      ["-build/include",
    130       "-readability/naming"]),
    131     ([# There is no clean way to avoid "yy_*" names used by flex.
    132       "Source/WebCore/css/CSSParser.cpp",
    133       # Qt code uses '_' in some places (such as private slots
    134       # and on test xxx_data methos on tests)
    135       "Source/JavaScriptCore/qt/",
    136       "Source/WebKit/qt/Api/",
    137       "Source/WebKit/qt/tests/",
    138       "Source/WebKit/qt/declarative/",
    139       "Source/WebKit/qt/examples/"],
    140      ["-readability/naming"]),
    141      ([# Qt's MiniBrowser has no config.h
    142        "Tools/MiniBrowser/qt"],
    143       ["-build/include"]),
    144     ([# The GTK+ APIs use GTK+ naming style, which includes
    145       # lower-cased, underscore-separated values, whitespace before
    146       # parens for function calls, and always having variable names.
    147       # Also, GTK+ allows the use of NULL.
    148       "Source/WebCore/bindings/scripts/test/GObject",
    149       "Source/WebKit/gtk/webkit/",
    150       "Tools/DumpRenderTree/gtk/"],
    151      ["-readability/naming",
    152       "-readability/parameter_name",
    153       "-readability/null",
    154       "-whitespace/parens"]),
    155     ([# Header files in ForwardingHeaders have no header guards or
    156       # exceptional header guards (e.g., WebCore_FWD_Debugger_h).
    157       "/ForwardingHeaders/"],
    158      ["-build/header_guard"]),
    159     ([# assembler has lots of opcodes that use underscores, so
    160       # we don't check for underscores in that directory.
    161       "/Source/JavaScriptCore/assembler/"],
    162      ["-readability/naming"]),
    163     ([# JITStubs has an usual syntax which causes false alarms for a few checks.
    164       "JavaScriptCore/jit/JITStubs.cpp"],
    165      ["-readability/parameter_name",
    166       "-whitespace/parens"]),
    167 
    168     ([# The EFL APIs use EFL naming style, which includes
    169       # both lower-cased and camel-cased, underscore-sparated
    170       # values.
    171       "Source/WebKit/efl/ewk/"],
    172      ["-readability/naming",
    173       "-readability/parameter_name"]),
    174 
    175     # WebKit2 rules:
    176     # WebKit2 and certain directories have idiosyncracies.
    177     ([# NPAPI has function names with underscores.
    178       "Source/WebKit2/WebProcess/Plugins/Netscape"],
    179      ["-readability/naming"]),
    180     ([# The WebKit2 C API has names with underscores and whitespace-aligned
    181       # struct members. Also, we allow unnecessary parameter names in
    182       # WebKit2 APIs because we're matching CF's header style.
    183       "Source/WebKit2/UIProcess/API/C/",
    184       "Source/WebKit2/Shared/API/c/",
    185       "Source/WebKit2/WebProcess/InjectedBundle/API/c/"],
    186      ["-readability/naming",
    187       "-readability/parameter_name",
    188       "-whitespace/declaration"]),
    189 
    190     # For third-party Python code, keep only the following checks--
    191     #
    192     #   No tabs: to avoid having to set the SVN allow-tabs property.
    193     #   No trailing white space: since this is easy to correct.
    194     #   No carriage-return line endings: since this is easy to correct.
    195     #
    196     (["webkitpy/thirdparty/"],
    197      ["-",
    198       "+pep8/W191",  # Tabs
    199       "+pep8/W291",  # Trailing white space
    200       "+whitespace/carriage_return"]),
    201 ]
    202 
    203 
    204 _CPP_FILE_EXTENSIONS = [
    205     'c',
    206     'cpp',
    207     'h',
    208     ]
    209 
    210 _PYTHON_FILE_EXTENSION = 'py'
    211 
    212 _TEXT_FILE_EXTENSIONS = [
    213     'ac',
    214     'cc',
    215     'cgi',
    216     'css',
    217     'exp',
    218     'flex',
    219     'gyp',
    220     'gypi',
    221     'html',
    222     'idl',
    223     'in',
    224     'js',
    225     'mm',
    226     'php',
    227     'pl',
    228     'pm',
    229     'pri',
    230     'pro',
    231     'rb',
    232     'sh',
    233     'txt',
    234     'wm',
    235     'xhtml',
    236     'y',
    237     ]
    238 
    239 _XML_FILE_EXTENSIONS = [
    240     'vcproj',
    241     'vsprops',
    242     ]
    243 
    244 # Files to skip that are less obvious.
    245 #
    246 # Some files should be skipped when checking style. For example,
    247 # WebKit maintains some files in Mozilla style on purpose to ease
    248 # future merges.
    249 _SKIPPED_FILES_WITH_WARNING = [
    250     "gtk2drawing.c", # WebCore/platform/gtk/gtk2drawing.c
    251     "gtkdrawing.h", # WebCore/platform/gtk/gtkdrawing.h
    252     "Source/WebKit/gtk/tests/",
    253     # Soup API that is still being cooked, will be removed from WebKit
    254     # in a few months when it is merged into soup proper. The style
    255     # follows the libsoup style completely.
    256     "Source/WebCore/platform/network/soup/cache/",
    257     ]
    258 
    259 
    260 # Files to skip that are more common or obvious.
    261 #
    262 # This list should be in addition to files with FileType.NONE.  Files
    263 # with FileType.NONE are automatically skipped without warning.
    264 _SKIPPED_FILES_WITHOUT_WARNING = [
    265     "LayoutTests" + os.path.sep,
    266     ]
    267 
    268 # Extensions of files which are allowed to contain carriage returns.
    269 _CARRIAGE_RETURN_ALLOWED_FILE_EXTENSIONS = [
    270     'vcproj',
    271     'vsprops',
    272     ]
    273 
    274 # The maximum number of errors to report per file, per category.
    275 # If a category is not a key, then it has no maximum.
    276 _MAX_REPORTS_PER_CATEGORY = {
    277     "whitespace/carriage_return": 1
    278 }
    279 
    280 
    281 def _all_categories():
    282     """Return the set of all categories used by check-webkit-style."""
    283     # Take the union across all checkers.
    284     categories = CommonCategories.union(CppChecker.categories)
    285     categories = categories.union(TestExpectationsChecker.categories)
    286 
    287     # FIXME: Consider adding all of the pep8 categories.  Since they
    288     #        are not too meaningful for documentation purposes, for
    289     #        now we add only the categories needed for the unit tests
    290     #        (which validate the consistency of the configuration
    291     #        settings against the known categories, etc).
    292     categories = categories.union(["pep8/W191", "pep8/W291", "pep8/E501"])
    293 
    294     return categories
    295 
    296 
    297 def _check_webkit_style_defaults():
    298     """Return the default command-line options for check-webkit-style."""
    299     return DefaultCommandOptionValues(min_confidence=_DEFAULT_MIN_CONFIDENCE,
    300                                       output_format=_DEFAULT_OUTPUT_FORMAT)
    301 
    302 
    303 # This function assists in optparser not having to import from checker.
    304 def check_webkit_style_parser():
    305     all_categories = _all_categories()
    306     default_options = _check_webkit_style_defaults()
    307     return ArgumentParser(all_categories=all_categories,
    308                           base_filter_rules=_BASE_FILTER_RULES,
    309                           default_options=default_options)
    310 
    311 
    312 def check_webkit_style_configuration(options):
    313     """Return a StyleProcessorConfiguration instance for check-webkit-style.
    314 
    315     Args:
    316       options: A CommandOptionValues instance.
    317 
    318     """
    319     filter_configuration = FilterConfiguration(
    320                                base_rules=_BASE_FILTER_RULES,
    321                                path_specific=_PATH_RULES_SPECIFIER,
    322                                user_rules=options.filter_rules)
    323 
    324     return StyleProcessorConfiguration(filter_configuration=filter_configuration,
    325                max_reports_per_category=_MAX_REPORTS_PER_CATEGORY,
    326                min_confidence=options.min_confidence,
    327                output_format=options.output_format,
    328                stderr_write=sys.stderr.write)
    329 
    330 
    331 def _create_log_handlers(stream):
    332     """Create and return a default list of logging.Handler instances.
    333 
    334     Format WARNING messages and above to display the logging level, and
    335     messages strictly below WARNING not to display it.
    336 
    337     Args:
    338       stream: See the configure_logging() docstring.
    339 
    340     """
    341     # Handles logging.WARNING and above.
    342     error_handler = logging.StreamHandler(stream)
    343     error_handler.setLevel(logging.WARNING)
    344     formatter = logging.Formatter("%(levelname)s: %(message)s")
    345     error_handler.setFormatter(formatter)
    346 
    347     # Create a logging.Filter instance that only accepts messages
    348     # below WARNING (i.e. filters out anything WARNING or above).
    349     non_error_filter = logging.Filter()
    350     # The filter method accepts a logging.LogRecord instance.
    351     non_error_filter.filter = lambda record: record.levelno < logging.WARNING
    352 
    353     non_error_handler = logging.StreamHandler(stream)
    354     non_error_handler.addFilter(non_error_filter)
    355     formatter = logging.Formatter("%(message)s")
    356     non_error_handler.setFormatter(formatter)
    357 
    358     return [error_handler, non_error_handler]
    359 
    360 
    361 def _create_debug_log_handlers(stream):
    362     """Create and return a list of logging.Handler instances for debugging.
    363 
    364     Args:
    365       stream: See the configure_logging() docstring.
    366 
    367     """
    368     handler = logging.StreamHandler(stream)
    369     formatter = logging.Formatter("%(name)s: %(levelname)-8s %(message)s")
    370     handler.setFormatter(formatter)
    371 
    372     return [handler]
    373 
    374 
    375 def configure_logging(stream, logger=None, is_verbose=False):
    376     """Configure logging, and return the list of handlers added.
    377 
    378     Returns:
    379       A list of references to the logging handlers added to the root
    380       logger.  This allows the caller to later remove the handlers
    381       using logger.removeHandler.  This is useful primarily during unit
    382       testing where the caller may want to configure logging temporarily
    383       and then undo the configuring.
    384 
    385     Args:
    386       stream: A file-like object to which to log.  The stream must
    387               define an "encoding" data attribute, or else logging
    388               raises an error.
    389       logger: A logging.logger instance to configure.  This parameter
    390               should be used only in unit tests.  Defaults to the
    391               root logger.
    392       is_verbose: A boolean value of whether logging should be verbose.
    393 
    394     """
    395     # If the stream does not define an "encoding" data attribute, the
    396     # logging module can throw an error like the following:
    397     #
    398     # Traceback (most recent call last):
    399     #   File "/System/Library/Frameworks/Python.framework/Versions/2.6/...
    400     #         lib/python2.6/logging/__init__.py", line 761, in emit
    401     #     self.stream.write(fs % msg.encode(self.stream.encoding))
    402     # LookupError: unknown encoding: unknown
    403     if logger is None:
    404         logger = logging.getLogger()
    405 
    406     if is_verbose:
    407         logging_level = logging.DEBUG
    408         handlers = _create_debug_log_handlers(stream)
    409     else:
    410         logging_level = logging.INFO
    411         handlers = _create_log_handlers(stream)
    412 
    413     handlers = _configure_logging(logging_level=logging_level, logger=logger,
    414                                   handlers=handlers)
    415 
    416     return handlers
    417 
    418 
    419 # Enum-like idiom
    420 class FileType:
    421 
    422     NONE = 0  # FileType.NONE evaluates to False.
    423     # Alphabetize remaining types
    424     CHANGELOG = 1
    425     CPP = 2
    426     PYTHON = 3
    427     TEXT = 4
    428     XML = 5
    429 
    430 
    431 class CheckerDispatcher(object):
    432 
    433     """Supports determining whether and how to check style, based on path."""
    434 
    435     def _file_extension(self, file_path):
    436         """Return the file extension without the leading dot."""
    437         return os.path.splitext(file_path)[1].lstrip(".")
    438 
    439     def should_skip_with_warning(self, file_path):
    440         """Return whether the given file should be skipped with a warning."""
    441         for skipped_file in _SKIPPED_FILES_WITH_WARNING:
    442             if file_path.find(skipped_file) >= 0:
    443                 return True
    444         return False
    445 
    446     def should_skip_without_warning(self, file_path):
    447         """Return whether the given file should be skipped without a warning."""
    448         if not self._file_type(file_path):  # FileType.NONE.
    449             return True
    450         # Since "LayoutTests" is in _SKIPPED_FILES_WITHOUT_WARNING, make
    451         # an exception to prevent files like "LayoutTests/ChangeLog" and
    452         # "LayoutTests/ChangeLog-2009-06-16" from being skipped.
    453         # Files like 'test_expectations.txt' and 'drt_expectations.txt'
    454         # are also should not be skipped.
    455         #
    456         # FIXME: Figure out a good way to avoid having to add special logic
    457         #        for this special case.
    458         basename = os.path.basename(file_path)
    459         if basename.startswith('ChangeLog'):
    460             return False
    461         elif basename == 'test_expectations.txt' or basename == 'drt_expectations.txt':
    462             return False
    463         for skipped_file in _SKIPPED_FILES_WITHOUT_WARNING:
    464             if file_path.find(skipped_file) >= 0:
    465                 return True
    466         return False
    467 
    468     def should_check_and_strip_carriage_returns(self, file_path):
    469         return self._file_extension(file_path) not in _CARRIAGE_RETURN_ALLOWED_FILE_EXTENSIONS
    470 
    471     def _file_type(self, file_path):
    472         """Return the file type corresponding to the given file."""
    473         file_extension = self._file_extension(file_path)
    474 
    475         if (file_extension in _CPP_FILE_EXTENSIONS) or (file_path == '-'):
    476             # FIXME: Do something about the comment below and the issue it
    477             #        raises since cpp_style already relies on the extension.
    478             #
    479             # Treat stdin as C++. Since the extension is unknown when
    480             # reading from stdin, cpp_style tests should not rely on
    481             # the extension.
    482             return FileType.CPP
    483         elif file_extension == _PYTHON_FILE_EXTENSION:
    484             return FileType.PYTHON
    485         elif file_extension in _XML_FILE_EXTENSIONS:
    486             return FileType.XML
    487         elif os.path.basename(file_path).startswith('ChangeLog'):
    488             return FileType.CHANGELOG
    489         elif ((not file_extension and os.path.join("Tools", "Scripts") in file_path) or
    490               file_extension in _TEXT_FILE_EXTENSIONS):
    491             return FileType.TEXT
    492         else:
    493             return FileType.NONE
    494 
    495     def _create_checker(self, file_type, file_path, handle_style_error,
    496                         min_confidence):
    497         """Instantiate and return a style checker based on file type."""
    498         if file_type == FileType.NONE:
    499             checker = None
    500         elif file_type == FileType.CHANGELOG:
    501             should_line_be_checked = None
    502             if handle_style_error:
    503                 should_line_be_checked = handle_style_error.should_line_be_checked
    504             checker = ChangeLogChecker(file_path, handle_style_error, should_line_be_checked)
    505         elif file_type == FileType.CPP:
    506             file_extension = self._file_extension(file_path)
    507             checker = CppChecker(file_path, file_extension,
    508                                  handle_style_error, min_confidence)
    509         elif file_type == FileType.PYTHON:
    510             checker = PythonChecker(file_path, handle_style_error)
    511         elif file_type == FileType.XML:
    512             checker = XMLChecker(file_path, handle_style_error)
    513         elif file_type == FileType.TEXT:
    514             basename = os.path.basename(file_path)
    515             if basename == 'test_expectations.txt' or basename == 'drt_expectations.txt':
    516                 checker = TestExpectationsChecker(file_path, handle_style_error)
    517             else:
    518                 checker = TextChecker(file_path, handle_style_error)
    519         else:
    520             raise ValueError('Invalid file type "%(file_type)s": the only valid file types '
    521                              "are %(NONE)s, %(CPP)s, and %(TEXT)s."
    522                              % {"file_type": file_type,
    523                                 "NONE": FileType.NONE,
    524                                 "CPP": FileType.CPP,
    525                                 "TEXT": FileType.TEXT})
    526 
    527         return checker
    528 
    529     def dispatch(self, file_path, handle_style_error, min_confidence):
    530         """Instantiate and return a style checker based on file path."""
    531         file_type = self._file_type(file_path)
    532 
    533         checker = self._create_checker(file_type,
    534                                        file_path,
    535                                        handle_style_error,
    536                                        min_confidence)
    537         return checker
    538 
    539 
    540 # FIXME: Remove the stderr_write attribute from this class and replace
    541 #        its use with calls to a logging module logger.
    542 class StyleProcessorConfiguration(object):
    543 
    544     """Stores configuration values for the StyleProcessor class.
    545 
    546     Attributes:
    547       min_confidence: An integer between 1 and 5 inclusive that is the
    548                       minimum confidence level of style errors to report.
    549 
    550       max_reports_per_category: The maximum number of errors to report
    551                                 per category, per file.
    552 
    553       stderr_write: A function that takes a string as a parameter and
    554                     serves as stderr.write.
    555 
    556     """
    557 
    558     def __init__(self,
    559                  filter_configuration,
    560                  max_reports_per_category,
    561                  min_confidence,
    562                  output_format,
    563                  stderr_write):
    564         """Create a StyleProcessorConfiguration instance.
    565 
    566         Args:
    567           filter_configuration: A FilterConfiguration instance.  The default
    568                                 is the "empty" filter configuration, which
    569                                 means that all errors should be checked.
    570 
    571           max_reports_per_category: The maximum number of errors to report
    572                                     per category, per file.
    573 
    574           min_confidence: An integer between 1 and 5 inclusive that is the
    575                           minimum confidence level of style errors to report.
    576                           The default is 1, which reports all style errors.
    577 
    578           output_format: A string that is the output format.  The supported
    579                          output formats are "emacs" which emacs can parse
    580                          and "vs7" which Microsoft Visual Studio 7 can parse.
    581 
    582           stderr_write: A function that takes a string as a parameter and
    583                         serves as stderr.write.
    584 
    585         """
    586         self._filter_configuration = filter_configuration
    587         self._output_format = output_format
    588 
    589         self.max_reports_per_category = max_reports_per_category
    590         self.min_confidence = min_confidence
    591         self.stderr_write = stderr_write
    592 
    593     def is_reportable(self, category, confidence_in_error, file_path):
    594         """Return whether an error is reportable.
    595 
    596         An error is reportable if both the confidence in the error is
    597         at least the minimum confidence level and the current filter
    598         says the category should be checked for the given path.
    599 
    600         Args:
    601           category: A string that is a style category.
    602           confidence_in_error: An integer between 1 and 5 inclusive that is
    603                                the application's confidence in the error.
    604                                A higher number means greater confidence.
    605           file_path: The path of the file being checked
    606 
    607         """
    608         if confidence_in_error < self.min_confidence:
    609             return False
    610 
    611         return self._filter_configuration.should_check(category, file_path)
    612 
    613     def write_style_error(self,
    614                           category,
    615                           confidence_in_error,
    616                           file_path,
    617                           line_number,
    618                           message):
    619         """Write a style error to the configured stderr."""
    620         if self._output_format == 'vs7':
    621             format_string = "%s(%s):  %s  [%s] [%d]\n"
    622         else:
    623             format_string = "%s:%s:  %s  [%s] [%d]\n"
    624 
    625         self.stderr_write(format_string % (file_path,
    626                                            line_number,
    627                                            message,
    628                                            category,
    629                                            confidence_in_error))
    630 
    631 
    632 class ProcessorBase(object):
    633 
    634     """The base class for processors of lists of lines."""
    635 
    636     def should_process(self, file_path):
    637         """Return whether the file at file_path should be processed.
    638 
    639         The TextFileReader class calls this method prior to reading in
    640         the lines of a file.  Use this method, for example, to prevent
    641         the style checker from reading binary files into memory.
    642 
    643         """
    644         raise NotImplementedError('Subclasses should implement.')
    645 
    646     def process(self, lines, file_path, **kwargs):
    647         """Process lines of text read from a file.
    648 
    649         Args:
    650           lines: A list of lines of text to process.
    651           file_path: The path from which the lines were read.
    652           **kwargs: This argument signifies that the process() method of
    653                     subclasses of ProcessorBase may support additional
    654                     keyword arguments.
    655                         For example, a style checker's check() method
    656                     may support a "reportable_lines" parameter that represents
    657                     the line numbers of the lines for which style errors
    658                     should be reported.
    659 
    660         """
    661         raise NotImplementedError('Subclasses should implement.')
    662 
    663 
    664 class StyleProcessor(ProcessorBase):
    665 
    666     """A ProcessorBase for checking style.
    667 
    668     Attributes:
    669       error_count: An integer that is the total number of reported
    670                    errors for the lifetime of this instance.
    671 
    672     """
    673 
    674     def __init__(self, configuration, mock_dispatcher=None,
    675                  mock_increment_error_count=None,
    676                  mock_carriage_checker_class=None):
    677         """Create an instance.
    678 
    679         Args:
    680           configuration: A StyleProcessorConfiguration instance.
    681           mock_dispatcher: A mock CheckerDispatcher instance.  This
    682                            parameter is for unit testing.  Defaults to a
    683                            CheckerDispatcher instance.
    684           mock_increment_error_count: A mock error-count incrementer.
    685           mock_carriage_checker_class: A mock class for checking and
    686                                        transforming carriage returns.
    687                                        This parameter is for unit testing.
    688                                        Defaults to CarriageReturnChecker.
    689 
    690         """
    691         if mock_dispatcher is None:
    692             dispatcher = CheckerDispatcher()
    693         else:
    694             dispatcher = mock_dispatcher
    695 
    696         if mock_increment_error_count is None:
    697             # The following blank line is present to avoid flagging by pep8.py.
    698 
    699             def increment_error_count():
    700                 """Increment the total count of reported errors."""
    701                 self.error_count += 1
    702         else:
    703             increment_error_count = mock_increment_error_count
    704 
    705         if mock_carriage_checker_class is None:
    706             # This needs to be a class rather than an instance since the
    707             # process() method instantiates one using parameters.
    708             carriage_checker_class = CarriageReturnChecker
    709         else:
    710             carriage_checker_class = mock_carriage_checker_class
    711 
    712         self.error_count = 0
    713 
    714         self._carriage_checker_class = carriage_checker_class
    715         self._configuration = configuration
    716         self._dispatcher = dispatcher
    717         self._increment_error_count = increment_error_count
    718 
    719     def should_process(self, file_path):
    720         """Return whether the file should be checked for style."""
    721         if self._dispatcher.should_skip_without_warning(file_path):
    722             return False
    723         if self._dispatcher.should_skip_with_warning(file_path):
    724             _log.warn('File exempt from style guide. Skipping: "%s"'
    725                       % file_path)
    726             return False
    727         return True
    728 
    729     def process(self, lines, file_path, line_numbers=None):
    730         """Check the given lines for style.
    731 
    732         Arguments:
    733           lines: A list of all lines in the file to check.
    734           file_path: The path of the file to process.  If possible, the path
    735                      should be relative to the source root.  Otherwise,
    736                      path-specific logic may not behave as expected.
    737           line_numbers: A list of line numbers of the lines for which
    738                         style errors should be reported, or None if errors
    739                         for all lines should be reported.  When not None, this
    740                         list normally contains the line numbers corresponding
    741                         to the modified lines of a patch.
    742 
    743         """
    744         _log.debug("Checking style: " + file_path)
    745 
    746         style_error_handler = DefaultStyleErrorHandler(
    747             configuration=self._configuration,
    748             file_path=file_path,
    749             increment_error_count=self._increment_error_count,
    750             line_numbers=line_numbers)
    751 
    752         carriage_checker = self._carriage_checker_class(style_error_handler)
    753 
    754         # Check for and remove trailing carriage returns ("\r").
    755         if self._dispatcher.should_check_and_strip_carriage_returns(file_path):
    756             lines = carriage_checker.check(lines)
    757 
    758         min_confidence = self._configuration.min_confidence
    759         checker = self._dispatcher.dispatch(file_path,
    760                                                       style_error_handler,
    761                                                       min_confidence)
    762 
    763         if checker is None:
    764             raise AssertionError("File should not be checked: '%s'" % file_path)
    765 
    766         _log.debug("Using class: " + checker.__class__.__name__)
    767 
    768         checker.check(lines)
    769