@@ -416,6 +416,7 @@ def visit_ClassDef(self, node):
416
416
self .check_for_b903 (node )
417
417
self .check_for_b018 (node )
418
418
self .check_for_b021 (node )
419
+ self .check_for_b024 (node )
419
420
self .generic_visit (node )
420
421
421
422
def visit_Try (self , node ):
@@ -608,6 +609,37 @@ def check_for_b023(self, loop_node):
608
609
if reassigned_in_loop .issuperset (err .vars ):
609
610
self .errors .append (err )
610
611
612
+ def check_for_b024 (self , node : ast .ClassDef ):
613
+ """Check for inheritance from abstract classes in abc and lack of
614
+ any methods decorated with abstract*"""
615
+
616
+ def is_abc_class (value ):
617
+ if isinstance (value , ast .keyword ):
618
+ return value .arg == "metaclass" and is_abc_class (value .value )
619
+ abc_names = ("ABC" , "ABCMeta" )
620
+ return (isinstance (value , ast .Name ) and value .id in abc_names ) or (
621
+ isinstance (value , ast .Attribute )
622
+ and value .attr in abc_names
623
+ and isinstance (value .value , ast .Name )
624
+ and value .value .id == "abc"
625
+ )
626
+
627
+ def is_abstract_decorator (expr ):
628
+ return (isinstance (expr , ast .Name ) and expr .id [:8 ] == "abstract" ) or (
629
+ isinstance (expr , ast .Attribute ) and expr .attr [:8 ] == "abstract"
630
+ )
631
+
632
+ if not any (map (is_abc_class , (* node .bases , * node .keywords ))):
633
+ return
634
+
635
+ for stmt in node .body :
636
+ if isinstance (stmt , (ast .FunctionDef , ast .AsyncFunctionDef )) and any (
637
+ map (is_abstract_decorator , stmt .decorator_list )
638
+ ):
639
+ return
640
+
641
+ self .errors .append (B024 (node .lineno , node .col_offset , vars = (node .name ,)))
642
+
611
643
def _get_assigned_names (self , loop_node ):
612
644
loop_targets = (ast .For , ast .AsyncFor , ast .comprehension )
613
645
for node in children_in_scope (loop_node ):
@@ -1139,6 +1171,12 @@ def visit_Lambda(self, node):
1139
1171
)
1140
1172
1141
1173
B023 = Error (message = "B023 Function definition does not bind loop variable {!r}." )
1174
+ B024 = Error (
1175
+ message = (
1176
+ "{} is an abstract base class, but it has no abstract methods. Remember to use"
1177
+ " @abstractmethod, @abstractclassmethod and/or @abstractproperty decorators."
1178
+ )
1179
+ )
1142
1180
1143
1181
# Warnings disabled by default.
1144
1182
B901 = Error (
0 commit comments