Skip to content

Commit 0560ddf

Browse files
Observe specific enables/disables even if followed by "all" (#8819)
1 parent 2dab7ff commit 0560ddf

9 files changed

+126
-2
lines changed

doc/whatsnew/fragments/3696.breaking

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
Enabling or disabling individual messages will now take effect even if an
2+
``--enable=all`` or ``disable=all`` follows in the same configuration file
3+
(or on the command line).
4+
5+
This means for the following example, ``fixme`` messages will now be emitted::
6+
7+
.. code-block::
8+
9+
pylint my_module --enable=fixme --disable=all
10+
11+
To regain the prior behavior, remove the superfluous earlier option.
12+
13+
Closes #3696

pylint/config/config_initialization.py

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@
1313

1414
from pylint import reporters
1515
from pylint.config.config_file_parser import _ConfigurationFileParser
16-
from pylint.config.exceptions import _UnrecognizedOptionError
16+
from pylint.config.exceptions import (
17+
ArgumentPreprocessingError,
18+
_UnrecognizedOptionError,
19+
)
1720
from pylint.utils import utils
1821

1922
if TYPE_CHECKING:
@@ -46,6 +49,9 @@ def _config_initialization(
4649
print(ex, file=sys.stderr)
4750
sys.exit(32)
4851

52+
# Order --enable=all or --disable=all to come first.
53+
config_args = _order_all_first(config_args, joined=False)
54+
4955
# Run init hook, if present, before loading plugins
5056
if "init-hook" in config_data:
5157
exec(utils._unquote(config_data["init-hook"])) # pylint: disable=exec-used
@@ -73,6 +79,7 @@ def _config_initialization(
7379

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

7885
# Remove the positional arguments separator from the list of arguments if it exists
@@ -147,3 +154,48 @@ def _config_initialization(
147154
for arg in parsed_args_list
148155
)
149156
)
157+
158+
159+
def _order_all_first(config_args: list[str], *, joined: bool) -> list[str]:
160+
"""Reorder config_args such that --enable=all or --disable=all comes first.
161+
162+
Raise if both are given.
163+
164+
If joined is True, expect args in the form '--enable=all,for-any-all'.
165+
If joined is False, expect args in the form '--enable', 'all,for-any-all'.
166+
"""
167+
indexes_to_prepend = []
168+
all_action = ""
169+
170+
for i, arg in enumerate(config_args):
171+
if joined and (arg.startswith("--enable=") or arg.startswith("--disable=")):
172+
value = arg.split("=")[1]
173+
elif arg in {"--enable", "--disable"}:
174+
value = config_args[i + 1]
175+
else:
176+
continue
177+
178+
if "all" not in (msg.strip() for msg in value.split(",")):
179+
continue
180+
181+
arg = arg.split("=")[0]
182+
if all_action and (arg != all_action):
183+
raise ArgumentPreprocessingError(
184+
"--enable=all and --disable=all are incompatible."
185+
)
186+
all_action = arg
187+
188+
indexes_to_prepend.append(i)
189+
if not joined:
190+
indexes_to_prepend.append(i + 1)
191+
192+
returned_args = []
193+
for i in indexes_to_prepend:
194+
returned_args.append(config_args[i])
195+
196+
for i, arg in enumerate(config_args):
197+
if i in indexes_to_prepend:
198+
continue
199+
returned_args.append(arg)
200+
201+
return returned_args
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[tool.pylint."messages control"]
2+
disable = "all"
3+
enable = "all"
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[tool.pylint."messages control"]
2+
disable = "fixme"
3+
enable = "all"
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[tool.pylint."messages control"]
2+
enable = "fixme"
3+
disable = "all"

tests/config/test_config.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import pytest
1212
from pytest import CaptureFixture
1313

14+
from pylint.config.exceptions import ArgumentPreprocessingError
1415
from pylint.interfaces import CONFIDENCE_LEVEL_NAMES
1516
from pylint.lint import Run as LintRun
1617
from pylint.testutils import create_files
@@ -20,6 +21,7 @@
2021
HERE = Path(__file__).parent.absolute()
2122
REGRTEST_DATA_DIR = HERE / ".." / "regrtest_data"
2223
EMPTY_MODULE = REGRTEST_DATA_DIR / "empty.py"
24+
FIXME_MODULE = REGRTEST_DATA_DIR / "fixme.py"
2325

2426

2527
def check_configuration_file_reader(
@@ -175,3 +177,45 @@ def test_clear_cache_post_run() -> None:
175177

176178
assert not run_before_edit.linter.stats.by_msg
177179
assert run_after_edit.linter.stats.by_msg
180+
181+
182+
def test_enable_all_disable_all_mutually_exclusive() -> None:
183+
with pytest.raises(ArgumentPreprocessingError):
184+
runner = Run(["--enable=all", "--disable=all", str(EMPTY_MODULE)], exit=False)
185+
186+
runner = Run(["--enable=all", "--enable=all", str(EMPTY_MODULE)], exit=False)
187+
assert not runner.linter.stats.by_msg
188+
189+
with pytest.raises(ArgumentPreprocessingError):
190+
run_using_a_configuration_file(
191+
HERE
192+
/ "functional"
193+
/ "toml"
194+
/ "toml_with_mutually_exclusive_disable_enable_all.toml",
195+
)
196+
197+
198+
def test_disable_before_enable_all_takes_effect() -> None:
199+
runner = Run(["--disable=fixme", "--enable=all", str(FIXME_MODULE)], exit=False)
200+
assert not runner.linter.stats.by_msg
201+
202+
_, _, toml_runner = run_using_a_configuration_file(
203+
HERE
204+
/ "functional"
205+
/ "toml"
206+
/ "toml_with_specific_disable_before_enable_all.toml",
207+
)
208+
assert not toml_runner.linter.is_message_enabled("fixme")
209+
210+
211+
def test_enable_before_disable_all_takes_effect() -> None:
212+
runner = Run(["--enable=fixme", "--disable=all", str(FIXME_MODULE)], exit=False)
213+
assert runner.linter.stats.by_msg
214+
215+
_, _, toml_runner = run_using_a_configuration_file(
216+
HERE
217+
/ "functional"
218+
/ "toml"
219+
/ "toml_with_specific_enable_before_disable_all.toml",
220+
)
221+
assert toml_runner.linter.is_message_enabled("fixme")

tests/config/test_functional_config_loading.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,11 @@
4343
str(path.relative_to(FUNCTIONAL_DIR))
4444
for ext in ACCEPTED_CONFIGURATION_EXTENSIONS
4545
for path in FUNCTIONAL_DIR.rglob(f"*.{ext}")
46+
if (str_path := str(path))
47+
# The enable/disable all tests are not practical with this framework.
48+
# They require manually listing ~400 messages, which will
49+
# require constant updates.
50+
and "enable_all" not in str_path and "disable_all" not in str_path
4651
]
4752

4853

tests/regrtest_data/fixme.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# TODO: implement

tests/test_self.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,7 @@ def test_enable_all_works(self) -> None:
266266
"""
267267
)
268268
self._test_output(
269-
[module, "--disable=all", "--enable=all", "-rn"], expected_output=expected
269+
[module, "--disable=I", "--enable=all", "-rn"], expected_output=expected
270270
)
271271

272272
def test_wrong_import_position_when_others_disabled(self) -> None:

0 commit comments

Comments
 (0)