Include static and class methods in in abstract decorator list (#4298)

This commit is contained in:
Charlie Marsh 2023-05-08 21:54:02 -04:00 committed by GitHub
parent f23851130a
commit d365dab904
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 108 additions and 79 deletions

View file

@ -4,7 +4,12 @@ B027 - on lines 13, 16, 19, 23
""" """
import abc import abc
from abc import ABC from abc import ABC
from abc import abstractmethod, abstractproperty from abc import (
abstractmethod,
abstractproperty,
abstractclassmethod,
abstractstaticmethod,
)
from abc import abstractmethod as notabstract from abc import abstractmethod as notabstract
from abc import abstractproperty as notabstract_property from abc import abstractproperty as notabstract_property
@ -55,6 +60,22 @@ class AbstractClass(ABC):
def abstract_6(self): def abstract_6(self):
... ...
@abstractclassmethod
def abstract_7(self):
pass
@abc.abstractclassmethod
def abstract_8(self):
...
@abstractstaticmethod
def abstract_9(self):
pass
@abc.abstractstaticmethod
def abstract_10(self):
...
def body_1(self): def body_1(self):
print("foo") print("foo")
... ...

View file

@ -1,96 +1,96 @@
--- ---
source: crates/ruff/src/rules/flake8_bugbear/mod.rs source: crates/ruff/src/rules/flake8_bugbear/mod.rs
--- ---
B027.py:13:5: B027 [*] `AbstractClass.empty_1` is an empty method in an abstract base class, but has no abstract decorator B027.py:18:5: B027 [*] `AbstractClass.empty_1` is an empty method in an abstract base class, but has no abstract decorator
| |
13 | class AbstractClass(ABC): 18 | class AbstractClass(ABC):
14 | def empty_1(self): # error 19 | def empty_1(self): # error
| _____^ | _____^
15 | | ... 20 | | ...
| |___________^ B027 | |___________^ B027
16 | 21 |
17 | def empty_2(self): # error 22 | def empty_2(self): # error
| |
= help: Add the `@abstractmethod` decorator = help: Add the `@abstractmethod` decorator
Suggested fix Suggested fix
10 10 |
11 11 |
12 12 | class AbstractClass(ABC):
13 |+ @notabstract
13 14 | def empty_1(self): # error
14 15 | ...
15 16 |
B027.py:16:5: B027 [*] `AbstractClass.empty_2` is an empty method in an abstract base class, but has no abstract decorator
|
16 | ...
17 |
18 | def empty_2(self): # error
| _____^
19 | | pass
| |____________^ B027
20 |
21 | def empty_3(self): # error
|
= help: Add the `@abstractmethod` decorator
Suggested fix
13 13 | def empty_1(self): # error
14 14 | ...
15 15 | 15 15 |
16 |+ @notabstract 16 16 |
16 17 | def empty_2(self): # error 17 17 | class AbstractClass(ABC):
17 18 | pass 18 |+ @notabstract
18 19 | 18 19 | def empty_1(self): # error
19 20 | ...
20 21 |
B027.py:19:5: B027 [*] `AbstractClass.empty_3` is an empty method in an abstract base class, but has no abstract decorator B027.py:21:5: B027 [*] `AbstractClass.empty_2` is an empty method in an abstract base class, but has no abstract decorator
| |
19 | pass 21 | ...
20 | 22 |
21 | def empty_3(self): # error 23 | def empty_2(self): # error
| _____^ | _____^
22 | | """docstring""" 24 | | pass
23 | | ...
| |___________^ B027
24 |
25 | def empty_4(self): # error
|
= help: Add the `@abstractmethod` decorator
Suggested fix
16 16 | def empty_2(self): # error
17 17 | pass
18 18 |
19 |+ @notabstract
19 20 | def empty_3(self): # error
20 21 | """docstring"""
21 22 | ...
B027.py:23:5: B027 [*] `AbstractClass.empty_4` is an empty method in an abstract base class, but has no abstract decorator
|
23 | ...
24 |
25 | def empty_4(self): # error
| _____^
26 | | """multiple ellipsis/pass"""
27 | | ...
28 | | pass
29 | | ...
30 | | pass
| |____________^ B027 | |____________^ B027
31 | 25 |
32 | @notabstract 26 | def empty_3(self): # error
| |
= help: Add the `@abstractmethod` decorator = help: Add the `@abstractmethod` decorator
Suggested fix Suggested fix
20 20 | """docstring""" 18 18 | def empty_1(self): # error
21 21 | ... 19 19 | ...
22 22 | 20 20 |
23 |+ @notabstract 21 |+ @notabstract
23 24 | def empty_4(self): # error 21 22 | def empty_2(self): # error
24 25 | """multiple ellipsis/pass""" 22 23 | pass
25 26 | ... 23 24 |
B027.py:24:5: B027 [*] `AbstractClass.empty_3` is an empty method in an abstract base class, but has no abstract decorator
|
24 | pass
25 |
26 | def empty_3(self): # error
| _____^
27 | | """docstring"""
28 | | ...
| |___________^ B027
29 |
30 | def empty_4(self): # error
|
= help: Add the `@abstractmethod` decorator
Suggested fix
21 21 | def empty_2(self): # error
22 22 | pass
23 23 |
24 |+ @notabstract
24 25 | def empty_3(self): # error
25 26 | """docstring"""
26 27 | ...
B027.py:28:5: B027 [*] `AbstractClass.empty_4` is an empty method in an abstract base class, but has no abstract decorator
|
28 | ...
29 |
30 | def empty_4(self): # error
| _____^
31 | | """multiple ellipsis/pass"""
32 | | ...
33 | | pass
34 | | ...
35 | | pass
| |____________^ B027
36 |
37 | @notabstract
|
= help: Add the `@abstractmethod` decorator
Suggested fix
25 25 | """docstring"""
26 26 | ...
27 27 |
28 |+ @notabstract
28 29 | def empty_4(self): # error
29 30 | """multiple ellipsis/pass"""
30 31 | ...

View file

@ -60,13 +60,21 @@ pub fn is_override(ctx: &Context, decorator_list: &[Expr]) -> bool {
.any(|expr| ctx.match_typing_expr(map_callable(expr), "override")) .any(|expr| ctx.match_typing_expr(map_callable(expr), "override"))
} }
/// Returns `true` if a function definition is an `@abstractmethod`. /// Returns `true` if a function definition is an abstract method based on its decorators.
pub fn is_abstract(ctx: &Context, decorator_list: &[Expr]) -> bool { pub fn is_abstract(ctx: &Context, decorator_list: &[Expr]) -> bool {
decorator_list.iter().any(|expr| { decorator_list.iter().any(|expr| {
ctx.resolve_call_path(map_callable(expr)) ctx.resolve_call_path(map_callable(expr))
.map_or(false, |call_path| { .map_or(false, |call_path| {
call_path.as_slice() == ["abc", "abstractmethod"] matches!(
|| call_path.as_slice() == ["abc", "abstractproperty"] call_path.as_slice(),
[
"abc",
"abstractmethod"
| "abstractclassmethod"
| "abstractstaticmethod"
| "abstractproperty"
]
)
}) })
}) })
} }