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