5a7771011fab7099071a96b8e90a2a446d95b3f5
[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
203
204 _CPP_FILE_EXTENSIONS = [
205     'c',
206     'cpp',
207     'h',
208     ]
209
210 _PYTHON_FILE_EXTENSION = 'py'
211
212 _TEXT_FILE_EXTENSIONS = [
213     'ac',
214     'cc',
215     'cgi',
216     'css',
217     'exp',
218     'flex',
219     'gyp',
220     'gypi',
221     'html',
222     'idl',
223     'in',
224     'js',
225     'mm',
226     'php',
227     'pl',
228     'pm',
229     'pri',
230     'pro',
231     'rb',
232     'sh',
233     'txt',
234     'wm',
235     'xhtml',
236     'y',
237     ]
238
239 _XML_FILE_EXTENSIONS = [
240     'vcproj',
241     'vsprops',
242     ]
243
244 # Files to skip that are less obvious.
245 #
246 # Some files should be skipped when checking style. For example,
247 # WebKit maintains some files in Mozilla style on purpose to ease
248 # future merges.
249 _SKIPPED_FILES_WITH_WARNING = [
250     "gtk2drawing.c", # WebCore/platform/gtk/gtk2drawing.c
251     "gtkdrawing.h", # WebCore/platform/gtk/gtkdrawing.h
252     "Source/WebKit/gtk/tests/",
253     # Soup API that is still being cooked, will be removed from WebKit
254     # in a few months when it is merged into soup proper. The style
255     # follows the libsoup style completely.
256     "Source/WebCore/platform/network/soup/cache/",
257     ]
258
259
260 # Files to skip that are more common or obvious.
261 #
262 # This list should be in addition to files with FileType.NONE.  Files
263 # with FileType.NONE are automatically skipped without warning.
264 _SKIPPED_FILES_WITHOUT_WARNING = [
265     "LayoutTests" + os.path.sep,
266     ]
267
268 # Extensions of files which are allowed to contain carriage returns.
269 _CARRIAGE_RETURN_ALLOWED_FILE_EXTENSIONS = [
270     'vcproj',
271     'vsprops',
272     ]
273
274 # The maximum number of errors to report per file, per category.
275 # If a category is not a key, then it has no maximum.
276 _MAX_REPORTS_PER_CATEGORY = {
277     "whitespace/carriage_return": 1
278 }
279
280
281 def _all_categories():
282     """Return the set of all categories used by check-webkit-style."""
283     # Take the union across all checkers.
284     categories = CommonCategories.union(CppChecker.categories)
285     categories = categories.union(TestExpectationsChecker.categories)
286
287     # FIXME: Consider adding all of the pep8 categories.  Since they
288     #        are not too meaningful for documentation purposes, for
289     #        now we add only the categories needed for the unit tests
290     #        (which validate the consistency of the configuration
291     #        settings against the known categories, etc).
292     categories = categories.union(["pep8/W191", "pep8/W291", "pep8/E501"])
293
294     return categories
295
296
297 def _check_webkit_style_defaults():
298     """Return the default command-line options for check-webkit-style."""
299     return DefaultCommandOptionValues(min_confidence=_DEFAULT_MIN_CONFIDENCE,
300                                       output_format=_DEFAULT_OUTPUT_FORMAT)
301
302
303 # This function assists in optparser not having to import from checker.
304 def check_webkit_style_parser():
305     all_categories = _all_categories()
306     default_options = _check_webkit_style_defaults()
307     return ArgumentParser(all_categories=all_categories,
308                           base_filter_rules=_BASE_FILTER_RULES,
309                           default_options=default_options)
310
311
312 def check_webkit_style_configuration(options):
313     """Return a StyleProcessorConfiguration instance for check-webkit-style.
314
315     Args:
316       options: A CommandOptionValues instance.
317
318     """
319     filter_configuration = FilterConfiguration(
320                                base_rules=_BASE_FILTER_RULES,
321                                path_specific=_PATH_RULES_SPECIFIER,
322                                user_rules=options.filter_rules)
323
324     return StyleProcessorConfiguration(filter_configuration=filter_configuration,
325                max_reports_per_category=_MAX_REPORTS_PER_CATEGORY,
326                min_confidence=options.min_confidence,
327                output_format=options.output_format,
328                stderr_write=sys.stderr.write)
329
330
331 def _create_log_handlers(stream):
332     """Create and return a default list of logging.Handler instances.
333
334     Format WARNING messages and above to display the logging level, and
335     messages strictly below WARNING not to display it.
336
337     Args:
338       stream: See the configure_logging() docstring.
339
340     """
341     # Handles logging.WARNING and above.
342     error_handler = logging.StreamHandler(stream)
343     error_handler.setLevel(logging.WARNING)
344     formatter = logging.Formatter("%(levelname)s: %(message)s")
345     error_handler.setFormatter(formatter)
346
347     # Create a logging.Filter instance that only accepts messages
348     # below WARNING (i.e. filters out anything WARNING or above).
349     non_error_filter = logging.Filter()
350     # The filter method accepts a logging.LogRecord instance.
351     non_error_filter.filter = lambda record: record.levelno < logging.WARNING
352
353     non_error_handler = logging.StreamHandler(stream)
354     non_error_handler.addFilter(non_error_filter)
355     formatter = logging.Formatter("%(message)s")
356     non_error_handler.setFormatter(formatter)
357
358     return [error_handler, non_error_handler]
359
360
361 def _create_debug_log_handlers(stream):
362     """Create and return a list of logging.Handler instances for debugging.
363
364     Args:
365       stream: See the configure_logging() docstring.
366
367     """
368     handler = logging.StreamHandler(stream)
369     formatter = logging.Formatter("%(name)s: %(levelname)-8s %(message)s")
370     handler.setFormatter(formatter)
371
372     return [handler]
373
374
375 def configure_logging(stream, logger=None, is_verbose=False):
376     """Configure logging, and return the list of handlers added.
377
378     Returns:
379       A list of references to the logging handlers added to the root
380       logger.  This allows the caller to later remove the handlers
381       using logger.removeHandler.  This is useful primarily during unit
382       testing where the caller may want to configure logging temporarily
383       and then undo the configuring.
384
385     Args:
386       stream: A file-like object to which to log.  The stream must
387               define an "encoding" data attribute, or else logging
388               raises an error.
389       logger: A logging.logger instance to configure.  This parameter
390               should be used only in unit tests.  Defaults to the
391               root logger.
392       is_verbose: A boolean value of whether logging should be verbose.
393
394     """
395     # If the stream does not define an "encoding" data attribute, the
396     # logging module can throw an error like the following:
397     #
398     # Traceback (most recent call last):
399     #   File "/System/Library/Frameworks/Python.framework/Versions/2.6/...
400     #         lib/python2.6/logging/__init__.py", line 761, in emit
401     #     self.stream.write(fs % msg.encode(self.stream.encoding))
402     # LookupError: unknown encoding: unknown
403     if logger is None:
404         logger = logging.getLogger()
405
406     if is_verbose:
407         logging_level = logging.DEBUG
408         handlers = _create_debug_log_handlers(stream)
409     else:
410         logging_level = logging.INFO
411         handlers = _create_log_handlers(stream)
412
413     handlers = _configure_logging(logging_level=logging_level, logger=logger,
414                                   handlers=handlers)
415
416     return handlers
417
418
419 # Enum-like idiom
420 class FileType:
421
422     NONE = 0  # FileType.NONE evaluates to False.
423     # Alphabetize remaining types
424     CHANGELOG = 1
425     CPP = 2
426     PYTHON = 3
427     TEXT = 4
428     XML = 5
429
430
431 class CheckerDispatcher(object):
432
433     """Supports determining whether and how to check style, based on path."""
434
435     def _file_extension(self, file_path):
436         """Return the file extension without the leading dot."""
437         return os.path.splitext(file_path)[1].lstrip(".")
438
439     def should_skip_with_warning(self, file_path):
440         """Return whether the given file should be skipped with a warning."""
441         for skipped_file in _SKIPPED_FILES_WITH_WARNING:
442             if file_path.find(skipped_file) >= 0:
443                 return True
444         return False
445
446     def should_skip_without_warning(self, file_path):
447         """Return whether the given file should be skipped without a warning."""
448         if not self._file_type(file_path):  # FileType.NONE.
449             return True
450         # Since "LayoutTests" is in _SKIPPED_FILES_WITHOUT_WARNING, make
451         # an exception to prevent files like "LayoutTests/ChangeLog" and
452         # "LayoutTests/ChangeLog-2009-06-16" from being skipped.
453         # Files like 'test_expectations.txt' and 'drt_expectations.txt'
454         # are also should not be skipped.
455         #
456         # FIXME: Figure out a good way to avoid having to add special logic
457         #        for this special case.
458         basename = os.path.basename(file_path)
459         if basename.startswith('ChangeLog'):
460             return False
461         elif basename == 'test_expectations.txt' or basename == 'drt_expectations.txt':
462             return False
463         for skipped_file in _SKIPPED_FILES_WITHOUT_WARNING:
464             if file_path.find(skipped_file) >= 0:
465                 return True
466         return False
467
468     def should_check_and_strip_carriage_returns(self, file_path):
469         return self._file_extension(file_path) not in _CARRIAGE_RETURN_ALLOWED_FILE_EXTENSIONS
470
471     def _file_type(self, file_path):
472         """Return the file type corresponding to the given file."""
473         file_extension = self._file_extension(file_path)
474
475         if (file_extension in _CPP_FILE_EXTENSIONS) or (file_path == '-'):
476             # FIXME: Do something about the comment below and the issue it
477             #        raises since cpp_style already relies on the extension.
478             #
479             # Treat stdin as C++. Since the extension is unknown when
480             # reading from stdin, cpp_style tests should not rely on
481             # the extension.
482             return FileType.CPP
483         elif file_extension == _PYTHON_FILE_EXTENSION:
484             return FileType.PYTHON
485         elif file_extension in _XML_FILE_EXTENSIONS:
486             return FileType.XML
487         elif os.path.basename(file_path).startswith('ChangeLog'):
488             return FileType.CHANGELOG
489         elif ((not file_extension and os.path.join("Tools", "Scripts") in file_path) or
490               file_extension in _TEXT_FILE_EXTENSIONS):
491             return FileType.TEXT
492         else:
493             return FileType.NONE
494
495     def _create_checker(self, file_type, file_path, handle_style_error,
496                         min_confidence):
497         """Instantiate and return a style checker based on file type."""
498         if file_type == FileType.NONE:
499             checker = None
500         elif file_type == FileType.CHANGELOG:
501             checker = ChangeLogChecker(file_path, handle_style_error)
502         elif file_type == FileType.CPP:
503             file_extension = self._file_extension(file_path)
504             checker = CppChecker(file_path, file_extension,
505                                  handle_style_error, min_confidence)
506         elif file_type == FileType.PYTHON:
507             checker = PythonChecker(file_path, handle_style_error)
508         elif file_type == FileType.XML:
509             checker = XMLChecker(file_path, handle_style_error)
510         elif file_type == FileType.TEXT:
511             basename = os.path.basename(file_path)
512             if basename == 'test_expectations.txt' or basename == 'drt_expectations.txt':
513                 checker = TestExpectationsChecker(file_path, handle_style_error)
514             else:
515                 checker = TextChecker(file_path, handle_style_error)
516         else:
517             raise ValueError('Invalid file type "%(file_type)s": the only valid file types '
518                              "are %(NONE)s, %(CPP)s, and %(TEXT)s."
519                              % {"file_type": file_type,
520                                 "NONE": FileType.NONE,
521                                 "CPP": FileType.CPP,
522                                 "TEXT": FileType.TEXT})
523
524         return checker
525
526     def dispatch(self, file_path, handle_style_error, min_confidence):
527         """Instantiate and return a style checker based on file path."""
528         file_type = self._file_type(file_path)
529
530         checker = self._create_checker(file_type,
531                                        file_path,
532                                        handle_style_error,
533                                        min_confidence)
534         return checker
535
536
537 # FIXME: Remove the stderr_write attribute from this class and replace
538 #        its use with calls to a logging module logger.
539 class StyleProcessorConfiguration(object):
540
541     """Stores configuration values for the StyleProcessor class.
542
543     Attributes:
544       min_confidence: An integer between 1 and 5 inclusive that is the
545                       minimum confidence level of style errors to report.
546
547       max_reports_per_category: The maximum number of errors to report
548                                 per category, per file.
549
550       stderr_write: A function that takes a string as a parameter and
551                     serves as stderr.write.
552
553     """
554
555     def __init__(self,
556                  filter_configuration,
557                  max_reports_per_category,
558                  min_confidence,
559                  output_format,
560                  stderr_write):
561         """Create a StyleProcessorConfiguration instance.
562
563         Args:
564           filter_configuration: A FilterConfiguration instance.  The default
565                                 is the "empty" filter configuration, which
566                                 means that all errors should be checked.
567
568           max_reports_per_category: The maximum number of errors to report
569                                     per category, per file.
570
571           min_confidence: An integer between 1 and 5 inclusive that is the
572                           minimum confidence level of style errors to report.
573                           The default is 1, which reports all style errors.
574
575           output_format: A string that is the output format.  The supported
576                          output formats are "emacs" which emacs can parse
577                          and "vs7" which Microsoft Visual Studio 7 can parse.
578
579           stderr_write: A function that takes a string as a parameter and
580                         serves as stderr.write.
581
582         """
583         self._filter_configuration = filter_configuration
584         self._output_format = output_format
585
586         self.max_reports_per_category = max_reports_per_category
587         self.min_confidence = min_confidence
588         self.stderr_write = stderr_write
589
590     def is_reportable(self, category, confidence_in_error, file_path):
591         """Return whether an error is reportable.
592
593         An error is reportable if both the confidence in the error is
594         at least the minimum confidence level and the current filter
595         says the category should be checked for the given path.
596
597         Args:
598           category: A string that is a style category.
599           confidence_in_error: An integer between 1 and 5 inclusive that is
600                                the application's confidence in the error.
601                                A higher number means greater confidence.
602           file_path: The path of the file being checked
603
604         """
605         if confidence_in_error < self.min_confidence:
606             return False
607
608         return self._filter_configuration.should_check(category, file_path)
609
610     def write_style_error(self,
611                           category,
612                           confidence_in_error,
613                           file_path,
614                           line_number,
615                           message):
616         """Write a style error to the configured stderr."""
617         if self._output_format == 'vs7':
618             format_string = "%s(%s):  %s  [%s] [%d]\n"
619         else:
620             format_string = "%s:%s:  %s  [%s] [%d]\n"
621
622         self.stderr_write(format_string % (file_path,
623                                            line_number,
624                                            message,
625                                            category,
626                                            confidence_in_error))
627
628
629 class ProcessorBase(object):
630
631     """The base class for processors of lists of lines."""
632
633     def should_process(self, file_path):
634         """Return whether the file at file_path should be processed.
635
636         The TextFileReader class calls this method prior to reading in
637         the lines of a file.  Use this method, for example, to prevent
638         the style checker from reading binary files into memory.
639
640         """
641         raise NotImplementedError('Subclasses should implement.')
642
643     def process(self, lines, file_path, **kwargs):
644         """Process lines of text read from a file.
645
646         Args:
647           lines: A list of lines of text to process.
648           file_path: The path from which the lines were read.
649           **kwargs: This argument signifies that the process() method of
650                     subclasses of ProcessorBase may support additional
651                     keyword arguments.
652                         For example, a style checker's check() method
653                     may support a "reportable_lines" parameter that represents
654                     the line numbers of the lines for which style errors
655                     should be reported.
656
657         """
658         raise NotImplementedError('Subclasses should implement.')
659
660
661 class StyleProcessor(ProcessorBase):
662
663     """A ProcessorBase for checking style.
664
665     Attributes:
666       error_count: An integer that is the total number of reported
667                    errors for the lifetime of this instance.
668
669     """
670
671     def __init__(self, configuration, mock_dispatcher=None,
672                  mock_increment_error_count=None,
673                  mock_carriage_checker_class=None):
674         """Create an instance.
675
676         Args:
677           configuration: A StyleProcessorConfiguration instance.
678           mock_dispatcher: A mock CheckerDispatcher instance.  This
679                            parameter is for unit testing.  Defaults to a
680                            CheckerDispatcher instance.
681           mock_increment_error_count: A mock error-count incrementer.
682           mock_carriage_checker_class: A mock class for checking and
683                                        transforming carriage returns.
684                                        This parameter is for unit testing.
685                                        Defaults to CarriageReturnChecker.
686
687         """
688         if mock_dispatcher is None:
689             dispatcher = CheckerDispatcher()
690         else:
691             dispatcher = mock_dispatcher
692
693         if mock_increment_error_count is None:
694             # The following blank line is present to avoid flagging by pep8.py.
695
696             def increment_error_count():
697                 """Increment the total count of reported errors."""
698                 self.error_count += 1
699         else:
700             increment_error_count = mock_increment_error_count
701
702         if mock_carriage_checker_class is None:
703             # This needs to be a class rather than an instance since the
704             # process() method instantiates one using parameters.
705             carriage_checker_class = CarriageReturnChecker
706         else:
707             carriage_checker_class = mock_carriage_checker_class
708
709         self.error_count = 0
710
711         self._carriage_checker_class = carriage_checker_class
712         self._configuration = configuration
713         self._dispatcher = dispatcher
714         self._increment_error_count = increment_error_count
715
716     def should_process(self, file_path):
717         """Return whether the file should be checked for style."""
718         if self._dispatcher.should_skip_without_warning(file_path):
719             return False
720         if self._dispatcher.should_skip_with_warning(file_path):
721             _log.warn('File exempt from style guide. Skipping: "%s"'
722                       % file_path)
723             return False
724         return True
725
726     def process(self, lines, file_path, line_numbers=None):
727         """Check the given lines for style.
728
729         Arguments:
730           lines: A list of all lines in the file to check.
731           file_path: The path of the file to process.  If possible, the path
732                      should be relative to the source root.  Otherwise,
733                      path-specific logic may not behave as expected.
734           line_numbers: A list of line numbers of the lines for which
735                         style errors should be reported, or None if errors
736                         for all lines should be reported.  When not None, this
737                         list normally contains the line numbers corresponding
738                         to the modified lines of a patch.
739
740         """
741         _log.debug("Checking style: " + file_path)
742
743         style_error_handler = DefaultStyleErrorHandler(
744             configuration=self._configuration,
745             file_path=file_path,
746             increment_error_count=self._increment_error_count,
747             line_numbers=line_numbers)
748
749         carriage_checker = self._carriage_checker_class(style_error_handler)
750
751         # Check for and remove trailing carriage returns ("\r").
752         if self._dispatcher.should_check_and_strip_carriage_returns(file_path):
753             lines = carriage_checker.check(lines)
754
755         min_confidence = self._configuration.min_confidence
756         checker = self._dispatcher.dispatch(file_path,
757                                                       style_error_handler,
758                                                       min_confidence)
759
760         if checker is None:
761             raise AssertionError("File should not be checked: '%s'" % file_path)
762
763         _log.debug("Using class: " + checker.__class__.__name__)
764
765         checker.check(lines)