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