Audit `remove_argument` usages to use end-of-function (#5480)

## Summary

This PR applies the fix in #5478 to a variety of other call-sites, and
fixes some other range hygienic stuff in the rules that were modified.
This commit is contained in:
Charlie Marsh 2023-07-03 12:21:01 -04:00 committed by GitHub
parent 1e4b88969c
commit d2450c25ab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 245 additions and 315 deletions

View File

@ -4,7 +4,9 @@ x = pd.DataFrame()
x.drop(["a"], axis=1, inplace=True) x.drop(["a"], axis=1, inplace=True)
x.drop(["a"], axis=1, inplace=True) x.y.drop(["a"], axis=1, inplace=True)
x["y"].drop(["a"], axis=1, inplace=True)
x.drop( x.drop(
inplace=True, inplace=True,
@ -23,6 +25,7 @@ x.drop(["a"], axis=1, **kwargs, inplace=True)
x.drop(["a"], axis=1, inplace=True, **kwargs) x.drop(["a"], axis=1, inplace=True, **kwargs)
f(x.drop(["a"], axis=1, inplace=True)) f(x.drop(["a"], axis=1, inplace=True))
x.apply(lambda x: x.sort_values('a', inplace=True)) x.apply(lambda x: x.sort_values("a", inplace=True))
import torch import torch
torch.m.ReLU(inplace=True) # safe because this isn't a pandas call
torch.m.ReLU(inplace=True) # safe because this isn't a pandas call

View File

@ -668,21 +668,17 @@ where
} }
if !self.is_stub { if !self.is_stub {
if self.enabled(Rule::DjangoModelWithoutDunderStr) { if self.enabled(Rule::DjangoModelWithoutDunderStr) {
if let Some(diagnostic) = flake8_django::rules::model_without_dunder_str(self, class_def);
flake8_django::rules::model_without_dunder_str(self, bases, body, stmt)
{
self.diagnostics.push(diagnostic);
}
} }
} }
if self.enabled(Rule::GlobalStatement) { if self.enabled(Rule::GlobalStatement) {
pylint::rules::global_statement(self, name); pylint::rules::global_statement(self, name);
} }
if self.enabled(Rule::UselessObjectInheritance) { if self.enabled(Rule::UselessObjectInheritance) {
pyupgrade::rules::useless_object_inheritance(self, class_def, stmt); pyupgrade::rules::useless_object_inheritance(self, class_def);
} }
if self.enabled(Rule::UnnecessaryClassParentheses) { if self.enabled(Rule::UnnecessaryClassParentheses) {
pyupgrade::rules::unnecessary_class_parentheses(self, class_def, stmt); pyupgrade::rules::unnecessary_class_parentheses(self, class_def);
} }
if self.enabled(Rule::AmbiguousClassName) { if self.enabled(Rule::AmbiguousClassName) {
if let Some(diagnostic) = if let Some(diagnostic) =
@ -2756,17 +2752,12 @@ where
flake8_debugger::rules::debugger_call(self, expr, func); flake8_debugger::rules::debugger_call(self, expr, func);
} }
if self.enabled(Rule::PandasUseOfInplaceArgument) { if self.enabled(Rule::PandasUseOfInplaceArgument) {
self.diagnostics.extend( pandas_vet::rules::inplace_argument(self, expr, func, args, keywords);
pandas_vet::rules::inplace_argument(self, expr, func, args, keywords)
.into_iter(),
);
} }
pandas_vet::rules::call(self, func); pandas_vet::rules::call(self, func);
if self.enabled(Rule::PandasUseOfPdMerge) { if self.enabled(Rule::PandasUseOfPdMerge) {
if let Some(diagnostic) = pandas_vet::rules::use_of_pd_merge(func) { pandas_vet::rules::use_of_pd_merge(self, func);
self.diagnostics.push(diagnostic);
};
} }
if self.enabled(Rule::CallDatetimeWithoutTzinfo) { if self.enabled(Rule::CallDatetimeWithoutTzinfo) {
flake8_datetimez::rules::call_datetime_without_tzinfo( flake8_datetimez::rules::call_datetime_without_tzinfo(

View File

@ -52,21 +52,20 @@ impl Violation for DjangoModelWithoutDunderStr {
/// DJ008 /// DJ008
pub(crate) fn model_without_dunder_str( pub(crate) fn model_without_dunder_str(
checker: &Checker, checker: &mut Checker,
bases: &[Expr], ast::StmtClassDef {
body: &[Stmt], name, bases, body, ..
class_location: &Stmt, }: &ast::StmtClassDef,
) -> Option<Diagnostic> { ) {
if !is_non_abstract_model(bases, body, checker.semantic()) { if !is_non_abstract_model(bases, body, checker.semantic()) {
return None; return;
} }
if !has_dunder_method(body) { if has_dunder_method(body) {
return Some(Diagnostic::new( return;
DjangoModelWithoutDunderStr,
class_location.range(),
));
} }
None checker
.diagnostics
.push(Diagnostic::new(DjangoModelWithoutDunderStr, name.range()));
} }
fn has_dunder_method(body: &[Stmt]) -> bool { fn has_dunder_method(body: &[Stmt]) -> bool {

View File

@ -1,58 +1,26 @@
--- ---
source: crates/ruff/src/rules/flake8_django/mod.rs source: crates/ruff/src/rules/flake8_django/mod.rs
--- ---
DJ008.py:6:1: DJ008 Model does not define `__str__` method DJ008.py:6:7: DJ008 Model does not define `__str__` method
|
5 | # Models without __str__
6 | class TestModel1(models.Model):
| ^^^^^^^^^^ DJ008
7 | new_field = models.CharField(max_length=10)
|
DJ008.py:21:7: DJ008 Model does not define `__str__` method
| |
5 | # Models without __str__ 21 | class TestModel2(Model):
6 | / class TestModel1(models.Model): | ^^^^^^^^^^ DJ008
7 | | new_field = models.CharField(max_length=10) 22 | new_field = models.CharField(max_length=10)
8 | |
9 | | class Meta:
10 | | verbose_name = "test model"
11 | | verbose_name_plural = "test models"
12 | |
13 | | @property
14 | | def my_brand_new_property(self):
15 | | return 1
16 | |
17 | | def my_beautiful_method(self):
18 | | return 2
| |________________^ DJ008
| |
DJ008.py:21:1: DJ008 Model does not define `__str__` method DJ008.py:36:7: DJ008 Model does not define `__str__` method
| |
21 | / class TestModel2(Model): 36 | class TestModel3(Model):
22 | | new_field = models.CharField(max_length=10) | ^^^^^^^^^^ DJ008
23 | | 37 | new_field = models.CharField(max_length=10)
24 | | class Meta:
25 | | verbose_name = "test model"
26 | | verbose_name_plural = "test models"
27 | |
28 | | @property
29 | | def my_brand_new_property(self):
30 | | return 1
31 | |
32 | | def my_beautiful_method(self):
33 | | return 2
| |________________^ DJ008
|
DJ008.py:36:1: DJ008 Model does not define `__str__` method
|
36 | / class TestModel3(Model):
37 | | new_field = models.CharField(max_length=10)
38 | |
39 | | class Meta:
40 | | abstract = False
41 | |
42 | | @property
43 | | def my_brand_new_property(self):
44 | | return 1
45 | |
46 | | def my_beautiful_method(self):
47 | | return 2
| |________________^ DJ008
| |

View File

@ -1,9 +1,8 @@
use std::fmt; use std::fmt;
use anyhow::Result; use ruff_text_size::{TextLen, TextRange};
use ruff_text_size::{TextLen, TextRange, TextSize};
use rustpython_parser::ast::Decorator; use rustpython_parser::ast::Decorator;
use rustpython_parser::ast::{self, ArgWithDefault, Arguments, Expr, Keyword, Ranged, Stmt}; use rustpython_parser::ast::{self, ArgWithDefault, Arguments, Expr, Ranged, Stmt};
use ruff_diagnostics::{AlwaysAutofixableViolation, Violation}; use ruff_diagnostics::{AlwaysAutofixableViolation, Violation};
use ruff_diagnostics::{Diagnostic, Edit, Fix}; use ruff_diagnostics::{Diagnostic, Edit, Fix};
@ -11,7 +10,6 @@ use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::call_path::collect_call_path; use ruff_python_ast::call_path::collect_call_path;
use ruff_python_ast::helpers::collect_arg_names; use ruff_python_ast::helpers::collect_arg_names;
use ruff_python_ast::identifier::Identifier; use ruff_python_ast::identifier::Identifier;
use ruff_python_ast::source_code::Locator;
use ruff_python_ast::visitor; use ruff_python_ast::visitor;
use ruff_python_ast::visitor::Visitor; use ruff_python_ast::visitor::Visitor;
use ruff_python_semantic::analyze::visibility::is_abstract; use ruff_python_semantic::analyze::visibility::is_abstract;
@ -25,21 +23,6 @@ use super::helpers::{
get_mark_decorators, is_pytest_fixture, is_pytest_yield_fixture, keyword_is_literal, get_mark_decorators, is_pytest_fixture, is_pytest_yield_fixture, keyword_is_literal,
}; };
#[derive(Debug, PartialEq, Eq)]
pub(crate) enum Parentheses {
None,
Empty,
}
impl fmt::Display for Parentheses {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
match self {
Parentheses::None => fmt.write_str(""),
Parentheses::Empty => fmt.write_str("()"),
}
}
}
#[violation] #[violation]
pub struct PytestFixtureIncorrectParenthesesStyle { pub struct PytestFixtureIncorrectParenthesesStyle {
expected: Parentheses, expected: Parentheses,
@ -196,8 +179,23 @@ impl AlwaysAutofixableViolation for PytestUnnecessaryAsyncioMarkOnFixture {
} }
} }
#[derive(Default)] #[derive(Debug, PartialEq, Eq)]
enum Parentheses {
None,
Empty,
}
impl fmt::Display for Parentheses {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
match self {
Parentheses::None => fmt.write_str(""),
Parentheses::Empty => fmt.write_str("()"),
}
}
}
/// Visitor that skips functions /// Visitor that skips functions
#[derive(Debug, Default)]
struct SkipFunctionsVisitor<'a> { struct SkipFunctionsVisitor<'a> {
has_return_with_value: bool, has_return_with_value: bool,
has_yield_from: bool, has_yield_from: bool,
@ -245,7 +243,7 @@ where
} }
} }
fn get_fixture_decorator<'a>( fn fixture_decorator<'a>(
decorators: &'a [Decorator], decorators: &'a [Decorator],
semantic: &SemanticModel, semantic: &SemanticModel,
) -> Option<&'a Decorator> { ) -> Option<&'a Decorator> {
@ -271,16 +269,6 @@ fn pytest_fixture_parentheses(
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }
pub(crate) fn fix_extraneous_scope_function(
locator: &Locator,
stmt_at: TextSize,
expr_range: TextRange,
args: &[Expr],
keywords: &[Keyword],
) -> Result<Edit> {
remove_argument(locator, stmt_at, expr_range, args, keywords, false)
}
/// PT001, PT002, PT003 /// PT001, PT002, PT003
fn check_fixture_decorator(checker: &mut Checker, func_name: &str, decorator: &Decorator) { fn check_fixture_decorator(checker: &mut Checker, func_name: &str, decorator: &Decorator) {
match &decorator.expression { match &decorator.expression {
@ -290,28 +278,31 @@ fn check_fixture_decorator(checker: &mut Checker, func_name: &str, decorator: &D
keywords, keywords,
range: _, range: _,
}) => { }) => {
if checker.enabled(Rule::PytestFixtureIncorrectParenthesesStyle) if checker.enabled(Rule::PytestFixtureIncorrectParenthesesStyle) {
&& !checker.settings.flake8_pytest_style.fixture_parentheses if !checker.settings.flake8_pytest_style.fixture_parentheses
&& args.is_empty() && args.is_empty()
&& keywords.is_empty() && keywords.is_empty()
{ {
let fix = Fix::automatic(Edit::deletion(func.end(), decorator.end())); let fix = Fix::automatic(Edit::deletion(func.end(), decorator.end()));
pytest_fixture_parentheses( pytest_fixture_parentheses(
checker, checker,
decorator, decorator,
fix, fix,
Parentheses::None, Parentheses::None,
Parentheses::Empty, Parentheses::Empty,
); );
}
} }
if checker.enabled(Rule::PytestFixturePositionalArgs) && !args.is_empty() { if checker.enabled(Rule::PytestFixturePositionalArgs) {
checker.diagnostics.push(Diagnostic::new( if !args.is_empty() {
PytestFixturePositionalArgs { checker.diagnostics.push(Diagnostic::new(
function: func_name.to_string(), PytestFixturePositionalArgs {
}, function: func_name.to_string(),
decorator.range(), },
)); decorator.range(),
));
}
} }
if checker.enabled(Rule::PytestExtraneousScopeFunction) { if checker.enabled(Rule::PytestExtraneousScopeFunction) {
@ -324,16 +315,16 @@ fn check_fixture_decorator(checker: &mut Checker, func_name: &str, decorator: &D
let mut diagnostic = let mut diagnostic =
Diagnostic::new(PytestExtraneousScopeFunction, scope_keyword.range()); Diagnostic::new(PytestExtraneousScopeFunction, scope_keyword.range());
if checker.patch(diagnostic.kind.rule()) { if checker.patch(diagnostic.kind.rule()) {
let expr_range = diagnostic.range(); diagnostic.try_set_fix(|| {
#[allow(deprecated)] remove_argument(
diagnostic.try_set_fix_from_edit(|| {
fix_extraneous_scope_function(
checker.locator, checker.locator,
decorator.start(), func.end(),
expr_range, scope_keyword.range,
args, args,
keywords, keywords,
false,
) )
.map(Fix::suggested)
}); });
} }
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
@ -342,20 +333,20 @@ fn check_fixture_decorator(checker: &mut Checker, func_name: &str, decorator: &D
} }
} }
_ => { _ => {
if checker.enabled(Rule::PytestFixtureIncorrectParenthesesStyle) if checker.enabled(Rule::PytestFixtureIncorrectParenthesesStyle) {
&& checker.settings.flake8_pytest_style.fixture_parentheses if checker.settings.flake8_pytest_style.fixture_parentheses {
{ let fix = Fix::automatic(Edit::insertion(
let fix = Fix::automatic(Edit::insertion( Parentheses::Empty.to_string(),
Parentheses::Empty.to_string(), decorator.end(),
decorator.end(), ));
)); pytest_fixture_parentheses(
pytest_fixture_parentheses( checker,
checker, decorator,
decorator, fix,
fix, Parentheses::Empty,
Parentheses::Empty, Parentheses::None,
Parentheses::None, );
); }
} }
} }
} }
@ -511,7 +502,7 @@ pub(crate) fn fixture(
decorators: &[Decorator], decorators: &[Decorator],
body: &[Stmt], body: &[Stmt],
) { ) {
let decorator = get_fixture_decorator(decorators, checker.semantic()); let decorator = fixture_decorator(decorators, checker.semantic());
if let Some(decorator) = decorator { if let Some(decorator) = decorator {
if checker.enabled(Rule::PytestFixtureIncorrectParenthesesStyle) if checker.enabled(Rule::PytestFixtureIncorrectParenthesesStyle)
|| checker.enabled(Rule::PytestFixturePositionalArgs) || checker.enabled(Rule::PytestFixturePositionalArgs)

View File

@ -1,44 +0,0 @@
use ruff_text_size::TextRange;
use rustpython_parser::ast::{self, Expr, Keyword, Ranged};
use ruff_diagnostics::{Edit, Fix};
use ruff_python_ast::source_code::Locator;
use crate::autofix::edits::remove_argument;
fn match_name(expr: &Expr) -> Option<&str> {
if let Expr::Call(ast::ExprCall { func, .. }) = expr {
if let Expr::Attribute(ast::ExprAttribute { value, .. }) = func.as_ref() {
if let Expr::Name(ast::ExprName { id, .. }) = value.as_ref() {
return Some(id);
}
}
}
None
}
/// Remove the `inplace` argument from a function call and replace it with an
/// assignment.
pub(super) fn convert_inplace_argument_to_assignment(
locator: &Locator,
expr: &Expr,
violation_range: TextRange,
args: &[Expr],
keywords: &[Keyword],
) -> Option<Fix> {
// Add the assignment.
let name = match_name(expr)?;
let insert_assignment = Edit::insertion(format!("{name} = "), expr.start());
// Remove the `inplace` argument.
let remove_argument = remove_argument(
locator,
expr.start(),
violation_range,
args,
keywords,
false,
)
.ok()?;
Some(Fix::suggested_edits(insert_assignment, [remove_argument]))
}

View File

@ -1,5 +1,4 @@
//! Rules from [pandas-vet](https://pypi.org/project/pandas-vet/). //! Rules from [pandas-vet](https://pypi.org/project/pandas-vet/).
pub(crate) mod fixes;
pub(crate) mod helpers; pub(crate) mod helpers;
pub(crate) mod rules; pub(crate) mod rules;

View File

@ -1,12 +1,15 @@
use rustpython_parser::ast::{self, Constant, Expr, Keyword, Ranged}; use ruff_text_size::TextRange;
use rustpython_parser::ast::{Expr, Keyword, Ranged};
use ruff_diagnostics::{AutofixKind, Diagnostic, Violation}; use ruff_diagnostics::{AutofixKind, Diagnostic, Edit, Fix, Violation};
use ruff_macros::{derive_message_formats, violation}; use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::helpers::is_const_true;
use ruff_python_ast::source_code::Locator;
use ruff_python_semantic::{BindingKind, Import}; use ruff_python_semantic::{BindingKind, Import};
use crate::autofix::edits::remove_argument;
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
use crate::registry::AsRule; use crate::registry::AsRule;
use crate::rules::pandas_vet::fixes::convert_inplace_argument_to_assignment;
/// ## What it does /// ## What it does
/// Checks for `inplace=True` usages in `pandas` function and method /// Checks for `inplace=True` usages in `pandas` function and method
@ -50,23 +53,17 @@ impl Violation for PandasUseOfInplaceArgument {
/// PD002 /// PD002
pub(crate) fn inplace_argument( pub(crate) fn inplace_argument(
checker: &Checker, checker: &mut Checker,
expr: &Expr, expr: &Expr,
func: &Expr, func: &Expr,
args: &[Expr], args: &[Expr],
keywords: &[Keyword], keywords: &[Keyword],
) -> Option<Diagnostic> { ) {
let mut seen_star = false; // If the function was imported from another module, and it's _not_ Pandas, abort.
let mut is_checkable = false;
let mut is_pandas = false;
if let Some(call_path) = checker.semantic().resolve_call_path(func) { if let Some(call_path) = checker.semantic().resolve_call_path(func) {
is_checkable = true; if !call_path
.first()
let module = call_path[0]; .and_then(|module| checker.semantic().find_binding(module))
is_pandas = checker
.semantic()
.find_binding(module)
.map_or(false, |binding| { .map_or(false, |binding| {
matches!( matches!(
binding.kind, binding.kind,
@ -74,23 +71,20 @@ pub(crate) fn inplace_argument(
qualified_name: "pandas" qualified_name: "pandas"
}) })
) )
}); })
{
return;
}
} }
let mut seen_star = false;
for keyword in keywords.iter().rev() { for keyword in keywords.iter().rev() {
let Some(arg) = &keyword.arg else { let Some(arg) = &keyword.arg else {
seen_star = true; seen_star = true;
continue; continue;
}; };
if arg == "inplace" { if arg == "inplace" {
let is_true_literal = match &keyword.value { if is_const_true(&keyword.value) {
Expr::Constant(ast::ExprConstant {
value: Constant::Bool(boolean),
..
}) => *boolean,
_ => false,
};
if is_true_literal {
let mut diagnostic = Diagnostic::new(PandasUseOfInplaceArgument, keyword.range()); let mut diagnostic = Diagnostic::new(PandasUseOfInplaceArgument, keyword.range());
if checker.patch(diagnostic.kind.rule()) { if checker.patch(diagnostic.kind.rule()) {
// Avoid applying the fix if: // Avoid applying the fix if:
@ -110,7 +104,7 @@ pub(crate) fn inplace_argument(
if let Some(fix) = convert_inplace_argument_to_assignment( if let Some(fix) = convert_inplace_argument_to_assignment(
checker.locator, checker.locator,
expr, expr,
diagnostic.range(), keyword.range(),
args, args,
keywords, keywords,
) { ) {
@ -119,18 +113,35 @@ pub(crate) fn inplace_argument(
} }
} }
// Without a static type system, only module-level functions could potentially be checker.diagnostics.push(diagnostic);
// non-pandas calls. If they're not, `inplace` should be considered safe.
if is_checkable && !is_pandas {
return None;
}
return Some(diagnostic);
} }
// Duplicate keywords is a syntax error, so we can stop here. // Duplicate keywords is a syntax error, so we can stop here.
break; break;
} }
} }
None }
/// Remove the `inplace` argument from a function call and replace it with an
/// assignment.
fn convert_inplace_argument_to_assignment(
locator: &Locator,
expr: &Expr,
expr_range: TextRange,
args: &[Expr],
keywords: &[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(),
);
// Remove the `inplace` argument.
let remove_argument =
remove_argument(locator, call.func.end(), expr_range, args, keywords, false).ok()?;
Some(Fix::suggested_edits(insert_assignment, [remove_argument]))
} }

View File

@ -1,5 +1,6 @@
use rustpython_parser::ast::{self, Expr, Ranged}; use rustpython_parser::ast::{self, Expr, Ranged};
use crate::checkers::ast::Checker;
use ruff_diagnostics::{Diagnostic, Violation}; use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation}; use ruff_macros::{derive_message_formats, violation};
@ -17,13 +18,14 @@ impl Violation for PandasUseOfPdMerge {
} }
/// PD015 /// PD015
pub(crate) fn use_of_pd_merge(func: &Expr) -> Option<Diagnostic> { pub(crate) fn use_of_pd_merge(checker: &mut Checker, func: &Expr) {
if let Expr::Attribute(ast::ExprAttribute { attr, value, .. }) = func { if let Expr::Attribute(ast::ExprAttribute { attr, value, .. }) = func {
if let Expr::Name(ast::ExprName { id, .. }) = value.as_ref() { if let Expr::Name(ast::ExprName { id, .. }) = value.as_ref() {
if id == "pd" && attr == "merge" { if id == "pd" && attr == "merge" {
return Some(Diagnostic::new(PandasUseOfPdMerge, func.range())); checker
.diagnostics
.push(Diagnostic::new(PandasUseOfPdMerge, func.range()));
} }
} }
} }
None
} }

View File

@ -8,7 +8,7 @@ PD002.py:5:23: PD002 [*] `inplace=True` should be avoided; it has inconsistent b
5 | x.drop(["a"], axis=1, inplace=True) 5 | x.drop(["a"], axis=1, inplace=True)
| ^^^^^^^^^^^^ PD002 | ^^^^^^^^^^^^ PD002
6 | 6 |
7 | x.drop(["a"], axis=1, inplace=True) 7 | x.y.drop(["a"], axis=1, inplace=True)
| |
= help: Assign to variable; remove `inplace` arg = help: Assign to variable; remove `inplace` arg
@ -19,17 +19,17 @@ PD002.py:5:23: PD002 [*] `inplace=True` should be avoided; it has inconsistent b
5 |-x.drop(["a"], axis=1, inplace=True) 5 |-x.drop(["a"], axis=1, inplace=True)
5 |+x = x.drop(["a"], axis=1) 5 |+x = x.drop(["a"], axis=1)
6 6 | 6 6 |
7 7 | x.drop(["a"], axis=1, inplace=True) 7 7 | x.y.drop(["a"], axis=1, inplace=True)
8 8 | 8 8 |
PD002.py:7:23: PD002 [*] `inplace=True` should be avoided; it has inconsistent behavior PD002.py:7:25: PD002 [*] `inplace=True` should be avoided; it has inconsistent behavior
| |
5 | x.drop(["a"], axis=1, inplace=True) 5 | x.drop(["a"], axis=1, inplace=True)
6 | 6 |
7 | x.drop(["a"], axis=1, inplace=True) 7 | x.y.drop(["a"], axis=1, inplace=True)
| ^^^^^^^^^^^^ PD002 | ^^^^^^^^^^^^ PD002
8 | 8 |
9 | x.drop( 9 | x["y"].drop(["a"], axis=1, inplace=True)
| |
= help: Assign to variable; remove `inplace` arg = help: Assign to variable; remove `inplace` arg
@ -37,104 +37,124 @@ PD002.py:7:23: PD002 [*] `inplace=True` should be avoided; it has inconsistent b
4 4 | 4 4 |
5 5 | x.drop(["a"], axis=1, inplace=True) 5 5 | x.drop(["a"], axis=1, inplace=True)
6 6 | 6 6 |
7 |-x.drop(["a"], axis=1, inplace=True) 7 |-x.y.drop(["a"], axis=1, inplace=True)
7 |+x = x.drop(["a"], axis=1) 7 |+x.y = x.y.drop(["a"], axis=1)
8 8 | 8 8 |
9 9 | x.drop( 9 9 | x["y"].drop(["a"], axis=1, inplace=True)
10 10 | inplace=True, 10 10 |
PD002.py:10:5: PD002 [*] `inplace=True` should be avoided; it has inconsistent behavior PD002.py:9:28: PD002 [*] `inplace=True` should be avoided; it has inconsistent behavior
| |
9 | x.drop( 7 | x.y.drop(["a"], axis=1, inplace=True)
10 | inplace=True, 8 |
| ^^^^^^^^^^^^ PD002 9 | x["y"].drop(["a"], axis=1, inplace=True)
11 | columns=["a"], | ^^^^^^^^^^^^ PD002
12 | axis=1, 10 |
11 | x.drop(
| |
= help: Assign to variable; remove `inplace` arg = help: Assign to variable; remove `inplace` arg
Suggested fix Suggested fix
6 6 | 6 6 |
7 7 | x.drop(["a"], axis=1, inplace=True) 7 7 | x.y.drop(["a"], axis=1, inplace=True)
8 8 | 8 8 |
9 |-x.drop( 9 |-x["y"].drop(["a"], axis=1, inplace=True)
10 |- inplace=True, 9 |+x["y"] = x["y"].drop(["a"], axis=1)
9 |+x = x.drop( 10 10 |
11 10 | columns=["a"], 11 11 | x.drop(
12 11 | axis=1, 12 12 | inplace=True,
13 12 | )
PD002.py:17:9: PD002 [*] `inplace=True` should be avoided; it has inconsistent behavior PD002.py:12:5: PD002 [*] `inplace=True` should be avoided; it has inconsistent behavior
| |
15 | if True: 11 | x.drop(
16 | x.drop( 12 | inplace=True,
17 | inplace=True, | ^^^^^^^^^^^^ PD002
13 | columns=["a"],
14 | axis=1,
|
= help: Assign to variable; remove `inplace` arg
Suggested fix
8 8 |
9 9 | x["y"].drop(["a"], axis=1, inplace=True)
10 10 |
11 |-x.drop(
12 |- inplace=True,
11 |+x = x.drop(
13 12 | columns=["a"],
14 13 | axis=1,
15 14 | )
PD002.py:19:9: PD002 [*] `inplace=True` should be avoided; it has inconsistent behavior
|
17 | if True:
18 | x.drop(
19 | inplace=True,
| ^^^^^^^^^^^^ PD002 | ^^^^^^^^^^^^ PD002
18 | columns=["a"], 20 | columns=["a"],
19 | axis=1, 21 | axis=1,
| |
= help: Assign to variable; remove `inplace` arg = help: Assign to variable; remove `inplace` arg
Suggested fix Suggested fix
13 13 | ) 15 15 | )
14 14 | 16 16 |
15 15 | if True: 17 17 | if True:
16 |- x.drop( 18 |- x.drop(
17 |- inplace=True, 19 |- inplace=True,
16 |+ x = x.drop( 18 |+ x = x.drop(
18 17 | columns=["a"], 20 19 | columns=["a"],
19 18 | axis=1, 21 20 | axis=1,
20 19 | ) 22 21 | )
PD002.py:22:33: PD002 [*] `inplace=True` should be avoided; it has inconsistent behavior PD002.py:24:33: PD002 [*] `inplace=True` should be avoided; it has inconsistent behavior
| |
20 | ) 22 | )
21 | 23 |
22 | x.drop(["a"], axis=1, **kwargs, inplace=True) 24 | x.drop(["a"], axis=1, **kwargs, inplace=True)
| ^^^^^^^^^^^^ PD002 | ^^^^^^^^^^^^ PD002
23 | x.drop(["a"], axis=1, inplace=True, **kwargs) 25 | x.drop(["a"], axis=1, inplace=True, **kwargs)
24 | f(x.drop(["a"], axis=1, inplace=True)) 26 | f(x.drop(["a"], axis=1, inplace=True))
| |
= help: Assign to variable; remove `inplace` arg = help: Assign to variable; remove `inplace` arg
Suggested fix Suggested fix
19 19 | axis=1, 21 21 | axis=1,
20 20 | ) 22 22 | )
21 21 | 23 23 |
22 |-x.drop(["a"], axis=1, **kwargs, inplace=True) 24 |-x.drop(["a"], axis=1, **kwargs, inplace=True)
22 |+x = x.drop(["a"], axis=1, **kwargs) 24 |+x = x.drop(["a"], axis=1, **kwargs)
23 23 | x.drop(["a"], axis=1, inplace=True, **kwargs) 25 25 | x.drop(["a"], axis=1, inplace=True, **kwargs)
24 24 | f(x.drop(["a"], axis=1, inplace=True)) 26 26 | f(x.drop(["a"], axis=1, inplace=True))
25 25 | 27 27 |
PD002.py:23:23: PD002 `inplace=True` should be avoided; it has inconsistent behavior PD002.py:25:23: PD002 `inplace=True` should be avoided; it has inconsistent behavior
| |
22 | x.drop(["a"], axis=1, **kwargs, inplace=True) 24 | x.drop(["a"], axis=1, **kwargs, inplace=True)
23 | x.drop(["a"], axis=1, inplace=True, **kwargs) 25 | x.drop(["a"], axis=1, inplace=True, **kwargs)
| ^^^^^^^^^^^^ PD002 | ^^^^^^^^^^^^ PD002
24 | f(x.drop(["a"], axis=1, inplace=True)) 26 | f(x.drop(["a"], axis=1, inplace=True))
| |
= help: Assign to variable; remove `inplace` arg = help: Assign to variable; remove `inplace` arg
PD002.py:24:25: PD002 `inplace=True` should be avoided; it has inconsistent behavior PD002.py:26:25: PD002 `inplace=True` should be avoided; it has inconsistent behavior
| |
22 | x.drop(["a"], axis=1, **kwargs, inplace=True) 24 | x.drop(["a"], axis=1, **kwargs, inplace=True)
23 | x.drop(["a"], axis=1, inplace=True, **kwargs) 25 | x.drop(["a"], axis=1, inplace=True, **kwargs)
24 | f(x.drop(["a"], axis=1, inplace=True)) 26 | f(x.drop(["a"], axis=1, inplace=True))
| ^^^^^^^^^^^^ PD002 | ^^^^^^^^^^^^ PD002
25 | 27 |
26 | x.apply(lambda x: x.sort_values('a', inplace=True)) 28 | x.apply(lambda x: x.sort_values("a", inplace=True))
| |
= help: Assign to variable; remove `inplace` arg = help: Assign to variable; remove `inplace` arg
PD002.py:26:38: PD002 `inplace=True` should be avoided; it has inconsistent behavior PD002.py:28:38: PD002 `inplace=True` should be avoided; it has inconsistent behavior
| |
24 | f(x.drop(["a"], axis=1, inplace=True)) 26 | f(x.drop(["a"], axis=1, inplace=True))
25 | 27 |
26 | x.apply(lambda x: x.sort_values('a', inplace=True)) 28 | x.apply(lambda x: x.sort_values("a", inplace=True))
| ^^^^^^^^^^^^ PD002 | ^^^^^^^^^^^^ PD002
27 | import torch 29 | import torch
28 | torch.m.ReLU(inplace=True) # safe because this isn't a pandas call
| |
= help: Assign to variable; remove `inplace` arg = help: Assign to variable; remove `inplace` arg

View File

@ -69,7 +69,7 @@ fn generate_fix(
Edit::range_replacement("capture_output=True".to_string(), first.range()), Edit::range_replacement("capture_output=True".to_string(), first.range()),
[remove_argument( [remove_argument(
locator, locator,
func.start(), func.end(),
second.range(), second.range(),
args, args,
keywords, keywords,

View File

@ -1,11 +1,10 @@
use std::ops::Add; use std::ops::Add;
use ruff_text_size::{TextRange, TextSize}; use ruff_text_size::{TextRange, TextSize};
use rustpython_parser::ast::{self, Stmt}; use rustpython_parser::ast::{self, Ranged};
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix}; use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix};
use ruff_macros::{derive_message_formats, violation}; use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::identifier::Identifier;
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
use crate::registry::AsRule; use crate::registry::AsRule;
@ -44,16 +43,12 @@ impl AlwaysAutofixableViolation for UnnecessaryClassParentheses {
} }
/// UP039 /// UP039
pub(crate) fn unnecessary_class_parentheses( pub(crate) fn unnecessary_class_parentheses(checker: &mut Checker, class_def: &ast::StmtClassDef) {
checker: &mut Checker,
class_def: &ast::StmtClassDef,
stmt: &Stmt,
) {
if !class_def.bases.is_empty() || !class_def.keywords.is_empty() { if !class_def.bases.is_empty() || !class_def.keywords.is_empty() {
return; return;
} }
let offset = stmt.identifier().start(); let offset = class_def.name.end();
let contents = checker.locator.after(offset); let contents = checker.locator.after(offset);
// Find the open and closing parentheses between the class name and the colon, if they exist. // Find the open and closing parentheses between the class name and the colon, if they exist.

View File

@ -1,8 +1,7 @@
use rustpython_parser::ast::{self, Expr, Ranged, Stmt}; use rustpython_parser::ast::{self, Expr, Ranged};
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Fix}; use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Fix};
use ruff_macros::{derive_message_formats, violation}; use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::identifier::Identifier;
use crate::autofix::edits::remove_argument; use crate::autofix::edits::remove_argument;
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
@ -47,11 +46,7 @@ impl AlwaysAutofixableViolation for UselessObjectInheritance {
} }
/// UP004 /// UP004
pub(crate) fn useless_object_inheritance( pub(crate) fn useless_object_inheritance(checker: &mut Checker, class_def: &ast::StmtClassDef) {
checker: &mut Checker,
class_def: &ast::StmtClassDef,
stmt: &Stmt,
) {
for expr in &class_def.bases { for expr in &class_def.bases {
let Expr::Name(ast::ExprName { id, .. }) = expr else { let Expr::Name(ast::ExprName { id, .. }) = expr else {
continue; continue;
@ -73,7 +68,7 @@ pub(crate) fn useless_object_inheritance(
diagnostic.try_set_fix(|| { diagnostic.try_set_fix(|| {
let edit = remove_argument( let edit = remove_argument(
checker.locator, checker.locator,
stmt.identifier().start(), class_def.name.end(),
expr.range(), expr.range(),
&class_def.bases, &class_def.bases,
&class_def.keywords, &class_def.keywords,