Wire up last pieces to make --diff work

To make sure we reduce as much duplication as possible, we parse the
diff output in our main command-line Application. That then takes
responsibility for telling the StyleGuide about the line numbers that
are in the diff as well as telling the file checker manager which files
from the diff should be checked.
This commit is contained in:
Ian Cordasco 2016-06-06 13:03:55 -05:00
parent 0cdde196e8
commit 59c3ba75e6
No known key found for this signature in database
GPG key ID: 656D3395E4A9791A
4 changed files with 84 additions and 8 deletions

View file

@ -280,10 +280,15 @@ class Manager(object):
LOG.warning('Running in serial after OS exception, %r', oserr)
self.run_serial()
def start(self):
"""Start checking files."""
def start(self, paths=None):
"""Start checking files.
:param list paths:
Path names to check. This is passed directly to
:meth:`~Manager.make_checkers`.
"""
LOG.info('Making checkers')
self.make_checkers()
self.make_checkers(paths)
if not self.using_multiprocessing:
return

View file

@ -8,6 +8,7 @@ import flake8
from flake8 import checker
from flake8 import defaults
from flake8 import style_guide
from flake8 import utils
from flake8.options import aggregator
from flake8.options import manager
from flake8.plugins import manager as plugin_manager
@ -266,6 +267,11 @@ class Application(object):
#: flake8
self.result_count = 0
#: Whether the program is processing a diff or not
self.running_against_diff = False
#: The parsed diff information
self.parsed_diff = {}
def exit(self):
# type: () -> NoneType
"""Handle finalization and exiting the program.
@ -318,6 +324,10 @@ class Application(object):
self.option_manager, argv
)
self.running_against_diff = self.options.diff
if self.running_against_diff:
self.parsed_diff = utils.parse_unified_diff()
self.check_plugins.provide_options(self.option_manager, self.options,
self.args)
self.listening_plugins.provide_options(self.option_manager,
@ -349,6 +359,9 @@ class Application(object):
self.options, self.listener_trie, self.formatter
)
if self.running_against_diff:
self.guide.add_diff_ranges(self.parsed_diff)
def make_file_checker_manager(self):
# type: () -> NoneType
"""Initialize our FileChecker Manager."""
@ -367,7 +380,10 @@ class Application(object):
:class:`~flake8.checker.Manger` instance run the checks it is
managing.
"""
self.file_checker_manager.start()
files = None
if self.running_against_diff:
files = list(sorted(self.parsed_diff.keys()))
self.file_checker_manager.start(files)
self.file_checker_manager.run()
LOG.info('Finished running')
self.file_checker_manager.stop()

View file

@ -77,6 +77,7 @@ class StyleGuide(object):
self._selected = tuple(options.select)
self._ignored = tuple(options.ignore)
self._decision_cache = {}
self._parsed_diff = {}
def is_user_selected(self, code):
# type: (str) -> Union[Selected, Ignored]
@ -194,17 +195,68 @@ class StyleGuide(object):
error, codes_str)
return False
def is_in_diff(self, error):
# type: (Error) -> bool
"""Determine if an error is included in a diff's line ranges.
This function relies on the parsed data added via
:meth:`~StyleGuide.add_diff_ranges`. If that has not been called and
we are not evaluating files in a diff, then this will always return
True. If there are diff ranges, then this will return True if the
line number in the error falls inside one of the ranges for the file
(and assuming the file is part of the diff data). If there are diff
ranges, this will return False if the file is not part of the diff
data or the line number of the error is not in any of the ranges of
the diff.
:returns:
True if there is no diff or if the error is in the diff's line
number ranges. False if the error's line number falls outside
the diff's line number ranges.
:rtype:
bool
"""
if not self._parsed_diff:
return True
# NOTE(sigmavirus24): The parsed diff will be a defaultdict with
# a set as the default value (if we have received it from
# flake8.utils.parse_unified_diff). In that case ranges below
# could be an empty set (which is False-y) or if someone else
# is using this API, it could be None. If we could guarantee one
# or the other, we would check for it more explicitly.
line_numbers = self._parsed_diff.get(error.filename)
if not line_numbers:
return False
return error.line_number in line_numbers
def handle_error(self, code, filename, line_number, column_number, text,
physical_line=None):
# type: (str, str, int, int, str) -> NoneType
"""Handle an error reported by a check."""
error = Error(code, filename, line_number, column_number, text,
physical_line)
if (self.should_report_error(error.code) is Decision.Selected and
self.is_inline_ignored(error) is False):
error_is_selected = (self.should_report_error(error.code) is
Decision.Selected)
is_not_inline_ignored = self.is_inline_ignored(error) is False
is_included_in_diff = self.is_in_diff(error)
if (error_is_selected and is_not_inline_ignored and
is_included_in_diff):
self.formatter.handle(error)
self.listener.notify(error.code, error)
def add_diff_ranges(self, diffinfo):
"""Update the StyleGuide to filter out information not in the diff.
This provides information to the StyleGuide so that only the errors
in the line number ranges are reported.
:param dict diffinfo:
Dictionary mapping filenames to sets of line number ranges.
"""
self._parsed_diff = diffinfo
# Should separate style guide logic from code that runs checks
# StyleGuide should manage select/ignore logic as well as include/exclude
# logic. See also https://github.com/PyCQA/pep8/pull/433

View file

@ -71,6 +71,7 @@ def stdin_get_value():
else:
cached_type = io.StringIO
stdin_get_value.cached_stdin = cached_type(stdin_value)
cached_value = stdin_get_value.cached_stdin
return cached_value.getvalue()
@ -78,8 +79,10 @@ def parse_unified_diff():
# type: () -> List[str]
"""Parse the unified diff passed on stdin.
:returns: dictionary mapping file names to sets of ranges
:rtype: dict
:returns:
dictionary mapping file names to sets of line numbers
:rtype:
dict
"""
diff = stdin_get_value()
number_of_rows = None