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