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
33import logging
34import os.path
35import sys
36
37from checkers.common import categories as CommonCategories
38from checkers.common import CarriageReturnChecker
39from checkers.changelog import ChangeLogChecker
40from checkers.cpp import CppChecker
41from checkers.python import PythonChecker
42from checkers.test_expectations import TestExpectationsChecker
43from checkers.text import TextChecker
44from checkers.xml import XMLChecker
45from error_handlers import DefaultStyleErrorHandler
46from filter import FilterConfiguration
47from optparser import ArgumentParser
48from optparser import DefaultCommandOptionValues
49from 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
281def _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
297def _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.
304def 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
312def 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
331def _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
361def _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
375def 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
420class 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
431class 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.
542class 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
632class 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
664class 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)
769