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