initial import
[vuplus_webkit] / 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 #
5 # Redistribution and use in source and binary forms, with or without
6 # modification, are permitted provided that the following conditions are
7 # met:
8 #
9 #     * Redistributions of source code must retain the above copyright
10 # notice, this list of conditions and the following disclaimer.
11 #     * Redistributions in binary form must reproduce the above
12 # copyright notice, this list of conditions and the following disclaimer
13 # in the documentation and/or other materials provided with the
14 # distribution.
15 #     * Neither the name of Google Inc. nor the names of its
16 # contributors may be used to endorse or promote products derived from
17 # this software without specific prior written permission.
18 #
19 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
31 """Front end of some style-checker modules."""
32
33 import logging
34 import os.path
35 import sys
36
37 from checkers.common import categories as CommonCategories
38 from checkers.common import CarriageReturnChecker
39 from checkers.changelog import ChangeLogChecker
40 from checkers.cpp import CppChecker
41 from checkers.python import PythonChecker
42 from checkers.test_expectations import TestExpectationsChecker
43 from checkers.text import TextChecker
44 from checkers.xcodeproj import XcodeProjectFileChecker
45 from checkers.xml import XMLChecker
46 from error_handlers import DefaultStyleErrorHandler
47 from filter import FilterConfiguration
48 from optparser import ArgumentParser
49 from optparser import DefaultCommandOptionValues
50 from webkitpy.common.system.logutils import configure_logging as _configure_logging
51
52
53 _log = logging.getLogger(__name__)
54
55
56 # These are default option values for the command-line option parser.
57 _DEFAULT_MIN_CONFIDENCE = 1
58 _DEFAULT_OUTPUT_FORMAT = 'emacs'
59
60
61 # FIXME: For style categories we will never want to have, remove them.
62 #        For categories for which we want to have similar functionality,
63 #        modify the implementation and enable them.
64 #
65 # Throughout this module, we use "filter rule" rather than "filter"
66 # for an individual boolean filter flag like "+foo".  This allows us to
67 # reserve "filter" for what one gets by collectively applying all of
68 # the filter rules.
69 #
70 # The base filter rules are the filter rules that begin the list of
71 # filter rules used to check style.  For example, these rules precede
72 # any user-specified filter rules.  Since by default all categories are
73 # checked, this list should normally include only rules that begin
74 # with a "-" sign.
75 _BASE_FILTER_RULES = [
76     '-build/endif_comment',
77     '-build/include_what_you_use',  # <string> for std::string
78     '-build/storage_class',  # const static
79     '-legal/copyright',
80     '-readability/multiline_comment',
81     '-readability/braces',  # int foo() {};
82     '-readability/fn_size',
83     '-readability/casting',
84     '-readability/function',
85     '-runtime/arrays',  # variable length array
86     '-runtime/casting',
87     '-runtime/sizeof',
88     '-runtime/explicit',  # explicit
89     '-runtime/virtual',  # virtual dtor
90     '-runtime/printf',
91     '-runtime/threadsafe_fn',
92     '-runtime/rtti',
93     '-whitespace/blank_line',
94     '-whitespace/end_of_line',
95     '-whitespace/labels',
96     # List Python pep8 categories last.
97     #
98     # Because much of WebKit's Python code base does not abide by the
99     # PEP8 79 character limit, we ignore the 79-character-limit category
100     # pep8/E501 for now.
101     #
102     # FIXME: Consider bringing WebKit's Python code base into conformance
103     #        with the 79 character limit, or some higher limit that is
104     #        agreeable to the WebKit project.
105     '-pep8/E501',
106     ]
107
108
109 # The path-specific filter rules.
110 #
111 # This list is order sensitive.  Only the first path substring match
112 # is used.  See the FilterConfiguration documentation in filter.py
113 # for more information on this list.
114 #
115 # Each string appearing in this nested list should have at least
116 # one associated unit test assertion.  These assertions are located,
117 # for example, in the test_path_rules_specifier() unit test method of
118 # checker_unittest.py.
119 _PATH_RULES_SPECIFIER = [
120     # Files in these directories are consumers of the WebKit
121     # API and therefore do not follow the same header including
122     # discipline as WebCore.
123
124     ([# TestNetscapePlugIn has no config.h and uses funny names like
125       # NPP_SetWindow.
126       "Tools/DumpRenderTree/TestNetscapePlugIn/",
127       # The API test harnesses have no config.h and use funny macros like
128       # TEST_CLASS_NAME.
129       "Tools/WebKitAPITest/",
130       "Tools/TestWebKitAPI/",
131       "Source/WebKit/qt/tests/qdeclarativewebview"],
132      ["-build/include",
133       "-readability/naming"]),
134     ([# There is no clean way to avoid "yy_*" names used by flex.
135       "Source/WebCore/css/CSSParser.cpp",
136       # Qt code uses '_' in some places (such as private slots
137       # and on test xxx_data methos on tests)
138       "Source/JavaScriptCore/qt/",
139       "Source/WebKit/qt/Api/",
140       "Source/WebKit/qt/tests/",
141       "Source/WebKit/qt/declarative/",
142       "Source/WebKit/qt/examples/"],
143      ["-readability/naming"]),
144      ([# Qt's MiniBrowser has no config.h
145        "Tools/MiniBrowser/qt"],
146       ["-build/include"]),
147     ([# The GTK+ APIs use GTK+ naming style, which includes
148       # lower-cased, underscore-separated values, whitespace before
149       # parens for function calls, and always having variable names.
150       # Also, GTK+ allows the use of NULL.
151       "Source/WebCore/bindings/scripts/test/GObject",
152       "Source/WebKit/gtk/webkit/",
153       "Tools/DumpRenderTree/gtk/"],
154      ["-readability/naming",
155       "-readability/parameter_name",
156       "-readability/null",
157       "-whitespace/parens"]),
158     ([# Header files in ForwardingHeaders have no header guards or
159       # exceptional header guards (e.g., WebCore_FWD_Debugger_h).
160       "/ForwardingHeaders/"],
161      ["-build/header_guard"]),
162     ([# assembler has lots of opcodes that use underscores, so
163       # we don't check for underscores in that directory.
164       "/Source/JavaScriptCore/assembler/"],
165      ["-readability/naming"]),
166     ([# JITStubs has an usual syntax which causes false alarms for a few checks.
167       "JavaScriptCore/jit/JITStubs.cpp"],
168      ["-readability/parameter_name",
169       "-whitespace/parens"]),
170
171     ([# The EFL APIs use EFL naming style, which includes
172       # both lower-cased and camel-cased, underscore-sparated
173       # values.
174       "Source/WebKit/efl/ewk/",
175       "Source/WebKit2/UIProcess/API/efl/",
176       "Tools/EWebLauncher/",
177       "Tools/MiniBrowser/efl/"],
178      ["-readability/naming",
179       "-readability/parameter_name",
180       "-whitespace/declaration"]),
181
182     # WebKit2 rules:
183     # WebKit2 and certain directories have idiosyncracies.
184     ([# NPAPI has function names with underscores.
185       "Source/WebKit2/WebProcess/Plugins/Netscape"],
186      ["-readability/naming"]),
187     ([# The WebKit2 C API has names with underscores and whitespace-aligned
188       # struct members. Also, we allow unnecessary parameter names in
189       # WebKit2 APIs because we're matching CF's header style.
190       "Source/WebKit2/UIProcess/API/C/",
191       "Source/WebKit2/Shared/API/c/",
192       "Source/WebKit2/WebProcess/InjectedBundle/API/c/"],
193      ["-readability/naming",
194       "-readability/parameter_name",
195       "-whitespace/declaration"]),
196
197     # For third-party Python code, keep only the following checks--
198     #
199     #   No tabs: to avoid having to set the SVN allow-tabs property.
200     #   No trailing white space: since this is easy to correct.
201     #   No carriage-return line endings: since this is easy to correct.
202     #
203     (["webkitpy/thirdparty/"],
204      ["-",
205       "+pep8/W191",  # Tabs
206       "+pep8/W291",  # Trailing white space
207       "+whitespace/carriage_return"]),
208
209     ([# Qt Symbian platform plugin has no config.h or header guard.
210       # Qt code uses '_' in some places (such as private slots
211       # and on test xxx_data methos on tests).
212       "Source/WebKit/qt/symbian/platformplugin/"],
213      ["-readability/naming",
214       "-build/header_guard",
215       "-build/include_order"]),
216
217     ([# glu's libtess is third-party code, and doesn't follow WebKit style.
218       "Source/ThirdParty/glu"],
219      ["-readability",
220       "-whitespace",
221       "-build/header_guard",
222       "-build/include_order"]),
223 ]
224
225
226 _CPP_FILE_EXTENSIONS = [
227     'c',
228     'cpp',
229     'h',
230     ]
231
232 _PYTHON_FILE_EXTENSION = 'py'
233
234 _TEXT_FILE_EXTENSIONS = [
235     'ac',
236     'cc',
237     'cgi',
238     'css',
239     'exp',
240     'flex',
241     'gyp',
242     'gypi',
243     'html',
244     'idl',
245     'in',
246     'js',
247     'mm',
248     'php',
249     'pl',
250     'pm',
251     'pri',
252     'pro',
253     'rb',
254     'sh',
255     'txt',
256     'wm',
257     'xhtml',
258     'y',
259     ]
260
261 _XCODEPROJ_FILE_EXTENSION = 'pbxproj'
262
263 _XML_FILE_EXTENSIONS = [
264     'vcproj',
265     'vsprops',
266     ]
267
268 # Files to skip that are less obvious.
269 #
270 # Some files should be skipped when checking style. For example,
271 # WebKit maintains some files in Mozilla style on purpose to ease
272 # future merges.
273 _SKIPPED_FILES_WITH_WARNING = [
274     "gtk2drawing.c", # WebCore/platform/gtk/gtk2drawing.c
275     "gtkdrawing.h", # WebCore/platform/gtk/gtkdrawing.h
276     "Source/WebKit/gtk/tests/",
277     # Soup API that is still being cooked, will be removed from WebKit
278     # in a few months when it is merged into soup proper. The style
279     # follows the libsoup style completely.
280     "Source/WebCore/platform/network/soup/cache/",
281     ]
282
283
284 # Files to skip that are more common or obvious.
285 #
286 # This list should be in addition to files with FileType.NONE.  Files
287 # with FileType.NONE are automatically skipped without warning.
288 _SKIPPED_FILES_WITHOUT_WARNING = [
289     "LayoutTests" + os.path.sep,
290     ]
291
292 # Extensions of files which are allowed to contain carriage returns.
293 _CARRIAGE_RETURN_ALLOWED_FILE_EXTENSIONS = [
294     'vcproj',
295     'vsprops',
296     ]
297
298 # The maximum number of errors to report per file, per category.
299 # If a category is not a key, then it has no maximum.
300 _MAX_REPORTS_PER_CATEGORY = {
301     "whitespace/carriage_return": 1
302 }
303
304
305 def _all_categories():
306     """Return the set of all categories used by check-webkit-style."""
307     # Take the union across all checkers.
308     categories = CommonCategories.union(CppChecker.categories)
309     categories = categories.union(TestExpectationsChecker.categories)
310
311     # FIXME: Consider adding all of the pep8 categories.  Since they
312     #        are not too meaningful for documentation purposes, for
313     #        now we add only the categories needed for the unit tests
314     #        (which validate the consistency of the configuration
315     #        settings against the known categories, etc).
316     categories = categories.union(["pep8/W191", "pep8/W291", "pep8/E501"])
317
318     return categories
319
320
321 def _check_webkit_style_defaults():
322     """Return the default command-line options for check-webkit-style."""
323     return DefaultCommandOptionValues(min_confidence=_DEFAULT_MIN_CONFIDENCE,
324                                       output_format=_DEFAULT_OUTPUT_FORMAT)
325
326
327 # This function assists in optparser not having to import from checker.
328 def check_webkit_style_parser():
329     all_categories = _all_categories()
330     default_options = _check_webkit_style_defaults()
331     return ArgumentParser(all_categories=all_categories,
332                           base_filter_rules=_BASE_FILTER_RULES,
333                           default_options=default_options)
334
335
336 def check_webkit_style_configuration(options):
337     """Return a StyleProcessorConfiguration instance for check-webkit-style.
338
339     Args:
340       options: A CommandOptionValues instance.
341
342     """
343     filter_configuration = FilterConfiguration(
344                                base_rules=_BASE_FILTER_RULES,
345                                path_specific=_PATH_RULES_SPECIFIER,
346                                user_rules=options.filter_rules)
347
348     return StyleProcessorConfiguration(filter_configuration=filter_configuration,
349                max_reports_per_category=_MAX_REPORTS_PER_CATEGORY,
350                min_confidence=options.min_confidence,
351                output_format=options.output_format,
352                stderr_write=sys.stderr.write)
353
354
355 def _create_log_handlers(stream):
356     """Create and return a default list of logging.Handler instances.
357
358     Format WARNING messages and above to display the logging level, and
359     messages strictly below WARNING not to display it.
360
361     Args:
362       stream: See the configure_logging() docstring.
363
364     """
365     # Handles logging.WARNING and above.
366     error_handler = logging.StreamHandler(stream)
367     error_handler.setLevel(logging.WARNING)
368     formatter = logging.Formatter("%(levelname)s: %(message)s")
369     error_handler.setFormatter(formatter)
370
371     # Create a logging.Filter instance that only accepts messages
372     # below WARNING (i.e. filters out anything WARNING or above).
373     non_error_filter = logging.Filter()
374     # The filter method accepts a logging.LogRecord instance.
375     non_error_filter.filter = lambda record: record.levelno < logging.WARNING
376
377     non_error_handler = logging.StreamHandler(stream)
378     non_error_handler.addFilter(non_error_filter)
379     formatter = logging.Formatter("%(message)s")
380     non_error_handler.setFormatter(formatter)
381
382     return [error_handler, non_error_handler]
383
384
385 def _create_debug_log_handlers(stream):
386     """Create and return a list of logging.Handler instances for debugging.
387
388     Args:
389       stream: See the configure_logging() docstring.
390
391     """
392     handler = logging.StreamHandler(stream)
393     formatter = logging.Formatter("%(name)s: %(levelname)-8s %(message)s")
394     handler.setFormatter(formatter)
395
396     return [handler]
397
398
399 def configure_logging(stream, logger=None, is_verbose=False):
400     """Configure logging, and return the list of handlers added.
401
402     Returns:
403       A list of references to the logging handlers added to the root
404       logger.  This allows the caller to later remove the handlers
405       using logger.removeHandler.  This is useful primarily during unit
406       testing where the caller may want to configure logging temporarily
407       and then undo the configuring.
408
409     Args:
410       stream: A file-like object to which to log.  The stream must
411               define an "encoding" data attribute, or else logging
412               raises an error.
413       logger: A logging.logger instance to configure.  This parameter
414               should be used only in unit tests.  Defaults to the
415               root logger.
416       is_verbose: A boolean value of whether logging should be verbose.
417
418     """
419     # If the stream does not define an "encoding" data attribute, the
420     # logging module can throw an error like the following:
421     #
422     # Traceback (most recent call last):
423     #   File "/System/Library/Frameworks/Python.framework/Versions/2.6/...
424     #         lib/python2.6/logging/__init__.py", line 761, in emit
425     #     self.stream.write(fs % msg.encode(self.stream.encoding))
426     # LookupError: unknown encoding: unknown
427     if logger is None:
428         logger = logging.getLogger()
429
430     if is_verbose:
431         logging_level = logging.DEBUG
432         handlers = _create_debug_log_handlers(stream)
433     else:
434         logging_level = logging.INFO
435         handlers = _create_log_handlers(stream)
436
437     handlers = _configure_logging(logging_level=logging_level, logger=logger,
438                                   handlers=handlers)
439
440     return handlers
441
442
443 # Enum-like idiom
444 class FileType:
445
446     NONE = 0  # FileType.NONE evaluates to False.
447     # Alphabetize remaining types
448     CHANGELOG = 1
449     CPP = 2
450     PYTHON = 3
451     TEXT = 4
452     XML = 5
453     XCODEPROJ = 6
454
455
456 class CheckerDispatcher(object):
457
458     """Supports determining whether and how to check style, based on path."""
459
460     def _file_extension(self, file_path):
461         """Return the file extension without the leading dot."""
462         return os.path.splitext(file_path)[1].lstrip(".")
463
464     def should_skip_with_warning(self, file_path):
465         """Return whether the given file should be skipped with a warning."""
466         for skipped_file in _SKIPPED_FILES_WITH_WARNING:
467             if file_path.find(skipped_file) >= 0:
468                 return True
469         return False
470
471     def should_skip_without_warning(self, file_path):
472         """Return whether the given file should be skipped without a warning."""
473         if not self._file_type(file_path):  # FileType.NONE.
474             return True
475         # Since "LayoutTests" is in _SKIPPED_FILES_WITHOUT_WARNING, make
476         # an exception to prevent files like "LayoutTests/ChangeLog" and
477         # "LayoutTests/ChangeLog-2009-06-16" from being skipped.
478         # Files like 'test_expectations.txt' and 'drt_expectations.txt'
479         # are also should not be skipped.
480         #
481         # FIXME: Figure out a good way to avoid having to add special logic
482         #        for this special case.
483         basename = os.path.basename(file_path)
484         if basename.startswith('ChangeLog'):
485             return False
486         elif basename == 'test_expectations.txt' or basename == 'drt_expectations.txt':
487             return False
488         for skipped_file in _SKIPPED_FILES_WITHOUT_WARNING:
489             if file_path.find(skipped_file) >= 0:
490                 return True
491         return False
492
493     def should_check_and_strip_carriage_returns(self, file_path):
494         return self._file_extension(file_path) not in _CARRIAGE_RETURN_ALLOWED_FILE_EXTENSIONS
495
496     def _file_type(self, file_path):
497         """Return the file type corresponding to the given file."""
498         file_extension = self._file_extension(file_path)
499
500         if (file_extension in _CPP_FILE_EXTENSIONS) or (file_path == '-'):
501             # FIXME: Do something about the comment below and the issue it
502             #        raises since cpp_style already relies on the extension.
503             #
504             # Treat stdin as C++. Since the extension is unknown when
505             # reading from stdin, cpp_style tests should not rely on
506             # the extension.
507             return FileType.CPP
508         elif file_extension == _PYTHON_FILE_EXTENSION:
509             return FileType.PYTHON
510         elif file_extension in _XML_FILE_EXTENSIONS:
511             return FileType.XML
512         elif os.path.basename(file_path).startswith('ChangeLog'):
513             return FileType.CHANGELOG
514         elif file_extension == _XCODEPROJ_FILE_EXTENSION:
515             return FileType.XCODEPROJ
516         elif ((not file_extension and os.path.join("Tools", "Scripts") in file_path) or
517               file_extension in _TEXT_FILE_EXTENSIONS):
518             return FileType.TEXT
519         else:
520             return FileType.NONE
521
522     def _create_checker(self, file_type, file_path, handle_style_error,
523                         min_confidence):
524         """Instantiate and return a style checker based on file type."""
525         if file_type == FileType.NONE:
526             checker = None
527         elif file_type == FileType.CHANGELOG:
528             should_line_be_checked = None
529             if handle_style_error:
530                 should_line_be_checked = handle_style_error.should_line_be_checked
531             checker = ChangeLogChecker(file_path, handle_style_error, should_line_be_checked)
532         elif file_type == FileType.CPP:
533             file_extension = self._file_extension(file_path)
534             checker = CppChecker(file_path, file_extension,
535                                  handle_style_error, min_confidence)
536         elif file_type == FileType.PYTHON:
537             checker = PythonChecker(file_path, handle_style_error)
538         elif file_type == FileType.XML:
539             checker = XMLChecker(file_path, handle_style_error)
540         elif file_type == FileType.XCODEPROJ:
541             checker = XcodeProjectFileChecker(file_path, handle_style_error)
542         elif file_type == FileType.TEXT:
543             basename = os.path.basename(file_path)
544             if basename == 'test_expectations.txt' or basename == 'drt_expectations.txt':
545                 checker = TestExpectationsChecker(file_path, handle_style_error)
546             else:
547                 checker = TextChecker(file_path, handle_style_error)
548         else:
549             raise ValueError('Invalid file type "%(file_type)s": the only valid file types '
550                              "are %(NONE)s, %(CPP)s, and %(TEXT)s."
551                              % {"file_type": file_type,
552                                 "NONE": FileType.NONE,
553                                 "CPP": FileType.CPP,
554                                 "TEXT": FileType.TEXT})
555
556         return checker
557
558     def dispatch(self, file_path, handle_style_error, min_confidence):
559         """Instantiate and return a style checker based on file path."""
560         file_type = self._file_type(file_path)
561
562         checker = self._create_checker(file_type,
563                                        file_path,
564                                        handle_style_error,
565                                        min_confidence)
566         return checker
567
568
569 # FIXME: Remove the stderr_write attribute from this class and replace
570 #        its use with calls to a logging module logger.
571 class StyleProcessorConfiguration(object):
572
573     """Stores configuration values for the StyleProcessor class.
574
575     Attributes:
576       min_confidence: An integer between 1 and 5 inclusive that is the
577                       minimum confidence level of style errors to report.
578
579       max_reports_per_category: The maximum number of errors to report
580                                 per category, per file.
581
582       stderr_write: A function that takes a string as a parameter and
583                     serves as stderr.write.
584
585     """
586
587     def __init__(self,
588                  filter_configuration,
589                  max_reports_per_category,
590                  min_confidence,
591                  output_format,
592                  stderr_write):
593         """Create a StyleProcessorConfiguration instance.
594
595         Args:
596           filter_configuration: A FilterConfiguration instance.  The default
597                                 is the "empty" filter configuration, which
598                                 means that all errors should be checked.
599
600           max_reports_per_category: The maximum number of errors to report
601                                     per category, per file.
602
603           min_confidence: An integer between 1 and 5 inclusive that is the
604                           minimum confidence level of style errors to report.
605                           The default is 1, which reports all style errors.
606
607           output_format: A string that is the output format.  The supported
608                          output formats are "emacs" which emacs can parse
609                          and "vs7" which Microsoft Visual Studio 7 can parse.
610
611           stderr_write: A function that takes a string as a parameter and
612                         serves as stderr.write.
613
614         """
615         self._filter_configuration = filter_configuration
616         self._output_format = output_format
617
618         self.max_reports_per_category = max_reports_per_category
619         self.min_confidence = min_confidence
620         self.stderr_write = stderr_write
621
622     def is_reportable(self, category, confidence_in_error, file_path):
623         """Return whether an error is reportable.
624
625         An error is reportable if both the confidence in the error is
626         at least the minimum confidence level and the current filter
627         says the category should be checked for the given path.
628
629         Args:
630           category: A string that is a style category.
631           confidence_in_error: An integer between 1 and 5 inclusive that is
632                                the application's confidence in the error.
633                                A higher number means greater confidence.
634           file_path: The path of the file being checked
635
636         """
637         if confidence_in_error < self.min_confidence:
638             return False
639
640         return self._filter_configuration.should_check(category, file_path)
641
642     def write_style_error(self,
643                           category,
644                           confidence_in_error,
645                           file_path,
646                           line_number,
647                           message):
648         """Write a style error to the configured stderr."""
649         if self._output_format == 'vs7':
650             format_string = "%s(%s):  %s  [%s] [%d]\n"
651         else:
652             format_string = "%s:%s:  %s  [%s] [%d]\n"
653
654         self.stderr_write(format_string % (file_path,
655                                            line_number,
656                                            message,
657                                            category,
658                                            confidence_in_error))
659
660
661 class ProcessorBase(object):
662
663     """The base class for processors of lists of lines."""
664
665     def should_process(self, file_path):
666         """Return whether the file at file_path should be processed.
667
668         The TextFileReader class calls this method prior to reading in
669         the lines of a file.  Use this method, for example, to prevent
670         the style checker from reading binary files into memory.
671
672         """
673         raise NotImplementedError('Subclasses should implement.')
674
675     def process(self, lines, file_path, **kwargs):
676         """Process lines of text read from a file.
677
678         Args:
679           lines: A list of lines of text to process.
680           file_path: The path from which the lines were read.
681           **kwargs: This argument signifies that the process() method of
682                     subclasses of ProcessorBase may support additional
683                     keyword arguments.
684                         For example, a style checker's check() method
685                     may support a "reportable_lines" parameter that represents
686                     the line numbers of the lines for which style errors
687                     should be reported.
688
689         """
690         raise NotImplementedError('Subclasses should implement.')
691
692
693 class StyleProcessor(ProcessorBase):
694
695     """A ProcessorBase for checking style.
696
697     Attributes:
698       error_count: An integer that is the total number of reported
699                    errors for the lifetime of this instance.
700
701     """
702
703     def __init__(self, configuration, mock_dispatcher=None,
704                  mock_increment_error_count=None,
705                  mock_carriage_checker_class=None):
706         """Create an instance.
707
708         Args:
709           configuration: A StyleProcessorConfiguration instance.
710           mock_dispatcher: A mock CheckerDispatcher instance.  This
711                            parameter is for unit testing.  Defaults to a
712                            CheckerDispatcher instance.
713           mock_increment_error_count: A mock error-count incrementer.
714           mock_carriage_checker_class: A mock class for checking and
715                                        transforming carriage returns.
716                                        This parameter is for unit testing.
717                                        Defaults to CarriageReturnChecker.
718
719         """
720         if mock_dispatcher is None:
721             dispatcher = CheckerDispatcher()
722         else:
723             dispatcher = mock_dispatcher
724
725         if mock_increment_error_count is None:
726             # The following blank line is present to avoid flagging by pep8.py.
727
728             def increment_error_count():
729                 """Increment the total count of reported errors."""
730                 self.error_count += 1
731         else:
732             increment_error_count = mock_increment_error_count
733
734         if mock_carriage_checker_class is None:
735             # This needs to be a class rather than an instance since the
736             # process() method instantiates one using parameters.
737             carriage_checker_class = CarriageReturnChecker
738         else:
739             carriage_checker_class = mock_carriage_checker_class
740
741         self.error_count = 0
742
743         self._carriage_checker_class = carriage_checker_class
744         self._configuration = configuration
745         self._dispatcher = dispatcher
746         self._increment_error_count = increment_error_count
747
748     def should_process(self, file_path):
749         """Return whether the file should be checked for style."""
750         if self._dispatcher.should_skip_without_warning(file_path):
751             return False
752         if self._dispatcher.should_skip_with_warning(file_path):
753             _log.warn('File exempt from style guide. Skipping: "%s"'
754                       % file_path)
755             return False
756         return True
757
758     def process(self, lines, file_path, line_numbers=None):
759         """Check the given lines for style.
760
761         Arguments:
762           lines: A list of all lines in the file to check.
763           file_path: The path of the file to process.  If possible, the path
764                      should be relative to the source root.  Otherwise,
765                      path-specific logic may not behave as expected.
766           line_numbers: A list of line numbers of the lines for which
767                         style errors should be reported, or None if errors
768                         for all lines should be reported.  When not None, this
769                         list normally contains the line numbers corresponding
770                         to the modified lines of a patch.
771
772         """
773         _log.debug("Checking style: " + file_path)
774
775         style_error_handler = DefaultStyleErrorHandler(
776             configuration=self._configuration,
777             file_path=file_path,
778             increment_error_count=self._increment_error_count,
779             line_numbers=line_numbers)
780
781         carriage_checker = self._carriage_checker_class(style_error_handler)
782
783         # Check for and remove trailing carriage returns ("\r").
784         if self._dispatcher.should_check_and_strip_carriage_returns(file_path):
785             lines = carriage_checker.check(lines)
786
787         min_confidence = self._configuration.min_confidence
788         checker = self._dispatcher.dispatch(file_path,
789                                             style_error_handler,
790                                             min_confidence)
791
792         if checker is None:
793             raise AssertionError("File should not be checked: '%s'" % file_path)
794
795         _log.debug("Using class: " + checker.__class__.__name__)
796
797         checker.check(lines)