mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-29 21:34:57 +00:00
[pyupgrade
]: new rule UP050 (useless-class-metaclass-type
) (#18334)
Co-authored-by: Micha Reiser <micha@reiser.io>
This commit is contained in:
parent
602dd5c039
commit
9ce83c215d
8 changed files with 409 additions and 0 deletions
84
crates/ruff_linter/resources/test/fixtures/pyupgrade/UP050.py
vendored
Normal file
84
crates/ruff_linter/resources/test/fixtures/pyupgrade/UP050.py
vendored
Normal file
|
@ -0,0 +1,84 @@
|
|||
class A:
|
||||
...
|
||||
|
||||
|
||||
class A(metaclass=type):
|
||||
...
|
||||
|
||||
|
||||
class A(
|
||||
metaclass=type
|
||||
):
|
||||
...
|
||||
|
||||
|
||||
class A(
|
||||
metaclass=type
|
||||
#
|
||||
):
|
||||
...
|
||||
|
||||
|
||||
class A(
|
||||
#
|
||||
metaclass=type
|
||||
):
|
||||
...
|
||||
|
||||
|
||||
class A(
|
||||
metaclass=type,
|
||||
#
|
||||
):
|
||||
...
|
||||
|
||||
|
||||
class A(
|
||||
#
|
||||
metaclass=type,
|
||||
#
|
||||
):
|
||||
...
|
||||
|
||||
|
||||
class B(A, metaclass=type):
|
||||
...
|
||||
|
||||
|
||||
class B(
|
||||
A,
|
||||
metaclass=type,
|
||||
):
|
||||
...
|
||||
|
||||
|
||||
class B(
|
||||
A,
|
||||
# comment
|
||||
metaclass=type,
|
||||
):
|
||||
...
|
||||
|
||||
|
||||
def foo():
|
||||
class A(metaclass=type):
|
||||
...
|
||||
|
||||
|
||||
class A(
|
||||
metaclass=type # comment
|
||||
,
|
||||
):
|
||||
...
|
||||
|
||||
|
||||
type = str
|
||||
|
||||
class Foo(metaclass=type):
|
||||
...
|
||||
|
||||
|
||||
import builtins
|
||||
|
||||
class A(metaclass=builtins.type):
|
||||
...
|
|
@ -439,6 +439,9 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
|||
if checker.enabled(Rule::UselessObjectInheritance) {
|
||||
pyupgrade::rules::useless_object_inheritance(checker, class_def);
|
||||
}
|
||||
if checker.enabled(Rule::UselessClassMetaclassType) {
|
||||
pyupgrade::rules::useless_class_metaclass_type(checker, class_def);
|
||||
}
|
||||
if checker.enabled(Rule::ReplaceStrEnum) {
|
||||
if checker.target_version() >= PythonVersion::PY311 {
|
||||
pyupgrade::rules::replace_str_enum(checker, class_def);
|
||||
|
|
|
@ -552,6 +552,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
|||
(Pyupgrade, "046") => (RuleGroup::Preview, rules::pyupgrade::rules::NonPEP695GenericClass),
|
||||
(Pyupgrade, "047") => (RuleGroup::Preview, rules::pyupgrade::rules::NonPEP695GenericFunction),
|
||||
(Pyupgrade, "049") => (RuleGroup::Preview, rules::pyupgrade::rules::PrivateTypeParameter),
|
||||
(Pyupgrade, "050") => (RuleGroup::Preview, rules::pyupgrade::rules::UselessClassMetaclassType),
|
||||
|
||||
// pydocstyle
|
||||
(Pydocstyle, "100") => (RuleGroup::Stable, rules::pydocstyle::rules::UndocumentedPublicModule),
|
||||
|
|
|
@ -111,6 +111,7 @@ mod tests {
|
|||
#[test_case(Rule::NonPEP695GenericFunction, Path::new("UP047.py"))]
|
||||
#[test_case(Rule::PrivateTypeParameter, Path::new("UP049_0.py"))]
|
||||
#[test_case(Rule::PrivateTypeParameter, Path::new("UP049_1.py"))]
|
||||
#[test_case(Rule::UselessClassMetaclassType, Path::new("UP050.py"))]
|
||||
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
|
||||
let snapshot = path.to_string_lossy().to_string();
|
||||
let diagnostics = test_path(
|
||||
|
|
|
@ -37,6 +37,7 @@ pub(crate) use unpacked_list_comprehension::*;
|
|||
pub(crate) use use_pep585_annotation::*;
|
||||
pub(crate) use use_pep604_annotation::*;
|
||||
pub(crate) use use_pep604_isinstance::*;
|
||||
pub(crate) use useless_class_metaclass_type::*;
|
||||
pub(crate) use useless_metaclass_type::*;
|
||||
pub(crate) use useless_object_inheritance::*;
|
||||
pub(crate) use yield_in_for_loop::*;
|
||||
|
@ -80,6 +81,7 @@ mod unpacked_list_comprehension;
|
|||
mod use_pep585_annotation;
|
||||
mod use_pep604_annotation;
|
||||
mod use_pep604_isinstance;
|
||||
mod useless_class_metaclass_type;
|
||||
mod useless_metaclass_type;
|
||||
mod useless_object_inheritance;
|
||||
mod yield_in_for_loop;
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
use crate::checkers::ast::Checker;
|
||||
use crate::fix::edits::{Parentheses, remove_argument};
|
||||
use ruff_diagnostics::{Diagnostic, Fix, FixAvailability, Violation};
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::StmtClassDef;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for `metaclass=type` in class definitions.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Since Python 3, the default metaclass is `type`, so specifying it explicitly is redundant.
|
||||
///
|
||||
/// Even though `__prepare__` is not required, the default metaclass (`type`) implements it,
|
||||
/// for the convenience of subclasses calling it via `super()`.
|
||||
/// ## Example
|
||||
///
|
||||
/// ```python
|
||||
/// class Foo(metaclass=type): ...
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
///
|
||||
/// ```python
|
||||
/// class Foo: ...
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [PEP 3115 – Metaclasses in Python 3000](https://peps.python.org/pep-3115/)
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct UselessClassMetaclassType {
|
||||
name: String,
|
||||
}
|
||||
|
||||
impl Violation for UselessClassMetaclassType {
|
||||
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;
|
||||
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
let UselessClassMetaclassType { name } = self;
|
||||
format!("Class `{name}` uses `metaclass=type`, which is redundant")
|
||||
}
|
||||
|
||||
fn fix_title(&self) -> Option<String> {
|
||||
Some("Remove `metaclass=type`".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
/// UP050
|
||||
pub(crate) fn useless_class_metaclass_type(checker: &Checker, class_def: &StmtClassDef) {
|
||||
let Some(arguments) = class_def.arguments.as_deref() else {
|
||||
return;
|
||||
};
|
||||
|
||||
for keyword in &arguments.keywords {
|
||||
if let (Some("metaclass"), expr) = (keyword.arg.as_deref(), &keyword.value) {
|
||||
if checker.semantic().match_builtin_expr(expr, "type") {
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
UselessClassMetaclassType {
|
||||
name: class_def.name.to_string(),
|
||||
},
|
||||
keyword.range(),
|
||||
);
|
||||
|
||||
diagnostic.try_set_fix(|| {
|
||||
remove_argument(
|
||||
keyword,
|
||||
arguments,
|
||||
Parentheses::Remove,
|
||||
checker.locator().contents(),
|
||||
)
|
||||
.map(Fix::safe_edit)
|
||||
});
|
||||
|
||||
checker.report_diagnostic(diagnostic);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,237 @@
|
|||
---
|
||||
source: crates/ruff_linter/src/rules/pyupgrade/mod.rs
|
||||
---
|
||||
UP050.py:5:9: UP050 [*] Class `A` uses `metaclass=type`, which is redundant
|
||||
|
|
||||
5 | class A(metaclass=type):
|
||||
| ^^^^^^^^^^^^^^ UP050
|
||||
6 | ...
|
||||
|
|
||||
= help: Remove `metaclass=type`
|
||||
|
||||
ℹ Safe fix
|
||||
2 2 | ...
|
||||
3 3 |
|
||||
4 4 |
|
||||
5 |-class A(metaclass=type):
|
||||
5 |+class A:
|
||||
6 6 | ...
|
||||
7 7 |
|
||||
8 8 |
|
||||
|
||||
UP050.py:10:5: UP050 [*] Class `A` uses `metaclass=type`, which is redundant
|
||||
|
|
||||
9 | class A(
|
||||
10 | metaclass=type
|
||||
| ^^^^^^^^^^^^^^ UP050
|
||||
11 | ):
|
||||
12 | ...
|
||||
|
|
||||
= help: Remove `metaclass=type`
|
||||
|
||||
ℹ Safe fix
|
||||
6 6 | ...
|
||||
7 7 |
|
||||
8 8 |
|
||||
9 |-class A(
|
||||
10 |- metaclass=type
|
||||
11 |-):
|
||||
9 |+class A:
|
||||
12 10 | ...
|
||||
13 11 |
|
||||
14 12 |
|
||||
|
||||
UP050.py:16:5: UP050 [*] Class `A` uses `metaclass=type`, which is redundant
|
||||
|
|
||||
15 | class A(
|
||||
16 | metaclass=type
|
||||
| ^^^^^^^^^^^^^^ UP050
|
||||
17 | #
|
||||
18 | ):
|
||||
|
|
||||
= help: Remove `metaclass=type`
|
||||
|
||||
ℹ Safe fix
|
||||
12 12 | ...
|
||||
13 13 |
|
||||
14 14 |
|
||||
15 |-class A(
|
||||
16 |- metaclass=type
|
||||
17 |- #
|
||||
18 |-):
|
||||
15 |+class A:
|
||||
19 16 | ...
|
||||
20 17 |
|
||||
21 18 |
|
||||
|
||||
UP050.py:24:5: UP050 [*] Class `A` uses `metaclass=type`, which is redundant
|
||||
|
|
||||
22 | class A(
|
||||
23 | #
|
||||
24 | metaclass=type
|
||||
| ^^^^^^^^^^^^^^ UP050
|
||||
25 | ):
|
||||
26 | ...
|
||||
|
|
||||
= help: Remove `metaclass=type`
|
||||
|
||||
ℹ Safe fix
|
||||
19 19 | ...
|
||||
20 20 |
|
||||
21 21 |
|
||||
22 |-class A(
|
||||
23 |- #
|
||||
24 |- metaclass=type
|
||||
25 |-):
|
||||
22 |+class A:
|
||||
26 23 | ...
|
||||
27 24 |
|
||||
28 25 |
|
||||
|
||||
UP050.py:30:5: UP050 [*] Class `A` uses `metaclass=type`, which is redundant
|
||||
|
|
||||
29 | class A(
|
||||
30 | metaclass=type,
|
||||
| ^^^^^^^^^^^^^^ UP050
|
||||
31 | #
|
||||
32 | ):
|
||||
|
|
||||
= help: Remove `metaclass=type`
|
||||
|
||||
ℹ Safe fix
|
||||
26 26 | ...
|
||||
27 27 |
|
||||
28 28 |
|
||||
29 |-class A(
|
||||
30 |- metaclass=type,
|
||||
31 |- #
|
||||
32 |-):
|
||||
29 |+class A:
|
||||
33 30 | ...
|
||||
34 31 |
|
||||
35 32 |
|
||||
|
||||
UP050.py:38:5: UP050 [*] Class `A` uses `metaclass=type`, which is redundant
|
||||
|
|
||||
36 | class A(
|
||||
37 | #
|
||||
38 | metaclass=type,
|
||||
| ^^^^^^^^^^^^^^ UP050
|
||||
39 | #
|
||||
40 | ):
|
||||
|
|
||||
= help: Remove `metaclass=type`
|
||||
|
||||
ℹ Safe fix
|
||||
33 33 | ...
|
||||
34 34 |
|
||||
35 35 |
|
||||
36 |-class A(
|
||||
37 |- #
|
||||
38 |- metaclass=type,
|
||||
39 |- #
|
||||
40 |-):
|
||||
36 |+class A:
|
||||
41 37 | ...
|
||||
42 38 |
|
||||
43 39 |
|
||||
|
||||
UP050.py:44:12: UP050 [*] Class `B` uses `metaclass=type`, which is redundant
|
||||
|
|
||||
44 | class B(A, metaclass=type):
|
||||
| ^^^^^^^^^^^^^^ UP050
|
||||
45 | ...
|
||||
|
|
||||
= help: Remove `metaclass=type`
|
||||
|
||||
ℹ Safe fix
|
||||
41 41 | ...
|
||||
42 42 |
|
||||
43 43 |
|
||||
44 |-class B(A, metaclass=type):
|
||||
44 |+class B(A):
|
||||
45 45 | ...
|
||||
46 46 |
|
||||
47 47 |
|
||||
|
||||
UP050.py:50:5: UP050 [*] Class `B` uses `metaclass=type`, which is redundant
|
||||
|
|
||||
48 | class B(
|
||||
49 | A,
|
||||
50 | metaclass=type,
|
||||
| ^^^^^^^^^^^^^^ UP050
|
||||
51 | ):
|
||||
52 | ...
|
||||
|
|
||||
= help: Remove `metaclass=type`
|
||||
|
||||
ℹ Safe fix
|
||||
47 47 |
|
||||
48 48 | class B(
|
||||
49 49 | A,
|
||||
50 |- metaclass=type,
|
||||
51 50 | ):
|
||||
52 51 | ...
|
||||
53 52 |
|
||||
|
||||
UP050.py:58:5: UP050 [*] Class `B` uses `metaclass=type`, which is redundant
|
||||
|
|
||||
56 | A,
|
||||
57 | # comment
|
||||
58 | metaclass=type,
|
||||
| ^^^^^^^^^^^^^^ UP050
|
||||
59 | ):
|
||||
60 | ...
|
||||
|
|
||||
= help: Remove `metaclass=type`
|
||||
|
||||
ℹ Safe fix
|
||||
54 54 |
|
||||
55 55 | class B(
|
||||
56 56 | A,
|
||||
57 |- # comment
|
||||
58 |- metaclass=type,
|
||||
59 57 | ):
|
||||
60 58 | ...
|
||||
61 59 |
|
||||
|
||||
UP050.py:69:5: UP050 [*] Class `A` uses `metaclass=type`, which is redundant
|
||||
|
|
||||
68 | class A(
|
||||
69 | metaclass=type # comment
|
||||
| ^^^^^^^^^^^^^^ UP050
|
||||
70 | ,
|
||||
71 | ):
|
||||
|
|
||||
= help: Remove `metaclass=type`
|
||||
|
||||
ℹ Safe fix
|
||||
65 65 | ...
|
||||
66 66 |
|
||||
67 67 |
|
||||
68 |-class A(
|
||||
69 |- metaclass=type # comment
|
||||
70 |- ,
|
||||
71 |-):
|
||||
68 |+class A:
|
||||
72 69 | ...
|
||||
73 70 |
|
||||
74 71 |
|
||||
|
||||
UP050.py:83:9: UP050 [*] Class `A` uses `metaclass=type`, which is redundant
|
||||
|
|
||||
81 | import builtins
|
||||
82 |
|
||||
83 | class A(metaclass=builtins.type):
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^ UP050
|
||||
84 | ...
|
||||
|
|
||||
= help: Remove `metaclass=type`
|
||||
|
||||
ℹ Safe fix
|
||||
80 80 |
|
||||
81 81 | import builtins
|
||||
82 82 |
|
||||
83 |-class A(metaclass=builtins.type):
|
||||
83 |+class A:
|
||||
84 84 | ...
|
2
ruff.schema.json
generated
2
ruff.schema.json
generated
|
@ -4299,6 +4299,8 @@
|
|||
"UP046",
|
||||
"UP047",
|
||||
"UP049",
|
||||
"UP05",
|
||||
"UP050",
|
||||
"W",
|
||||
"W1",
|
||||
"W19",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue