diff options
-rw-r--r-- | doc/whatsnew/fragments/7343.feature | 3 | ||||
-rw-r--r-- | pylint/checkers/classes/class_checker.py | 23 | ||||
-rw-r--r-- | tests/functional/p/protected_access.py | 11 | ||||
-rw-r--r-- | tests/functional/p/protected_access.rc | 2 | ||||
-rw-r--r-- | tests/functional/p/protected_access.txt | 8 |
5 files changed, 36 insertions, 11 deletions
diff --git a/doc/whatsnew/fragments/7343.feature b/doc/whatsnew/fragments/7343.feature new file mode 100644 index 000000000..2e9fa83c1 --- /dev/null +++ b/doc/whatsnew/fragments/7343.feature @@ -0,0 +1,3 @@ +Accept values of the form ``<class name>.<attribute name>`` for the ``exclude-protected`` list. + +Closes #7343 diff --git a/pylint/checkers/classes/class_checker.py b/pylint/checkers/classes/class_checker.py index 16e7cbd14..f4fc2a42a 100644 --- a/pylint/checkers/classes/class_checker.py +++ b/pylint/checkers/classes/class_checker.py @@ -815,6 +815,7 @@ a metaclass class method.", "_replace", "_source", "_make", + "os._exit", ), "type": "csv", "metavar": "<protected access exclusions>", @@ -1782,21 +1783,30 @@ a metaclass class method.", or attrname in self.linter.config.exclude_protected ): return - klass = node_frame_class(node) - - # In classes, check we are not getting a parent method - # through the class object or through super - callee = node.expr.as_string() # Typing annotations in function definitions can include protected members if utils.is_node_in_type_annotation_context(node): return - # We are not in a class, no remaining valid case + # Return if `attrname` is defined at the module-level or as a class attribute + # and is listed in `exclude-protected`. + inferred = safe_infer(node.expr) + if ( + inferred + and isinstance(inferred, (nodes.ClassDef, nodes.Module)) + and f"{inferred.name}.{attrname}" in self.linter.config.exclude_protected + ): + return + + klass = node_frame_class(node) if klass is None: + # We are not in a class, no remaining valid case self.add_message("protected-access", node=node, args=attrname) return + # In classes, check we are not getting a parent method + # through the class object or through super + # If the expression begins with a call to super, that's ok. if ( isinstance(node.expr, nodes.Call) @@ -1812,6 +1822,7 @@ a metaclass class method.", # Check if we are inside the scope of a class or nested inner class inside_klass = True outer_klass = klass + callee = node.expr.as_string() parents_callee = callee.split(".") parents_callee.reverse() for callee in parents_callee: diff --git a/tests/functional/p/protected_access.py b/tests/functional/p/protected_access.py index 209f571c4..6587e50a7 100644 --- a/tests/functional/p/protected_access.py +++ b/tests/functional/p/protected_access.py @@ -3,6 +3,8 @@ # pylint: disable=missing-function-docstring, invalid-metaclass, no-member # pylint: disable=no-self-argument, undefined-variable, unused-variable +import os + # Test that exclude-protected can be used to exclude names from protected-access warning class Protected: def __init__(self): @@ -41,3 +43,12 @@ class Light: def func(light: Light) -> None: print(light._light_internal) # [protected-access] + + +# os._exit is excluded from the protected-access check by default +print(os._exit) + +# BaseTomato._sauce is included in the `exclude-protected` list +# and does not emit a `protected-access` message: +class BaseTomato: + _sauce = 42 diff --git a/tests/functional/p/protected_access.rc b/tests/functional/p/protected_access.rc index 3ebab1b6f..c599f2a4a 100644 --- a/tests/functional/p/protected_access.rc +++ b/tests/functional/p/protected_access.rc @@ -1,2 +1,2 @@ [CLASSES] -exclude-protected=_meta,_manager +exclude-protected=_meta,_manager,os._exit,BaseTomato._sauce diff --git a/tests/functional/p/protected_access.txt b/tests/functional/p/protected_access.txt index 50e6c5d5b..e0bebb76a 100644 --- a/tests/functional/p/protected_access.txt +++ b/tests/functional/p/protected_access.txt @@ -1,4 +1,4 @@ -protected-access:17:0:17:9::Access to a protected member _teta of a client class:UNDEFINED -protected-access:29:16:29:26:Application.__no_special__:Access to a protected member _nargs of a client class:UNDEFINED -protected-access:39:14:39:35:Light.func:Access to a protected member _light_internal of a client class:UNDEFINED -protected-access:43:10:43:31:func:Access to a protected member _light_internal of a client class:UNDEFINED +protected-access:19:0:19:9::Access to a protected member _teta of a client class:UNDEFINED +protected-access:31:16:31:26:Application.__no_special__:Access to a protected member _nargs of a client class:UNDEFINED +protected-access:41:14:41:35:Light.func:Access to a protected member _light_internal of a client class:UNDEFINED +protected-access:45:10:45:31:func:Access to a protected member _light_internal of a client class:UNDEFINED |