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