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