Anyone know of a Python linter that will flag `@ab...
# random
c
Anyone know of a Python linter that will flag
@abc.abstractmethod
on a class that doesn't derive from
abc.ABC
?
w
Huh, I thought bugbear would have had one - but apparently not... Could always write a ruff/flake extension?
Or, go a little bit nutty and write an AST parser.... .... ...
Copy code
class MissingABCVisitor(ast.NodeVisitor):
    def __init__(self) -> None:
        super().__init__()
        self.has_abc = False

    def visit_ClassDef(self, node: ast.ClassDef):
        """Check if the class has a base class of ABC or a metaclass of ABCMeta"""
        for base in node.bases:
            if isinstance(base, ast.Name):
                if "abc" in base.id.lower():
                    self.has_abc = True
            if isinstance(base, ast.Attribute):
                if "abc" in base.attr.lower():
                    self.has_abc = True

        for kw in node.keywords:
            if kw.arg != "metaclass":
                continue
            if isinstance(kw.value, ast.Name):
                if "abcmeta" in kw.value.id.lower():
                    self.has_abc = True
            if isinstance(kw.value, ast.Attribute):
                if "abcmeta" in kw.value.attr.lower():
                    self.has_abc = True

        super().generic_visit(node)

    def visit_FunctionDef(self, node: ast.FunctionDef):
        """Check if the function has a decorator of abc.abstractmethod"""
        if self.has_abc:
            return
        
        for decorator in node.decorator_list:
            if isinstance(decorator, ast.Attribute):
                if "abstractmethod" in decorator.attr.lower():
                    print(f"{node.name} has abc.abstractmethod decorator but no ABC base class or ABCMeta metaclass")

            if isinstance(decorator, ast.Name):
                if "abstractmethod" in decorator.id.lower():
                    print(f"{node.name} has abc.abstractmethod decorator but no ABC base class or ABCMeta metaclass")
Copy code
a = """
class A(metaclass=abc.ABCMeta):
    @abc.abstractmethod
    def func_a(self):
        ...
"""

b = """
class B:
    @abc.abstractmethod
    def func_b(self):
        ...
"""

c = """
class C(abc.ABC):
    @abc.abstractmethod
    def func_c(self):
        ...
"""

d = """
class D(ABC):
    @abc.abstractmethod
    def func_d(self):
        ...
"""

e = """
class E(metaclass=ABCMeta):
    @abc.abstractmethod
    def func_e(self):
        ...
"""

f = """
class F(metaclass=ABCMeta):
    @abstractmethod
    def hello(self):
        ...
"""


for code in [a, b, c, d, e, f]:
    visitor = MissingABCVisitor()
    tree = ast.parse(code)
    visitor.visit(tree)