[pyupgrade]: new rule UP050 (useless-class-metaclass-type) (#18334)

Co-authored-by: Micha Reiser <micha@reiser.io>
This commit is contained in:
chiri 2025-05-28 10:22:44 +03:00 committed by GitHub
parent 602dd5c039
commit 9ce83c215d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 409 additions and 0 deletions

View 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):
...

View file

@ -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);

View file

@ -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),

View file

@ -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(

View file

@ -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;

View file

@ -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);
}
}
}
}

View file

@ -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
View file

@ -4299,6 +4299,8 @@
"UP046",
"UP047",
"UP049",
"UP05",
"UP050",
"W",
"W1",
"W19",