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