|
1 | 1 | use itertools::Itertools; |
2 | | - |
3 | 2 | use ruff_diagnostics::Applicability; |
4 | 3 | use ruff_macros::{ViolationMetadata, derive_message_formats}; |
5 | | -use ruff_python_ast::{StmtClassDef}; |
| 4 | +use ruff_python_ast::StmtClassDef; |
6 | 5 | use ruff_python_semantic::analyze; |
7 | | -use ruff_text_size::{Ranged, TextRange}; |
| 6 | +use ruff_text_size::Ranged; |
8 | 7 |
|
9 | 8 | use crate::checkers::ast::Checker; |
| 9 | +use crate::fix::edits::{Parentheses, remove_argument}; |
10 | 10 | use crate::importer::ImportRequest; |
11 | 11 | use crate::{AlwaysFixableViolation, Edit, Fix}; |
12 | 12 |
|
@@ -64,6 +64,12 @@ impl AlwaysFixableViolation for MetaClassABCMeta { |
64 | 64 |
|
65 | 65 | /// FURB180 |
66 | 66 | pub(crate) fn metaclass_abcmeta(checker: &Checker, class_def: &StmtClassDef) { |
| 67 | + |
| 68 | + // Determine whether the class definition contains at least one attribute. |
| 69 | + let Some(arguments) = &class_def.arguments.as_ref() else { |
| 70 | + return; |
| 71 | + }; |
| 72 | + |
67 | 73 | // Identify the `metaclass` keyword. |
68 | 74 | let Some((position, keyword)) = class_def.keywords().iter().find_position(|&keyword| { |
69 | 75 | keyword |
@@ -105,32 +111,42 @@ pub(crate) fn metaclass_abcmeta(checker: &Checker, class_def: &StmtClassDef) { |
105 | 111 | ); |
106 | 112 |
|
107 | 113 | Ok(if position > 0 { |
108 | | - // When the `abc.ABCMeta` is not the first keyword, put `abc.ABC` before the first |
109 | | - // keyword, but only if it not already presented. |
110 | | - let rest = if has_abc { |
| 114 | + // When the `abc.ABCMeta` is not the first keyword and `abc.ABC` is not |
| 115 | + // in base classes put `abc.ABC` before the first keyword argument. |
| 116 | + let edits = if has_abc { |
111 | 117 | vec![] |
112 | 118 | } else { |
113 | 119 | vec![ |
114 | 120 | Edit::insertion(format!("{binding}, "), class_def.keywords()[0].start()), |
115 | 121 | import_edit, |
116 | 122 | ] |
117 | 123 | }; |
| 124 | + |
118 | 125 | Fix::applicable_edits( |
119 | | - // Delete from the previous argument, to the end of the `metaclass` argument. |
120 | | - Edit::range_deletion(TextRange::new( |
121 | | - class_def.keywords()[position - 1].end(), |
122 | | - keyword.end(), |
123 | | - )), |
124 | | - // Insert `abc.ABC` before the first keyword if needed. |
125 | | - rest, |
| 126 | + // Delete the `metaclass` keyword. |
| 127 | + remove_argument( |
| 128 | + keyword, |
| 129 | + arguments, |
| 130 | + Parentheses::Remove, |
| 131 | + checker.source(), |
| 132 | + checker.tokens(), |
| 133 | + )?, |
| 134 | + // Add `abc.ABC` if needed. |
| 135 | + edits, |
126 | 136 | applicability, |
127 | 137 | ) |
128 | 138 | } else { |
129 | | - // If class already inherits the `abc.ABC` we need to delete the entire |
130 | | - // `metaclass` argument, otherwise, replace metaclass-keyword with `abc.ABC`. |
131 | 139 | let edit_action = if has_abc { |
132 | | - Edit::range_deletion(keyword.range) |
| 140 | + // Class already inherits the `abc.ABC`, delete the `metaclass` keyword only. |
| 141 | + remove_argument( |
| 142 | + keyword, |
| 143 | + arguments, |
| 144 | + Parentheses::Remove, |
| 145 | + checker.source(), |
| 146 | + checker.tokens(), |
| 147 | + )? |
133 | 148 | } else { |
| 149 | + // Replace `metaclass` keyword by `abc.ABC`. |
134 | 150 | Edit::range_replacement(binding, keyword.range) |
135 | 151 | }; |
136 | 152 |
|
|
0 commit comments