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