summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniƫl van Noord <13665637+DanielNoord@users.noreply.github.com>2021-10-25 09:27:47 +0200
committerGitHub <noreply@github.com>2021-10-25 09:27:47 +0200
commit8cfce138e9519f63e969e89359e86e140b2f0f13 (patch)
treeeff12c3d285d53b7a6e4cf387f265ca029c72b51
parented3449fee063d91f050c6b733030d3b3d7ad719f (diff)
downloadpylint-git-8cfce138e9519f63e969e89359e86e140b2f0f13.tar.gz
Add ``mixin-class-rgx`` option (#5203)
Co-authored-by: Alpha <alpha@pokesplash.net> Co-authored-by: Pierre Sassoulas <pierre.sassoulas@gmail.com>
-rw-r--r--CONTRIBUTORS.txt2
-rw-r--r--ChangeLog2
-rw-r--r--doc/faq.rst5
-rw-r--r--doc/whatsnew/2.12.rst2
-rw-r--r--pylint/checkers/async.py10
-rw-r--r--pylint/checkers/classes.py5
-rw-r--r--pylint/checkers/typecheck.py29
-rw-r--r--pylint/utils/utils.py5
-rw-r--r--pylintrc7
-rw-r--r--tests/functional/m/mixin_class_rgx.py60
-rw-r--r--tests/functional/m/mixin_class_rgx.rc3
-rw-r--r--tests/functional/m/mixin_class_rgx.txt3
12 files changed, 118 insertions, 15 deletions
diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt
index 545691405..881d53db6 100644
--- a/CONTRIBUTORS.txt
+++ b/CONTRIBUTORS.txt
@@ -390,6 +390,8 @@ contributors:
* Ram Rachum (cool-RR)
+* D. Alphus (Alphadelta14): contributor
+
* Pieter Engelbrecht
* Ethan Leba: contributor
diff --git a/ChangeLog b/ChangeLog
index 12365068a..732a62f35 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -74,6 +74,8 @@ Release date: TBA
Closes #4981
+* Support configuring mixin class pattern via ``mixin-class-rgx``
+
* Added new checker ``use-implicit-booleaness-not-comparison``: Emitted when
collection literal comparison is being used to check for emptiness.
diff --git a/doc/faq.rst b/doc/faq.rst
index 479d3c052..d0a02cf3a 100644
--- a/doc/faq.rst
+++ b/doc/faq.rst
@@ -238,8 +238,9 @@ methods is doing nothing but raising NotImplementedError.
-------------------------------------------------------------------------------
To do so you have to set the ignore-mixin-members option to
-"yes" (this is the default value) and to name your mixin class with
-a name which ends with "mixin" (whatever case).
+"yes" (this is the default value) and name your mixin class with
+a name which ends with "Mixin" or "mixin" (default) or change the
+default value by changing the mixin-class-rgx option.
6. Troubleshooting
diff --git a/doc/whatsnew/2.12.rst b/doc/whatsnew/2.12.rst
index 8b5b5f0c2..af5b33a43 100644
--- a/doc/whatsnew/2.12.rst
+++ b/doc/whatsnew/2.12.rst
@@ -90,6 +90,8 @@ Other Changes
Closes #4426
+* Support configuring mixin class pattern via ``mixin-class-rgx``
+
* Normalize the input to the ``ignore-paths`` option to allow both Posix and
Windows paths
diff --git a/pylint/checkers/async.py b/pylint/checkers/async.py
index f9d1e15dd..9aaead7c4 100644
--- a/pylint/checkers/async.py
+++ b/pylint/checkers/async.py
@@ -44,6 +44,7 @@ class AsyncChecker(checkers.BaseChecker):
self._ignore_mixin_members = utils.get_global_option(
self, "ignore-mixin-members"
)
+ self._mixin_class_rgx = utils.get_global_option(self, "mixin-class-rgx")
self._async_generators = ["contextlib.asynccontextmanager"]
@checker_utils.check_messages("yield-inside-async-function")
@@ -81,10 +82,11 @@ class AsyncChecker(checkers.BaseChecker):
# just skip it.
if not checker_utils.has_known_bases(inferred):
continue
- # Just ignore mixin classes.
- if self._ignore_mixin_members:
- if inferred.name[-5:].lower() == "mixin":
- continue
+ # Ignore mixin classes if they match the rgx option.
+ if self._ignore_mixin_members and self._mixin_class_rgx.match(
+ inferred.name
+ ):
+ continue
else:
continue
self.add_message(
diff --git a/pylint/checkers/classes.py b/pylint/checkers/classes.py
index 0b15861b0..2ab66fd0b 100644
--- a/pylint/checkers/classes.py
+++ b/pylint/checkers/classes.py
@@ -808,6 +808,9 @@ a metaclass class method.",
self._first_attrs = []
self._meth_could_be_func = None
+ def open(self) -> None:
+ self._mixin_class_rgx = get_global_option(self, "mixin-class-rgx")
+
@astroid.decorators.cachedproperty
def _dummy_rgx(self):
return get_global_option(self, "dummy-variables-rgx", default=None)
@@ -1029,7 +1032,7 @@ a metaclass class method.",
def _check_attribute_defined_outside_init(self, cnode: nodes.ClassDef) -> None:
# check access to existent members on non metaclass classes
- if self._ignore_mixin and cnode.name[-5:].lower() == "mixin":
+ if self._ignore_mixin and self._mixin_class_rgx.match(cnode.name):
# We are in a mixin class. No need to try to figure out if
# something is missing, since it is most likely that it will
# miss.
diff --git a/pylint/checkers/typecheck.py b/pylint/checkers/typecheck.py
index 8fa80ee21..549cd7f4c 100644
--- a/pylint/checkers/typecheck.py
+++ b/pylint/checkers/typecheck.py
@@ -442,7 +442,14 @@ SEQUENCE_TYPES = {
}
-def _emit_no_member(node, owner, owner_name, ignored_mixins=True, ignored_none=True):
+def _emit_no_member(
+ node,
+ owner,
+ owner_name,
+ mixin_class_rgx: Pattern[str],
+ ignored_mixins=True,
+ ignored_none=True,
+):
"""Try to see if no-member should be emitted for the given owner.
The following cases are ignored:
@@ -462,7 +469,7 @@ def _emit_no_member(node, owner, owner_name, ignored_mixins=True, ignored_none=T
return False
if is_super(owner) or getattr(owner, "type", None) == "metaclass":
return False
- if owner_name and ignored_mixins and owner_name[-5:].lower() == "mixin":
+ if owner_name and ignored_mixins and mixin_class_rgx.match(owner_name):
return False
if isinstance(owner, nodes.FunctionDef) and (
owner.decorators or owner.is_abstract()
@@ -781,14 +788,24 @@ class TypeChecker(BaseChecker):
},
),
(
+ "mixin-class-rgx",
+ {
+ "default": ".*[Mm]ixin",
+ "type": "regexp",
+ "metavar": "<regexp>",
+ "help": "Regex pattern to define which classes are considered mixins "
+ "ignore-mixin-members is set to 'yes'",
+ },
+ ),
+ (
"ignore-mixin-members",
{
"default": True,
"type": "yn",
"metavar": "<y_or_n>",
- "help": 'Tells whether missing members accessed in mixin \
-class should be ignored. A mixin class is detected if its name ends with \
-"mixin" (case insensitive).',
+ "help": "Tells whether missing members accessed in mixin "
+ "class should be ignored. A class is considered mixin if its name matches "
+ "the mixin-class-rgx option.",
},
),
(
@@ -898,6 +915,7 @@ accessed. Python regular expressions are accepted.",
def open(self) -> None:
py_version = get_global_option(self, "py-version")
self._py310_plus = py_version >= (3, 10)
+ self._mixin_class_rgx = get_global_option(self, "mixin-class-rgx")
@astroid.decorators.cachedproperty
def _suggestion_mode(self):
@@ -1040,6 +1058,7 @@ accessed. Python regular expressions are accepted.",
node,
owner,
name,
+ self._mixin_class_rgx,
ignored_mixins=self.config.ignore_mixin_members,
ignored_none=self.config.ignore_none,
):
diff --git a/pylint/utils/utils.py b/pylint/utils/utils.py
index 6c9f11298..8243bc322 100644
--- a/pylint/utils/utils.py
+++ b/pylint/utils/utils.py
@@ -54,7 +54,10 @@ GLOBAL_OPTION_BOOL = Literal[
GLOBAL_OPTION_INT = Literal["max-line-length", "docstring-min-length"]
GLOBAL_OPTION_LIST = Literal["ignored-modules"]
GLOBAL_OPTION_PATTERN = Literal[
- "no-docstring-rgx", "dummy-variables-rgx", "ignored-argument-names"
+ "no-docstring-rgx",
+ "dummy-variables-rgx",
+ "ignored-argument-names",
+ "mixin-class-rgx",
]
GLOBAL_OPTION_PATTERN_LIST = Literal["ignore-paths"]
GLOBAL_OPTION_TUPLE_INT = Literal["py-version"]
diff --git a/pylintrc b/pylintrc
index b4a4e9fac..e5cbc1220 100644
--- a/pylintrc
+++ b/pylintrc
@@ -270,10 +270,13 @@ property-classes=abc.abstractproperty
[TYPECHECK]
-# Tells whether missing members accessed in mixin class should be ignored. A
-# mixin class is detected if its name ends with "mixin" (case insensitive).
+# Tells whether missing members accessed in mixin class should be ignored.
+# A class is considered mixin if its name matches the mixin-class-rgx option.
ignore-mixin-members=yes
+# Regex pattern to define which classes are considered mixins.
+mixin-class-rgx=.*MixIn
+
# List of module names for which member attributes should not be checked
# (useful for modules/projects where namespaces are manipulated during runtime
# and thus existing member attributes cannot be deduced by static analysis)
diff --git a/tests/functional/m/mixin_class_rgx.py b/tests/functional/m/mixin_class_rgx.py
new file mode 100644
index 000000000..1f6ea21e6
--- /dev/null
+++ b/tests/functional/m/mixin_class_rgx.py
@@ -0,0 +1,60 @@
+"""Tests for the mixin-class-rgx option"""
+# pylint: disable=too-few-public-methods
+
+
+# Tests for not-async-context-manager
+
+
+class AsyncManagerMixedin:
+ """Class that does not match the option pattern"""
+
+ def __aenter__(self):
+ pass
+
+
+class AsyncManagerMixin:
+ """Class that does match the option pattern"""
+
+ def __aenter__(self):
+ pass
+
+
+async def check_not_async_context_manager():
+ """Function calling the classes for not-async-context-manager"""
+ async with AsyncManagerMixedin: # [not-async-context-manager]
+ pass
+ async with AsyncManagerMixin():
+ pass
+
+
+# Tests for attribute-defined-outside-init
+
+
+class OutsideInitMixedin:
+ """Class that does not match the option pattern"""
+
+ def set_attribute(self):
+ """Set an attribute outside of __init__"""
+ self.attr = 1 # [attribute-defined-outside-init]
+
+
+class OutsideInitMixin:
+ """Class that does match the option pattern"""
+
+ def set_attribute(self):
+ """Set an attribute outside of __init__"""
+ self.attr = 1
+
+
+# Tests for no-member
+
+
+class NoMemberMixedin:
+ """Class that does not match the option pattern"""
+
+MY_CLASS = OutsideInitMixedin().method() # [no-member]
+
+class NoMemberMixin:
+ """Class that does match the option pattern"""
+
+MY_OTHER_CLASS = NoMemberMixin().method()
diff --git a/tests/functional/m/mixin_class_rgx.rc b/tests/functional/m/mixin_class_rgx.rc
new file mode 100644
index 000000000..4ca300e4a
--- /dev/null
+++ b/tests/functional/m/mixin_class_rgx.rc
@@ -0,0 +1,3 @@
+[TYPECHECK]
+ignore-mixin-members=yes
+mixin-class-rgx=.*[Mm]ixin
diff --git a/tests/functional/m/mixin_class_rgx.txt b/tests/functional/m/mixin_class_rgx.txt
new file mode 100644
index 000000000..eb39a1185
--- /dev/null
+++ b/tests/functional/m/mixin_class_rgx.txt
@@ -0,0 +1,3 @@
+not-async-context-manager:24:4:check_not_async_context_manager:Async context manager 'AsyncManagerMixedin' doesn't implement __aenter__ and __aexit__.:HIGH
+attribute-defined-outside-init:38:8:OutsideInitMixedin.set_attribute:Attribute 'attr' defined outside __init__:HIGH
+no-member:55:11::Instance of 'OutsideInitMixedin' has no 'method' member:INFERENCE