2010-04-05 Chris Jerdonek <cjerdonek@webkit.org>
[WebKit.git] / WebKitTools / 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 codecs
34 import logging
35 import os.path
36 import sys
37
38 from error_handlers import DefaultStyleErrorHandler
39 from filter import FilterConfiguration
40 from optparser import ArgumentParser
41 from optparser import DefaultCommandOptionValues
42 from processors.common import categories as CommonCategories
43 from processors.common import CarriageReturnProcessor
44 from processors.cpp import CppProcessor
45 from processors.python import PythonProcessor
46 from processors.text import TextProcessor
47 from webkitpy.style_references import parse_patch
48 from webkitpy.style_references import configure_logging as _configure_logging
49
50 _log = logging.getLogger("webkitpy.style.checker")
51
52 # These are default option values for the command-line option parser.
53 _DEFAULT_MIN_CONFIDENCE = 1
54 _DEFAULT_OUTPUT_FORMAT = 'emacs'
55
56
57 # FIXME: For style categories we will never want to have, remove them.
58 #        For categories for which we want to have similar functionality,
59 #        modify the implementation and enable them.
60 #
61 # Throughout this module, we use "filter rule" rather than "filter"
62 # for an individual boolean filter flag like "+foo".  This allows us to
63 # reserve "filter" for what one gets by collectively applying all of
64 # the filter rules.
65 #
66 # The base filter rules are the filter rules that begin the list of
67 # filter rules used to check style.  For example, these rules precede
68 # any user-specified filter rules.  Since by default all categories are
69 # checked, this list should normally include only rules that begin
70 # with a "-" sign.
71 _BASE_FILTER_RULES = [
72     '-build/endif_comment',
73     '-build/include_what_you_use',  # <string> for std::string
74     '-build/storage_class',  # const static
75     '-legal/copyright',
76     '-readability/multiline_comment',
77     '-readability/braces',  # int foo() {};
78     '-readability/fn_size',
79     '-readability/casting',
80     '-readability/function',
81     '-runtime/arrays',  # variable length array
82     '-runtime/casting',
83     '-runtime/sizeof',
84     '-runtime/explicit',  # explicit
85     '-runtime/virtual',  # virtual dtor
86     '-runtime/printf',
87     '-runtime/threadsafe_fn',
88     '-runtime/rtti',
89     '-whitespace/blank_line',
90     '-whitespace/end_of_line',
91     '-whitespace/labels',
92     # List Python pep8 categories last.
93     #
94     # Because much of WebKit's Python code base does not abide by the
95     # PEP8 79 character limit, we ignore the 79-character-limit category
96     # pep8/E501 for now.
97     #
98     # FIXME: Consider bringing WebKit's Python code base into conformance
99     #        with the 79 character limit, or some higher limit that is
100     #        agreeable to the WebKit project.
101     '-pep8/E501',
102     ]
103
104
105 # The path-specific filter rules.
106 #
107 # This list is order sensitive.  Only the first path substring match
108 # is used.  See the FilterConfiguration documentation in filter.py
109 # for more information on this list.
110 #
111 # Each string appearing in this nested list should have at least
112 # one associated unit test assertion.  These assertions are located,
113 # for example, in the test_path_rules_specifier() unit test method of
114 # checker_unittest.py.
115 _PATH_RULES_SPECIFIER = [
116     # Files in these directories are consumers of the WebKit
117     # API and therefore do not follow the same header including
118     # discipline as WebCore.
119     (["WebKitTools/WebKitAPITest/",
120       "WebKit/qt/QGVLauncher/"],
121      ["-build/include",
122       "-readability/streams"]),
123     ([# The EFL APIs use EFL naming style, which includes
124       # both lower-cased and camel-cased, underscore-sparated
125       # values.
126       "WebKit/efl/ewk/",
127       # There is no clean way to avoid "yy_*" names used by flex.
128       "WebCore/css/CSSParser.cpp",
129       # There is no clean way to avoid "xxx_data" methods inside
130       # Qt's autotests since they are called automatically by the
131       # QtTest module.
132       "WebKit/qt/tests/",
133       "JavaScriptCore/qt/tests"],
134      ["-readability/naming"]),
135     ([# The GTK+ APIs use GTK+ naming style, which includes
136       # lower-cased, underscore-separated values.
137       # Also, GTK+ allows the use of NULL.
138       "WebKit/gtk/webkit/",
139       "WebKitTools/DumpRenderTree/gtk/"],
140      ["-readability/naming",
141       "-readability/null"]),
142     ([# Header files in ForwardingHeaders have no header guards or
143       # exceptional header guards (e.g., WebCore_FWD_Debugger_h).
144       "/ForwardingHeaders/"],
145      ["-build/header_guard"]),
146
147     # For third-party Python code, keep only the following checks--
148     #
149     #   No tabs: to avoid having to set the SVN allow-tabs property.
150     #   No trailing white space: since this is easy to correct.
151     #   No carriage-return line endings: since this is easy to correct.
152     #
153     (["webkitpy/thirdparty/"],
154      ["-",
155       "+pep8/W191",  # Tabs
156       "+pep8/W291",  # Trailing white space
157       "+whitespace/carriage_return"]),
158 ]
159
160
161 # Some files should be skipped when checking style. For example,
162 # WebKit maintains some files in Mozilla style on purpose to ease
163 # future merges.
164 #
165 # Include a warning for skipped files that are less obvious.
166 _SKIPPED_FILES_WITH_WARNING = [
167     # The Qt API and tests do not follow WebKit style.
168     # They follow Qt style. :)
169     "gtk2drawing.c", # WebCore/platform/gtk/gtk2drawing.c
170     "gtk2drawing.h", # WebCore/platform/gtk/gtk2drawing.h
171     "JavaScriptCore/qt/api/",
172     "WebKit/gtk/tests/",
173     "WebKit/qt/Api/",
174     "WebKit/qt/tests/",
175     ]
176
177
178 # Don't include a warning for skipped files that are more common
179 # and more obvious.
180 _SKIPPED_FILES_WITHOUT_WARNING = [
181     "LayoutTests/",
182     ".pyc",
183     ]
184
185
186 # The maximum number of errors to report per file, per category.
187 # If a category is not a key, then it has no maximum.
188 _MAX_REPORTS_PER_CATEGORY = {
189     "whitespace/carriage_return": 1
190 }
191
192
193 def _all_categories():
194     """Return the set of all categories used by check-webkit-style."""
195     # Take the union across all processors.
196     categories = CommonCategories.union(CppProcessor.categories)
197
198     # FIXME: Consider adding all of the pep8 categories.  Since they
199     #        are not too meaningful for documentation purposes, for
200     #        now we add only the categories needed for the unit tests
201     #        (which validate the consistency of the configuration
202     #        settings against the known categories, etc).
203     categories = categories.union(["pep8/W191", "pep8/W291", "pep8/E501"])
204
205     return categories
206
207
208 def _check_webkit_style_defaults():
209     """Return the default command-line options for check-webkit-style."""
210     return DefaultCommandOptionValues(min_confidence=_DEFAULT_MIN_CONFIDENCE,
211                                       output_format=_DEFAULT_OUTPUT_FORMAT)
212
213
214 # This function assists in optparser not having to import from checker.
215 def check_webkit_style_parser():
216     all_categories = _all_categories()
217     default_options = _check_webkit_style_defaults()
218     return ArgumentParser(all_categories=all_categories,
219                           base_filter_rules=_BASE_FILTER_RULES,
220                           default_options=default_options)
221
222
223 def check_webkit_style_configuration(options):
224     """Return a StyleCheckerConfiguration instance for check-webkit-style.
225
226     Args:
227       options: A CommandOptionValues instance.
228
229     """
230     filter_configuration = FilterConfiguration(
231                                base_rules=_BASE_FILTER_RULES,
232                                path_specific=_PATH_RULES_SPECIFIER,
233                                user_rules=options.filter_rules)
234
235     return StyleCheckerConfiguration(filter_configuration=filter_configuration,
236                max_reports_per_category=_MAX_REPORTS_PER_CATEGORY,
237                min_confidence=options.min_confidence,
238                output_format=options.output_format,
239                stderr_write=sys.stderr.write)
240
241
242 def _create_log_handlers(stream):
243     """Create and return a default list of logging.Handler instances.
244
245     Format WARNING messages and above to display the logging level, and
246     messages strictly below WARNING not to display it.
247
248     Args:
249       stream: See the configure_logging() docstring.
250
251     """
252     # Handles logging.WARNING and above.
253     error_handler = logging.StreamHandler(stream)
254     error_handler.setLevel(logging.WARNING)
255     formatter = logging.Formatter("%(levelname)s: %(message)s")
256     error_handler.setFormatter(formatter)
257
258     # Create a logging.Filter instance that only accepts messages
259     # below WARNING (i.e. filters out anything WARNING or above).
260     non_error_filter = logging.Filter()
261     # The filter method accepts a logging.LogRecord instance.
262     non_error_filter.filter = lambda record: record.levelno < logging.WARNING
263
264     non_error_handler = logging.StreamHandler(stream)
265     non_error_handler.addFilter(non_error_filter)
266     formatter = logging.Formatter("%(message)s")
267     non_error_handler.setFormatter(formatter)
268
269     return [error_handler, non_error_handler]
270
271
272 def _create_debug_log_handlers(stream):
273     """Create and return a list of logging.Handler instances for debugging.
274
275     Args:
276       stream: See the configure_logging() docstring.
277
278     """
279     handler = logging.StreamHandler(stream)
280     formatter = logging.Formatter("%(name)s: %(levelname)-8s %(message)s")
281     handler.setFormatter(formatter)
282
283     return [handler]
284
285
286 def configure_logging(stream, logger=None, is_verbose=False):
287     """Configure logging, and return the list of handlers added.
288
289     Returns:
290       A list of references to the logging handlers added to the root
291       logger.  This allows the caller to later remove the handlers
292       using logger.removeHandler.  This is useful primarily during unit
293       testing where the caller may want to configure logging temporarily
294       and then undo the configuring.
295
296     Args:
297       stream: A file-like object to which to log.  The stream must
298               define an "encoding" data attribute, or else logging
299               raises an error.
300       logger: A logging.logger instance to configure.  This parameter
301               should be used only in unit tests.  Defaults to the
302               root logger.
303       is_verbose: A boolean value of whether logging should be verbose.
304
305     """
306     # If the stream does not define an "encoding" data attribute, the
307     # logging module can throw an error like the following:
308     #
309     # Traceback (most recent call last):
310     #   File "/System/Library/Frameworks/Python.framework/Versions/2.6/...
311     #         lib/python2.6/logging/__init__.py", line 761, in emit
312     #     self.stream.write(fs % msg.encode(self.stream.encoding))
313     # LookupError: unknown encoding: unknown
314     if logger is None:
315         logger = logging.getLogger()
316
317     if is_verbose:
318         logging_level = logging.DEBUG
319         handlers = _create_debug_log_handlers(stream)
320     else:
321         logging_level = logging.INFO
322         handlers = _create_log_handlers(stream)
323
324     handlers = _configure_logging(logging_level=logging_level, logger=logger,
325                                   handlers=handlers)
326
327     return handlers
328
329
330 # Enum-like idiom
331 class FileType:
332
333     NONE = 1
334     # Alphabetize remaining types
335     CPP = 2
336     PYTHON = 3
337     TEXT = 4
338
339
340 class ProcessorDispatcher(object):
341
342     """Supports determining whether and how to check style, based on path."""
343
344     cpp_file_extensions = (
345         'c',
346         'cpp',
347         'h',
348         )
349
350     text_file_extensions = (
351         'css',
352         'html',
353         'idl',
354         'js',
355         'mm',
356         'php',
357         'pm',
358         'txt',
359         )
360
361     def _file_extension(self, file_path):
362         """Return the file extension without the leading dot."""
363         return os.path.splitext(file_path)[1].lstrip(".")
364
365     def should_skip_with_warning(self, file_path):
366         """Return whether the given file should be skipped with a warning."""
367         for skipped_file in _SKIPPED_FILES_WITH_WARNING:
368             if file_path.find(skipped_file) >= 0:
369                 return True
370         return False
371
372     def should_skip_without_warning(self, file_path):
373         """Return whether the given file should be skipped without a warning."""
374         for skipped_file in _SKIPPED_FILES_WITHOUT_WARNING:
375             if file_path.find(skipped_file) >= 0:
376                 return True
377         return False
378
379     def _file_type(self, file_path):
380         """Return the file type corresponding to the given file."""
381         file_extension = self._file_extension(file_path)
382
383         if (file_extension in self.cpp_file_extensions) or (file_path == '-'):
384             # FIXME: Do something about the comment below and the issue it
385             #        raises since cpp_style already relies on the extension.
386             #
387             # Treat stdin as C++. Since the extension is unknown when
388             # reading from stdin, cpp_style tests should not rely on
389             # the extension.
390             return FileType.CPP
391         elif file_extension == "py":
392             return FileType.PYTHON
393         elif ("ChangeLog" in file_path or
394               (not file_extension and "WebKitTools/Scripts/" in file_path) or
395               file_extension in self.text_file_extensions):
396             return FileType.TEXT
397         else:
398             return FileType.NONE
399
400     def _create_processor(self, file_type, file_path, handle_style_error,
401                           min_confidence):
402         """Instantiate and return a style processor based on file type."""
403         if file_type == FileType.NONE:
404             processor = None
405         elif file_type == FileType.CPP:
406             file_extension = self._file_extension(file_path)
407             processor = CppProcessor(file_path, file_extension,
408                                      handle_style_error, min_confidence)
409         elif file_type == FileType.PYTHON:
410             processor = PythonProcessor(file_path, handle_style_error)
411         elif file_type == FileType.TEXT:
412             processor = TextProcessor(file_path, handle_style_error)
413         else:
414             raise ValueError('Invalid file type "%(file_type)s": the only valid file types '
415                              "are %(NONE)s, %(CPP)s, and %(TEXT)s."
416                              % {"file_type": file_type,
417                                 "NONE": FileType.NONE,
418                                 "CPP": FileType.CPP,
419                                 "TEXT": FileType.TEXT})
420
421         return processor
422
423     def dispatch_processor(self, file_path, handle_style_error, min_confidence):
424         """Instantiate and return a style processor based on file path."""
425         file_type = self._file_type(file_path)
426
427         processor = self._create_processor(file_type,
428                                            file_path,
429                                            handle_style_error,
430                                            min_confidence)
431         return processor
432
433
434 # FIXME: Remove the stderr_write attribute from this class and replace
435 #        its use with calls to a logging module logger.
436 class StyleCheckerConfiguration(object):
437
438     """Stores configuration values for the StyleChecker class.
439
440     Attributes:
441       min_confidence: An integer between 1 and 5 inclusive that is the
442                       minimum confidence level of style errors to report.
443
444       max_reports_per_category: The maximum number of errors to report
445                                 per category, per file.
446
447       stderr_write: A function that takes a string as a parameter and
448                     serves as stderr.write.
449
450     """
451
452     def __init__(self,
453                  filter_configuration,
454                  max_reports_per_category,
455                  min_confidence,
456                  output_format,
457                  stderr_write):
458         """Create a StyleCheckerConfiguration instance.
459
460         Args:
461           filter_configuration: A FilterConfiguration instance.  The default
462                                 is the "empty" filter configuration, which
463                                 means that all errors should be checked.
464
465           max_reports_per_category: The maximum number of errors to report
466                                     per category, per file.
467
468           min_confidence: An integer between 1 and 5 inclusive that is the
469                           minimum confidence level of style errors to report.
470                           The default is 1, which reports all style errors.
471
472           output_format: A string that is the output format.  The supported
473                          output formats are "emacs" which emacs can parse
474                          and "vs7" which Microsoft Visual Studio 7 can parse.
475
476           stderr_write: A function that takes a string as a parameter and
477                         serves as stderr.write.
478
479         """
480         self._filter_configuration = filter_configuration
481         self._output_format = output_format
482
483         self.max_reports_per_category = max_reports_per_category
484         self.min_confidence = min_confidence
485         self.stderr_write = stderr_write
486
487     def is_reportable(self, category, confidence_in_error, file_path):
488         """Return whether an error is reportable.
489
490         An error is reportable if both the confidence in the error is
491         at least the minimum confidence level and the current filter
492         says the category should be checked for the given path.
493
494         Args:
495           category: A string that is a style category.
496           confidence_in_error: An integer between 1 and 5 inclusive that is
497                                the application's confidence in the error.
498                                A higher number means greater confidence.
499           file_path: The path of the file being checked
500
501         """
502         if confidence_in_error < self.min_confidence:
503             return False
504
505         return self._filter_configuration.should_check(category, file_path)
506
507     def write_style_error(self,
508                           category,
509                           confidence_in_error,
510                           file_path,
511                           line_number,
512                           message):
513         """Write a style error to the configured stderr."""
514         if self._output_format == 'vs7':
515             format_string = "%s(%s):  %s  [%s] [%d]\n"
516         else:
517             format_string = "%s:%s:  %s  [%s] [%d]\n"
518
519         self.stderr_write(format_string % (file_path,
520                                            line_number,
521                                            message,
522                                            category,
523                                            confidence_in_error))
524
525
526 class StyleChecker(object):
527
528     """Supports checking style in files and patches.
529
530        Attributes:
531          error_count: An integer that is the total number of reported
532                       errors for the lifetime of this StyleChecker
533                       instance.
534          file_count: An integer that is the total number of processed
535                      files.  Note that the number of skipped files is
536                      included in this value.
537
538     """
539
540     def __init__(self, configuration):
541         """Create a StyleChecker instance.
542
543         Args:
544           configuration: A StyleCheckerConfiguration instance that controls
545                          the behavior of style checking.
546
547         """
548         self._configuration = configuration
549         self.error_count = 0
550         self.file_count = 0
551
552     def _increment_error_count(self):
553         """Increment the total count of reported errors."""
554         self.error_count += 1
555
556     def _read_lines(self, file_path):
557         """Read the file at a path, and return its lines.
558
559         Raises:
560           IOError: if the file does not exist or cannot be read.
561
562         """
563         # Support the UNIX convention of using "-" for stdin.
564         if file_path == '-':
565             file = codecs.StreamReaderWriter(sys.stdin,
566                                              codecs.getreader('utf8'),
567                                              codecs.getwriter('utf8'),
568                                              'replace')
569         else:
570             # We do not open the file with universal newline support
571             # (codecs does not support it anyway), so the resulting
572             # lines contain trailing "\r" characters if we are reading
573             # a file with CRLF endings.
574             file = codecs.open(file_path, 'r', 'utf8', 'replace')
575
576         contents = file.read()
577
578         lines = contents.split("\n")
579         return lines
580
581     def _process_file(self, processor, file_path, handle_style_error):
582         """Process the file using the given style processor."""
583         try:
584             lines = self._read_lines(file_path)
585         except IOError:
586             message = 'Could not read file. Skipping: "%s"' % file_path
587             _log.warn(message)
588             return
589
590         # Check for and remove trailing carriage returns ("\r").
591         #
592         # FIXME: We should probably use the SVN "eol-style" property
593         #        or a white list to decide whether or not to do
594         #        the carriage-return check. Originally, we did the
595         #        check only if (os.linesep != '\r\n').
596         carriage_return_processor = CarriageReturnProcessor(handle_style_error)
597         lines = carriage_return_processor.process(lines)
598
599         processor.process(lines)
600
601     def check_paths(self, paths, mock_check_file=None, mock_os=None):
602         """Check style in the given files or directories.
603
604         Args:
605           paths: A list of file paths and directory paths.
606           mock_check_file: A mock of self.check_file for unit testing.
607           mock_os: A mock os for unit testing.
608
609         """
610         check_file = self.check_file if mock_check_file is None else \
611                      mock_check_file
612         os_module = os if mock_os is None else mock_os
613
614         for path in paths:
615             if os_module.path.isdir(path):
616                 self._check_directory(directory=path,
617                                       check_file=check_file,
618                                       mock_os_walk=os_module.walk)
619             else:
620                 check_file(path)
621
622     def _check_directory(self, directory, check_file, mock_os_walk=None):
623         """Check style in all files in a directory, recursively.
624
625         Args:
626           directory: A path to a directory.
627           check_file: The function to use in place of self.check_file().
628           mock_os_walk: A mock os.walk for unit testing.
629
630         """
631         os_walk = os.walk if mock_os_walk is None else mock_os_walk
632
633         for dir_path, dir_names, file_names in os_walk(directory):
634             for file_name in file_names:
635                 file_path = os.path.join(dir_path, file_name)
636                 check_file(file_path)
637
638     def check_file(self, file_path, line_numbers=None,
639                    mock_handle_style_error=None,
640                    mock_os_path_exists=None,
641                    mock_process_file=None):
642         """Check style in the given file.
643
644         Args:
645           file_path: The path of the file to process.  If possible, the path
646                      should be relative to the source root.  Otherwise,
647                      path-specific logic may not behave as expected.
648           line_numbers: An array of line numbers of the lines for which
649                         style errors should be reported, or None if errors
650                         for all lines should be reported.  Normally, this
651                         array contains the line numbers corresponding to the
652                         modified lines of a patch.
653           mock_handle_style_error: A unit-testing replacement for the function
654                                    to call when a style error occurs. Defaults
655                                    to a DefaultStyleErrorHandler instance.
656           mock_os_path_exists: A unit-test replacement for os.path.exists.
657                                This parameter should only be used for unit
658                                tests.
659           mock_process_file: The function to call to process the file. This
660                              parameter should be used only for unit tests.
661                              Defaults to the file processing method of this
662                              class.
663
664         """
665         if mock_handle_style_error is None:
666             increment = self._increment_error_count
667             handle_style_error = DefaultStyleErrorHandler(
668                                      configuration=self._configuration,
669                                      file_path=file_path,
670                                      increment_error_count=increment,
671                                      line_numbers=line_numbers)
672         else:
673             handle_style_error = mock_handle_style_error
674
675         os_path_exists = (os.path.exists if mock_os_path_exists is None else
676                           mock_os_path_exists)
677         process_file = (self._process_file if mock_process_file is None else
678                         mock_process_file)
679
680         if not os_path_exists(file_path):
681             _log.warn("Skipping non-existent file: %s" % file_path)
682             return
683
684         _log.debug("Checking: " + file_path)
685
686         self.file_count += 1
687
688         dispatcher = ProcessorDispatcher()
689
690         if dispatcher.should_skip_without_warning(file_path):
691             return
692         if dispatcher.should_skip_with_warning(file_path):
693             _log.warn('File exempt from style guide. Skipping: "%s"'
694                       % file_path)
695             return
696
697         min_confidence = self._configuration.min_confidence
698         processor = dispatcher.dispatch_processor(file_path,
699                                                   handle_style_error,
700                                                   min_confidence)
701         if processor is None:
702             _log.debug('File not a recognized type to check. Skipping: "%s"'
703                        % file_path)
704             return
705
706         _log.debug("Using class: " + processor.__class__.__name__)
707
708         process_file(processor, file_path, handle_style_error)
709
710     # FIXME: Eliminate this method and move its logic to style/main.py.
711     #        Calls to check_patch() can be replaced by appropriate calls
712     #        to check_file() using the optional line_numbers parameter.
713     def check_patch(self, patch_string, mock_check_file=None):
714         """Check style in the given patch.
715
716         Args:
717           patch_string: A string that is a patch string.
718
719         """
720         check_file = (self.check_file if mock_check_file is None else
721                       mock_check_file)
722
723         patch_files = parse_patch(patch_string)
724
725         # The diff variable is a DiffFile instance.
726         for file_path, diff in patch_files.iteritems():
727             line_numbers = set()
728             for line in diff.lines:
729                 # When deleted line is not set, it means that
730                 # the line is newly added (or modified).
731                 if not line[0]:
732                     line_numbers.add(line[1])
733
734             _log.debug('Found %s modified lines in patch for: %s'
735                        % (len(line_numbers), file_path))
736
737             check_file(file_path=file_path, line_numbers=line_numbers)