Skip to content

Commit

Permalink
Observe specific enables/disables even if followed by "all" (pylint-d…
Browse files Browse the repository at this point in the history
  • Loading branch information
jacobtylerwalls authored Jul 11, 2023
1 parent 2dab7ff commit 0560ddf
Show file tree
Hide file tree
Showing 9 changed files with 126 additions and 2 deletions.
13 changes: 13 additions & 0 deletions doc/whatsnew/fragments/3696.breaking
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
Enabling or disabling individual messages will now take effect even if an
``--enable=all`` or ``disable=all`` follows in the same configuration file
(or on the command line).

This means for the following example, ``fixme`` messages will now be emitted::

.. code-block::

pylint my_module --enable=fixme --disable=all

To regain the prior behavior, remove the superfluous earlier option.

Closes #3696
54 changes: 53 additions & 1 deletion pylint/config/config_initialization.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@

from pylint import reporters
from pylint.config.config_file_parser import _ConfigurationFileParser
from pylint.config.exceptions import _UnrecognizedOptionError
from pylint.config.exceptions import (
ArgumentPreprocessingError,
_UnrecognizedOptionError,
)
from pylint.utils import utils

if TYPE_CHECKING:
Expand Down Expand Up @@ -46,6 +49,9 @@ def _config_initialization(
print(ex, file=sys.stderr)
sys.exit(32)

# Order --enable=all or --disable=all to come first.
config_args = _order_all_first(config_args, joined=False)

# Run init hook, if present, before loading plugins
if "init-hook" in config_data:
exec(utils._unquote(config_data["init-hook"])) # pylint: disable=exec-used
Expand Down Expand Up @@ -73,6 +79,7 @@ def _config_initialization(

# Now we parse any options from the command line, so they can override
# the configuration file
args_list = _order_all_first(args_list, joined=True)
parsed_args_list = linter._parse_command_line_configuration(args_list)

# Remove the positional arguments separator from the list of arguments if it exists
Expand Down Expand Up @@ -147,3 +154,48 @@ def _config_initialization(
for arg in parsed_args_list
)
)


def _order_all_first(config_args: list[str], *, joined: bool) -> list[str]:
"""Reorder config_args such that --enable=all or --disable=all comes first.
Raise if both are given.
If joined is True, expect args in the form '--enable=all,for-any-all'.
If joined is False, expect args in the form '--enable', 'all,for-any-all'.
"""
indexes_to_prepend = []
all_action = ""

for i, arg in enumerate(config_args):
if joined and (arg.startswith("--enable=") or arg.startswith("--disable=")):
value = arg.split("=")[1]
elif arg in {"--enable", "--disable"}:
value = config_args[i + 1]
else:
continue

if "all" not in (msg.strip() for msg in value.split(",")):
continue

arg = arg.split("=")[0]
if all_action and (arg != all_action):
raise ArgumentPreprocessingError(
"--enable=all and --disable=all are incompatible."
)
all_action = arg

indexes_to_prepend.append(i)
if not joined:
indexes_to_prepend.append(i + 1)

returned_args = []
for i in indexes_to_prepend:
returned_args.append(config_args[i])

for i, arg in enumerate(config_args):
if i in indexes_to_prepend:
continue
returned_args.append(arg)

return returned_args
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[tool.pylint."messages control"]
disable = "all"
enable = "all"
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[tool.pylint."messages control"]
disable = "fixme"
enable = "all"
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[tool.pylint."messages control"]
enable = "fixme"
disable = "all"
44 changes: 44 additions & 0 deletions tests/config/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import pytest
from pytest import CaptureFixture

from pylint.config.exceptions import ArgumentPreprocessingError
from pylint.interfaces import CONFIDENCE_LEVEL_NAMES
from pylint.lint import Run as LintRun
from pylint.testutils import create_files
Expand All @@ -20,6 +21,7 @@
HERE = Path(__file__).parent.absolute()
REGRTEST_DATA_DIR = HERE / ".." / "regrtest_data"
EMPTY_MODULE = REGRTEST_DATA_DIR / "empty.py"
FIXME_MODULE = REGRTEST_DATA_DIR / "fixme.py"


def check_configuration_file_reader(
Expand Down Expand Up @@ -175,3 +177,45 @@ def test_clear_cache_post_run() -> None:

assert not run_before_edit.linter.stats.by_msg
assert run_after_edit.linter.stats.by_msg


def test_enable_all_disable_all_mutually_exclusive() -> None:
with pytest.raises(ArgumentPreprocessingError):
runner = Run(["--enable=all", "--disable=all", str(EMPTY_MODULE)], exit=False)

runner = Run(["--enable=all", "--enable=all", str(EMPTY_MODULE)], exit=False)
assert not runner.linter.stats.by_msg

with pytest.raises(ArgumentPreprocessingError):
run_using_a_configuration_file(
HERE
/ "functional"
/ "toml"
/ "toml_with_mutually_exclusive_disable_enable_all.toml",
)


def test_disable_before_enable_all_takes_effect() -> None:
runner = Run(["--disable=fixme", "--enable=all", str(FIXME_MODULE)], exit=False)
assert not runner.linter.stats.by_msg

_, _, toml_runner = run_using_a_configuration_file(
HERE
/ "functional"
/ "toml"
/ "toml_with_specific_disable_before_enable_all.toml",
)
assert not toml_runner.linter.is_message_enabled("fixme")


def test_enable_before_disable_all_takes_effect() -> None:
runner = Run(["--enable=fixme", "--disable=all", str(FIXME_MODULE)], exit=False)
assert runner.linter.stats.by_msg

_, _, toml_runner = run_using_a_configuration_file(
HERE
/ "functional"
/ "toml"
/ "toml_with_specific_enable_before_disable_all.toml",
)
assert toml_runner.linter.is_message_enabled("fixme")
5 changes: 5 additions & 0 deletions tests/config/test_functional_config_loading.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@
str(path.relative_to(FUNCTIONAL_DIR))
for ext in ACCEPTED_CONFIGURATION_EXTENSIONS
for path in FUNCTIONAL_DIR.rglob(f"*.{ext}")
if (str_path := str(path))
# The enable/disable all tests are not practical with this framework.
# They require manually listing ~400 messages, which will
# require constant updates.
and "enable_all" not in str_path and "disable_all" not in str_path
]


Expand Down
1 change: 1 addition & 0 deletions tests/regrtest_data/fixme.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# TODO: implement
2 changes: 1 addition & 1 deletion tests/test_self.py
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@ def test_enable_all_works(self) -> None:
"""
)
self._test_output(
[module, "--disable=all", "--enable=all", "-rn"], expected_output=expected
[module, "--disable=I", "--enable=all", "-rn"], expected_output=expected
)

def test_wrong_import_position_when_others_disabled(self) -> None:
Expand Down

0 comments on commit 0560ddf

Please sign in to comment.