diff options
author | Rebecca Turner <rbt@sent.as> | 2021-07-28 15:45:19 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-07-28 21:45:19 +0200 |
commit | 24d03e9410520849494db1bde84334769217b3d4 (patch) | |
tree | 7b9dc434a13cf864044bb7716971136a07c05ab8 /pylint/checkers/design_analysis.py | |
parent | 67f40566c11168616079ece444fc558a81d336d2 (diff) | |
download | pylint-git-24d03e9410520849494db1bde84334769217b3d4.tar.gz |
Add ignored-parents option to design checker (#4758)
* Add ignored-parents option to design checker
This allows users to specify classes to ignore while counting parent
classes.
Partially closes #3057
Co-authored-by: Pierre Sassoulas <pierre.sassoulas@gmail.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Diffstat (limited to 'pylint/checkers/design_analysis.py')
-rw-r--r-- | pylint/checkers/design_analysis.py | 46 |
1 files changed, 42 insertions, 4 deletions
diff --git a/pylint/checkers/design_analysis.py b/pylint/checkers/design_analysis.py index a02969410..26cd77cc5 100644 --- a/pylint/checkers/design_analysis.py +++ b/pylint/checkers/design_analysis.py @@ -25,6 +25,7 @@ import re from collections import defaultdict +from typing import FrozenSet, List, Set, cast import astroid from astroid import nodes @@ -237,6 +238,35 @@ def _count_methods_in_class(node): return all_methods +def _get_parents( + node: nodes.ClassDef, ignored_parents: FrozenSet[str] +) -> Set[nodes.ClassDef]: + r"""Get parents of ``node``, excluding ancestors of ``ignored_parents``. + + If we have the following inheritance diagram: + + F + / + D E + \/ + B C + \/ + A # class A(B, C): ... + + And ``ignored_parents`` is ``{"E"}``, then this function will return + ``{A, B, C, D}`` -- both ``E`` and its ancestors are excluded. + """ + parents: Set[nodes.ClassDef] = set() + to_explore = cast(List[nodes.ClassDef], list(node.ancestors(recurs=False))) + while to_explore: + parent = to_explore.pop() + if parent.qname() in ignored_parents: + continue + parents.add(parent) + to_explore.extend(parent.ancestors(recurs=False)) # type: ignore + return parents + + class MisdesignChecker(BaseChecker): """checks for sign of poor/misdesign: * number of methods, attributes, local variables... @@ -308,6 +338,15 @@ class MisdesignChecker(BaseChecker): }, ), ( + "ignored-parents", + { + "default": (), + "type": "csv", + "metavar": "<comma separated list of class names>", + "help": "List of qualified class names to ignore when countint class parents (see R0901)", + }, + ), + ( "max-attributes", { "default": 7, @@ -379,11 +418,10 @@ class MisdesignChecker(BaseChecker): ) def visit_classdef(self, node: nodes.ClassDef): """check size of inheritance hierarchy and number of instance attributes""" - nb_parents = sum( - 1 - for ancestor in node.ancestors() - if ancestor.qname() not in STDLIB_CLASSES_IGNORE_ANCESTOR + parents = _get_parents( + node, STDLIB_CLASSES_IGNORE_ANCESTOR.union(self.config.ignored_parents) ) + nb_parents = len(parents) if nb_parents > self.config.max_parents: self.add_message( "too-many-ancestors", |