summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAshley Whetter <ashley@awhetter.co.uk>2019-10-06 22:37:34 -0700
committerAshley Whetter <AWhetter@users.noreply.github.com>2019-10-09 20:39:13 -0700
commit3fc8c98d0fa64723a6671424bb6f0ccd30cef11a (patch)
treebb452b52687c5d8b80381a3a68d7c5c7e638fc7b
parent1e05190ef09f6bdb3d4ce8573e2548853df30cdf (diff)
downloadpylint-git-3fc8c98d0fa64723a6671424bb6f0ccd30cef11a.tar.gz
Can read setup.cfg and pyproject.toml files
Closes #617
-rw-r--r--ChangeLog5
-rw-r--r--doc/user_guide/run.rst4
-rw-r--r--doc/whatsnew/2.5.rst27
-rw-r--r--doc/whatsnew/index.rst1
-rw-r--r--pylint/__pkginfo__.py7
-rw-r--r--pylint/config.py112
-rw-r--r--pylint/lint.py4
-rw-r--r--tests/test_config.py40
8 files changed, 166 insertions, 34 deletions
diff --git a/ChangeLog b/ChangeLog
index 409dfec8f..a22e2ce6f 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -7,6 +7,10 @@ What's New in Pylint 2.5.0?
Release date: TBA
+* Can read config from a setup.cfg or pyproject.toml file.
+
+ Close #617
+
What's New in Pylint 2.4.3?
===========================
@@ -22,6 +26,7 @@ Release date: TBA
Close #3175
+
What's New in Pylint 2.4.2?
===========================
diff --git a/doc/user_guide/run.rst b/doc/user_guide/run.rst
index 338a07064..d8268c36d 100644
--- a/doc/user_guide/run.rst
+++ b/doc/user_guide/run.rst
@@ -89,6 +89,10 @@ configuration file in the following order and uses the first one it finds:
#. ``pylintrc`` in the current working directory
#. ``.pylintrc`` in the current working directory
+#. ``pyproject.toml`` in the current working directory,
+ providing it has at least one ``tool.pylint.`` section.
+#. ``setup.cfg`` in the current working directory,
+ providing it has at least one ``pylint.`` section
#. If the current working directory is in a Python module, Pylint searches \
up the hierarchy of Python modules until it finds a ``pylintrc`` file. \
This allows you to specify coding standards on a module-by-module \
diff --git a/doc/whatsnew/2.5.rst b/doc/whatsnew/2.5.rst
new file mode 100644
index 000000000..a1d97fb6c
--- /dev/null
+++ b/doc/whatsnew/2.5.rst
@@ -0,0 +1,27 @@
+**************************
+ What's New in Pylint 2.5
+**************************
+
+:Release: 2.5
+:Date: TBC
+
+
+Summary -- Release highlights
+=============================
+
+
+New checkers
+============
+
+
+Other Changes
+=============
+
+* Configuration can be read from a setup.cfg or pyproject.toml file
+ in the current directory.
+ A setup.cfg must prepend pylintrc section names with ``pylint.``,
+ for example ``[pylint.MESSAGES CONTROL]``.
+ A pyproject.toml file must prepend section names with ``tool.pylint.``,
+ for example ``[tool.pylint.'MESSAGES CONTROL']``.
+ These files can also be passed in on the command line.
+
diff --git a/doc/whatsnew/index.rst b/doc/whatsnew/index.rst
index e7faa7cc7..542b9743b 100644
--- a/doc/whatsnew/index.rst
+++ b/doc/whatsnew/index.rst
@@ -9,6 +9,7 @@ High level descriptions of the most important changes between major Pylint versi
.. toctree::
:maxdepth: 1
+ 2.5.rst
2.4.rst
2.3.rst
2.2.rst
diff --git a/pylint/__pkginfo__.py b/pylint/__pkginfo__.py
index bfca25d80..cc3c14a1a 100644
--- a/pylint/__pkginfo__.py
+++ b/pylint/__pkginfo__.py
@@ -29,7 +29,12 @@ version = ".".join(str(num) for num in numversion)
if dev_version is not None:
version += "-dev" + str(dev_version)
-install_requires = ["astroid>=2.3.0,<2.4", "isort>=4.2.5,<5", "mccabe>=0.6,<0.7"]
+install_requires = [
+ "astroid>=2.3.0,<2.4",
+ "isort>=4.2.5,<5",
+ "mccabe>=0.6,<0.7",
+ "toml>=0.7.1",
+]
dependency_links = [] # type: ignore
diff --git a/pylint/config.py b/pylint/config.py
index 0925575b4..219bc6b78 100644
--- a/pylint/config.py
+++ b/pylint/config.py
@@ -46,6 +46,8 @@ import sys
import time
from typing import Any, Dict, Tuple
+import toml
+
from pylint import utils
USER_HOME = os.path.expanduser("~")
@@ -87,38 +89,70 @@ def save_results(results, base):
print("Unable to create file %s: %s" % (data_file, ex), file=sys.stderr)
-def find_pylintrc():
- """search the pylint rc file and return its path if it find it, else None
- """
- # is there a pylint rc file in the current directory ?
- if os.path.exists("pylintrc"):
- return os.path.abspath("pylintrc")
- if os.path.exists(".pylintrc"):
- return os.path.abspath(".pylintrc")
+def _toml_has_config(path):
+ with open(path, "r") as toml_handle:
+ content = toml.load(toml_handle)
+ try:
+ content["tool"]["pylint"]
+ except KeyError:
+ return False
+
+ return True
+
+
+def _cfg_has_config(path):
+ parser = configparser.ConfigParser()
+ parser.read(path)
+ return any(section.startswith("pylint.") for section in parser.sections())
+
+
+def find_default_config_files():
+ """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):
+ continue
+ if config_name.endswith(".cfg") and not _cfg_has_config(config_name):
+ continue
+
+ yield os.path.abspath(config_name)
+
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, ".."))
- if os.path.isfile(os.path.join(curdir, "pylintrc")):
- return os.path.join(curdir, "pylintrc")
- if os.path.isfile(os.path.join(curdir, ".pylintrc")):
- return os.path.join(curdir, ".pylintrc")
+ for rc_name in rc_names:
+ rc_path = os.path.join(curdir, rc_name)
+ if os.path.isfile(rc_path):
+ yield rc_path
+
if "PYLINTRC" in os.environ and os.path.exists(os.environ["PYLINTRC"]):
- pylintrc = os.environ["PYLINTRC"]
+ if os.path.isfile(os.environ["PYLINTRC"]):
+ yield os.environ["PYLINTRC"]
else:
user_home = os.path.expanduser("~")
- if user_home in ("~", "/root"):
- pylintrc = ".pylintrc"
- else:
- pylintrc = os.path.join(user_home, ".pylintrc")
- if not os.path.isfile(pylintrc):
- pylintrc = os.path.join(user_home, ".config", "pylintrc")
- if not os.path.isfile(pylintrc):
- if os.path.isfile("/etc/pylintrc"):
- pylintrc = "/etc/pylintrc"
- else:
- pylintrc = None
- return pylintrc
+ 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
+
+ if os.path.isfile("/etc/pylintrc"):
+ yield "/etc/pylintrc"
+
+
+def find_pylintrc():
+ """search the pylint rc file and return its path if it find it, else None
+ """
+ for config_file in find_default_config_files():
+ if config_file.endswith("pylintrc"):
+ return config_file
+
+ return None
PYLINTRC = find_pylintrc()
@@ -707,14 +741,28 @@ class OptionsManagerMixIn:
if use_config_file:
parser = self.cfgfile_parser
- # Use this encoding in order to strip the BOM marker, if any.
- with io.open(config_file, "r", encoding="utf_8_sig") as fp:
- parser.read_file(fp)
+ if config_file.endswith(".toml"):
+ with open(config_file, "r") as fp:
+ content = toml.load(fp)
- # normalize sections'title
- for sect, values in list(parser._sections.items()):
- if not sect.isupper() and values:
- parser._sections[sect.upper()] = values
+ try:
+ sections_values = content["tool"]["pylint"]
+ except KeyError:
+ pass
+ else:
+ for section, values in sections_values.items():
+ parser._sections[section.upper()] = values
+ else:
+ # Use this encoding in order to strip the BOM marker, if any.
+ with io.open(config_file, "r", encoding="utf_8_sig") as fp:
+ parser.read_file(fp)
+
+ # normalize sections'title
+ for sect, values in list(parser._sections.items()):
+ if sect.startswith("pylint."):
+ sect = sect[len("pylint.") :]
+ if not sect.isupper() and values:
+ parser._sections[sect.upper()] = values
if not verbose:
return
diff --git a/pylint/lint.py b/pylint/lint.py
index a98970b78..095eb0e2b 100644
--- a/pylint/lint.py
+++ b/pylint/lint.py
@@ -620,7 +620,9 @@ class PyLinter(
MessagesHandlerMixIn.__init__(self)
reporters.ReportsHandlerMixIn.__init__(self)
super(PyLinter, self).__init__(
- usage=__doc__, version=full_version, config_file=pylintrc or config.PYLINTRC
+ usage=__doc__,
+ version=full_version,
+ config_file=pylintrc or next(config.find_default_config_files(), None),
)
checkers.BaseTokenChecker.__init__(self)
# provided reports
diff --git a/tests/test_config.py b/tests/test_config.py
new file mode 100644
index 000000000..4d79a6a7f
--- /dev/null
+++ b/tests/test_config.py
@@ -0,0 +1,40 @@
+import unittest.mock
+
+import pylint.lint
+import pytest
+
+
+def test_can_read_toml(tmp_path):
+ config_file = tmp_path / "pyproject.toml"
+ config_file.write_text(
+ "[tool.pylint.'messages control']\n"
+ "disable='all'\n"
+ "enable='missing-module-docstring'\n"
+ "jobs=10\n"
+ )
+
+ linter = pylint.lint.PyLinter()
+ linter.global_set_option = unittest.mock.MagicMock()
+ linter.read_config_file(str(config_file))
+
+ assert linter.global_set_option.called_with("disable", "all")
+ assert linter.global_set_option.called_with("enable", "missing-module-docstring")
+ assert linter.global_set_option.called_with("jobs", 10)
+
+
+def test_can_read_setup_cfg(tmp_path):
+ config_file = tmp_path / "setup.cfg"
+ config_file.write_text(
+ "[pylint.messages control]\n"
+ "disable=all\n"
+ "enable=missing-module-docstring\n"
+ "jobs=10\n"
+ )
+
+ linter = pylint.lint.PyLinter()
+ linter.global_set_option = unittest.mock.MagicMock()
+ linter.read_config_file(str(config_file))
+
+ assert linter.global_set_option.called_with("disable", "all")
+ assert linter.global_set_option.called_with("enable", "missing-module-docstring")
+ assert linter.global_set_option.called_with("jobs", 10)