From 59c3ba75e6ee53c07ac07adc46b715701deb3fec Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Mon, 6 Jun 2016 13:03:55 -0500 Subject: [PATCH] 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. --- flake8/checker.py | 11 ++++++--- flake8/main/cli.py | 18 +++++++++++++- flake8/style_guide.py | 56 +++++++++++++++++++++++++++++++++++++++++-- flake8/utils.py | 7 ++++-- 4 files changed, 84 insertions(+), 8 deletions(-) diff --git a/flake8/checker.py b/flake8/checker.py index c96d6f6..75c98c1 100644 --- a/flake8/checker.py +++ b/flake8/checker.py @@ -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 diff --git a/flake8/main/cli.py b/flake8/main/cli.py index 455629f..b426e1e 100644 --- a/flake8/main/cli.py +++ b/flake8/main/cli.py @@ -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() diff --git a/flake8/style_guide.py b/flake8/style_guide.py index fd4692c..386edb1 100644 --- a/flake8/style_guide.py +++ b/flake8/style_guide.py @@ -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 diff --git a/flake8/utils.py b/flake8/utils.py index a0db636..f6ce384 100644 --- a/flake8/utils.py +++ b/flake8/utils.py @@ -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