1134344783f5e521b93365f6ea6d3df2525cb8e7
[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       # Nix platform API classes uses common names under Nix namespace
180       # so the include guards should also include the namespace to avoid
181       # name clashes.
182        "Source/Platform/nix"],
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       "-runtime/ctype_function",
208       "-whitespace/declaration",
209       "-build/include_order"]),
210
211     # WebKit2 rules:
212     # WebKit2 and certain directories have idiosyncracies.
213     ([# NPAPI has function names with underscores.
214       "Source/WebKit2/WebProcess/Plugins/Netscape"],
215      ["-readability/naming"]),
216     ([# The WebKit2 C API has names with underscores and whitespace-aligned
217       # struct members. Also, we allow unnecessary parameter names in
218       # WebKit2 APIs because we're matching CF's header style.
219       # Additionally, we use word which starts with non-capital letter 'k'
220       # for types of enums.
221       "Source/WebKit2/UIProcess/API/C/",
222       "Source/WebKit2/Shared/API/c/",
223       "Source/WebKit2/WebProcess/InjectedBundle/API/c/"],
224      ["-readability/enum_casing",
225       "-readability/naming",
226       "-readability/parameter_name",
227       "-whitespace/declaration"]),
228     ([# These files define GObjects, which implies some definitions of
229       # variables and functions containing underscores.
230       "Source/WebCore/bindings/gobject/WebKitDOMCustom.cpp",
231       "Source/WebCore/bindings/gobject/WebKitDOMDeprecated.cpp",
232       "Source/WebCore/bindings/gobject/WebKitDOMEventTarget.cpp",
233       "Source/WebCore/platform/graphics/gstreamer/VideoSinkGStreamer1.cpp",
234       "Source/WebCore/platform/graphics/gstreamer/VideoSinkGStreamer.cpp",
235       "Source/WebCore/platform/graphics/gstreamer/WebKitWebSourceGStreamer.cpp",
236       "Source/WebCore/platform/audio/gstreamer/WebKitWebAudioSourceGStreamer.cpp",
237       "Source/WebCore/platform/network/soup/ProxyResolverSoup.cpp",
238       "Source/WebCore/platform/network/soup/ProxyResolverSoup.h"],
239      ["-readability/naming",
240       "-readability/enum_casing"]),
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     ([# There is no way to avoid the symbols __jit_debug_register_code
255       # and __jit_debug_descriptor when integrating with gdb.
256       "Source/JavaScriptCore/jit/GDBInterface.cpp"],
257      ["-readability/naming"]),
258
259     ([# On some systems the trailing CR is causing parser failure.
260       "Source/JavaScriptCore/parser/Keywords.table"],
261      ["+whitespace/carriage_return"]),
262 ]
263
264
265 _CPP_FILE_EXTENSIONS = [
266     'c',
267     'cpp',
268     'h',
269     ]
270
271 _JS_FILE_EXTENSION = 'js'
272
273 _JSON_FILE_EXTENSION = 'json'
274
275 _PYTHON_FILE_EXTENSION = 'py'
276
277 _TEXT_FILE_EXTENSIONS = [
278     'ac',
279     'cc',
280     'cgi',
281     'css',
282     'exp',
283     'flex',
284     'gyp',
285     'gypi',
286     'html',
287     'idl',
288     'in',
289     'mm',
290     'php',
291     'pl',
292     'pm',
293     'pri',
294     'pro',
295     'rb',
296     'sh',
297     'table',
298     'txt',
299     'wm',
300     'xhtml',
301     'y',
302     ]
303
304 _XCODEPROJ_FILE_EXTENSION = 'pbxproj'
305
306 _XML_FILE_EXTENSIONS = [
307     'vcproj',
308     'vsprops',
309     ]
310
311 _PNG_FILE_EXTENSION = 'png'
312
313 _CMAKE_FILE_EXTENSION = 'cmake'
314
315 # Files to skip that are less obvious.
316 #
317 # Some files should be skipped when checking style. For example,
318 # WebKit maintains some files in Mozilla style on purpose to ease
319 # future merges.
320 _SKIPPED_FILES_WITH_WARNING = [
321     "Tools/TestWebKitAPI/Tests/WebKitGtk/",
322     # All WebKit*.h files in Source/WebKit2/UIProcess/API/gtk,
323     # except those ending in ...Private.h are GTK+ API headers,
324     # which differ greatly from WebKit coding style.
325     re.compile(r'Source/WebKit2/UIProcess/API/gtk/WebKit(?!.*Private\.h).*\.h$'),
326     re.compile(r'Source/WebKit2/WebProcess/InjectedBundle/API/gtk/WebKit(?!.*Private\.h).*\.h$'),
327     'Source/WebKit2/UIProcess/API/gtk/webkit2.h',
328     'Source/WebKit2/WebProcess/InjectedBundle/API/gtk/webkit-web-extension.h']
329
330 # Files to skip that are more common or obvious.
331 #
332 # This list should be in addition to files with FileType.NONE.  Files
333 # with FileType.NONE are automatically skipped without warning.
334 _SKIPPED_FILES_WITHOUT_WARNING = [
335     "LayoutTests" + os.path.sep,
336     "Source/ThirdParty/leveldb" + os.path.sep,
337     # Prevents this being recognized as a text file.
338     "Source/WebCore/GNUmakefile.features.am.in",
339     ]
340
341 # Extensions of files which are allowed to contain carriage returns.
342 _CARRIAGE_RETURN_ALLOWED_FILE_EXTENSIONS = [
343     'png',
344     'vcproj',
345     'vsprops',
346     ]
347
348 # The maximum number of errors to report per file, per category.
349 # If a category is not a key, then it has no maximum.
350 _MAX_REPORTS_PER_CATEGORY = {
351     "whitespace/carriage_return": 1
352 }
353
354
355 def _all_categories():
356     """Return the set of all categories used by check-webkit-style."""
357     # Take the union across all checkers.
358     categories = CommonCategories.union(CppChecker.categories)
359     categories = categories.union(JSChecker.categories)
360     categories = categories.union(JSONChecker.categories)
361     categories = categories.union(TestExpectationsChecker.categories)
362     categories = categories.union(ChangeLogChecker.categories)
363     categories = categories.union(PNGChecker.categories)
364     categories = categories.union(FeatureDefinesChecker.categories)
365
366     # FIXME: Consider adding all of the pep8 categories.  Since they
367     #        are not too meaningful for documentation purposes, for
368     #        now we add only the categories needed for the unit tests
369     #        (which validate the consistency of the configuration
370     #        settings against the known categories, etc).
371     categories = categories.union(["pep8/W191", "pep8/W291", "pep8/E501"])
372
373     return categories
374
375
376 def _check_webkit_style_defaults():
377     """Return the default command-line options for check-webkit-style."""
378     return DefaultCommandOptionValues(min_confidence=_DEFAULT_MIN_CONFIDENCE,
379                                       output_format=_DEFAULT_OUTPUT_FORMAT)
380
381
382 # This function assists in optparser not having to import from checker.
383 def check_webkit_style_parser():
384     all_categories = _all_categories()
385     default_options = _check_webkit_style_defaults()
386     return ArgumentParser(all_categories=all_categories,
387                           base_filter_rules=_BASE_FILTER_RULES,
388                           default_options=default_options)
389
390
391 def check_webkit_style_configuration(options):
392     """Return a StyleProcessorConfiguration instance for check-webkit-style.
393
394     Args:
395       options: A CommandOptionValues instance.
396
397     """
398     filter_configuration = FilterConfiguration(
399                                base_rules=_BASE_FILTER_RULES,
400                                path_specific=_PATH_RULES_SPECIFIER,
401                                user_rules=options.filter_rules)
402
403     return StyleProcessorConfiguration(filter_configuration=filter_configuration,
404                max_reports_per_category=_MAX_REPORTS_PER_CATEGORY,
405                min_confidence=options.min_confidence,
406                output_format=options.output_format,
407                commit_queue=options.commit_queue)
408
409
410 def _create_log_handlers(stream):
411     """Create and return a default list of logging.Handler instances.
412
413     Format WARNING messages and above to display the logging level, and
414     messages strictly below WARNING not to display it.
415
416     Args:
417       stream: See the configure_logging() docstring.
418
419     """
420     # Handles logging.WARNING and above.
421     error_handler = logging.StreamHandler(stream)
422     error_handler.setLevel(logging.WARNING)
423     formatter = logging.Formatter("%(levelname)s: %(message)s")
424     error_handler.setFormatter(formatter)
425
426     # Create a logging.Filter instance that only accepts messages
427     # below WARNING (i.e. filters out anything WARNING or above).
428     non_error_filter = logging.Filter()
429     # The filter method accepts a logging.LogRecord instance.
430     non_error_filter.filter = lambda record: record.levelno < logging.WARNING
431
432     non_error_handler = logging.StreamHandler(stream)
433     non_error_handler.addFilter(non_error_filter)
434     formatter = logging.Formatter("%(message)s")
435     non_error_handler.setFormatter(formatter)
436
437     return [error_handler, non_error_handler]
438
439
440 def _create_debug_log_handlers(stream):
441     """Create and return a list of logging.Handler instances for debugging.
442
443     Args:
444       stream: See the configure_logging() docstring.
445
446     """
447     handler = logging.StreamHandler(stream)
448     formatter = logging.Formatter("%(name)s: %(levelname)-8s %(message)s")
449     handler.setFormatter(formatter)
450
451     return [handler]
452
453
454 def configure_logging(stream, logger=None, is_verbose=False):
455     """Configure logging, and return the list of handlers added.
456
457     Returns:
458       A list of references to the logging handlers added to the root
459       logger.  This allows the caller to later remove the handlers
460       using logger.removeHandler.  This is useful primarily during unit
461       testing where the caller may want to configure logging temporarily
462       and then undo the configuring.
463
464     Args:
465       stream: A file-like object to which to log.  The stream must
466               define an "encoding" data attribute, or else logging
467               raises an error.
468       logger: A logging.logger instance to configure.  This parameter
469               should be used only in unit tests.  Defaults to the
470               root logger.
471       is_verbose: A boolean value of whether logging should be verbose.
472
473     """
474     # If the stream does not define an "encoding" data attribute, the
475     # logging module can throw an error like the following:
476     #
477     # Traceback (most recent call last):
478     #   File "/System/Library/Frameworks/Python.framework/Versions/2.6/...
479     #         lib/python2.6/logging/__init__.py", line 761, in emit
480     #     self.stream.write(fs % msg.encode(self.stream.encoding))
481     # LookupError: unknown encoding: unknown
482     if logger is None:
483         logger = logging.getLogger()
484
485     if is_verbose:
486         logging_level = logging.DEBUG
487         handlers = _create_debug_log_handlers(stream)
488     else:
489         logging_level = logging.INFO
490         handlers = _create_log_handlers(stream)
491
492     handlers = _configure_logging(logging_level=logging_level, logger=logger,
493                                   handlers=handlers)
494
495     return handlers
496
497
498 # Enum-like idiom
499 class FileType:
500
501     NONE = 0  # FileType.NONE evaluates to False.
502     # Alphabetize remaining types
503     CHANGELOG = 1
504     CPP = 2
505     JS = 3
506     JSON = 4
507     PNG = 5
508     PYTHON = 6
509     TEXT = 7
510     WATCHLIST = 8
511     XML = 9
512     XCODEPROJ = 10
513     CMAKE = 11
514     FEATUREDEFINES = 12
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 == _JS_FILE_EXTENSION:
579             return FileType.JS
580         elif file_extension == _JSON_FILE_EXTENSION:
581             return FileType.JSON
582         elif file_extension == _PYTHON_FILE_EXTENSION:
583             return FileType.PYTHON
584         elif file_extension in _XML_FILE_EXTENSIONS:
585             return FileType.XML
586         elif os.path.basename(file_path).startswith('ChangeLog'):
587             return FileType.CHANGELOG
588         elif os.path.basename(file_path) == 'watchlist':
589             return FileType.WATCHLIST
590         elif file_extension == _XCODEPROJ_FILE_EXTENSION:
591             return FileType.XCODEPROJ
592         elif file_extension == _PNG_FILE_EXTENSION:
593             return FileType.PNG
594         elif ((file_extension == _CMAKE_FILE_EXTENSION) or os.path.basename(file_path) == 'CMakeLists.txt'):
595             return FileType.CMAKE
596         elif ((not file_extension and os.path.join("Tools", "Scripts") in file_path) or
597               file_extension in _TEXT_FILE_EXTENSIONS or os.path.basename(file_path) == 'TestExpectations'):
598             return FileType.TEXT
599         elif os.path.basename(file_path) == "FeatureDefines.xcconfig":
600             return FileType.FEATUREDEFINES
601         else:
602             return FileType.NONE
603
604     def _create_checker(self, file_type, file_path, handle_style_error,
605                         min_confidence, commit_queue):
606         """Instantiate and return a style checker based on file type."""
607         if file_type == FileType.NONE:
608             checker = None
609         elif file_type == FileType.CHANGELOG:
610             should_line_be_checked = None
611             if handle_style_error:
612                 should_line_be_checked = handle_style_error.should_line_be_checked
613             checker = ChangeLogChecker(file_path, handle_style_error, should_line_be_checked)
614         elif file_type == FileType.CPP:
615             file_extension = self._file_extension(file_path)
616             checker = CppChecker(file_path, file_extension,
617                                  handle_style_error, min_confidence)
618         elif file_type == FileType.JS:
619             # Do not attempt to check non-Inspector or 3rd-party JavaScript files as JS.
620             if os.path.join('WebInspectorUI', 'UserInterface') in file_path and (not 'External' in file_path):
621                 checker = JSChecker(file_path, handle_style_error)
622             else:
623                 checker = TextChecker(file_path, handle_style_error)
624         elif file_type == FileType.JSON:
625             basename = os.path.basename(file_path)
626             if commit_queue and basename == 'contributors.json':
627                 checker = JSONContributorsChecker(file_path, handle_style_error)
628             else:
629                 checker = JSONChecker(file_path, handle_style_error)
630         elif file_type == FileType.PYTHON:
631             checker = PythonChecker(file_path, handle_style_error)
632         elif file_type == FileType.XML:
633             checker = XMLChecker(file_path, handle_style_error)
634         elif file_type == FileType.XCODEPROJ:
635             checker = XcodeProjectFileChecker(file_path, handle_style_error)
636         elif file_type == FileType.PNG:
637             checker = PNGChecker(file_path, handle_style_error)
638         elif file_type == FileType.CMAKE:
639             checker = CMakeChecker(file_path, handle_style_error)
640         elif file_type == FileType.TEXT:
641             basename = os.path.basename(file_path)
642             if basename == 'TestExpectations':
643                 checker = TestExpectationsChecker(file_path, handle_style_error)
644             elif file_path.endswith('.messages.in'):
645                 checker = MessagesInChecker(file_path, handle_style_error)
646             else:
647                 checker = TextChecker(file_path, handle_style_error)
648         elif file_type == FileType.WATCHLIST:
649             checker = WatchListChecker(file_path, handle_style_error)
650         elif file_type == FileType.FEATUREDEFINES:
651             checker = FeatureDefinesChecker(file_path, handle_style_error)
652         else:
653             raise ValueError('Invalid file type "%(file_type)s": the only valid file types '
654                              "are %(NONE)s, %(CPP)s, and %(TEXT)s."
655                              % {"file_type": file_type,
656                                 "NONE": FileType.NONE,
657                                 "CPP": FileType.CPP,
658                                 "TEXT": FileType.TEXT})
659
660         return checker
661
662     def dispatch(self, file_path, handle_style_error, min_confidence, commit_queue):
663         """Instantiate and return a style checker based on file path."""
664         file_type = self._file_type(file_path)
665
666         checker = self._create_checker(file_type,
667                                        file_path,
668                                        handle_style_error,
669                                        min_confidence,
670                                        commit_queue)
671         return checker
672
673
674 class StyleProcessorConfiguration(object):
675
676     """Stores configuration values for the StyleProcessor class.
677
678     Attributes:
679       min_confidence: An integer between 1 and 5 inclusive that is the
680                       minimum confidence level of style errors to report.
681
682       max_reports_per_category: The maximum number of errors to report
683                                 per category, per file.
684
685     """
686
687     def __init__(self,
688                  filter_configuration,
689                  max_reports_per_category,
690                  min_confidence,
691                  output_format,
692                  commit_queue):
693         """Create a StyleProcessorConfiguration instance.
694
695         Args:
696           filter_configuration: A FilterConfiguration instance.  The default
697                                 is the "empty" filter configuration, which
698                                 means that all errors should be checked.
699
700           max_reports_per_category: The maximum number of errors to report
701                                     per category, per file.
702
703           min_confidence: An integer between 1 and 5 inclusive that is the
704                           minimum confidence level of style errors to report.
705                           The default is 1, which reports all style errors.
706
707           output_format: A string that is the output format.  The supported
708                          output formats are "emacs" which emacs can parse
709                          and "vs7" which Microsoft Visual Studio 7 can parse.
710
711           commit_queue: A bool indicating whether the style check is performed
712                         by the commit queue or not.
713
714         """
715         self._filter_configuration = filter_configuration
716         self._output_format = output_format
717
718         self.max_reports_per_category = max_reports_per_category
719         self.min_confidence = min_confidence
720         self.commit_queue = commit_queue
721
722     def is_reportable(self, category, confidence_in_error, file_path):
723         """Return whether an error is reportable.
724
725         An error is reportable if both the confidence in the error is
726         at least the minimum confidence level and the current filter
727         says the category should be checked for the given path.
728
729         Args:
730           category: A string that is a style category.
731           confidence_in_error: An integer between 1 and 5 inclusive that is
732                                the application's confidence in the error.
733                                A higher number means greater confidence.
734           file_path: The path of the file being checked
735
736         """
737         if confidence_in_error < self.min_confidence:
738             return False
739
740         return self._filter_configuration.should_check(category, file_path)
741
742     def write_style_error(self,
743                           category,
744                           confidence_in_error,
745                           file_path,
746                           line_number,
747                           message):
748         """Write a style error to the configured stderr."""
749         if self._output_format == 'vs7':
750             format_string = "%s(%s):  %s  [%s] [%d]"
751         else:
752             format_string = "%s:%s:  %s  [%s] [%d]"
753
754         _log.error(format_string % (file_path,
755                                            line_number,
756                                            message,
757                                            category,
758                                            confidence_in_error))
759
760
761 class ProcessorBase(object):
762
763     """The base class for processors of lists of lines."""
764
765     def should_process(self, file_path):
766         """Return whether the file at file_path should be processed.
767
768         The TextFileReader class calls this method prior to reading in
769         the lines of a file.  Use this method, for example, to prevent
770         the style checker from reading binary files into memory.
771
772         """
773         raise NotImplementedError('Subclasses should implement.')
774
775     def process(self, lines, file_path, **kwargs):
776         """Process lines of text read from a file.
777
778         Args:
779           lines: A list of lines of text to process.
780           file_path: The path from which the lines were read.
781           **kwargs: This argument signifies that the process() method of
782                     subclasses of ProcessorBase may support additional
783                     keyword arguments.
784                         For example, a style checker's check() method
785                     may support a "reportable_lines" parameter that represents
786                     the line numbers of the lines for which style errors
787                     should be reported.
788
789         """
790         raise NotImplementedError('Subclasses should implement.')
791
792
793 class StyleProcessor(ProcessorBase):
794
795     """A ProcessorBase for checking style.
796
797     Attributes:
798       error_count: An integer that is the total number of reported
799                    errors for the lifetime of this instance.
800
801     """
802
803     def __init__(self, configuration, mock_dispatcher=None,
804                  mock_increment_error_count=None,
805                  mock_carriage_checker_class=None):
806         """Create an instance.
807
808         Args:
809           configuration: A StyleProcessorConfiguration instance.
810           mock_dispatcher: A mock CheckerDispatcher instance.  This
811                            parameter is for unit testing.  Defaults to a
812                            CheckerDispatcher instance.
813           mock_increment_error_count: A mock error-count incrementer.
814           mock_carriage_checker_class: A mock class for checking and
815                                        transforming carriage returns.
816                                        This parameter is for unit testing.
817                                        Defaults to CarriageReturnChecker.
818
819         """
820         if mock_dispatcher is None:
821             dispatcher = CheckerDispatcher()
822         else:
823             dispatcher = mock_dispatcher
824
825         if mock_increment_error_count is None:
826             # The following blank line is present to avoid flagging by pep8.py.
827
828             def increment_error_count():
829                 """Increment the total count of reported errors."""
830                 self.error_count += 1
831         else:
832             increment_error_count = mock_increment_error_count
833
834         if mock_carriage_checker_class is None:
835             # This needs to be a class rather than an instance since the
836             # process() method instantiates one using parameters.
837             carriage_checker_class = CarriageReturnChecker
838         else:
839             carriage_checker_class = mock_carriage_checker_class
840
841         self.error_count = 0
842
843         self._carriage_checker_class = carriage_checker_class
844         self._configuration = configuration
845         self._dispatcher = dispatcher
846         self._increment_error_count = increment_error_count
847
848     def should_process(self, file_path):
849         """Return whether the file should be checked for style."""
850         if self._dispatcher.should_skip_without_warning(file_path):
851             return False
852         if self._dispatcher.should_skip_with_warning(file_path):
853             _log.warn('File exempt from style guide. Skipping: "%s"'
854                       % file_path)
855             return False
856         return True
857
858     def process(self, lines, file_path, line_numbers=None):
859         """Check the given lines for style.
860
861         Arguments:
862           lines: A list of all lines in the file to check.
863           file_path: The path of the file to process.  If possible, the path
864                      should be relative to the source root.  Otherwise,
865                      path-specific logic may not behave as expected.
866           line_numbers: A list of line numbers of the lines for which
867                         style errors should be reported, or None if errors
868                         for all lines should be reported.  When not None, this
869                         list normally contains the line numbers corresponding
870                         to the modified lines of a patch.
871
872         """
873         _log.debug("Checking style: " + file_path)
874
875         style_error_handler = DefaultStyleErrorHandler(
876             configuration=self._configuration,
877             file_path=file_path,
878             increment_error_count=self._increment_error_count,
879             line_numbers=line_numbers)
880
881         carriage_checker = self._carriage_checker_class(style_error_handler)
882
883         # Check for and remove trailing carriage returns ("\r").
884         if self._dispatcher.should_check_and_strip_carriage_returns(file_path):
885             lines = carriage_checker.check(lines)
886
887         min_confidence = self._configuration.min_confidence
888         checker = self._dispatcher.dispatch(file_path,
889                                             style_error_handler,
890                                             min_confidence,
891                                             self._configuration.commit_queue)
892
893         if checker is None:
894             raise AssertionError("File should not be checked: '%s'" % file_path)
895
896         _log.debug("Using class: " + checker.__class__.__name__)
897
898         checker.check(lines)