diff options
author | Daniƫl van Noord <13665637+DanielNoord@users.noreply.github.com> | 2022-04-01 20:34:43 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-04-01 20:34:43 +0200 |
commit | a96c50cba6026ddbdf5915b91fd785f721ca4e15 (patch) | |
tree | defa5c2448699fd6347bfc0e58ca1bfbfc2b93af | |
parent | e01fa86c00b2cd879c44570b383115a9407547f1 (diff) | |
download | pylint-git-a96c50cba6026ddbdf5915b91fd785f721ca4e15.tar.gz |
Refactor ``find_default_config_files`` (#6067)
Co-authored-by: Pierre Sassoulas <pierre.sassoulas@gmail.com>
-rw-r--r-- | pylint/config/find_default_config_files.py | 61 | ||||
-rw-r--r-- | pylint/testutils/__init__.py | 2 | ||||
-rw-r--r-- | pylint/testutils/utils.py | 44 | ||||
-rw-r--r-- | tests/config/test_find_default_config_files.py | 132 | ||||
-rw-r--r-- | tests/lint/unittest_lint.py | 41 |
5 files changed, 211 insertions, 69 deletions
diff --git a/pylint/config/find_default_config_files.py b/pylint/config/find_default_config_files.py index 70e867ad6..71ba222b3 100644 --- a/pylint/config/find_default_config_files.py +++ b/pylint/config/find_default_config_files.py @@ -14,6 +14,9 @@ if sys.version_info >= (3, 11): else: import tomli as tomllib +RC_NAMES = (Path("pylintrc"), Path(".pylintrc")) +CONFIG_NAMES = RC_NAMES + (Path("pyproject.toml"), Path("setup.cfg")) + def _toml_has_config(path: Union[Path, str]) -> bool: with open(path, mode="rb") as toml_handle: @@ -34,43 +37,41 @@ def _cfg_has_config(path: Union[Path, str]) -> bool: return any(section.startswith("pylint.") for section in parser.sections()) -def find_default_config_files() -> Iterator[str]: +def find_default_config_files() -> Iterator[Path]: """Find all possible config files.""" - rc_names = ("pylintrc", ".pylintrc") - config_names = rc_names + ("pyproject.toml", "setup.cfg") - for config_name in config_names: - if os.path.isfile(config_name): - if config_name.endswith(".toml") and not _toml_has_config(config_name): + for config_name in CONFIG_NAMES: + if config_name.is_file(): + if config_name.suffix == ".toml" and not _toml_has_config(config_name): continue - if config_name.endswith(".cfg") and not _cfg_has_config(config_name): + if config_name.suffix == ".cfg" and not _cfg_has_config(config_name): continue - yield os.path.abspath(config_name) + yield config_name.resolve() - if os.path.isfile("__init__.py"): - curdir = os.path.abspath(os.getcwd()) - while os.path.isfile(os.path.join(curdir, "__init__.py")): - curdir = os.path.abspath(os.path.join(curdir, "..")) - for rc_name in rc_names: - rc_path = os.path.join(curdir, rc_name) - if os.path.isfile(rc_path): - yield rc_path + if Path("__init__.py").is_file(): + curdir = Path(os.getcwd()).resolve() + while (curdir / "__init__.py").is_file(): + curdir = curdir.parent + for rc_name in RC_NAMES: + rc_path = curdir / rc_name + if rc_path.is_file(): + yield rc_path.resolve() - if "PYLINTRC" in os.environ and os.path.exists(os.environ["PYLINTRC"]): - if os.path.isfile(os.environ["PYLINTRC"]): - yield os.environ["PYLINTRC"] + if "PYLINTRC" in os.environ and Path(os.environ["PYLINTRC"]).exists(): + if Path(os.environ["PYLINTRC"]).is_file(): + yield Path(os.environ["PYLINTRC"]).resolve() else: - user_home = os.path.expanduser("~") - if user_home not in ("~", "/root"): - home_rc = os.path.join(user_home, ".pylintrc") - if os.path.isfile(home_rc): - yield home_rc - home_rc = os.path.join(user_home, ".config", "pylintrc") - if os.path.isfile(home_rc): - yield home_rc + user_home = Path.home() + if str(user_home) not in ("~", "/root"): + home_rc = user_home / ".pylintrc" + if home_rc.is_file(): + yield home_rc.resolve() + home_rc = user_home / ".config" / "pylintrc" + if home_rc.is_file(): + yield home_rc.resolve() if os.path.isfile("/etc/pylintrc"): - yield "/etc/pylintrc" + yield Path("/etc/pylintrc").resolve() def find_pylintrc() -> Optional[str]: @@ -84,6 +85,6 @@ def find_pylintrc() -> Optional[str]: DeprecationWarning, ) for config_file in find_default_config_files(): - if config_file.endswith("pylintrc"): - return config_file + if str(config_file).endswith("pylintrc"): + return str(config_file) return None diff --git a/pylint/testutils/__init__.py b/pylint/testutils/__init__.py index 0b55fc8e9..e9b8de1de 100644 --- a/pylint/testutils/__init__.py +++ b/pylint/testutils/__init__.py @@ -18,6 +18,7 @@ __all__ = [ "UPDATE_FILE", "UPDATE_OPTION", "UnittestLinter", + "create_files", ] from pylint.testutils.checker_test_case import CheckerTestCase @@ -31,3 +32,4 @@ from pylint.testutils.output_line import MessageTest from pylint.testutils.reporter_for_tests import GenericTestReporter, MinimalTestReporter from pylint.testutils.tokenize_str import _tokenize_str from pylint.testutils.unittest_linter import UnittestLinter +from pylint.testutils.utils import create_files diff --git a/pylint/testutils/utils.py b/pylint/testutils/utils.py new file mode 100644 index 000000000..a6a588194 --- /dev/null +++ b/pylint/testutils/utils.py @@ -0,0 +1,44 @@ +# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html +# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt + +import os +from typing import List + + +def create_files(paths: List[str], chroot: str = ".") -> None: + """Creates directories and files found in <path>. + + :param list paths: list of relative paths to files or directories + :param str chroot: the root directory in which paths will be created + + >>> from os.path import isdir, isfile + >>> isdir('/tmp/a') + False + >>> create_files(['a/b/foo.py', 'a/b/c/', 'a/b/c/d/e.py'], '/tmp') + >>> isdir('/tmp/a') + True + >>> isdir('/tmp/a/b/c') + True + >>> isfile('/tmp/a/b/c/d/e.py') + True + >>> isfile('/tmp/a/b/foo.py') + True + """ + dirs, files = set(), set() + for path in paths: + path = os.path.join(chroot, path) + filename = os.path.basename(path) + # path is a directory path + if filename == "": + dirs.add(path) + # path is a filename path + else: + dirs.add(os.path.dirname(path)) + files.add(path) + for dirpath in dirs: + if not os.path.isdir(dirpath): + os.makedirs(dirpath) + for filepath in files: + with open(filepath, "w", encoding="utf-8"): + pass diff --git a/tests/config/test_find_default_config_files.py b/tests/config/test_find_default_config_files.py index 04392781b..ca1461bad 100644 --- a/tests/config/test_find_default_config_files.py +++ b/tests/config/test_find_default_config_files.py @@ -1,13 +1,145 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE # Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt + +import contextlib +import importlib +import os +import shutil +import sys +import tempfile from pathlib import Path +from typing import Iterator import pytest +from pylint import config, testutils from pylint.config.find_default_config_files import _cfg_has_config, _toml_has_config +@pytest.fixture +def pop_pylintrc() -> None: + """Remove the PYLINTRC environment variable""" + os.environ.pop("PYLINTRC", None) + + +if os.name == "java": + if os.name == "nt": + HOME = "USERPROFILE" + else: + HOME = "HOME" +elif sys.platform == "win32": + HOME = "USERPROFILE" +else: + HOME = "HOME" + + +@contextlib.contextmanager +def fake_home() -> Iterator[None]: + """Fake a home directory.""" + folder = tempfile.mkdtemp("fake-home") + old_home = os.environ.get(HOME) + try: + os.environ[HOME] = folder + yield + finally: + os.environ.pop("PYLINTRC", "") + if old_home is None: + del os.environ[HOME] + else: + os.environ[HOME] = old_home + shutil.rmtree(folder, ignore_errors=True) + + +@contextlib.contextmanager +def tempdir() -> Iterator[str]: + """Create a temp directory and change the current location to it. + + This is supposed to be used with a *with* statement. + """ + tmp = tempfile.mkdtemp() + + # Get real path of tempfile, otherwise test fail on mac os x + current_dir = os.getcwd() + os.chdir(tmp) + abs_tmp = os.path.abspath(".") + + try: + yield abs_tmp + finally: + os.chdir(current_dir) + shutil.rmtree(abs_tmp) + + +@pytest.mark.usefixtures("pop_pylintrc") +def test_pylintrc() -> None: + """Test that the environment variable is checked for existence.""" + with fake_home(): + current_dir = os.getcwd() + os.chdir(os.path.dirname(os.path.abspath(sys.executable))) + try: + assert not list(config.find_default_config_files()) + os.environ["PYLINTRC"] = os.path.join(tempfile.gettempdir(), ".pylintrc") + assert not list(config.find_default_config_files()) + os.environ["PYLINTRC"] = "." + assert not list(config.find_default_config_files()) + finally: + os.chdir(current_dir) + importlib.reload(config) + + +@pytest.mark.usefixtures("pop_pylintrc") +def test_pylintrc_parentdir() -> None: + """Test that the first pylintrc we find is the first parent directory.""" + with tempdir() as chroot: + chroot_path = Path(chroot) + testutils.create_files( + [ + "a/pylintrc", + "a/b/__init__.py", + "a/b/pylintrc", + "a/b/c/__init__.py", + "a/b/c/d/__init__.py", + "a/b/c/d/e/.pylintrc", + ] + ) + + with fake_home(): + assert not list(config.find_default_config_files()) + + results = { + "a": chroot_path / "a" / "pylintrc", + "a/b": chroot_path / "a" / "b" / "pylintrc", + "a/b/c": chroot_path / "a" / "b" / "pylintrc", + "a/b/c/d": chroot_path / "a" / "b" / "pylintrc", + "a/b/c/d/e": chroot_path / "a" / "b" / "c" / "d" / "e" / ".pylintrc", + } + for basedir, expected in results.items(): + os.chdir(chroot_path / basedir) + assert next(config.find_default_config_files()) == expected + + +@pytest.mark.usefixtures("pop_pylintrc") +def test_pylintrc_parentdir_no_package() -> None: + """Test that we don't find a pylintrc in sub-packages.""" + with tempdir() as chroot: + with fake_home(): + chroot_path = Path(chroot) + testutils.create_files( + ["a/pylintrc", "a/b/pylintrc", "a/b/c/d/__init__.py"] + ) + assert config.find_pylintrc() is None + results = { + "a": chroot_path / "a" / "pylintrc", + "a/b": chroot_path / "a" / "b" / "pylintrc", + "a/b/c": None, + "a/b/c/d": None, + } + for basedir, expected in results.items(): + os.chdir(chroot_path / basedir) + assert next(config.find_default_config_files(), None) == expected + + @pytest.mark.parametrize( "content,expected", [ diff --git a/tests/lint/unittest_lint.py b/tests/lint/unittest_lint.py index 90faa1c2f..d973cf892 100644 --- a/tests/lint/unittest_lint.py +++ b/tests/lint/unittest_lint.py @@ -12,7 +12,7 @@ from contextlib import contextmanager from importlib import reload from io import StringIO from os import chdir, getcwd -from os.path import abspath, basename, dirname, isdir, join, sep +from os.path import abspath, dirname, join, sep from shutil import rmtree from typing import Iterable, Iterator, List, Optional, Tuple @@ -32,6 +32,7 @@ from pylint.exceptions import InvalidMessageError from pylint.lint import ArgumentPreprocessingError, PyLinter, Run, preprocess_options from pylint.message import Message from pylint.reporters import text +from pylint.testutils import create_files from pylint.typing import MessageLocationTuple from pylint.utils import FileState, print_full_documentation, tokenize_module @@ -95,44 +96,6 @@ def tempdir() -> Iterator[str]: rmtree(abs_tmp) -def create_files(paths: List[str], chroot: str = ".") -> None: - """Creates directories and files found in <path>. - - :param list paths: list of relative paths to files or directories - :param str chroot: the root directory in which paths will be created - - >>> from os.path import isdir, isfile - >>> isdir('/tmp/a') - False - >>> create_files(['a/b/foo.py', 'a/b/c/', 'a/b/c/d/e.py'], '/tmp') - >>> isdir('/tmp/a') - True - >>> isdir('/tmp/a/b/c') - True - >>> isfile('/tmp/a/b/c/d/e.py') - True - >>> isfile('/tmp/a/b/foo.py') - True - """ - dirs, files = set(), set() - for path in paths: - path = join(chroot, path) - filename = basename(path) - # path is a directory path - if filename == "": - dirs.add(path) - # path is a filename path - else: - dirs.add(dirname(path)) - files.add(path) - for dirpath in dirs: - if not isdir(dirpath): - os.makedirs(dirpath) - for filepath in files: - with open(filepath, "w", encoding="utf-8"): - pass - - @pytest.fixture def fake_path() -> Iterator[Iterable[str]]: orig = list(sys.path) |