diff --git a/doc/whatsnew/fragments/5701.other b/doc/whatsnew/fragments/5701.other new file mode 100644 index 0000000000..42cd50fbd8 --- /dev/null +++ b/doc/whatsnew/fragments/5701.other @@ -0,0 +1,7 @@ +You can now set the ``files`` option in configuration files and on the command line. +Passing files without the ``--files`` flag is still supported. This allows to set +``files`` to ``files = my_source_directory`` and invoking ``pylint`` with only +the ``pylint`` command similar to how other CLI tools allow to do so. +The help message can always be invoked with ``pylint -h`` or ``pylint --help``. + +Closes #5701 diff --git a/pylint/config/config_initialization.py b/pylint/config/config_initialization.py index d26f0e8c58..22f3a15c4b 100644 --- a/pylint/config/config_initialization.py +++ b/pylint/config/config_initialization.py @@ -23,7 +23,7 @@ def _config_initialization( reporter: reporters.BaseReporter | reporters.MultiReporter | None = None, config_file: None | str | Path = None, verbose_mode: bool = False, -) -> list[str]: +) -> None: """Parse all available options, read config files and command line arguments and set options accordingly. """ @@ -119,5 +119,7 @@ def _config_initialization( linter._directory_namespaces[Path(".").resolve()] = (linter.config, {}) # parsed_args_list should now only be a list of files/directories to lint. - # All other options have been removed from the list. - return parsed_args_list + # All other options have been removed from the list. If there is anything + # left we overwrite any 'files' as given by the configuration file. + if parsed_args_list: + linter.config.files = parsed_args_list diff --git a/pylint/lint/base_options.py b/pylint/lint/base_options.py index 1c37eac2fb..533416e55a 100644 --- a/pylint/lint/base_options.py +++ b/pylint/lint/base_options.py @@ -401,6 +401,18 @@ def _make_linter_options(linter: PyLinter) -> Options: "Useful if running pylint in a server-like mode.", }, ), + ( + "files", + { + "type": "csv", + "default": [], + "help": "The files to lint. The flag can also be omitted as pylint will " + "try to lint any file passed as argument. This can be used to set files " + "to a directory in a configuration file and invoke pylint by only typing " + "pylint on the command line. Any file passed as argument will overwrite any " + "file set in the configuration file.", + }, + ), ) diff --git a/pylint/lint/run.py b/pylint/lint/run.py index 1f54670ef7..32cda4165e 100644 --- a/pylint/lint/run.py +++ b/pylint/lint/run.py @@ -163,7 +163,7 @@ def __init__( if self._is_pylint_config: _register_generate_config_options(linter._arg_parser) - args = _config_initialization( + _config_initialization( linter, args, reporter, config_file=self._rcfile, verbose_mode=self.verbose ) @@ -179,7 +179,7 @@ def __init__( return # Display help messages if there are no files to lint - if not args: + if not linter.config.files: print(linter.help()) sys.exit(32) @@ -203,13 +203,13 @@ def __init__( try: with open(self._output, "w", encoding="utf-8") as output: linter.reporter.out = output - linter.check(args) + linter.check(linter.config.files) score_value = linter.generate_reports() except OSError as ex: print(ex, file=sys.stderr) sys.exit(32) else: - linter.check(args) + linter.check(linter.config.files) score_value = linter.generate_reports() if do_exit is not UNUSED_PARAM_SENTINEL: diff --git a/tests/config/unittest_config.py b/tests/config/unittest_config.py index 3436636022..279f9b2a6d 100644 --- a/tests/config/unittest_config.py +++ b/tests/config/unittest_config.py @@ -7,12 +7,16 @@ from __future__ import annotations import re +from pathlib import Path import pytest from pylint import config from pylint.checkers import BaseChecker +from pylint.lint import Run as _Run from pylint.testutils import CheckerTestCase, set_config +from pylint.testutils._run import _Run as Run +from pylint.testutils.utils import _test_cwd from pylint.typing import MessageDefinitionTuple @@ -86,3 +90,43 @@ def test_ignore_paths_with_no_value(self) -> None: options = self.linter.config.ignore_paths assert options == [] + + +def test_files_can_be_set_in_config(tmp_path: Path) -> None: + """Test that the files option can be set in a config file.""" + with _test_cwd(tmp_path): + bad_file = tmp_path / "bad.py" + bad_file.write_text("1") + good_file = tmp_path / "good.py" + good_file.write_text("'''My module docstring'''\n") + init_file = tmp_path / "__init__.py" + init_file.write_text("") + + # Test that we run on files set in the config file + config_file = tmp_path / "pylintrc" + config_file.write_text("[MASTER]\nfiles=good.py,bad.py") + runner = Run(["--rcfile", str(config_file)], exit=False) + assert runner.linter.stats.by_msg + # Test that we can overrun the configuration file with a command line argument + runner = Run(["good.py"], exit=False) + assert not runner.linter.stats.by_msg + # Or by supplying --files directly + runner = Run(["--files", "good.py"], exit=False) + assert not runner.linter.stats.by_msg + + # Test that we can run on the current directory by specifying it + config_file = tmp_path / "pylintrc" + config_file.write_text("[MASTER]\nfiles=" + str(tmp_path)) + runner = Run(["--rcfile", str(config_file)], exit=False) + assert runner.linter.stats.by_msg + # Test that we can also use just the command 'pylint'. Using _Run + # makes sure that the --rcfile option doesn't get patched. + other_runner = _Run([], exit=False) + assert other_runner.linter.stats.by_msg + + # Test that we can also run on a directory set as files even if it is + # not our current cwd + config_file = tmp_path / "pylintrc" + config_file.write_text("[MASTER]\nfiles=" + str(tmp_path)) + runner = Run(["--rcfile", str(config_file)], exit=False) + assert runner.linter.stats.by_msg