diff options
author | Daniƫl van Noord <13665637+DanielNoord@users.noreply.github.com> | 2022-04-13 21:45:47 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-04-13 21:45:47 +0200 |
commit | 443802995bf0cf4921e51a5ef2f2807c22153fab (patch) | |
tree | 9743416df8a3dbe58903d9ce45960c3cb495ac7b | |
parent | 00321dc4fe3af28541cd24bf8dd3017f7b074dbc (diff) | |
download | pylint-git-443802995bf0cf4921e51a5ef2f2807c22153fab.tar.gz |
Add a ``--generate-toml-config`` option (#6199)
-rw-r--r-- | ChangeLog | 4 | ||||
-rw-r--r-- | doc/faq.rst | 8 | ||||
-rw-r--r-- | doc/intro.rst | 5 | ||||
-rw-r--r-- | doc/tutorial.rst | 5 | ||||
-rw-r--r-- | doc/user_guide/run.rst | 4 | ||||
-rw-r--r-- | doc/whatsnew/2.14.rst | 4 | ||||
-rw-r--r-- | pylint/config/arguments_manager.py | 75 | ||||
-rw-r--r-- | pylint/config/callback_actions.py | 14 | ||||
-rw-r--r-- | pylint/lint/run.py | 29 | ||||
-rw-r--r-- | setup.cfg | 4 | ||||
-rw-r--r-- | tests/test_self.py | 56 |
11 files changed, 197 insertions, 11 deletions
@@ -85,6 +85,10 @@ Release date: TBA Closes #5205 +* Added the ``generate-toml-config`` option. + + Ref #5462 + * Added new checker ``unnecessary-list-index-lookup`` for indexing into a list while iterating over ``enumerate()``. diff --git a/doc/faq.rst b/doc/faq.rst index 20e56b18e..0992cd7d6 100644 --- a/doc/faq.rst +++ b/doc/faq.rst @@ -100,16 +100,16 @@ localized using the following rules: * ".pylint.d" directory in the current directory -3.3 How do I find the option name (for pylintrc) corresponding to a specific command line option? +3.3 How do I find the option name corresponding to a specific command line option? -------------------------------------------------------------------------------------------------------- -You can generate a sample pylintrc file with --generate-rcfile +You can generate a sample configuration file with ``--generate-toml-config``. Every option present on the command line before this will be included in -the rc file +the toml file For example:: - pylint --disable=bare-except,invalid-name --class-rgx='[A-Z][a-z]+' --generate-rcfile + pylint --disable=bare-except,invalid-name --class-rgx='[A-Z][a-z]+' --generate-toml-config 3.4 I'd rather not run Pylint from the command line. Can I integrate it with my editor? --------------------------------------------------------------------------------------- diff --git a/doc/intro.rst b/doc/intro.rst index 02e6ec792..f1c58fcaf 100644 --- a/doc/intro.rst +++ b/doc/intro.rst @@ -53,5 +53,6 @@ The best way to tackle pylint's verboseness is to: all convention messages is simple as a ``--disable=C`` option added to pylint command. -* create a custom configuration file, tailored to your needs. You can generate - one using pylint's command ``--generate-rcfile``. +* manage the configuration through a configuration file. With the option + ``generate-toml-config`` you can create a piece of ``.toml`` text to put + in your ``pyproject.toml`` file. diff --git a/doc/tutorial.rst b/doc/tutorial.rst index 57eea6af8..173b255cd 100644 --- a/doc/tutorial.rst +++ b/doc/tutorial.rst @@ -59,7 +59,7 @@ A couple of the options that we'll focus on here are: :: Commands: --help-msg=<msg-id> - --generate-rcfile + --generate-toml-config Messages control: --disable=<msg-ids> Reports: @@ -273,7 +273,8 @@ example but go ahead and `read up`_ on them if you want. It would really be a pain to specify that regex on the command line all the time, particularly if we're using many other options. That's what a configuration file is for. We can configure our Pylint to store our options for us so we don't have to declare them on the command line. Using a configuration file is a nice way of formalizing your rules and - quickly sharing them with others. Invoking ``pylint --generate-rcfile`` will create a sample rcfile with all the options set and explained in comments. + quickly sharing them with others. Invoking ``pylint --generate-toml-config`` will create a sample ``.toml`` section with all the options set and explained in comments. + This can then be added to your ``pyproject.toml`` file or any other ``.toml`` file pointed to with the ``--rcfile`` option. That's it for the basic intro. More tutorials will follow. diff --git a/doc/user_guide/run.rst b/doc/user_guide/run.rst index 898a1b732..f9b8b8537 100644 --- a/doc/user_guide/run.rst +++ b/doc/user_guide/run.rst @@ -178,12 +178,12 @@ configuration file in the following order and uses the first one it finds: #. ``/etc/pylintrc`` -The ``--generate-rcfile`` option will generate a commented configuration file +The ``--generate-toml-config`` option will generate a commented configuration file on standard output according to the current configuration and exit. This includes: * Any configuration file found as explained above -* Options appearing before ``--generate-rcfile`` on the Pylint command line +* Options appearing before ``--generate-toml-config`` on the Pylint command line Of course you can also start with the default values and hand-tune the configuration. diff --git a/doc/whatsnew/2.14.rst b/doc/whatsnew/2.14.rst index 3e31f447a..525caa7e6 100644 --- a/doc/whatsnew/2.14.rst +++ b/doc/whatsnew/2.14.rst @@ -95,6 +95,10 @@ Other Changes Ref #5392 +* Added the ``generate-toml-config`` option. + + Ref #5462 + * Fix false negative for ``bad-string-format-type`` if the value to be formatted is passed in as a variable holding a constant. diff --git a/pylint/config/arguments_manager.py b/pylint/config/arguments_manager.py index 2bdd344b4..a4c865dfa 100644 --- a/pylint/config/arguments_manager.py +++ b/pylint/config/arguments_manager.py @@ -10,11 +10,15 @@ import configparser import copy import optparse # pylint: disable=deprecated-module import os +import re import sys +import textwrap import warnings from pathlib import Path from typing import TYPE_CHECKING, Any, Dict, List, Optional, TextIO, Tuple, Union +import tomlkit + from pylint import utils from pylint.config.argument import ( _Argument, @@ -595,3 +599,74 @@ class _ArgumentsManager: def global_set_option(self, opt, value): """Set option on the correct option provider.""" self._all_options[opt].set_option(opt, value) + + def _generate_config_file(self) -> None: + """Write a configuration file according to the current configuration into stdout.""" + toml_doc = tomlkit.document() + pylint_tool_table = tomlkit.table(is_super_table=True) + toml_doc.add(tomlkit.key(["tool", "pylint"]), pylint_tool_table) + + for group in sorted( + self._arg_parser._action_groups, + key=lambda x: (x.title != "Master", x.title), + ): + # Skip the options section with the --help option + if group.title == "options": + continue + + # Skip sections without options such as "positional arguments" + if not group._group_actions: + continue + + group_table = tomlkit.table() + for action in sorted( + group._group_actions, key=lambda x: x.option_strings[0][2:] + ): + optname = action.option_strings[0][2:] + + # We skip old name options that don't have their own optdict + try: + optdict = self._option_dicts[optname] + except KeyError: + continue + + if optdict.get("hide_from_config_file"): + continue + + # Add help comment + help_msg = optdict.get("help", "") + assert isinstance(help_msg, str) + help_text = textwrap.wrap(help_msg, width=79) + for line in help_text: + group_table.add(tomlkit.comment(line)) + + # Get current value of option + value = getattr(self.namespace, optname.replace("-", "_")) + + # Create a comment if the option has no value + if not value: + group_table.add(tomlkit.comment(f"{optname} =")) + group_table.add(tomlkit.nl()) + continue + + # Tomlkit doesn't support regular expressions + if isinstance(value, re.Pattern): + value = value.pattern + elif isinstance(value, (list, tuple)) and isinstance( + value[0], re.Pattern + ): + value = [i.pattern for i in value] + + # Add to table + group_table.add(optname, value) + group_table.add(tomlkit.nl()) + + assert group.title + pylint_tool_table.add(group.title.lower(), group_table) + + toml_string = tomlkit.dumps(toml_doc) + + # Make sure the string we produce is valid toml and can be parsed + tomllib.loads(toml_string) + + print(toml_string) diff --git a/pylint/config/callback_actions.py b/pylint/config/callback_actions.py index a3904c503..0d87ee122 100644 --- a/pylint/config/callback_actions.py +++ b/pylint/config/callback_actions.py @@ -245,6 +245,20 @@ class _GenerateRCFileAction(_AccessRunObjectAction): sys.exit(0) +class _GenerateConfigFileAction(_AccessRunObjectAction): + """Generate a .toml format configuration file.""" + + def __call__( + self, + parser: argparse.ArgumentParser, + namespace: argparse.Namespace, + values: Union[str, Sequence[Any], None], + option_string: Optional[str] = "--generate-toml-config", + ) -> None: + self.run.linter._generate_config_file() + sys.exit(0) + + class _ErrorsOnlyModeAction(_AccessRunObjectAction): """Turn on errors-only mode. diff --git a/pylint/lint/run.py b/pylint/lint/run.py index 0a17c1420..07336f78c 100644 --- a/pylint/lint/run.py +++ b/pylint/lint/run.py @@ -12,6 +12,7 @@ from pylint.config.callback_actions import ( _DoNothingAction, _ErrorsOnlyModeAction, _FullDocumentationAction, + _GenerateConfigFileAction, _GenerateRCFileAction, _ListCheckGroupsAction, _ListConfidenceLevelsAction, @@ -104,6 +105,7 @@ group are mutually exclusive.", "callback": Run._not_implemented_callback, "group": "Commands", "help": "Specify a configuration file to load.", + "hide_from_config_file": True, }, ), ( @@ -114,6 +116,7 @@ group are mutually exclusive.", "callback": Run._not_implemented_callback, "group": "Commands", "help": "Specify an output file.", + "hide_from_config_file": True, }, ), ( @@ -135,6 +138,7 @@ group are mutually exclusive.", "group": "Commands", "help": "Display a help message for the given message id and " "exit. The value may be a comma separated list of message ids.", + "hide_from_config_file": True, }, ), ( @@ -146,6 +150,7 @@ group are mutually exclusive.", "group": "Commands", "help": "Display a list of all pylint's messages divided by whether " "they are emittable with the given interpreter.", + "hide_from_config_file": True, }, ), ( @@ -157,6 +162,7 @@ group are mutually exclusive.", "group": "Commands", "help": "Display a list of what messages are enabled, " "disabled and non-emittable with the given configuration.", + "hide_from_config_file": True, }, ), ( @@ -167,6 +173,7 @@ group are mutually exclusive.", "callback": Run._not_implemented_callback, "group": "Commands", "help": "List pylint's message groups.", + "hide_from_config_file": True, }, ), ( @@ -177,6 +184,7 @@ group are mutually exclusive.", "kwargs": {"Run": self}, "group": "Commands", "help": "Generate pylint's confidence levels.", + "hide_from_config_file": True, }, ), ( @@ -187,6 +195,7 @@ group are mutually exclusive.", "callback": Run._not_implemented_callback, "group": "Commands", "help": "List available extensions.", + "hide_from_config_file": True, }, ), ( @@ -197,6 +206,7 @@ group are mutually exclusive.", "callback": Run._not_implemented_callback, "group": "Commands", "help": "Generate pylint's full documentation.", + "hide_from_config_file": True, }, ), ( @@ -210,6 +220,21 @@ group are mutually exclusive.", "the current configuration. You can put other options " "before this one to get them in the generated " "configuration.", + "hide_from_config_file": True, + }, + ), + ( + "generate-toml-config", + { + "action": _GenerateConfigFileAction, + "kwargs": {"Run": self}, + "callback": Run._not_implemented_callback, + "group": "Commands", + "help": "Generate a sample configuration file according to " + "the current configuration. You can put other options " + "before this one to get them in the generated " + "configuration. The config is in the .toml format.", + "hide_from_config_file": True, }, ), ( @@ -222,6 +247,7 @@ group are mutually exclusive.", "help": "In error mode, checkers without error messages are " "disabled and for others, only the ERROR messages are " "displayed, and no reports are done by default.", + "hide_from_config_file": True, }, ), ( @@ -233,6 +259,7 @@ group are mutually exclusive.", "short": "v", "help": "In verbose mode, extra non-checker-related info " "will be displayed.", + "hide_from_config_file": True, }, ), ( @@ -243,6 +270,7 @@ group are mutually exclusive.", "callback": Run._not_implemented_callback, "help": "Load and enable all available extensions. " "Use --list-extensions to see a list all available extensions.", + "hide_from_config_file": True, }, ), ( @@ -253,6 +281,7 @@ group are mutually exclusive.", "callback": Run._not_implemented_callback, "help": "Show more verbose help.", "group": "Commands", + "hide_from_config_file": True, }, ), ), @@ -51,6 +51,7 @@ install_requires = isort>=4.2.5,<6 mccabe>=0.6,<0.8 tomli>=1.1.0;python_version<"3.11" + tomlkit>=0.10.1 colorama;sys_platform=="win32" typing-extensions>=3.10.0;python_version<"3.10" python_requires = >=3.7.2 @@ -135,6 +136,9 @@ ignore_missing_imports = True [mypy-git.*] ignore_missing_imports = True +[mypy-tomlkit] +ignore_missing_imports = True + [mypy-sphinx.*] ignore_missing_imports = True diff --git a/tests/test_self.py b/tests/test_self.py index c8c943e10..24b564ede 100644 --- a/tests/test_self.py +++ b/tests/test_self.py @@ -15,7 +15,7 @@ import sys import textwrap import warnings from copy import copy -from io import StringIO +from io import BytesIO, StringIO from os.path import abspath, dirname, join from pathlib import Path from typing import TYPE_CHECKING, Any, Generator, Iterator, List, Optional, TextIO @@ -34,6 +34,12 @@ from pylint.reporters import JSONReporter from pylint.reporters.text import BaseReporter, ColorizedTextReporter, TextReporter from pylint.utils import utils +if sys.version_info >= (3, 11): + import tomllib +else: + import tomli as tomllib + + if TYPE_CHECKING: from pylint.reporters.ureports.nodes import Section @@ -1366,6 +1372,54 @@ class TestCallbackOptions: assert "suppressed-message" in messages @staticmethod + def test_generate_toml_config() -> None: + """Test the --generate-toml-config flag.""" + process = subprocess.run( + [ + sys.executable, + "-m", + "pylint", + "--preferred-modules=a:b", + "--generate-toml-config", + ], + capture_output=True, + encoding="utf-8", + check=False, + ) + assert "[tool.pylint.master]" in process.stdout + assert '"positional arguments"' not in process.stdout + assert 'preferred-modules = ["a:b"]' in process.stdout + + process_two = subprocess.run( + [ + sys.executable, + "-m", + "pylint", + "--preferred-modules=a:b", + "--generate-toml-config", + ], + capture_output=True, + encoding="utf-8", + check=False, + ) + assert process.stdout == process_two.stdout + + @staticmethod + def test_generate_toml_config_disable_symbolic_names() -> None: + """Test that --generate-toml-config puts symbolic names in the --disable option.""" + out = StringIO() + with _patch_streams(out): + with pytest.raises(SystemExit): + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + Run(["--generate-toml-config"]) + + bytes_out = BytesIO(out.getvalue().encode("utf-8")) + content = tomllib.load(bytes_out) + messages = content["tool"]["pylint"]["messages control"]["disable"] + assert "invalid-name" in messages, out.getvalue() + + @staticmethod def test_errors_only() -> None: """Test the --errors-only flag.""" with pytest.raises(SystemExit): |