summaryrefslogtreecommitdiff
path: root/pylint
diff options
context:
space:
mode:
authorMark Byrne <31762852+mbyrnepr2@users.noreply.github.com>2021-10-10 09:08:31 +0200
committerGitHub <noreply@github.com>2021-10-10 09:08:31 +0200
commit087fe6856aac60c8781106c96a2b014fedffffb8 (patch)
tree0775e4f7c2d7c3e6d61f5504615c126235fef28e /pylint
parent220e27dc5bdd6bdd9dbee56d5c7d33a946c8ad17 (diff)
downloadpylint-git-087fe6856aac60c8781106c96a2b014fedffffb8.tar.gz
Add checkers for typing.final for Python version 3.8 or later (#5133)
* Add checkers for typing.final for Python version 3.8 or later - overridden-final-method - subclassed-final-class Closes #3197
Diffstat (limited to 'pylint')
-rw-r--r--pylint/checkers/classes.py35
1 files changed, 35 insertions, 0 deletions
diff --git a/pylint/checkers/classes.py b/pylint/checkers/classes.py
index 14e0ef635..489bb5063 100644
--- a/pylint/checkers/classes.py
+++ b/pylint/checkers/classes.py
@@ -79,6 +79,7 @@ from pylint.checkers.utils import (
safe_infer,
unimplemented_abstract_methods,
)
+from pylint.constants import PY38_PLUS
from pylint.interfaces import IAstroidChecker
from pylint.utils import get_global_option
@@ -649,6 +650,16 @@ MSGS = { # pylint: disable=consider-using-namedtuple-or-dataclass
"unused-private-member",
"Emitted when a private member of a class is defined but not used.",
),
+ "W0239": (
+ "Method %r overrides a method decorated with typing.final which is defined in class %r",
+ "overridden-final-method",
+ "Used when a method decorated with typing.final has been overridden.",
+ ),
+ "W0240": (
+ "Class %r is a subclass of a class decorated with typing.final: %r",
+ "subclassed-final-class",
+ "Used when a class decorated with typing.final has been subclassed.",
+ ),
"E0236": (
"Invalid object %r in __slots__, must contain only non empty strings",
"invalid-slots-object",
@@ -860,6 +871,7 @@ a metaclass class method.",
self.add_message("no-init", args=node, node=node)
self._check_slots(node)
self._check_proper_bases(node)
+ self._check_typing_final(node)
self._check_consistent_mro(node)
def _check_consistent_mro(self, node):
@@ -898,6 +910,23 @@ a metaclass class method.",
"useless-object-inheritance", args=node.name, node=node
)
+ def _check_typing_final(self, node: nodes.ClassDef) -> None:
+ """Detect that a class does not subclass a class decorated with `typing.final`"""
+ if not PY38_PLUS:
+ return
+ for base in node.bases:
+ ancestor = safe_infer(base)
+ if not ancestor:
+ continue
+ if isinstance(ancestor, nodes.ClassDef) and decorated_with(
+ ancestor, ["typing.final"]
+ ):
+ self.add_message(
+ "subclassed-final-class",
+ args=(node.name, ancestor.name),
+ node=node,
+ )
+
@check_messages("unused-private-member", "attribute-defined-outside-init")
def leave_classdef(self, node: nodes.ClassDef) -> None:
"""close a class node:
@@ -1347,6 +1376,12 @@ a metaclass class method.",
args=(function_node.name, "non-async", "async"),
node=function_node,
)
+ if decorated_with(parent_function_node, ["typing.final"]) and PY38_PLUS:
+ self.add_message(
+ "overridden-final-method",
+ args=(function_node.name, parent_function_node.parent.name),
+ node=function_node,
+ )
def _check_slots(self, node):
if "__slots__" not in node.locals: