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