summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Fiedler <miketheman@gmail.com>2021-10-26 03:07:55 -0400
committerGitHub <noreply@github.com>2021-10-26 09:07:55 +0200
commit448485a98b66a40fae6db6dbb4f25f4516e5e539 (patch)
tree108bef8231bac4745076cc062829c832274e0f98
parent17d69269580161456c12c1112b7129f4a5cbb807 (diff)
downloadpylint-git-448485a98b66a40fae6db6dbb4f25f4516e5e539.tar.gz
Add configuration option ``exclude-too-few-public-methods`` (#5191)
Allow excluding classes based on their ancestors from the ``too-few-public-methods`` checker. Closes #3370 Signed-off-by: Mike Fiedler <miketheman@gmail.com> Co-authored-by: Daniƫl van Noord <13665637+DanielNoord@users.noreply.github.com>
-rw-r--r--CONTRIBUTORS.txt2
-rw-r--r--ChangeLog5
-rw-r--r--doc/whatsnew/2.12.rst5
-rw-r--r--pylint/checkers/design_analysis.py22
-rw-r--r--pylint/utils/utils.py2
-rw-r--r--pylintrc3
-rw-r--r--tests/checkers/unittest_design.py18
-rw-r--r--tests/functional/t/too/too_few_public_methods_excluded.py14
-rw-r--r--tests/functional/t/too/too_few_public_methods_excluded.rc4
-rw-r--r--tests/functional/t/too/too_few_public_methods_excluded.txt1
10 files changed, 75 insertions, 1 deletions
diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt
index 881d53db6..28b2e332c 100644
--- a/CONTRIBUTORS.txt
+++ b/CONTRIBUTORS.txt
@@ -562,3 +562,5 @@ contributors:
* Youngsoo Sung: contributor
* Arianna Yang: contributor
+
+* Mike Fiedler (miketheman): contributor
diff --git a/ChangeLog b/ChangeLog
index 541e6543f..9f60f43d1 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -107,6 +107,11 @@ Release date: TBA
* Fix ``missing-function-docstring`` not being able to check ``__init__`` and other
magic methods even if the ``no-docstring-rgx`` setting was set to do so
+* Added configuration option ``exclude-too-few-public-methods`` to allow excluding
+ classes from the ``min-public-methods`` checker.
+
+ Closes #3370
+
What's New in Pylint 2.11.2?
============================
diff --git a/doc/whatsnew/2.12.rst b/doc/whatsnew/2.12.rst
index 0573ddc50..52b5ba595 100644
--- a/doc/whatsnew/2.12.rst
+++ b/doc/whatsnew/2.12.rst
@@ -43,6 +43,11 @@ New checkers
Closes #4774
+* Added configuration option ``exclude-too-few-public-methods`` to allow excluding
+ classes from the ``min-public-methods`` checker.
+
+ Closes #3370
+
Removed checkers
================
diff --git a/pylint/checkers/design_analysis.py b/pylint/checkers/design_analysis.py
index 3b5056a9e..8486aa89e 100644
--- a/pylint/checkers/design_analysis.py
+++ b/pylint/checkers/design_analysis.py
@@ -402,6 +402,16 @@ class MisdesignChecker(BaseChecker):
"statement (see R0916).",
},
),
+ (
+ "exclude-too-few-public-methods",
+ {
+ "default": [],
+ "type": "regexp_csv",
+ "metavar": "<pattern>[,<pattern>...]",
+ "help": "List of regular expressions of class ancestor names "
+ "to ignore when counting public methods (see R0903)",
+ },
+ ),
)
def __init__(self, linter=None):
@@ -416,6 +426,9 @@ class MisdesignChecker(BaseChecker):
self._returns = []
self._branches = defaultdict(int)
self._stmts = []
+ self._exclude_too_few_public_methods = utils.get_global_option(
+ self, "exclude-too-few-public-methods", default=[]
+ )
def _inc_all_stmts(self, amount):
for i, _ in enumerate(self._stmts):
@@ -472,6 +485,15 @@ class MisdesignChecker(BaseChecker):
args=(my_methods, self.config.max_public_methods),
)
+ # Stop here if the class is excluded via configuration.
+ if node.type == "class" and self._exclude_too_few_public_methods:
+ for ancestor in node.ancestors():
+ if any(
+ pattern.match(ancestor.qname())
+ for pattern in self._exclude_too_few_public_methods
+ ):
+ return
+
# Stop here for exception, metaclass, interface classes and other
# classes for which we don't need to count the methods.
if node.type != "class" or _is_exempt_from_public_methods(node):
diff --git a/pylint/utils/utils.py b/pylint/utils/utils.py
index 8243bc322..25b0ad3f1 100644
--- a/pylint/utils/utils.py
+++ b/pylint/utils/utils.py
@@ -59,7 +59,7 @@ GLOBAL_OPTION_PATTERN = Literal[
"ignored-argument-names",
"mixin-class-rgx",
]
-GLOBAL_OPTION_PATTERN_LIST = Literal["ignore-paths"]
+GLOBAL_OPTION_PATTERN_LIST = Literal["exclude-too-few-public-methods", "ignore-paths"]
GLOBAL_OPTION_TUPLE_INT = Literal["py-version"]
GLOBAL_OPTION_NAMES = Union[
GLOBAL_OPTION_BOOL,
diff --git a/pylintrc b/pylintrc
index 7669da6e8..8df15f713 100644
--- a/pylintrc
+++ b/pylintrc
@@ -350,6 +350,9 @@ min-public-methods=2
# Maximum number of public methods for a class (see R0904).
max-public-methods=25
+# List of regular expressions of class ancestor names to
+# ignore when counting public methods (see R0903).
+exclude-too-few-public-methods=
[CLASSES]
diff --git a/tests/checkers/unittest_design.py b/tests/checkers/unittest_design.py
index 5c6f38816..441793a2e 100644
--- a/tests/checkers/unittest_design.py
+++ b/tests/checkers/unittest_design.py
@@ -11,6 +11,7 @@ import astroid
from pylint.checkers import design_analysis
from pylint.testutils import CheckerTestCase, set_config
+from pylint.utils.utils import get_global_option
class TestDesignChecker(CheckerTestCase):
@@ -42,3 +43,20 @@ class TestDesignChecker(CheckerTestCase):
)
with self.assertNoMessages():
self.checker.visit_classdef(node)
+
+ @set_config(exclude_too_few_public_methods="toml.*")
+ def test_exclude_too_few_methods_with_value(self) -> None:
+ """Test exclude-too-few-public-methods option with value"""
+ options = get_global_option(self.checker, "exclude-too-few-public-methods")
+
+ assert any(i.match("toml") for i in options)
+ assert any(i.match("toml.*") for i in options)
+ assert any(i.match("toml.TomlEncoder") for i in options)
+
+ def test_ignore_paths_with_no_value(self) -> None:
+ """Test exclude-too-few-public-methods option with no value.
+ Compare against actual list to see if validator works."""
+ options = get_global_option(self.checker, "exclude-too-few-public-methods")
+
+ # pylint: disable-next=use-implicit-booleaness-not-comparison
+ assert options == []
diff --git a/tests/functional/t/too/too_few_public_methods_excluded.py b/tests/functional/t/too/too_few_public_methods_excluded.py
new file mode 100644
index 000000000..35ba873ee
--- /dev/null
+++ b/tests/functional/t/too/too_few_public_methods_excluded.py
@@ -0,0 +1,14 @@
+# pylint: disable=missing-docstring
+from json import JSONEncoder
+
+class Control: # [too-few-public-methods]
+ ...
+
+
+class MyJsonEncoder(JSONEncoder):
+ ...
+
+class InheritedInModule(Control):
+ """This class inherits from a class that doesn't have enough mehods,
+ and its parent is excluded via config, so it doesn't raise."""
+ ...
diff --git a/tests/functional/t/too/too_few_public_methods_excluded.rc b/tests/functional/t/too/too_few_public_methods_excluded.rc
new file mode 100644
index 000000000..00c025832
--- /dev/null
+++ b/tests/functional/t/too/too_few_public_methods_excluded.rc
@@ -0,0 +1,4 @@
+[testoptions]
+min-public-methods=10 # to combat inherited methods
+
+exclude-too-few-public-methods=json.*,^.*Control$
diff --git a/tests/functional/t/too/too_few_public_methods_excluded.txt b/tests/functional/t/too/too_few_public_methods_excluded.txt
new file mode 100644
index 000000000..c88cde25f
--- /dev/null
+++ b/tests/functional/t/too/too_few_public_methods_excluded.txt
@@ -0,0 +1 @@
+too-few-public-methods:4:0:Control:Too few public methods (0/10):HIGH