2011-03-29 David Levin <levin@chromium.org>
[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             should_line_be_checked = None
502             if handle_style_error:
503                 should_line_be_checked = handle_style_error.should_line_be_checked
504             checker = ChangeLogChecker(file_path, handle_style_error, should_line_be_checked)
505         elif file_type == FileType.CPP:
506             file_extension = self._file_extension(file_path)
507             checker = CppChecker(file_path, file_extension,
508                                  handle_style_error, min_confidence)
509         elif file_type == FileType.PYTHON:
510             checker = PythonChecker(file_path, handle_style_error)
511         elif file_type == FileType.XML:
512             checker = XMLChecker(file_path, handle_style_error)
513         elif file_type == FileType.TEXT:
514             basename = os.path.basename(file_path)
515             if basename == 'test_expectations.txt' or basename == 'drt_expectations.txt':
516                 checker = TestExpectationsChecker(file_path, handle_style_error)
517             else:
518                 checker = TextChecker(file_path, handle_style_error)
519         else:
520             raise ValueError('Invalid file type "%(file_type)s": the only valid file types '
521                              "are %(NONE)s, %(CPP)s, and %(TEXT)s."
522                              % {"file_type": file_type,
523                                 "NONE": FileType.NONE,
524                                 "CPP": FileType.CPP,
525                                 "TEXT": FileType.TEXT})
526
527         return checker
528
529     def dispatch(self, file_path, handle_style_error, min_confidence):
530         """Instantiate and return a style checker based on file path."""
531         file_type = self._file_type(file_path)
532
533         checker = self._create_checker(file_type,
534                                        file_path,
535                                        handle_style_error,
536                                        min_confidence)
537         return checker
538
539
540 # FIXME: Remove the stderr_write attribute from this class and replace
541 #        its use with calls to a logging module logger.
542 class StyleProcessorConfiguration(object):
543
544     """Stores configuration values for the StyleProcessor class.
545
546     Attributes:
547       min_confidence: An integer between 1 and 5 inclusive that is the
548                       minimum confidence level of style errors to report.
549
550       max_reports_per_category: The maximum number of errors to report
551                                 per category, per file.
552
553       stderr_write: A function that takes a string as a parameter and
554                     serves as stderr.write.
555
556     """
557
558     def __init__(self,
559                  filter_configuration,
560                  max_reports_per_category,
561                  min_confidence,
562                  output_format,
563                  stderr_write):
564         """Create a StyleProcessorConfiguration instance.
565
566         Args:
567           filter_configuration: A FilterConfiguration instance.  The default
568                                 is the "empty" filter configuration, which
569                                 means that all errors should be checked.
570
571           max_reports_per_category: The maximum number of errors to report
572                                     per category, per file.
573
574           min_confidence: An integer between 1 and 5 inclusive that is the
575                           minimum confidence level of style errors to report.
576                           The default is 1, which reports all style errors.
577
578           output_format: A string that is the output format.  The supported
579                          output formats are "emacs" which emacs can parse
580                          and "vs7" which Microsoft Visual Studio 7 can parse.
581
582           stderr_write: A function that takes a string as a parameter and
583                         serves as stderr.write.
584
585         """
586         self._filter_configuration = filter_configuration
587         self._output_format = output_format
588
589         self.max_reports_per_category = max_reports_per_category
590         self.min_confidence = min_confidence
591         self.stderr_write = stderr_write
592
593     def is_reportable(self, category, confidence_in_error, file_path):
594         """Return whether an error is reportable.
595
596         An error is reportable if both the confidence in the error is
597         at least the minimum confidence level and the current filter
598         says the category should be checked for the given path.
599
600         Args:
601           category: A string that is a style category.
602           confidence_in_error: An integer between 1 and 5 inclusive that is
603                                the application's confidence in the error.
604                                A higher number means greater confidence.
605           file_path: The path of the file being checked
606
607         """
608         if confidence_in_error < self.min_confidence:
609             return False
610
611         return self._filter_configuration.should_check(category, file_path)
612
613     def write_style_error(self,
614                           category,
615                           confidence_in_error,
616                           file_path,
617                           line_number,
618                           message):
619         """Write a style error to the configured stderr."""
620         if self._output_format == 'vs7':
621             format_string = "%s(%s):  %s  [%s] [%d]\n"
622         else:
623             format_string = "%s:%s:  %s  [%s] [%d]\n"
624
625         self.stderr_write(format_string % (file_path,
626                                            line_number,
627                                            message,
628                                            category,
629                                            confidence_in_error))
630
631
632 class ProcessorBase(object):
633
634     """The base class for processors of lists of lines."""
635
636     def should_process(self, file_path):
637         """Return whether the file at file_path should be processed.
638
639         The TextFileReader class calls this method prior to reading in
640         the lines of a file.  Use this method, for example, to prevent
641         the style checker from reading binary files into memory.
642
643         """
644         raise NotImplementedError('Subclasses should implement.')
645
646     def process(self, lines, file_path, **kwargs):
647         """Process lines of text read from a file.
648
649         Args:
650           lines: A list of lines of text to process.
651           file_path: The path from which the lines were read.
652           **kwargs: This argument signifies that the process() method of
653                     subclasses of ProcessorBase may support additional
654                     keyword arguments.
655                         For example, a style checker's check() method
656                     may support a "reportable_lines" parameter that represents
657                     the line numbers of the lines for which style errors
658                     should be reported.
659
660         """
661         raise NotImplementedError('Subclasses should implement.')
662
663
664 class StyleProcessor(ProcessorBase):
665
666     """A ProcessorBase for checking style.
667
668     Attributes:
669       error_count: An integer that is the total number of reported
670                    errors for the lifetime of this instance.
671
672     """
673
674     def __init__(self, configuration, mock_dispatcher=None,
675                  mock_increment_error_count=None,
676                  mock_carriage_checker_class=None):
677         """Create an instance.
678
679         Args:
680           configuration: A StyleProcessorConfiguration instance.
681           mock_dispatcher: A mock CheckerDispatcher instance.  This
682                            parameter is for unit testing.  Defaults to a
683                            CheckerDispatcher instance.
684           mock_increment_error_count: A mock error-count incrementer.
685           mock_carriage_checker_class: A mock class for checking and
686                                        transforming carriage returns.
687                                        This parameter is for unit testing.
688                                        Defaults to CarriageReturnChecker.
689
690         """
691         if mock_dispatcher is None:
692             dispatcher = CheckerDispatcher()
693         else:
694             dispatcher = mock_dispatcher
695
696         if mock_increment_error_count is None:
697             # The following blank line is present to avoid flagging by pep8.py.
698
699             def increment_error_count():
700                 """Increment the total count of reported errors."""
701                 self.error_count += 1
702         else:
703             increment_error_count = mock_increment_error_count
704
705         if mock_carriage_checker_class is None:
706             # This needs to be a class rather than an instance since the
707             # process() method instantiates one using parameters.
708             carriage_checker_class = CarriageReturnChecker
709         else:
710             carriage_checker_class = mock_carriage_checker_class
711
712         self.error_count = 0
713
714         self._carriage_checker_class = carriage_checker_class
715         self._configuration = configuration
716         self._dispatcher = dispatcher
717         self._increment_error_count = increment_error_count
718
719     def should_process(self, file_path):
720         """Return whether the file should be checked for style."""
721         if self._dispatcher.should_skip_without_warning(file_path):
722             return False
723         if self._dispatcher.should_skip_with_warning(file_path):
724             _log.warn('File exempt from style guide. Skipping: "%s"'
725                       % file_path)
726             return False
727         return True
728
729     def process(self, lines, file_path, line_numbers=None):
730         """Check the given lines for style.
731
732         Arguments:
733           lines: A list of all lines in the file to check.
734           file_path: The path of the file to process.  If possible, the path
735                      should be relative to the source root.  Otherwise,
736                      path-specific logic may not behave as expected.
737           line_numbers: A list of line numbers of the lines for which
738                         style errors should be reported, or None if errors
739                         for all lines should be reported.  When not None, this
740                         list normally contains the line numbers corresponding
741                         to the modified lines of a patch.
742
743         """
744         _log.debug("Checking style: " + file_path)
745
746         style_error_handler = DefaultStyleErrorHandler(
747             configuration=self._configuration,
748             file_path=file_path,
749             increment_error_count=self._increment_error_count,
750             line_numbers=line_numbers)
751
752         carriage_checker = self._carriage_checker_class(style_error_handler)
753
754         # Check for and remove trailing carriage returns ("\r").
755         if self._dispatcher.should_check_and_strip_carriage_returns(file_path):
756             lines = carriage_checker.check(lines)
757
758         min_confidence = self._configuration.min_confidence
759         checker = self._dispatcher.dispatch(file_path,
760                                                       style_error_handler,
761                                                       min_confidence)
762
763         if checker is None:
764             raise AssertionError("File should not be checked: '%s'" % file_path)
765
766         _log.debug("Using class: " + checker.__class__.__name__)
767
768         checker.check(lines)