From 31aad5975a6ed85b0437cba54f16446138eb79ee Mon Sep 17 00:00:00 2001 From: "Eric N. Vander Weele" Date: Wed, 28 Aug 2019 15:27:51 -0400 Subject: [PATCH] Normalize 'per-file-ignores' file paths Leverage the `type` keyword argument to `ArgumentParser.add_option()` for converting the raw string, either from a configuration file or the command-line, into a data structure for later use. Now with `OptionManager.parse_args()` feeding configuration file values as default values to be considered by the `ArgumentParser.parse_args()` framework, normalization of the file paths occur in single place. --- src/flake8/main/options.py | 15 +++++++++++++++ src/flake8/style_guide.py | 7 ++----- src/flake8/utils.py | 8 +++----- tests/integration/test_main.py | 10 +++++----- tests/unit/test_style_guide.py | 16 ++++++++-------- 5 files changed, 33 insertions(+), 23 deletions(-) diff --git a/src/flake8/main/options.py b/src/flake8/main/options.py index 75d2c8f..1fd8b86 100644 --- a/src/flake8/main/options.py +++ b/src/flake8/main/options.py @@ -1,11 +1,25 @@ """Contains the logic for all of the default options for Flake8.""" +import argparse import functools +from typing import List, Tuple from flake8 import defaults +from flake8 import exceptions +from flake8 import utils from flake8.main import debug from flake8.main import vcs +def _convert_per_file_ignores(value): + # type: (str) -> List[Tuple[str, List[str]]] + try: + mappings = utils.parse_files_to_codes_mapping(value) + except exceptions.ExecutionError as e: + raise argparse.ArgumentTypeError(e) + + return [(utils.normalize_path(path), codes) for path, codes in mappings] + + def register_default_options(option_manager): """Register the default options on our OptionManager. @@ -159,6 +173,7 @@ def register_default_options(option_manager): add_option( "--per-file-ignores", + type=_convert_per_file_ignores, default="", parse_from_config=True, help="A pairing of filenames and violation codes that defines which " diff --git a/src/flake8/style_guide.py b/src/flake8/style_guide.py index 8d3c8cf..088363e 100644 --- a/src/flake8/style_guide.py +++ b/src/flake8/style_guide.py @@ -347,7 +347,7 @@ class StyleGuideManager(object): """Generate style guides from the per-file-ignores option. :param options: - The original options parsed from the CLI and config file. + The normalized options parsed from the CLI and config file. :type options: :class:`~argparse.Namespace` :returns: @@ -355,10 +355,7 @@ class StyleGuideManager(object): :rtype: :class:`~flake8.style_guide.StyleGuide` """ - per_file = utils.parse_files_to_codes_mapping( - options.per_file_ignores - ) - for filename, violations in per_file: + for filename, violations in options.per_file_ignores: yield self.default_style_guide.copy( filename=filename, extend_ignore_with=violations ) diff --git a/src/flake8/utils.py b/src/flake8/utils.py index 92fec71..0d80fa9 100644 --- a/src/flake8/utils.py +++ b/src/flake8/utils.py @@ -116,11 +116,9 @@ def parse_files_to_codes_mapping(value_): # noqa: C901 return " " + s.strip().replace("\n", "\n ") return exceptions.ExecutionError( - "Expected `per-file-ignores` to be a mapping from file exclude " - "patterns to ignore codes.\n\n" - "Configured `per-file-ignores` setting:\n\n{}".format( - _indent(value) - ) + "Expected a mapping from file exclude patterns to ignore " + "codes.\n\n" + "Found:\n\n{}".format(_indent(value)) ) for token in _tokenize_files_to_codes_mapping(value): diff --git a/tests/integration/test_main.py b/tests/integration/test_main.py index 72e9621..59da4be 100644 --- a/tests/integration/test_main.py +++ b/tests/integration/test_main.py @@ -94,14 +94,14 @@ per-file-ignores = with tmpdir.as_cwd(): tmpdir.join('setup.cfg').write(setup_cfg) - _call_main(['.'], retv=1) + _call_main(['.'], retv=2) out, err = capsys.readouterr() - assert out == '''\ -There was a critical error during execution of Flake8: -Expected `per-file-ignores` to be a mapping from file exclude patterns to ignore codes. + assert err == '''\ +usage: flake8 [options] file file ... +flake8: error: argument --per-file-ignores: Expected a mapping from file exclude patterns to ignore codes. -Configured `per-file-ignores` setting: +Found: incorrect/* values/* diff --git a/tests/unit/test_style_guide.py b/tests/unit/test_style_guide.py index 38121c1..d00f33e 100644 --- a/tests/unit/test_style_guide.py +++ b/tests/unit/test_style_guide.py @@ -45,11 +45,11 @@ def test_style_guide_manager(): assert len(guide.style_guides) == 1 -PER_FILE_IGNORES_UNPARSED = [ - "first_file.py:W9", - "second_file.py:F4,F9", - "third_file.py:E3", - "sub_dir/*:F4", +PER_FILE_IGNORES_PARSED = [ + ("first_file.py", ["W9"]), + ("second_file.py", ["F4", "F9"]), + ("third_file.py", ["E3"]), + ("sub_dir/*", ["F4"]), ] @@ -75,7 +75,7 @@ def test_style_guide_applies_to(style_guide_file, filename, expected): def test_style_guide_manager_pre_file_ignores_parsing(): """Verify how the StyleGuideManager creates a default style guide.""" formatter = mock.create_autospec(base.BaseFormatter, instance=True) - options = create_options(per_file_ignores=PER_FILE_IGNORES_UNPARSED) + options = create_options(per_file_ignores=PER_FILE_IGNORES_PARSED) guide = style_guide.StyleGuideManager(options, formatter=formatter) assert len(guide.style_guides) == 5 assert list(map(utils.normalize_path, @@ -98,7 +98,7 @@ def test_style_guide_manager_pre_file_ignores(ignores, violation, filename, formatter = mock.create_autospec(base.BaseFormatter, instance=True) options = create_options(ignore=ignores, select=['E', 'F', 'W'], - per_file_ignores=PER_FILE_IGNORES_UNPARSED) + per_file_ignores=PER_FILE_IGNORES_PARSED) guide = style_guide.StyleGuideManager(options, formatter=formatter) assert (guide.handle_error(violation, filename, 1, 1, "Fake text") == handle_error_return) @@ -115,7 +115,7 @@ def test_style_guide_manager_pre_file_ignores(ignores, violation, filename, def test_style_guide_manager_style_guide_for(filename, expected): """Verify the style guide selection function.""" formatter = mock.create_autospec(base.BaseFormatter, instance=True) - options = create_options(per_file_ignores=PER_FILE_IGNORES_UNPARSED) + options = create_options(per_file_ignores=PER_FILE_IGNORES_PARSED) guide = style_guide.StyleGuideManager(options, formatter=formatter) file_guide = guide.style_guide_for(filename)