Skip to content

Commit

Permalink
fixes PyCQA#277, most of PyCQA#278, and PyCQA#280
Browse files Browse the repository at this point in the history
  • Loading branch information
jakkdl committed Sep 29, 2022
1 parent 592b722 commit 8462c1f
Show file tree
Hide file tree
Showing 3 changed files with 42 additions and 16 deletions.
19 changes: 13 additions & 6 deletions bugbear.py
Expand Up @@ -616,13 +616,15 @@ def check_for_b024_and_b027(self, node: ast.ClassDef):
"""Check for inheritance from abstract classes in abc and lack of
any methods decorated with abstract*"""

def is_abc_class(value):
def is_abc_class(value, name="ABC"):
# class foo(metaclass = [abc.]ABCMeta)
if isinstance(value, ast.keyword):
return value.arg == "metaclass" and is_abc_class(value.value)
abc_names = ("ABC", "ABCMeta")
return (isinstance(value, ast.Name) and value.id in abc_names) or (
return value.arg == "metaclass" and is_abc_class(value.value, "ABCMeta")
# class foo(ABC)
# class foo(abc.ABC)
return (isinstance(value, ast.Name) and value.id == name) or (
isinstance(value, ast.Attribute)
and value.attr in abc_names
and value.attr == name
and isinstance(value.value, ast.Name)
and value.value.id == "abc"
)
Expand All @@ -647,6 +649,11 @@ def empty_body(body) -> bool:
for stmt in body
)

# don't check multiple inheritance
# https://github.com/PyCQA/flake8-bugbear/issues/277
if len(node.bases) + len(node.keywords) > 1:
return

# only check abstract classes
if not any(map(is_abc_class, (*node.bases, *node.keywords))):
return
Expand All @@ -666,7 +673,7 @@ def empty_body(body) -> bool:

if not has_abstract_decorator and empty_body(stmt.body):
self.errors.append(
B025(stmt.lineno, stmt.col_offset, vars=(stmt.name,))
B027(stmt.lineno, stmt.col_offset, vars=(stmt.name,))
)

if not has_abstract_method:
Expand Down
25 changes: 23 additions & 2 deletions tests/b024.py
Expand Up @@ -81,11 +81,32 @@ def method(self):
foo()


class multi_super_1(notabc.ABC, abc.ABCMeta): # error
class multi_super_1(notabc.ABC, abc.ABCMeta): # safe
def method(self):
foo()


class multi_super_2(notabc.ABC, metaclass=abc.ABCMeta): # error
class multi_super_2(notabc.ABC, metaclass=abc.ABCMeta): # safe
def method(self):
foo()


class non_keyword_abcmeta_1(ABCMeta): # safe
def method(self):
foo()


class non_keyword_abcmeta_2(abc.ABCMeta): # safe
def method(self):
foo()


# very invalid code, but that's up to mypy et al to check
class keyword_abc_1(metaclass=ABC): # safe
def method(self):
foo()


class keyword_abc_2(metaclass=abc.ABC): # safe
def method(self):
foo()
14 changes: 6 additions & 8 deletions tests/test_bugbear.py
Expand Up @@ -364,8 +364,6 @@ def test_b024(self):
B024(58, 0, vars=("MetaBase_1",)),
B024(69, 0, vars=("abc_Base_1",)),
B024(74, 0, vars=("abc_Base_2",)),
B024(84, 0, vars=("multi_super_1",)),
B024(89, 0, vars=("multi_super_2",)),
)
self.assertEqual(errors, expected)

Expand Down Expand Up @@ -401,15 +399,15 @@ def test_b026(self):
)

def test_b027(self):
filename = Path(__file__).absolute().parent / "b025.py"
filename = Path(__file__).absolute().parent / "b027.py"
bbc = BugBearChecker(filename=str(filename))
errors = list(bbc.run())
expected = self.errors(
B025(13, 4, vars=("empty_1",)),
B025(16, 4, vars=("empty_2",)),
B025(19, 4, vars=("empty_3",)),
B025(23, 4, vars=("empty_4",)),
B025(31, 4, vars=("empty_5",)),
B027(13, 4, vars=("empty_1",)),
B027(16, 4, vars=("empty_2",)),
B027(19, 4, vars=("empty_3",)),
B027(23, 4, vars=("empty_4",)),
B027(31, 4, vars=("empty_5",)),
)
self.assertEqual(errors, expected)

Expand Down

0 comments on commit 8462c1f

Please sign in to comment.