Ensures help text defaults not clobbered by config

- Matches help text default format with invocation docs
- Resolves #1666
This commit is contained in:
chrisRedwine 2022-08-14 02:41:57 -05:00
parent e249dc47df
commit ec7e7c8a27
2 changed files with 62 additions and 4 deletions

View file

@ -36,6 +36,10 @@ _optparse_callable_map: Dict[str, Union[Type[Any], _ARG]] = {
}
_optparse_default: str = "%default"
_argparse_default: str = "%(default)s"
class _CallbackAction(argparse.Action):
"""Shim for optparse-style callback actions."""
@ -189,13 +193,18 @@ class Option:
short_option_name, long_option_name = _ARG.NO, short_option_name
# optparse -> argparse `%default` => `%(default)s`
if help is not _ARG.NO and "%default" in help:
if help is not _ARG.NO and _optparse_default in help:
LOG.warning(
"option %s: please update `help=` text to use %%(default)s "
"instead of %%default -- this will be an error in the future",
"option %s: please update `help=` text to use %s "
"instead of %s -- this will be an error in the future",
long_option_name,
_argparse_default,
_optparse_default,
)
help = help.replace("%default", "%(default)s")
help = help.replace(_optparse_default, _argparse_default)
# ensure help text doesn't get clobbered by later updates to defaults
help = self._bind_defaults_to_help_text(help, default)
# optparse -> argparse for `callback`
if action == "callback":
@ -294,6 +303,26 @@ class Option:
parts.append(f"{k}={v!r}")
return f"Option({', '.join(parts)})"
@staticmethod
def _bind_defaults_to_help_text(
help: Union[str, _ARG] = _ARG.NO,
default: Union[Any, _ARG] = _ARG.NO,
long_option_name: Union[str, _ARG] = _ARG.NO,
) -> Union[str, _ARG]:
"""Ensure help text doesn't get clobbered by updates to defaults."""
if help is not _ARG.NO and _argparse_default in help:
if default is _ARG.NO:
LOG.warning(
"option %s: %s detected in `help=` text, "
"but no default value was provided",
long_option_name,
_argparse_default,
)
else:
help = help.replace(_argparse_default, "%s") % default
return help
def normalize(self, value: Any, *normalize_args: str) -> Any:
"""Normalize the value based on the option configuration."""
if self.comma_separated_list and isinstance(value, str):

View file

@ -1,12 +1,14 @@
"""Integration tests for the main entrypoint of flake8."""
import json
import os
import re
import sys
from unittest import mock
import pytest
from flake8 import utils
from flake8.defaults import EXCLUDE
from flake8.main import cli
from flake8.options import config
@ -149,6 +151,33 @@ def test_extend_exclude(tmpdir, capsys):
assert err == ""
def test_config_value_does_not_clobber_default_help_text(tmpdir, capsys):
"""Test a config value does not clobber the default help text output."""
setup_cfg = """\
[flake8]
exclude = 1,2
"""
expected = ",".join(EXCLUDE)
default_pattern = re.compile(r"\(Default:(.*?)\)")
whitespace_pattern = re.compile(r"\s+")
with tmpdir.as_cwd():
tmpdir.join("setup.cfg").write(setup_cfg)
with pytest.raises(SystemExit):
cli.main(["-h"])
out, err = capsys.readouterr()
for section in out.split("--"):
if section.startswith("exclude patterns"):
section = re.sub(whitespace_pattern, "", section)
default_groups = re.search(default_pattern, section)
assert default_groups is not None
default = default_groups.group(1)
assert default == expected
def test_malformed_per_file_ignores_error(tmpdir, capsys):
"""Test the error message for malformed `per-file-ignores`."""
setup_cfg = """\