summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMikhail f. Shiryaev <mr.felixoid@gmail.com>2023-04-30 22:48:34 +0200
committerGitHub <noreply@github.com>2023-04-30 22:48:34 +0200
commitf8288842e58a8b6007ff15ab380c84b00eb9b7a3 (patch)
tree727cd5397b5fa88d9fa3f0d7f55af36280911bc9
parentc2ac6f1e905b263adb32102c87150a0c607e4db0 (diff)
downloadpylint-git-f8288842e58a8b6007ff15ab380c84b00eb9b7a3.tar.gz
Search for pyproject.toml config file in parent dirs (#7163)
Co-authored-by: Pierre Sassoulas <pierre.sassoulas@gmail.com> Co-authored-by: Daniƫl van Noord <13665637+DanielNoord@users.noreply.github.com>
-rw-r--r--.pyenchant_pylint_custom_dict.txt3
-rw-r--r--doc/user_guide/usage/run.rst3
-rw-r--r--doc/whatsnew/fragments/7163.other3
-rw-r--r--pylint/config/find_default_config_files.py30
-rw-r--r--tests/config/test_find_default_config_files.py41
5 files changed, 79 insertions, 1 deletions
diff --git a/.pyenchant_pylint_custom_dict.txt b/.pyenchant_pylint_custom_dict.txt
index 507babde2..6cd91e1ba 100644
--- a/.pyenchant_pylint_custom_dict.txt
+++ b/.pyenchant_pylint_custom_dict.txt
@@ -261,6 +261,7 @@ pylint
pylintdict
pylintrc
pylint's
+pyproject
pypy
pyreverse
pytest
@@ -318,6 +319,8 @@ subtree
supcls
superclass
symilar
+symlink
+symlinks
sys
tbump
tempfile
diff --git a/doc/user_guide/usage/run.rst b/doc/user_guide/usage/run.rst
index b9dfedc88..7e6e1a830 100644
--- a/doc/user_guide/usage/run.rst
+++ b/doc/user_guide/usage/run.rst
@@ -105,6 +105,9 @@ configuration file in the following order and uses the first one it finds:
providing it has at least one ``pylint.`` section
#. ``tox.ini`` in the current working directory,
providing it has at least one ``pylint.`` section
+#. Pylint will search for the ``pyproject.toml`` file up the directories hierarchy
+ unless it's found, or a ``.git``/``.hg`` directory is found, or the file system root
+ is approached.
#. If the current working directory is in a Python package, Pylint searches \
up the hierarchy of Python packages until it finds a ``pylintrc`` file. \
This allows you to specify coding standards on a module-by-module \
diff --git a/doc/whatsnew/fragments/7163.other b/doc/whatsnew/fragments/7163.other
new file mode 100644
index 000000000..93f731aae
--- /dev/null
+++ b/doc/whatsnew/fragments/7163.other
@@ -0,0 +1,3 @@
+Search for ``pyproject.toml`` recursively in parent directories up to a project or file system root.
+
+Refs #7163, Closes #3289
diff --git a/pylint/config/find_default_config_files.py b/pylint/config/find_default_config_files.py
index a121b32a3..3b03f6357 100644
--- a/pylint/config/find_default_config_files.py
+++ b/pylint/config/find_default_config_files.py
@@ -16,7 +16,28 @@ else:
import tomli as tomllib
RC_NAMES = (Path("pylintrc"), Path(".pylintrc"))
-CONFIG_NAMES = (*RC_NAMES, Path("pyproject.toml"), Path("setup.cfg"))
+PYPROJECT_NAME = Path("pyproject.toml")
+CONFIG_NAMES = (*RC_NAMES, PYPROJECT_NAME, Path("setup.cfg"))
+
+
+def _find_pyproject() -> Path:
+ """Search for file pyproject.toml in the parent directories recursively.
+
+ It resolves symlinks, so if there is any symlink up in the tree, it does not respect them
+ """
+ current_dir = Path.cwd().resolve()
+ is_root = False
+ while not is_root:
+ if (current_dir / PYPROJECT_NAME).is_file():
+ return current_dir / PYPROJECT_NAME
+ is_root = (
+ current_dir == current_dir.parent
+ or (current_dir / ".git").is_dir()
+ or (current_dir / ".hg").is_dir()
+ )
+ current_dir = current_dir.parent
+
+ return current_dir
def _toml_has_config(path: Path | str) -> bool:
@@ -100,6 +121,13 @@ def find_default_config_files() -> Iterator[Path]:
pass
try:
+ parent_pyproject = _find_pyproject()
+ if parent_pyproject.is_file() and _toml_has_config(parent_pyproject):
+ yield parent_pyproject.resolve()
+ except OSError:
+ pass
+
+ try:
yield from _find_config_in_home_or_environment()
except OSError:
pass
diff --git a/tests/config/test_find_default_config_files.py b/tests/config/test_find_default_config_files.py
index f78f640b2..0b513a3d5 100644
--- a/tests/config/test_find_default_config_files.py
+++ b/tests/config/test_find_default_config_files.py
@@ -130,6 +130,47 @@ def test_pylintrc_parentdir() -> None:
@pytest.mark.usefixtures("pop_pylintrc")
+def test_pyproject_toml_parentdir() -> None:
+ """Test the search of pyproject.toml file in parent directories"""
+ with tempdir() as chroot:
+ with fake_home():
+ chroot_path = Path(chroot)
+ files = [
+ "pyproject.toml",
+ "git/pyproject.toml",
+ "git/a/pyproject.toml",
+ "git/a/.git",
+ "git/a/b/c/__init__.py",
+ "hg/pyproject.toml",
+ "hg/a/pyproject.toml",
+ "hg/a/.hg",
+ "hg/a/b/c/__init__.py",
+ "none/sub/__init__.py",
+ ]
+ testutils.create_files(files)
+ for config_file in files:
+ if config_file.endswith("pyproject.toml"):
+ with open(config_file, "w", encoding="utf-8") as fd:
+ fd.write('[tool.pylint."messages control"]\n')
+ results = {
+ "": chroot_path / "pyproject.toml",
+ "git": chroot_path / "git" / "pyproject.toml",
+ "git/a": chroot_path / "git" / "a" / "pyproject.toml",
+ "git/a/b": chroot_path / "git" / "a" / "pyproject.toml",
+ "git/a/b/c": chroot_path / "git" / "a" / "pyproject.toml",
+ "hg": chroot_path / "hg" / "pyproject.toml",
+ "hg/a": chroot_path / "hg" / "a" / "pyproject.toml",
+ "hg/a/b": chroot_path / "hg" / "a" / "pyproject.toml",
+ "hg/a/b/c": chroot_path / "hg" / "a" / "pyproject.toml",
+ "none": chroot_path / "pyproject.toml",
+ "none/sub": chroot_path / "pyproject.toml",
+ }
+ for basedir, expected in results.items():
+ os.chdir(chroot_path / basedir)
+ assert next(config.find_default_config_files(), None) == 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: