summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorhippo91 <guillaume.peillex@gmail.com>2019-08-06 15:51:39 +0200
committerClaudiu Popa <pcmanticore@gmail.com>2019-08-06 15:51:39 +0200
commit5309e1d6dacbcbc30f2a4b077903e551dedc2385 (patch)
tree65ddbbecf02ee3b65f008e2baea520175ae62b70
parent41c95225fe398dd3f62f48d777080dc8827b145c (diff)
downloadpylint-git-5309e1d6dacbcbc30f2a4b077903e551dedc2385.tar.gz
Don't emit protected-acces for single underscore prefixed attributes in special methods
This PR prevents the emission of protected-access message when a single underscore prefixed attribute is accessed inside a special method. Close #1802
-rw-r--r--ChangeLog5
-rw-r--r--doc/whatsnew/2.4.rst5
-rw-r--r--pylint/checkers/classes.py17
-rw-r--r--tests/functional/access_to_protected_members.py42
-rw-r--r--tests/functional/access_to_protected_members.txt3
-rw-r--r--tests/unittest_checker_classes.py2
6 files changed, 73 insertions, 1 deletions
diff --git a/ChangeLog b/ChangeLog
index a0af1f5c6..6f5406114 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -7,6 +7,11 @@ What's New in Pylint 2.4.0?
Release date: TBA
+* Don't emit ``protected-access`` when a single underscore prefixed attribute
+ is used inside a special method
+
+ Close #1802
+
* Fix the "statement" values in the PyLinter's stats reports by module.
* Added a new check, ``invalid-overridden-method``
diff --git a/doc/whatsnew/2.4.rst b/doc/whatsnew/2.4.rst
index a01635146..ceaf437bf 100644
--- a/doc/whatsnew/2.4.rst
+++ b/doc/whatsnew/2.4.rst
@@ -103,6 +103,11 @@ New checkers
Other Changes
=============
+* Don't emit ``protected-access`` when a single underscore prefixed attribute is used
+ inside a special method
+
+ Close #1802
+
* ``len-as-condition`` now only fires when a ``len(x)`` call is made without an explicit comparison.
The message and description accompanying this checker has been changed
diff --git a/pylint/checkers/classes.py b/pylint/checkers/classes.py
index 79724656a..1bd298b35 100644
--- a/pylint/checkers/classes.py
+++ b/pylint/checkers/classes.py
@@ -1351,8 +1351,25 @@ a metaclass class method.",
if _is_attribute_property(name, klass):
return
+ #  A licit use of protected member is inside a special method
+ if not attrname.startswith(
+ "__"
+ ) and self._is_called_inside_special_method(node):
+ return
+
self.add_message("protected-access", node=node, args=attrname)
+ @staticmethod
+ def _is_called_inside_special_method(node: astroid.node_classes.NodeNG) -> bool:
+ """
+ Returns true if the node is located inside a special (aka dunder) method
+ """
+ try:
+ frame_name = node.frame().name
+ except AttributeError:
+ return False
+ return frame_name and frame_name in PYMETHODS
+
def _is_type_self_call(self, expr):
return (
isinstance(expr, astroid.Call)
diff --git a/tests/functional/access_to_protected_members.py b/tests/functional/access_to_protected_members.py
index 1c97a3b50..a88dee82b 100644
--- a/tests/functional/access_to_protected_members.py
+++ b/tests/functional/access_to_protected_members.py
@@ -57,3 +57,45 @@ class Issue1031(object):
if self._attr == 1:
return type(INST)._protected # [protected-access]
return None
+
+
+class Issue1802(object):
+ """Test for GitHub issue 1802"""
+ def __init__(self, value):
+ self._foo = value
+ self.__private = 2 * value
+
+ def __eq__(self, other):
+ """Test a correct access as the access to protected member is in a special method"""
+ if isinstance(other, self.__class__):
+ answer = self._foo == other._foo
+ return answer and self.__private == other.__private # [protected-access]
+ return False
+
+ def not_in_special(self, other):
+ """
+ Test an incorrect access as the access to protected member is not inside a special method
+ """
+ if isinstance(other, self.__class__):
+ return self._foo == other._foo # [protected-access]
+ return False
+
+ def __le__(self, other):
+ """
+ Test a correct access as the access to protected member
+ is inside a special method even if it is deeply nested
+ """
+ if 2 > 1:
+ if isinstance(other, self.__class__):
+ if "answer" == "42":
+ return self._foo == other._foo
+ return False
+
+ def __fake_special__(self, other):
+ """
+ Test an incorrect access as the access
+ to protected member is not inside a licit special method
+ """
+ if isinstance(other, self.__class__):
+ return self._foo == other._foo # [protected-access]
+ return False
diff --git a/tests/functional/access_to_protected_members.txt b/tests/functional/access_to_protected_members.txt
index fb5c152e6..f1a9ceaa4 100644
--- a/tests/functional/access_to_protected_members.txt
+++ b/tests/functional/access_to_protected_members.txt
@@ -4,3 +4,6 @@ protected-access:42::Access to a protected member _protected of a client class
protected-access:43::Access to a protected member _cls_protected of a client class
protected-access:44::Access to a protected member _cls_protected of a client class
protected-access:58:Issue1031.incorrect_access:Access to a protected member _protected of a client class
+protected-access:72:Issue1802.__eq__:Access to a protected member __private of a client class
+protected-access:80:Issue1802.not_in_special:Access to a protected member _foo of a client class
+protected-access:100:Issue1802.__fake_special__:Access to a protected member _foo of a client class
diff --git a/tests/unittest_checker_classes.py b/tests/unittest_checker_classes.py
index 3925cc36d..da880362b 100644
--- a/tests/unittest_checker_classes.py
+++ b/tests/unittest_checker_classes.py
@@ -105,7 +105,7 @@ class TestVariablesChecker(CheckerTestCase):
return 1 if self._nargs else 2
class Application(metaclass=MC):
- def __new__(cls):
+ def __no_special__(cls):
nargs = obj._nargs #@
"""
)