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