Use Arguments node to power remove_argument method (#6278)

## Summary

Internal refactor to take advantage of the new `Arguments` node, to
power our `remove_argument` fix action.

## Test Plan

`cargo test`
This commit is contained in:
Charlie Marsh 2023-08-02 12:38:43 -04:00 committed by GitHub
parent 4c53bfe896
commit 8c40886f87
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 165 additions and 231 deletions

View file

@ -1,14 +1,15 @@
//! Interface for generating autofix edits from higher-level actions (e.g., "remove an argument").
use anyhow::{bail, Result};
use ruff_python_ast::{self as ast, ExceptHandler, Expr, Keyword, Ranged, Stmt};
use ruff_python_parser::{lexer, Mode};
use ruff_text_size::{TextLen, TextRange, TextSize};
use ruff_diagnostics::Edit;
use ruff_python_ast::{self as ast, Arguments, ExceptHandler, Expr, Keyword, Ranged, Stmt};
use ruff_python_codegen::Stylist;
use ruff_python_index::Indexer;
use ruff_python_parser::{lexer, Mode};
use ruff_python_trivia::{has_leading_content, is_python_whitespace, PythonWhitespace};
use ruff_source_file::{Locator, NewlineWithTrailingNewline};
use ruff_text_size::{TextLen, TextRange, TextSize};
use crate::autofix::codemods;
@ -68,108 +69,101 @@ pub(crate) fn remove_unused_imports<'a>(
}
}
#[derive(Debug, Copy, Clone)]
pub(crate) enum Parentheses {
/// Remove parentheses, if the removed argument is the only argument left.
Remove,
/// Preserve parentheses, even if the removed argument is the only argument
Preserve,
}
/// Generic function to remove arguments or keyword arguments in function
/// calls and class definitions. (For classes `args` should be considered
/// `bases`)
///
/// Supports the removal of parentheses when this is the only (kw)arg left.
/// For this behavior, set `remove_parentheses` to `true`.
///
/// TODO(charlie): Migrate this signature to take [`Arguments`] rather than
/// separate args and keywords.
pub(crate) fn remove_argument(
pub(crate) fn remove_argument<T: Ranged>(
argument: &T,
arguments: &Arguments,
parentheses: Parentheses,
locator: &Locator,
call_at: TextSize,
expr_range: TextRange,
args: &[Expr],
keywords: &[Keyword],
remove_parentheses: bool,
) -> Result<Edit> {
// TODO(sbrugman): Preserve trailing comments.
let contents = locator.after(call_at);
if arguments.keywords.len() + arguments.args.len() > 1 {
let mut fix_start = None;
let mut fix_end = None;
let mut fix_start = None;
let mut fix_end = None;
let n_arguments = keywords.len() + args.len();
if n_arguments == 0 {
bail!("No arguments or keywords to remove");
}
if n_arguments == 1 {
// Case 1: there is only one argument.
let mut count = 0u32;
for (tok, range) in lexer::lex_starts_at(contents, Mode::Module, call_at).flatten() {
if tok.is_lpar() {
if count == 0 {
fix_start = Some(if remove_parentheses {
range.start()
} else {
range.start() + TextSize::from(1)
});
}
count = count.saturating_add(1);
}
if tok.is_rpar() {
count = count.saturating_sub(1);
if count == 0 {
fix_end = Some(if remove_parentheses {
if arguments
.args
.iter()
.map(Expr::start)
.chain(arguments.keywords.iter().map(Keyword::start))
.any(|location| location > argument.start())
{
// Case 1: argument or keyword is _not_ the last node, so delete from the start of the
// argument to the end of the subsequent comma.
let mut seen_comma = false;
for (tok, range) in lexer::lex_starts_at(
locator.slice(arguments.range()),
Mode::Module,
arguments.start(),
)
.flatten()
{
if seen_comma {
if tok.is_non_logical_newline() {
// Also delete any non-logical newlines after the comma.
continue;
}
fix_end = Some(if tok.is_newline() {
range.end()
} else {
range.end() - TextSize::from(1)
range.start()
});
break;
}
if range.start() == argument.start() {
fix_start = Some(range.start());
}
if fix_start.is_some() && tok.is_comma() {
seen_comma = true;
}
}
} else {
// Case 2: argument or keyword is the last node, so delete from the start of the
// previous comma to the end of the argument.
for (tok, range) in lexer::lex_starts_at(
locator.slice(arguments.range()),
Mode::Module,
arguments.start(),
)
.flatten()
{
if range.start() == argument.start() {
fix_end = Some(argument.end());
break;
}
if tok.is_comma() {
fix_start = Some(range.start());
}
}
}
} else if args
.iter()
.map(Expr::start)
.chain(keywords.iter().map(Keyword::start))
.any(|location| location > expr_range.start())
{
// Case 2: argument or keyword is _not_ the last node.
let mut seen_comma = false;
for (tok, range) in lexer::lex_starts_at(contents, Mode::Module, call_at).flatten() {
if seen_comma {
if tok.is_non_logical_newline() {
// Also delete any non-logical newlines after the comma.
continue;
}
fix_end = Some(if tok.is_newline() {
range.end()
} else {
range.start()
});
break;
}
if range.start() == expr_range.start() {
fix_start = Some(range.start());
}
if fix_start.is_some() && tok.is_comma() {
seen_comma = true;
match (fix_start, fix_end) {
(Some(start), Some(end)) => Ok(Edit::deletion(start, end)),
_ => {
bail!("No fix could be constructed")
}
}
} else {
// Case 3: argument or keyword is the last node, so we have to find the last
// comma in the stmt.
for (tok, range) in lexer::lex_starts_at(contents, Mode::Module, call_at).flatten() {
if range.start() == expr_range.start() {
fix_end = Some(expr_range.end());
break;
// Only one argument; remove it (but preserve parentheses, if needed).
Ok(match parentheses {
Parentheses::Remove => Edit::deletion(arguments.start(), arguments.end()),
Parentheses::Preserve => {
Edit::replacement("()".to_string(), arguments.start(), arguments.end())
}
if tok.is_comma() {
fix_start = Some(range.start());
}
}
}
match (fix_start, fix_end) {
(Some(start), Some(end)) => Ok(Edit::deletion(start, end)),
_ => {
bail!("No fix could be constructed")
}
})
}
}
@ -297,11 +291,11 @@ fn next_stmt_break(semicolon: TextSize, locator: &Locator) -> TextSize {
#[cfg(test)]
mod tests {
use anyhow::Result;
use ruff_python_ast::Ranged;
use ruff_python_parser::parse_suite;
use ruff_text_size::TextSize;
use ruff_source_file::Locator;
use ruff_text_size::TextSize;
use crate::autofix::edits::{next_stmt_break, trailing_semicolon};

View file

@ -426,7 +426,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
pyupgrade::rules::super_call_with_parameters(checker, expr, func, args);
}
if checker.enabled(Rule::UnnecessaryEncodeUTF8) {
pyupgrade::rules::unnecessary_encode_utf8(checker, expr, func, args, keywords);
pyupgrade::rules::unnecessary_encode_utf8(checker, call);
}
if checker.enabled(Rule::RedundantOpenModes) {
pyupgrade::rules::redundant_open_modes(checker, expr);
@ -441,7 +441,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
pyupgrade::rules::replace_universal_newlines(checker, func, keywords);
}
if checker.enabled(Rule::ReplaceStdoutStderr) {
pyupgrade::rules::replace_stdout_stderr(checker, expr, func, args, keywords);
pyupgrade::rules::replace_stdout_stderr(checker, call);
}
if checker.enabled(Rule::OSErrorAlias) {
pyupgrade::rules::os_error_alias_call(checker, func);
@ -677,7 +677,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
flake8_debugger::rules::debugger_call(checker, expr, func);
}
if checker.enabled(Rule::PandasUseOfInplaceArgument) {
pandas_vet::rules::inplace_argument(checker, expr, func, args, keywords);
pandas_vet::rules::inplace_argument(checker, call);
}
pandas_vet::rules::call(checker, func);
if checker.enabled(Rule::PandasUseOfDotReadTable) {

View file

@ -1,9 +1,5 @@
use std::fmt;
use ruff_python_ast::{self as ast, Expr, ParameterWithDefault, Parameters, Ranged, Stmt};
use ruff_python_ast::{Arguments, Decorator};
use ruff_text_size::{TextLen, TextRange};
use ruff_diagnostics::{AlwaysAutofixableViolation, Violation};
use ruff_diagnostics::{Diagnostic, Edit, Fix};
use ruff_macros::{derive_message_formats, violation};
@ -12,10 +8,13 @@ use ruff_python_ast::helpers::{find_keyword, includes_arg_name};
use ruff_python_ast::identifier::Identifier;
use ruff_python_ast::visitor;
use ruff_python_ast::visitor::Visitor;
use ruff_python_ast::Decorator;
use ruff_python_ast::{self as ast, Expr, ParameterWithDefault, Parameters, Ranged, Stmt};
use ruff_python_semantic::analyze::visibility::is_abstract;
use ruff_python_semantic::SemanticModel;
use ruff_text_size::{TextLen, TextRange};
use crate::autofix::edits::remove_argument;
use crate::autofix::edits;
use crate::checkers::ast::Checker;
use crate::registry::{AsRule, Rule};
@ -476,18 +475,13 @@ fn check_fixture_decorator(checker: &mut Checker, func_name: &str, decorator: &D
match &decorator.expression {
Expr::Call(ast::ExprCall {
func,
arguments:
Arguments {
args,
keywords,
range: _,
},
arguments,
range: _,
}) => {
if checker.enabled(Rule::PytestFixtureIncorrectParenthesesStyle) {
if !checker.settings.flake8_pytest_style.fixture_parentheses
&& args.is_empty()
&& keywords.is_empty()
&& arguments.args.is_empty()
&& arguments.keywords.is_empty()
{
let fix = Fix::automatic(Edit::deletion(func.end(), decorator.end()));
pytest_fixture_parentheses(
@ -501,7 +495,7 @@ fn check_fixture_decorator(checker: &mut Checker, func_name: &str, decorator: &D
}
if checker.enabled(Rule::PytestFixturePositionalArgs) {
if !args.is_empty() {
if !arguments.args.is_empty() {
checker.diagnostics.push(Diagnostic::new(
PytestFixturePositionalArgs {
function: func_name.to_string(),
@ -512,19 +506,17 @@ fn check_fixture_decorator(checker: &mut Checker, func_name: &str, decorator: &D
}
if checker.enabled(Rule::PytestExtraneousScopeFunction) {
if let Some(scope_keyword) = find_keyword(keywords, "scope") {
if keyword_is_literal(scope_keyword, "function") {
if let Some(keyword) = find_keyword(&arguments.keywords, "scope") {
if keyword_is_literal(keyword, "function") {
let mut diagnostic =
Diagnostic::new(PytestExtraneousScopeFunction, scope_keyword.range());
Diagnostic::new(PytestExtraneousScopeFunction, keyword.range());
if checker.patch(diagnostic.kind.rule()) {
diagnostic.try_set_fix(|| {
remove_argument(
edits::remove_argument(
keyword,
arguments,
edits::Parentheses::Preserve,
checker.locator(),
func.end(),
scope_keyword.range,
args,
keywords,
false,
)
.map(Fix::suggested)
});

View file

@ -1,13 +1,11 @@
use ruff_python_ast::{Expr, Keyword, Ranged};
use ruff_text_size::TextRange;
use ruff_diagnostics::{AutofixKind, Diagnostic, Edit, Fix, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::helpers::is_const_true;
use ruff_python_ast::{self as ast, Keyword, Ranged};
use ruff_python_semantic::{BindingKind, Import};
use ruff_source_file::Locator;
use crate::autofix::edits::remove_argument;
use crate::autofix::edits::{remove_argument, Parentheses};
use crate::checkers::ast::Checker;
use crate::registry::AsRule;
@ -52,15 +50,9 @@ impl Violation for PandasUseOfInplaceArgument {
}
/// PD002
pub(crate) fn inplace_argument(
checker: &mut Checker,
expr: &Expr,
func: &Expr,
args: &[Expr],
keywords: &[Keyword],
) {
pub(crate) fn inplace_argument(checker: &mut Checker, call: &ast::ExprCall) {
// If the function was imported from another module, and it's _not_ Pandas, abort.
if let Some(call_path) = checker.semantic().resolve_call_path(func) {
if let Some(call_path) = checker.semantic().resolve_call_path(&call.func) {
if !call_path
.first()
.and_then(|module| checker.semantic().find_binding(module))
@ -78,7 +70,7 @@ pub(crate) fn inplace_argument(
}
let mut seen_star = false;
for keyword in keywords.iter().rev() {
for keyword in call.arguments.keywords.iter().rev() {
let Some(arg) = &keyword.arg else {
seen_star = true;
continue;
@ -101,13 +93,9 @@ pub(crate) fn inplace_argument(
&& checker.semantic().expr_parent().is_none()
&& !checker.semantic().scope().kind.is_lambda()
{
if let Some(fix) = convert_inplace_argument_to_assignment(
checker.locator(),
expr,
keyword.range(),
args,
keywords,
) {
if let Some(fix) =
convert_inplace_argument_to_assignment(checker.locator(), call, keyword)
{
diagnostic.set_fix(fix);
}
}
@ -126,22 +114,19 @@ pub(crate) fn inplace_argument(
/// assignment.
fn convert_inplace_argument_to_assignment(
locator: &Locator,
expr: &Expr,
expr_range: TextRange,
args: &[Expr],
keywords: &[Keyword],
call: &ast::ExprCall,
keyword: &Keyword,
) -> Option<Fix> {
// Add the assignment.
let call = expr.as_call_expr()?;
let attr = call.func.as_attribute_expr()?;
let insert_assignment = Edit::insertion(
format!("{name} = ", name = locator.slice(attr.value.range())),
expr.start(),
call.start(),
);
// Remove the `inplace` argument.
let remove_argument =
remove_argument(locator, call.func.end(), expr_range, args, keywords, false).ok()?;
remove_argument(keyword, &call.arguments, Parentheses::Preserve, locator).ok()?;
Some(Fix::suggested_edits(insert_assignment, [remove_argument]))
}

View file

@ -1,12 +1,12 @@
use anyhow::Result;
use ruff_python_ast::{Expr, Keyword, Ranged};
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::helpers::find_keyword;
use ruff_python_ast::{self as ast, Keyword, Ranged};
use ruff_source_file::Locator;
use crate::autofix::edits::remove_argument;
use crate::autofix::edits::{remove_argument, Parentheses};
use crate::checkers::ast::Checker;
use crate::registry::AsRule;
@ -53,12 +53,10 @@ impl AlwaysAutofixableViolation for ReplaceStdoutStderr {
/// Generate a [`Edit`] for a `stdout` and `stderr` [`Keyword`] pair.
fn generate_fix(
locator: &Locator,
func: &Expr,
args: &[Expr],
keywords: &[Keyword],
stdout: &Keyword,
stderr: &Keyword,
call: &ast::ExprCall,
locator: &Locator,
) -> Result<Fix> {
let (first, second) = if stdout.start() < stderr.start() {
(stdout, stderr)
@ -68,34 +66,26 @@ fn generate_fix(
Ok(Fix::suggested_edits(
Edit::range_replacement("capture_output=True".to_string(), first.range()),
[remove_argument(
second,
&call.arguments,
Parentheses::Preserve,
locator,
func.end(),
second.range(),
args,
keywords,
false,
)?],
))
}
/// UP022
pub(crate) fn replace_stdout_stderr(
checker: &mut Checker,
expr: &Expr,
func: &Expr,
args: &[Expr],
keywords: &[Keyword],
) {
pub(crate) fn replace_stdout_stderr(checker: &mut Checker, call: &ast::ExprCall) {
if checker
.semantic()
.resolve_call_path(func)
.resolve_call_path(&call.func)
.is_some_and(|call_path| matches!(call_path.as_slice(), ["subprocess", "run"]))
{
// Find `stdout` and `stderr` kwargs.
let Some(stdout) = find_keyword(keywords, "stdout") else {
let Some(stdout) = find_keyword(&call.arguments.keywords, "stdout") else {
return;
};
let Some(stderr) = find_keyword(keywords, "stderr") else {
let Some(stderr) = find_keyword(&call.arguments.keywords, "stderr") else {
return;
};
@ -112,11 +102,9 @@ pub(crate) fn replace_stdout_stderr(
return;
}
let mut diagnostic = Diagnostic::new(ReplaceStdoutStderr, expr.range());
let mut diagnostic = Diagnostic::new(ReplaceStdoutStderr, call.range());
if checker.patch(diagnostic.kind.rule()) {
diagnostic.try_set_fix(|| {
generate_fix(checker.locator(), func, args, keywords, stdout, stderr)
});
diagnostic.try_set_fix(|| generate_fix(stdout, stderr, call, checker.locator()));
}
checker.diagnostics.push(diagnostic);
}

View file

@ -1,12 +1,11 @@
use ruff_python_ast::{self as ast, Constant, Expr, Keyword, Ranged};
use ruff_python_parser::{lexer, Mode, Tok};
use ruff_text_size::TextRange;
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::{self as ast, Arguments, Constant, Expr, Keyword, Ranged};
use ruff_python_parser::{lexer, Mode, Tok};
use ruff_source_file::Locator;
use ruff_text_size::TextRange;
use crate::autofix::edits::remove_argument;
use crate::autofix::edits::{remove_argument, Parentheses};
use crate::checkers::ast::Checker;
use crate::registry::Rule;
@ -95,23 +94,21 @@ enum EncodingArg<'a> {
/// Return the encoding argument to an `encode` call, if it can be determined to be a
/// UTF-8-equivalent encoding.
fn match_encoding_arg<'a>(args: &'a [Expr], kwargs: &'a [Keyword]) -> Option<EncodingArg<'a>> {
match (args.len(), kwargs.len()) {
fn match_encoding_arg(arguments: &Arguments) -> Option<EncodingArg> {
match (arguments.args.as_slice(), arguments.keywords.as_slice()) {
// Ex `"".encode()`
(0, 0) => return Some(EncodingArg::Empty),
([], []) => return Some(EncodingArg::Empty),
// Ex `"".encode(encoding)`
(1, 0) => {
let arg = &args[0];
([arg], []) => {
if is_utf8_encoding_arg(arg) {
return Some(EncodingArg::Positional(arg));
}
}
// Ex `"".encode(kwarg=kwarg)`
(0, 1) => {
let kwarg = &kwargs[0];
if kwarg.arg.as_ref().is_some_and(|arg| arg == "encoding") {
if is_utf8_encoding_arg(&kwarg.value) {
return Some(EncodingArg::Keyword(kwarg));
([], [keyword]) => {
if keyword.arg.as_ref().is_some_and(|arg| arg == "encoding") {
if is_utf8_encoding_arg(&keyword.value) {
return Some(EncodingArg::Keyword(keyword));
}
}
}
@ -122,7 +119,7 @@ fn match_encoding_arg<'a>(args: &'a [Expr], kwargs: &'a [Keyword]) -> Option<Enc
}
/// Return a [`Fix`] replacing the call to encode with a byte string.
fn replace_with_bytes_literal(locator: &Locator, expr: &Expr) -> Fix {
fn replace_with_bytes_literal<T: Ranged>(locator: &Locator, expr: &T) -> Fix {
// Build up a replacement string by prefixing all string tokens with `b`.
let contents = locator.slice(expr.range());
let mut replacement = String::with_capacity(contents.len() + 1);
@ -149,14 +146,8 @@ fn replace_with_bytes_literal(locator: &Locator, expr: &Expr) -> Fix {
}
/// UP012
pub(crate) fn unnecessary_encode_utf8(
checker: &mut Checker,
expr: &Expr,
func: &Expr,
args: &[Expr],
kwargs: &[Keyword],
) {
let Some(variable) = match_encoded_variable(func) else {
pub(crate) fn unnecessary_encode_utf8(checker: &mut Checker, call: &ast::ExprCall) {
let Some(variable) = match_encoded_variable(&call.func) else {
return;
};
match variable {
@ -165,17 +156,17 @@ pub(crate) fn unnecessary_encode_utf8(
..
}) => {
// Ex) `"str".encode()`, `"str".encode("utf-8")`
if let Some(encoding_arg) = match_encoding_arg(args, kwargs) {
if let Some(encoding_arg) = match_encoding_arg(&call.arguments) {
if literal.is_ascii() {
// Ex) Convert `"foo".encode()` to `b"foo"`.
let mut diagnostic = Diagnostic::new(
UnnecessaryEncodeUTF8 {
reason: Reason::BytesLiteral,
},
expr.range(),
call.range(),
);
if checker.patch(Rule::UnnecessaryEncodeUTF8) {
diagnostic.set_fix(replace_with_bytes_literal(checker.locator(), expr));
diagnostic.set_fix(replace_with_bytes_literal(checker.locator(), call));
}
checker.diagnostics.push(diagnostic);
} else if let EncodingArg::Keyword(kwarg) = encoding_arg {
@ -185,17 +176,15 @@ pub(crate) fn unnecessary_encode_utf8(
UnnecessaryEncodeUTF8 {
reason: Reason::DefaultArgument,
},
expr.range(),
call.range(),
);
if checker.patch(Rule::UnnecessaryEncodeUTF8) {
diagnostic.try_set_fix(|| {
remove_argument(
kwarg,
&call.arguments,
Parentheses::Preserve,
checker.locator(),
func.end(),
kwarg.range(),
args,
kwargs,
false,
)
.map(Fix::automatic)
});
@ -207,17 +196,15 @@ pub(crate) fn unnecessary_encode_utf8(
UnnecessaryEncodeUTF8 {
reason: Reason::DefaultArgument,
},
expr.range(),
call.range(),
);
if checker.patch(Rule::UnnecessaryEncodeUTF8) {
diagnostic.try_set_fix(|| {
remove_argument(
arg,
&call.arguments,
Parentheses::Preserve,
checker.locator(),
func.end(),
arg.range(),
args,
kwargs,
false,
)
.map(Fix::automatic)
});
@ -228,7 +215,7 @@ pub(crate) fn unnecessary_encode_utf8(
}
// Ex) `f"foo{bar}".encode("utf-8")`
Expr::JoinedStr(_) => {
if let Some(encoding_arg) = match_encoding_arg(args, kwargs) {
if let Some(encoding_arg) = match_encoding_arg(&call.arguments) {
if let EncodingArg::Keyword(kwarg) = encoding_arg {
// Ex) Convert `f"unicode text©".encode(encoding="utf-8")` to
// `f"unicode text©".encode()`.
@ -236,17 +223,15 @@ pub(crate) fn unnecessary_encode_utf8(
UnnecessaryEncodeUTF8 {
reason: Reason::DefaultArgument,
},
expr.range(),
call.range(),
);
if checker.patch(Rule::UnnecessaryEncodeUTF8) {
diagnostic.try_set_fix(|| {
remove_argument(
kwarg,
&call.arguments,
Parentheses::Preserve,
checker.locator(),
func.end(),
kwarg.range(),
args,
kwargs,
false,
)
.map(Fix::automatic)
});
@ -258,17 +243,15 @@ pub(crate) fn unnecessary_encode_utf8(
UnnecessaryEncodeUTF8 {
reason: Reason::DefaultArgument,
},
expr.range(),
call.range(),
);
if checker.patch(Rule::UnnecessaryEncodeUTF8) {
diagnostic.try_set_fix(|| {
remove_argument(
arg,
&call.arguments,
Parentheses::Preserve,
checker.locator(),
func.end(),
arg.range(),
args,
kwargs,
false,
)
.map(Fix::automatic)
});

View file

@ -1,9 +1,8 @@
use ruff_python_ast::{self as ast, Expr, Ranged};
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Fix};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::{self as ast, Expr, Ranged};
use crate::autofix::edits::remove_argument;
use crate::autofix::edits::{remove_argument, Parentheses};
use crate::checkers::ast::Checker;
use crate::registry::AsRule;
@ -51,8 +50,8 @@ pub(crate) fn useless_object_inheritance(checker: &mut Checker, class_def: &ast:
return;
};
for expr in &arguments.args {
let Expr::Name(ast::ExprName { id, .. }) = expr else {
for base in &arguments.args {
let Expr::Name(ast::ExprName { id, .. }) = base else {
continue;
};
if id != "object" {
@ -66,19 +65,12 @@ pub(crate) fn useless_object_inheritance(checker: &mut Checker, class_def: &ast:
UselessObjectInheritance {
name: class_def.name.to_string(),
},
expr.range(),
base.range(),
);
if checker.patch(diagnostic.kind.rule()) {
diagnostic.try_set_fix(|| {
let edit = remove_argument(
checker.locator(),
class_def.name.end(),
expr.range(),
&arguments.args,
&arguments.keywords,
true,
)?;
Ok(Fix::automatic(edit))
remove_argument(base, arguments, Parentheses::Remove, checker.locator())
.map(Fix::automatic)
});
}
checker.diagnostics.push(diagnostic);