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 re
     36 import sys
     37 
     38 from checkers.common import categories as CommonCategories
     39 from checkers.common import CarriageReturnChecker
     40 from checkers.cpp import CppChecker
     41 from checkers.jsonchecker import JSONChecker
     42 from checkers.png import PNGChecker
     43 from checkers.python import PythonChecker
     44 from checkers.test_expectations import TestExpectationsChecker
     45 from checkers.text import TextChecker
     46 from checkers.xcodeproj import XcodeProjectFileChecker
     47 from checkers.xml import XMLChecker
     48 from error_handlers import DefaultStyleErrorHandler
     49 from filter import FilterConfiguration
     50 from optparser import ArgumentParser
     51 from optparser import DefaultCommandOptionValues
     52 from webkitpy.common.system.logutils import configure_logging as _configure_logging
     53 
     54 
     55 _log = logging.getLogger(__name__)
     56 
     57 
     58 # These are default option values for the command-line option parser.
     59 _DEFAULT_MIN_CONFIDENCE = 1
     60 _DEFAULT_OUTPUT_FORMAT = 'emacs'
     61 
     62 
     63 # FIXME: For style categories we will never want to have, remove them.
     64 #        For categories for which we want to have similar functionality,
     65 #        modify the implementation and enable them.
     66 #
     67 # Throughout this module, we use "filter rule" rather than "filter"
     68 # for an individual boolean filter flag like "+foo".  This allows us to
     69 # reserve "filter" for what one gets by collectively applying all of
     70 # the filter rules.
     71 #
     72 # The base filter rules are the filter rules that begin the list of
     73 # filter rules used to check style.  For example, these rules precede
     74 # any user-specified filter rules.  Since by default all categories are
     75 # checked, this list should normally include only rules that begin
     76 # with a "-" sign.
     77 _BASE_FILTER_RULES = [
     78     '-build/endif_comment',
     79     '-build/include_what_you_use',  # <string> for std::string
     80     '-build/storage_class',  # const static
     81     '-legal/copyright',
     82     '-readability/multiline_comment',
     83     '-readability/braces',  # int foo() {};
     84     '-readability/fn_size',
     85     '-readability/casting',
     86     '-readability/function',
     87     '-runtime/arrays',  # variable length array
     88     '-runtime/casting',
     89     '-runtime/sizeof',
     90     '-runtime/explicit',  # explicit
     91     '-runtime/virtual',  # virtual dtor
     92     '-runtime/printf',
     93     '-runtime/threadsafe_fn',
     94     '-runtime/rtti',
     95     '-whitespace/blank_line',
     96     '-whitespace/end_of_line',
     97     # List Python pep8 categories last.
     98     #
     99     # Because much of WebKit's Python code base does not abide by the
    100     # PEP8 79 character limit, we ignore the 79-character-limit category
    101     # pep8/E501 for now.
    102     #
    103     # FIXME: Consider bringing WebKit's Python code base into conformance
    104     #        with the 79 character limit, or some higher limit that is
    105     #        agreeable to the WebKit project.
    106     '-pep8/E501',
    107 
    108     # FIXME: Move the pylint rules from the pylintrc to here. This will
    109     # also require us to re-work lint-webkitpy to produce the equivalent output.
    110     ]
    111 
    112 
    113 # The path-specific filter rules.
    114 #
    115 # This list is order sensitive.  Only the first path substring match
    116 # is used.  See the FilterConfiguration documentation in filter.py
    117 # for more information on this list.
    118 #
    119 # Each string appearing in this nested list should have at least
    120 # one associated unit test assertion.  These assertions are located,
    121 # for example, in the test_path_rules_specifier() unit test method of
    122 # checker_unittest.py.
    123 _PATH_RULES_SPECIFIER = [
    124     # Files in these directories are consumers of the WebKit
    125     # API and therefore do not follow the same header including
    126     # discipline as WebCore.
    127 
    128     ([# There is no clean way to avoid "yy_*" names used by flex.
    129       "Source/core/css/CSSParser-in.cpp"],
    130      ["-readability/naming"]),
    131 
    132     # For third-party Python code, keep only the following checks--
    133     #
    134     #   No tabs: to avoid having to set the SVN allow-tabs property.
    135     #   No trailing white space: since this is easy to correct.
    136     #   No carriage-return line endings: since this is easy to correct.
    137     #
    138     (["webkitpy/thirdparty/"],
    139      ["-",
    140       "+pep8/W191",  # Tabs
    141       "+pep8/W291",  # Trailing white space
    142       "+whitespace/carriage_return"]),
    143 
    144     ([# Jinja templates: files have .cpp or .h extensions, but contain
    145       # template code, which can't be handled, so disable tests.
    146       "Source/bindings/templates",
    147       "Source/build/scripts/templates"],
    148      ["-"]),
    149 
    150     ([# IDL compiler reference output
    151       # Conforming to style significantly increases the complexity of the code
    152       # generator and decreases *its* readability, which is of more concern
    153       # than style of the machine-generated code itself.
    154       "Source/bindings/tests/results"],
    155      ["-"]),
    156 ]
    157 
    158 
    159 _CPP_FILE_EXTENSIONS = [
    160     'c',
    161     'cpp',
    162     'h',
    163     ]
    164 
    165 _JSON_FILE_EXTENSION = 'json'
    166 
    167 _PYTHON_FILE_EXTENSION = 'py'
    168 
    169 _TEXT_FILE_EXTENSIONS = [
    170     'cc',
    171     'cgi',
    172     'css',
    173     'gyp',
    174     'gypi',
    175     'html',
    176     'idl',
    177     'in',
    178     'js',
    179     'mm',
    180     'php',
    181     'pl',
    182     'pm',
    183     'rb',
    184     'sh',
    185     'txt',
    186     'xhtml',
    187     'y',
    188     ]
    189 
    190 _XCODEPROJ_FILE_EXTENSION = 'pbxproj'
    191 
    192 _XML_FILE_EXTENSIONS = [
    193     'vcproj',
    194     'vsprops',
    195     ]
    196 
    197 _PNG_FILE_EXTENSION = 'png'
    198 
    199 # Files to skip that are less obvious.
    200 #
    201 # Some files should be skipped when checking style. For example,
    202 # WebKit maintains some files in Mozilla style on purpose to ease
    203 # future merges.
    204 _SKIPPED_FILES_WITH_WARNING = [
    205     "Source/WebKit/gtk/tests/",
    206     # All WebKit*.h files in Source/WebKit2/UIProcess/API/gtk,
    207     # except those ending in ...Private.h are GTK+ API headers,
    208     # which differ greatly from WebKit coding style.
    209     re.compile(r'Source/WebKit2/UIProcess/API/gtk/WebKit(?!.*Private\.h).*\.h$'),
    210     re.compile(r'Source/WebKit2/WebProcess/InjectedBundle/API/gtk/WebKit(?!.*Private\.h).*\.h$'),
    211     'Source/WebKit2/UIProcess/API/gtk/webkit2.h',
    212     'Source/WebKit2/WebProcess/InjectedBundle/API/gtk/webkit-web-extension.h']
    213 
    214 # Files to skip that are more common or obvious.
    215 #
    216 # This list should be in addition to files with FileType.NONE.  Files
    217 # with FileType.NONE are automatically skipped without warning.
    218 _SKIPPED_FILES_WITHOUT_WARNING = [
    219     "LayoutTests" + os.path.sep,
    220     "Source/ThirdParty/leveldb" + os.path.sep,
    221     # Prevents this being recognized as a text file.
    222     "Source/WebCore/GNUmakefile.features.am.in",
    223     ]
    224 
    225 # Extensions of files which are allowed to contain carriage returns.
    226 _CARRIAGE_RETURN_ALLOWED_FILE_EXTENSIONS = [
    227     'png',
    228     'vcproj',
    229     'vsprops',
    230     ]
    231 
    232 # The maximum number of errors to report per file, per category.
    233 # If a category is not a key, then it has no maximum.
    234 _MAX_REPORTS_PER_CATEGORY = {
    235     "whitespace/carriage_return": 1
    236 }
    237 
    238 
    239 def _all_categories():
    240     """Return the set of all categories used by check-webkit-style."""
    241     # Take the union across all checkers.
    242     categories = CommonCategories.union(CppChecker.categories)
    243     categories = categories.union(JSONChecker.categories)
    244     categories = categories.union(TestExpectationsChecker.categories)
    245     categories = categories.union(PNGChecker.categories)
    246 
    247     # FIXME: Consider adding all of the pep8 categories.  Since they
    248     #        are not too meaningful for documentation purposes, for
    249     #        now we add only the categories needed for the unit tests
    250     #        (which validate the consistency of the configuration
    251     #        settings against the known categories, etc).
    252     categories = categories.union(["pep8/W191", "pep8/W291", "pep8/E501"])
    253 
    254     return categories
    255 
    256 
    257 def _check_webkit_style_defaults():
    258     """Return the default command-line options for check-webkit-style."""
    259     return DefaultCommandOptionValues(min_confidence=_DEFAULT_MIN_CONFIDENCE,
    260                                       output_format=_DEFAULT_OUTPUT_FORMAT)
    261 
    262 
    263 # This function assists in optparser not having to import from checker.
    264 def check_webkit_style_parser():
    265     all_categories = _all_categories()
    266     default_options = _check_webkit_style_defaults()
    267     return ArgumentParser(all_categories=all_categories,
    268                           base_filter_rules=_BASE_FILTER_RULES,
    269                           default_options=default_options)
    270 
    271 
    272 def check_webkit_style_configuration(options):
    273     """Return a StyleProcessorConfiguration instance for check-webkit-style.
    274 
    275     Args:
    276       options: A CommandOptionValues instance.
    277 
    278     """
    279     filter_configuration = FilterConfiguration(
    280                                base_rules=_BASE_FILTER_RULES,
    281                                path_specific=_PATH_RULES_SPECIFIER,
    282                                user_rules=options.filter_rules)
    283 
    284     return StyleProcessorConfiguration(filter_configuration=filter_configuration,
    285                max_reports_per_category=_MAX_REPORTS_PER_CATEGORY,
    286                min_confidence=options.min_confidence,
    287                output_format=options.output_format,
    288                stderr_write=sys.stderr.write)
    289 
    290 
    291 def _create_log_handlers(stream):
    292     """Create and return a default list of logging.Handler instances.
    293 
    294     Format WARNING messages and above to display the logging level, and
    295     messages strictly below WARNING not to display it.
    296 
    297     Args:
    298       stream: See the configure_logging() docstring.
    299 
    300     """
    301     # Handles logging.WARNING and above.
    302     error_handler = logging.StreamHandler(stream)
    303     error_handler.setLevel(logging.WARNING)
    304     formatter = logging.Formatter("%(levelname)s: %(message)s")
    305     error_handler.setFormatter(formatter)
    306 
    307     # Create a logging.Filter instance that only accepts messages
    308     # below WARNING (i.e. filters out anything WARNING or above).
    309     non_error_filter = logging.Filter()
    310     # The filter method accepts a logging.LogRecord instance.
    311     non_error_filter.filter = lambda record: record.levelno < logging.WARNING
    312 
    313     non_error_handler = logging.StreamHandler(stream)
    314     non_error_handler.addFilter(non_error_filter)
    315     formatter = logging.Formatter("%(message)s")
    316     non_error_handler.setFormatter(formatter)
    317 
    318     return [error_handler, non_error_handler]
    319 
    320 
    321 def _create_debug_log_handlers(stream):
    322     """Create and return a list of logging.Handler instances for debugging.
    323 
    324     Args:
    325       stream: See the configure_logging() docstring.
    326 
    327     """
    328     handler = logging.StreamHandler(stream)
    329     formatter = logging.Formatter("%(name)s: %(levelname)-8s %(message)s")
    330     handler.setFormatter(formatter)
    331 
    332     return [handler]
    333 
    334 
    335 def configure_logging(stream, logger=None, is_verbose=False):
    336     """Configure logging, and return the list of handlers added.
    337 
    338     Returns:
    339       A list of references to the logging handlers added to the root
    340       logger.  This allows the caller to later remove the handlers
    341       using logger.removeHandler.  This is useful primarily during unit
    342       testing where the caller may want to configure logging temporarily
    343       and then undo the configuring.
    344 
    345     Args:
    346       stream: A file-like object to which to log.  The stream must
    347               define an "encoding" data attribute, or else logging
    348               raises an error.
    349       logger: A logging.logger instance to configure.  This parameter
    350               should be used only in unit tests.  Defaults to the
    351               root logger.
    352       is_verbose: A boolean value of whether logging should be verbose.
    353 
    354     """
    355     # If the stream does not define an "encoding" data attribute, the
    356     # logging module can throw an error like the following:
    357     #
    358     # Traceback (most recent call last):
    359     #   File "/System/Library/Frameworks/Python.framework/Versions/2.6/...
    360     #         lib/python2.6/logging/__init__.py", line 761, in emit
    361     #     self.stream.write(fs % msg.encode(self.stream.encoding))
    362     # LookupError: unknown encoding: unknown
    363     if logger is None:
    364         logger = logging.getLogger()
    365 
    366     if is_verbose:
    367         logging_level = logging.DEBUG
    368         handlers = _create_debug_log_handlers(stream)
    369     else:
    370         logging_level = logging.INFO
    371         handlers = _create_log_handlers(stream)
    372 
    373     handlers = _configure_logging(logging_level=logging_level, logger=logger,
    374                                   handlers=handlers)
    375 
    376     return handlers
    377 
    378 
    379 # Enum-like idiom
    380 class FileType:
    381 
    382     NONE = 0  # FileType.NONE evaluates to False.
    383     # Alphabetize remaining types
    384     # CHANGELOG = 1
    385     CPP = 2
    386     JSON = 3
    387     PNG = 4
    388     PYTHON = 5
    389     TEXT = 6
    390     # WATCHLIST = 7
    391     XML = 8
    392     XCODEPROJ = 9
    393 
    394 
    395 class CheckerDispatcher(object):
    396 
    397     """Supports determining whether and how to check style, based on path."""
    398 
    399     def _file_extension(self, file_path):
    400         """Return the file extension without the leading dot."""
    401         return os.path.splitext(file_path)[1].lstrip(".")
    402 
    403     def _should_skip_file_path(self, file_path, skip_array_entry):
    404         match = re.search("\s*png$", file_path)
    405         if match:
    406             return False
    407         if isinstance(skip_array_entry, str):
    408             if file_path.find(skip_array_entry) >= 0:
    409                 return True
    410         elif skip_array_entry.match(file_path):
    411                 return True
    412         return False
    413 
    414     def should_skip_with_warning(self, file_path):
    415         """Return whether the given file should be skipped with a warning."""
    416         for skipped_file in _SKIPPED_FILES_WITH_WARNING:
    417             if self._should_skip_file_path(file_path, skipped_file):
    418                 return True
    419         return False
    420 
    421     def should_skip_without_warning(self, file_path):
    422         """Return whether the given file should be skipped without a warning."""
    423         if not self._file_type(file_path):  # FileType.NONE.
    424             return True
    425         # Since "LayoutTests" is in _SKIPPED_FILES_WITHOUT_WARNING, make
    426         # an exception to prevent files like 'TestExpectations' from being skipped.
    427         #
    428         # FIXME: Figure out a good way to avoid having to add special logic
    429         #        for this special case.
    430         basename = os.path.basename(file_path)
    431         if basename == 'TestExpectations':
    432             return False
    433         for skipped_file in _SKIPPED_FILES_WITHOUT_WARNING:
    434             if self._should_skip_file_path(file_path, skipped_file):
    435                 return True
    436         return False
    437 
    438     def should_check_and_strip_carriage_returns(self, file_path):
    439         return self._file_extension(file_path) not in _CARRIAGE_RETURN_ALLOWED_FILE_EXTENSIONS
    440 
    441     def _file_type(self, file_path):
    442         """Return the file type corresponding to the given file."""
    443         file_extension = self._file_extension(file_path)
    444 
    445         if (file_extension in _CPP_FILE_EXTENSIONS) or (file_path == '-'):
    446             # FIXME: Do something about the comment below and the issue it
    447             #        raises since cpp_style already relies on the extension.
    448             #
    449             # Treat stdin as C++. Since the extension is unknown when
    450             # reading from stdin, cpp_style tests should not rely on
    451             # the extension.
    452             return FileType.CPP
    453         elif file_extension == _JSON_FILE_EXTENSION:
    454             return FileType.JSON
    455         elif file_extension == _PYTHON_FILE_EXTENSION:
    456             return FileType.PYTHON
    457         elif file_extension in _XML_FILE_EXTENSIONS:
    458             return FileType.XML
    459         elif file_extension == _XCODEPROJ_FILE_EXTENSION:
    460             return FileType.XCODEPROJ
    461         elif file_extension == _PNG_FILE_EXTENSION:
    462             return FileType.PNG
    463         elif ((not file_extension and os.path.join("Tools", "Scripts") in file_path) or
    464               file_extension in _TEXT_FILE_EXTENSIONS or os.path.basename(file_path) == 'TestExpectations'):
    465             return FileType.TEXT
    466         else:
    467             return FileType.NONE
    468 
    469     def _create_checker(self, file_type, file_path, handle_style_error,
    470                         min_confidence):
    471         """Instantiate and return a style checker based on file type."""
    472         if file_type == FileType.NONE:
    473             checker = None
    474         elif file_type == FileType.CPP:
    475             file_extension = self._file_extension(file_path)
    476             checker = CppChecker(file_path, file_extension,
    477                                  handle_style_error, min_confidence)
    478         elif file_type == FileType.JSON:
    479             checker = JSONChecker(file_path, handle_style_error)
    480         elif file_type == FileType.PYTHON:
    481             checker = PythonChecker(file_path, handle_style_error)
    482         elif file_type == FileType.XML:
    483             checker = XMLChecker(file_path, handle_style_error)
    484         elif file_type == FileType.XCODEPROJ:
    485             checker = XcodeProjectFileChecker(file_path, handle_style_error)
    486         elif file_type == FileType.PNG:
    487             checker = PNGChecker(file_path, handle_style_error)
    488         elif file_type == FileType.TEXT:
    489             basename = os.path.basename(file_path)
    490             if basename == 'TestExpectations':
    491                 checker = TestExpectationsChecker(file_path, handle_style_error)
    492             else:
    493                 checker = TextChecker(file_path, handle_style_error)
    494         else:
    495             raise ValueError('Invalid file type "%(file_type)s": the only valid file types '
    496                              "are %(NONE)s, %(CPP)s, and %(TEXT)s."
    497                              % {"file_type": file_type,
    498                                 "NONE": FileType.NONE,
    499                                 "CPP": FileType.CPP,
    500                                 "TEXT": FileType.TEXT})
    501 
    502         return checker
    503 
    504     def dispatch(self, file_path, handle_style_error, min_confidence):
    505         """Instantiate and return a style checker based on file path."""
    506         file_type = self._file_type(file_path)
    507 
    508         checker = self._create_checker(file_type,
    509                                        file_path,
    510                                        handle_style_error,
    511                                        min_confidence)
    512         return checker
    513 
    514 
    515 # FIXME: Remove the stderr_write attribute from this class and replace
    516 #        its use with calls to a logging module logger.
    517 class StyleProcessorConfiguration(object):
    518 
    519     """Stores configuration values for the StyleProcessor class.
    520 
    521     Attributes:
    522       min_confidence: An integer between 1 and 5 inclusive that is the
    523                       minimum confidence level of style errors to report.
    524 
    525       max_reports_per_category: The maximum number of errors to report
    526                                 per category, per file.
    527 
    528       stderr_write: A function that takes a string as a parameter and
    529                     serves as stderr.write.
    530 
    531     """
    532 
    533     def __init__(self,
    534                  filter_configuration,
    535                  max_reports_per_category,
    536                  min_confidence,
    537                  output_format,
    538                  stderr_write):
    539         """Create a StyleProcessorConfiguration instance.
    540 
    541         Args:
    542           filter_configuration: A FilterConfiguration instance.  The default
    543                                 is the "empty" filter configuration, which
    544                                 means that all errors should be checked.
    545 
    546           max_reports_per_category: The maximum number of errors to report
    547                                     per category, per file.
    548 
    549           min_confidence: An integer between 1 and 5 inclusive that is the
    550                           minimum confidence level of style errors to report.
    551                           The default is 1, which reports all style errors.
    552 
    553           output_format: A string that is the output format.  The supported
    554                          output formats are "emacs" which emacs can parse
    555                          and "vs7" which Microsoft Visual Studio 7 can parse.
    556 
    557           stderr_write: A function that takes a string as a parameter and
    558                         serves as stderr.write.
    559 
    560         """
    561         self._filter_configuration = filter_configuration
    562         self._output_format = output_format
    563 
    564         self.max_reports_per_category = max_reports_per_category
    565         self.min_confidence = min_confidence
    566         self.stderr_write = stderr_write
    567 
    568     def is_reportable(self, category, confidence_in_error, file_path):
    569         """Return whether an error is reportable.
    570 
    571         An error is reportable if both the confidence in the error is
    572         at least the minimum confidence level and the current filter
    573         says the category should be checked for the given path.
    574 
    575         Args:
    576           category: A string that is a style category.
    577           confidence_in_error: An integer between 1 and 5 inclusive that is
    578                                the application's confidence in the error.
    579                                A higher number means greater confidence.
    580           file_path: The path of the file being checked
    581 
    582         """
    583         if confidence_in_error < self.min_confidence:
    584             return False
    585 
    586         return self._filter_configuration.should_check(category, file_path)
    587 
    588     def write_style_error(self,
    589                           category,
    590                           confidence_in_error,
    591                           file_path,
    592                           line_number,
    593                           message):
    594         """Write a style error to the configured stderr."""
    595         if self._output_format == 'vs7':
    596             format_string = "%s(%s):  %s  [%s] [%d]\n"
    597         else:
    598             format_string = "%s:%s:  %s  [%s] [%d]\n"
    599 
    600         self.stderr_write(format_string % (file_path,
    601                                            line_number,
    602                                            message,
    603                                            category,
    604                                            confidence_in_error))
    605 
    606 
    607 class ProcessorBase(object):
    608 
    609     """The base class for processors of lists of lines."""
    610 
    611     def should_process(self, file_path):
    612         """Return whether the file at file_path should be processed.
    613 
    614         The TextFileReader class calls this method prior to reading in
    615         the lines of a file.  Use this method, for example, to prevent
    616         the style checker from reading binary files into memory.
    617 
    618         """
    619         raise NotImplementedError('Subclasses should implement.')
    620 
    621     def process(self, lines, file_path, **kwargs):
    622         """Process lines of text read from a file.
    623 
    624         Args:
    625           lines: A list of lines of text to process.
    626           file_path: The path from which the lines were read.
    627           **kwargs: This argument signifies that the process() method of
    628                     subclasses of ProcessorBase may support additional
    629                     keyword arguments.
    630                         For example, a style checker's check() method
    631                     may support a "reportable_lines" parameter that represents
    632                     the line numbers of the lines for which style errors
    633                     should be reported.
    634 
    635         """
    636         raise NotImplementedError('Subclasses should implement.')
    637 
    638 
    639 class StyleProcessor(ProcessorBase):
    640 
    641     """A ProcessorBase for checking style.
    642 
    643     Attributes:
    644       error_count: An integer that is the total number of reported
    645                    errors for the lifetime of this instance.
    646 
    647     """
    648 
    649     def __init__(self, configuration, mock_dispatcher=None,
    650                  mock_increment_error_count=None,
    651                  mock_carriage_checker_class=None):
    652         """Create an instance.
    653 
    654         Args:
    655           configuration: A StyleProcessorConfiguration instance.
    656           mock_dispatcher: A mock CheckerDispatcher instance.  This
    657                            parameter is for unit testing.  Defaults to a
    658                            CheckerDispatcher instance.
    659           mock_increment_error_count: A mock error-count incrementer.
    660           mock_carriage_checker_class: A mock class for checking and
    661                                        transforming carriage returns.
    662                                        This parameter is for unit testing.
    663                                        Defaults to CarriageReturnChecker.
    664 
    665         """
    666         if mock_dispatcher is None:
    667             dispatcher = CheckerDispatcher()
    668         else:
    669             dispatcher = mock_dispatcher
    670 
    671         if mock_increment_error_count is None:
    672             # The following blank line is present to avoid flagging by pep8.py.
    673 
    674             def increment_error_count():
    675                 """Increment the total count of reported errors."""
    676                 self.error_count += 1
    677         else:
    678             increment_error_count = mock_increment_error_count
    679 
    680         if mock_carriage_checker_class is None:
    681             # This needs to be a class rather than an instance since the
    682             # process() method instantiates one using parameters.
    683             carriage_checker_class = CarriageReturnChecker
    684         else:
    685             carriage_checker_class = mock_carriage_checker_class
    686 
    687         self.error_count = 0
    688 
    689         self._carriage_checker_class = carriage_checker_class
    690         self._configuration = configuration
    691         self._dispatcher = dispatcher
    692         self._increment_error_count = increment_error_count
    693 
    694     def should_process(self, file_path):
    695         """Return whether the file should be checked for style."""
    696         if self._dispatcher.should_skip_without_warning(file_path):
    697             return False
    698         if self._dispatcher.should_skip_with_warning(file_path):
    699             _log.warn('File exempt from style guide. Skipping: "%s"'
    700                       % file_path)
    701             return False
    702         return True
    703 
    704     def process(self, lines, file_path, line_numbers=None):
    705         """Check the given lines for style.
    706 
    707         Arguments:
    708           lines: A list of all lines in the file to check.
    709           file_path: The path of the file to process.  If possible, the path
    710                      should be relative to the source root.  Otherwise,
    711                      path-specific logic may not behave as expected.
    712           line_numbers: A list of line numbers of the lines for which
    713                         style errors should be reported, or None if errors
    714                         for all lines should be reported.  When not None, this
    715                         list normally contains the line numbers corresponding
    716                         to the modified lines of a patch.
    717 
    718         """
    719         _log.debug("Checking style: " + file_path)
    720 
    721         style_error_handler = DefaultStyleErrorHandler(
    722             configuration=self._configuration,
    723             file_path=file_path,
    724             increment_error_count=self._increment_error_count,
    725             line_numbers=line_numbers)
    726 
    727         carriage_checker = self._carriage_checker_class(style_error_handler)
    728 
    729         # Check for and remove trailing carriage returns ("\r").
    730         if self._dispatcher.should_check_and_strip_carriage_returns(file_path):
    731             lines = carriage_checker.check(lines)
    732 
    733         min_confidence = self._configuration.min_confidence
    734         checker = self._dispatcher.dispatch(file_path,
    735                                             style_error_handler,
    736                                             min_confidence)
    737 
    738         if checker is None:
    739             raise AssertionError("File should not be checked: '%s'" % file_path)
    740 
    741         _log.debug("Using class: " + checker.__class__.__name__)
    742 
    743         checker.check(lines)
    744