diff --git a/src/flake8/defaults.py b/src/flake8/defaults.py index 4ba0048..e3edf32 100644 --- a/src/flake8/defaults.py +++ b/src/flake8/defaults.py @@ -43,3 +43,5 @@ NOQA_INLINE_REGEXP = re.compile( ) NOQA_FILE = re.compile(r"\s*# flake8[:=]\s*noqa", re.I) + +VALID_CODE_PREFIX = re.compile("^[A-Z]{1,3}[0-9]{0,3}$", re.ASCII) diff --git a/src/flake8/options/config.py b/src/flake8/options/config.py index e158737..a9ced1b 100644 --- a/src/flake8/options/config.py +++ b/src/flake8/options/config.py @@ -7,6 +7,7 @@ import os.path from typing import Any from flake8 import exceptions +from flake8.defaults import VALID_CODE_PREFIX from flake8.options.manager import OptionManager LOG = logging.getLogger(__name__) @@ -120,6 +121,16 @@ def parse_config( LOG.debug('Option "%s" returned value: %r', option_name, value) final_value = option.normalize(value, cfg_dir) + + if option_name in {"ignore", "extend-ignore"}: + for error_code in final_value: + if not VALID_CODE_PREFIX.match(error_code): + raise ValueError( + f"Error code {error_code!r} " + f"supplied to {option_name!r} option " + f"does not match {VALID_CODE_PREFIX.pattern!r}" + ) + assert option.config_name is not None config_dict[option.config_name] = final_value diff --git a/src/flake8/plugins/finder.py b/src/flake8/plugins/finder.py index c051488..4a43ccb 100644 --- a/src/flake8/plugins/finder.py +++ b/src/flake8/plugins/finder.py @@ -5,7 +5,6 @@ import configparser import inspect import itertools import logging -import re import sys from typing import Any from typing import Generator @@ -14,13 +13,12 @@ from typing import NamedTuple from flake8 import utils from flake8._compat import importlib_metadata +from flake8.defaults import VALID_CODE_PREFIX from flake8.exceptions import ExecutionError from flake8.exceptions import FailedToLoadPlugin LOG = logging.getLogger(__name__) -VALID_CODE = re.compile("^[A-Z]{1,3}[0-9]{0,3}$", re.ASCII) - FLAKE8_GROUPS = frozenset(("flake8.extension", "flake8.report")) BANNED_PLUGINS = { @@ -337,10 +335,10 @@ def _classify_plugins( raise NotImplementedError(f"what plugin type? {loaded}") for loaded in itertools.chain(tree, logical_line, physical_line): - if not VALID_CODE.match(loaded.entry_name): + if not VALID_CODE_PREFIX.match(loaded.entry_name): raise ExecutionError( f"plugin code for `{loaded.display_name}` does not match " - f"{VALID_CODE.pattern}" + f"{VALID_CODE_PREFIX.pattern}" ) return Plugins( diff --git a/tests/unit/plugins/finder_test.py b/tests/unit/plugins/finder_test.py index d526fd1..cd5cf4a 100644 --- a/tests/unit/plugins/finder_test.py +++ b/tests/unit/plugins/finder_test.py @@ -31,37 +31,6 @@ def _loaded(plugin=None, obj=None, parameters=None): return finder.LoadedPlugin(plugin, obj, parameters) -@pytest.mark.parametrize( - "s", - ( - "E", - "E1", - "E123", - "ABC", - "ABC1", - "ABC123", - ), -) -def test_valid_plugin_prefixes(s): - assert finder.VALID_CODE.match(s) - - -@pytest.mark.parametrize( - "s", - ( - "", - "A1234", - "ABCD", - "abc", - "a-b", - "☃", - "A𝟗", - ), -) -def test_invalid_plugin_prefixes(s): - assert finder.VALID_CODE.match(s) is None - - def test_loaded_plugin_entry_name_vs_display_name(): loaded = _loaded(_plugin(package="package-name", ep=_ep(name="Q"))) assert loaded.entry_name == "Q" diff --git a/tests/unit/test_defaults.py b/tests/unit/test_defaults.py new file mode 100644 index 0000000..822b8f0 --- /dev/null +++ b/tests/unit/test_defaults.py @@ -0,0 +1,36 @@ +from __future__ import annotations + +import pytest + +from flake8.defaults import VALID_CODE_PREFIX + + +@pytest.mark.parametrize( + "s", + ( + "E", + "E1", + "E123", + "ABC", + "ABC1", + "ABC123", + ), +) +def test_valid_plugin_prefixes(s): + assert VALID_CODE_PREFIX.match(s) + + +@pytest.mark.parametrize( + "s", + ( + "", + "A1234", + "ABCD", + "abc", + "a-b", + "☃", + "A𝟗", + ), +) +def test_invalid_plugin_prefixes(s): + assert VALID_CODE_PREFIX.match(s) is None diff --git a/tests/unit/test_options_config.py b/tests/unit/test_options_config.py index 8c8f0cb..43d8104 100644 --- a/tests/unit/test_options_config.py +++ b/tests/unit/test_options_config.py @@ -220,3 +220,35 @@ def test_parse_config_ignores_unknowns(tmp_path, opt_manager, caplog): def test_load_config_missing_file_raises_exception(capsys): with pytest.raises(exceptions.ExecutionError): config.load_config("foo.cfg", []) + + +def test_invalid_ignore_codes_raise_error(tmpdir, opt_manager): + tmpdir.join("setup.cfg").write("[flake8]\nignore = E203, //comment") + with tmpdir.as_cwd(): + cfg, _ = config.load_config("setup.cfg", [], isolated=False) + + with pytest.raises(ValueError) as excinfo: + config.parse_config(opt_manager, cfg, tmpdir) + + expected = ( + "Error code '//comment' supplied to 'ignore' option " + "does not match '^[A-Z]{1,3}[0-9]{0,3}$'" + ) + (msg,) = excinfo.value.args + assert msg == expected + + +def test_invalid_extend_ignore_codes_raise_error(tmpdir, opt_manager): + tmpdir.join("setup.cfg").write("[flake8]\nextend-ignore = E203, //comment") + with tmpdir.as_cwd(): + cfg, _ = config.load_config("setup.cfg", [], isolated=False) + + with pytest.raises(ValueError) as excinfo: + config.parse_config(opt_manager, cfg, tmpdir) + + expected = ( + "Error code '//comment' supplied to 'extend-ignore' option " + "does not match '^[A-Z]{1,3}[0-9]{0,3}$'" + ) + (msg,) = excinfo.value.args + assert msg == expected