-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
## Summary Implement [use-abc-shorthand (FURB180)](https://github.com/dosisod/refurb/blob/master/refurb/checks/readability/use_abc_shorthand.py) lint. I changed the name to be more conformant with ruff rule-naming rules. ## Test Plan cargo test
- Loading branch information
Showing
8 changed files
with
251 additions
and
0 deletions.
There are no files selected for viewing
58 changes: 58 additions & 0 deletions
58
crates/ruff_linter/resources/test/fixtures/refurb/FURB180.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
import abc | ||
from abc import abstractmethod, ABCMeta | ||
|
||
|
||
# Errors | ||
|
||
class A0(metaclass=abc.ABCMeta): | ||
@abstractmethod | ||
def foo(self): pass | ||
|
||
|
||
class A1(metaclass=ABCMeta): | ||
@abstractmethod | ||
def foo(self): pass | ||
|
||
|
||
class B0: | ||
def __init_subclass__(cls, **kwargs): | ||
super().__init_subclass__() | ||
|
||
|
||
class B1: | ||
pass | ||
|
||
|
||
class A2(B0, B1, metaclass=ABCMeta): | ||
@abstractmethod | ||
def foo(self): pass | ||
|
||
|
||
class A3(B0, before_metaclass=1, metaclass=abc.ABCMeta): | ||
pass | ||
|
||
|
||
# OK | ||
|
||
class Meta(type): | ||
def __new__(cls, *args, **kwargs): | ||
return super().__new__(cls, *args) | ||
|
||
|
||
class A4(metaclass=Meta, no_metaclass=ABCMeta): | ||
@abstractmethod | ||
def foo(self): pass | ||
|
||
|
||
class A5(metaclass=Meta): | ||
pass | ||
|
||
|
||
class A6(abc.ABC): | ||
@abstractmethod | ||
def foo(self): pass | ||
|
||
|
||
class A7(B0, abc.ABC, B1): | ||
@abstractmethod | ||
def foo(self): pass |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
104 changes: 104 additions & 0 deletions
104
crates/ruff_linter/src/rules/refurb/rules/metaclass_abcmeta.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
use itertools::Itertools; | ||
|
||
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; | ||
use ruff_macros::{derive_message_formats, violation}; | ||
use ruff_python_ast::StmtClassDef; | ||
use ruff_text_size::{Ranged, TextRange}; | ||
|
||
use crate::checkers::ast::Checker; | ||
use crate::importer::ImportRequest; | ||
|
||
/// ## What it does | ||
/// Checks for uses of `metaclass=abc.ABCMeta` to define abstract base classes | ||
/// (ABCs). | ||
/// | ||
/// ## Why is this bad? | ||
/// | ||
/// Instead of `class C(metaclass=abc.ABCMeta): ...`, use `class C(ABC): ...` | ||
/// to define an abstract base class. Inheriting from the `ABC` wrapper class | ||
/// is semantically identical to setting `metaclass=abc.ABCMeta`, but more | ||
/// succinct. | ||
/// | ||
/// ## Example | ||
/// ```python | ||
/// class C(metaclass=ABCMeta): | ||
/// pass | ||
/// ``` | ||
/// | ||
/// Use instead: | ||
/// ```python | ||
/// class C(ABC): | ||
/// pass | ||
/// ``` | ||
/// | ||
/// ## References | ||
/// - [Python documentation: `abc.ABC`](https://docs.python.org/3/library/abc.html#abc.ABC) | ||
/// - [Python documentation: `abc.ABCMeta`](https://docs.python.org/3/library/abc.html#abc.ABCMeta) | ||
#[violation] | ||
pub struct MetaClassABCMeta; | ||
|
||
impl AlwaysFixableViolation for MetaClassABCMeta { | ||
#[derive_message_formats] | ||
fn message(&self) -> String { | ||
format!("Use of `metaclass=abc.ABCMeta` to define abstract base class") | ||
} | ||
|
||
fn fix_title(&self) -> String { | ||
format!("Replace with `abc.ABC`") | ||
} | ||
} | ||
|
||
/// FURB180 | ||
pub(crate) fn metaclass_abcmeta(checker: &mut Checker, class_def: &StmtClassDef) { | ||
// Identify the `metaclass` keyword. | ||
let Some((position, keyword)) = class_def.keywords().iter().find_position(|&keyword| { | ||
keyword | ||
.arg | ||
.as_ref() | ||
.is_some_and(|arg| arg.as_str() == "metaclass") | ||
}) else { | ||
return; | ||
}; | ||
|
||
// Determine whether it's assigned to `abc.ABCMeta`. | ||
if !checker | ||
.semantic() | ||
.resolve_call_path(&keyword.value) | ||
.is_some_and(|call_path| matches!(call_path.as_slice(), ["abc", "ABCMeta"])) | ||
{ | ||
return; | ||
} | ||
|
||
let mut diagnostic = Diagnostic::new(MetaClassABCMeta, keyword.range); | ||
|
||
diagnostic.try_set_fix(|| { | ||
let (import_edit, binding) = checker.importer().get_or_import_symbol( | ||
&ImportRequest::import("abc", "ABC"), | ||
keyword.range.start(), | ||
checker.semantic(), | ||
)?; | ||
Ok(if position > 0 { | ||
// When the `abc.ABCMeta` is not the first keyword, put `abc.ABC` before the first | ||
// keyword. | ||
Fix::safe_edits( | ||
// Delete from the previous argument, to the end of the `metaclass` argument. | ||
Edit::range_deletion(TextRange::new( | ||
class_def.keywords()[position - 1].end(), | ||
keyword.end(), | ||
)), | ||
// Insert `abc.ABC` before the first keyword. | ||
[ | ||
Edit::insertion(format!("{binding}, "), class_def.keywords()[0].start()), | ||
import_edit, | ||
], | ||
) | ||
} else { | ||
Fix::safe_edits( | ||
Edit::range_replacement(binding, keyword.range), | ||
[import_edit], | ||
) | ||
}) | ||
}); | ||
|
||
checker.diagnostics.push(diagnostic); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
81 changes: 81 additions & 0 deletions
81
...ter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB180_FURB180.py.snap
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
--- | ||
source: crates/ruff_linter/src/rules/refurb/mod.rs | ||
--- | ||
FURB180.py:7:10: FURB180 [*] Use of `metaclass=abc.ABCMeta` to define abstract base class | ||
| | ||
5 | # Errors | ||
6 | | ||
7 | class A0(metaclass=abc.ABCMeta): | ||
| ^^^^^^^^^^^^^^^^^^^^^ FURB180 | ||
8 | @abstractmethod | ||
9 | def foo(self): pass | ||
| | ||
= help: Replace with `abc.ABC` | ||
|
||
ℹ Safe fix | ||
4 4 | | ||
5 5 | # Errors | ||
6 6 | | ||
7 |-class A0(metaclass=abc.ABCMeta): | ||
7 |+class A0(abc.ABC): | ||
8 8 | @abstractmethod | ||
9 9 | def foo(self): pass | ||
10 10 | | ||
|
||
FURB180.py:12:10: FURB180 [*] Use of `metaclass=abc.ABCMeta` to define abstract base class | ||
| | ||
12 | class A1(metaclass=ABCMeta): | ||
| ^^^^^^^^^^^^^^^^^ FURB180 | ||
13 | @abstractmethod | ||
14 | def foo(self): pass | ||
| | ||
= help: Replace with `abc.ABC` | ||
|
||
ℹ Safe fix | ||
9 9 | def foo(self): pass | ||
10 10 | | ||
11 11 | | ||
12 |-class A1(metaclass=ABCMeta): | ||
12 |+class A1(abc.ABC): | ||
13 13 | @abstractmethod | ||
14 14 | def foo(self): pass | ||
15 15 | | ||
|
||
FURB180.py:26:18: FURB180 [*] Use of `metaclass=abc.ABCMeta` to define abstract base class | ||
| | ||
26 | class A2(B0, B1, metaclass=ABCMeta): | ||
| ^^^^^^^^^^^^^^^^^ FURB180 | ||
27 | @abstractmethod | ||
28 | def foo(self): pass | ||
| | ||
= help: Replace with `abc.ABC` | ||
|
||
ℹ Safe fix | ||
23 23 | pass | ||
24 24 | | ||
25 25 | | ||
26 |-class A2(B0, B1, metaclass=ABCMeta): | ||
26 |+class A2(B0, B1, abc.ABC): | ||
27 27 | @abstractmethod | ||
28 28 | def foo(self): pass | ||
29 29 | | ||
|
||
FURB180.py:31:34: FURB180 [*] Use of `metaclass=abc.ABCMeta` to define abstract base class | ||
| | ||
31 | class A3(B0, before_metaclass=1, metaclass=abc.ABCMeta): | ||
| ^^^^^^^^^^^^^^^^^^^^^ FURB180 | ||
32 | pass | ||
| | ||
= help: Replace with `abc.ABC` | ||
|
||
ℹ Safe fix | ||
28 28 | def foo(self): pass | ||
29 29 | | ||
30 30 | | ||
31 |-class A3(B0, before_metaclass=1, metaclass=abc.ABCMeta): | ||
31 |+class A3(B0, abc.ABC, before_metaclass=1): | ||
32 32 | pass | ||
33 33 | | ||
34 34 | | ||
|
||
|
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.