Skip to content

Commit

Permalink
autofixes for PD003, PD004, PD008, PD009 and PD011
Browse files Browse the repository at this point in the history
  • Loading branch information
sbrugman committed Feb 10, 2023
1 parent d2b09d7 commit a0dc90c
Show file tree
Hide file tree
Showing 11 changed files with 221 additions and 20 deletions.
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1322,13 +1322,13 @@ For more, see [pandas-vet](https://pypi.org/project/pandas-vet/) on PyPI.
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| PD002 | use-of-inplace-argument | `inplace=True` should be avoided; it has inconsistent behavior | 🛠 |
| PD003 | use-of-dot-is-null | `.isna` is preferred to `.isnull`; functionality is equivalent | |
| PD004 | use-of-dot-not-null | `.notna` is preferred to `.notnull`; functionality is equivalent | |
| PD003 | use-of-dot-is-null | `.isna` is preferred to `.isnull`; functionality is equivalent | 🛠 |
| PD004 | use-of-dot-not-null | `.notna` is preferred to `.notnull`; functionality is equivalent | 🛠 |
| PD007 | use-of-dot-ix | `.ix` is deprecated; use more explicit `.loc` or `.iloc` | |
| PD008 | use-of-dot-at | Use `.loc` instead of `.at`. If speed is important, use numpy. | |
| PD009 | use-of-dot-iat | Use `.iloc` instead of `.iat`. If speed is important, use numpy. | |
| PD008 | use-of-dot-at | Use `.loc` instead of `.at`. If speed is important, use numpy. | 🛠 |
| PD009 | use-of-dot-iat | Use `.iloc` instead of `.iat`. If speed is important, use numpy. | 🛠 |
| PD010 | use-of-dot-pivot-or-unstack | `.pivot_table` is preferred to `.pivot` or `.unstack`; provides same functionality | |
| PD011 | use-of-dot-values | Use `.to_numpy()` instead of `.values` | |
| PD011 | use-of-dot-values | Use `.to_numpy()` instead of `.values` | 🛠 |
| PD012 | use-of-dot-read-table | `.read_csv` is preferred to `.read_table`; provides same functionality | |
| PD013 | use-of-dot-stack | `.melt` is preferred to `.stack`; provides same functionality | |
| PD015 | use-of-pd-merge | Use `.merge` method instead of `pd.merge` function. They have equivalent functionality. | |
Expand Down
14 changes: 14 additions & 0 deletions crates/ruff/resources/test/fixtures/pandas_vet/fixes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import pandas as pd

df = pd.DataFrame()

df.isnull()
df.isna()
df.notnull()
df.notna()
df.at[1]
df.loc[1]
df.iat[1]
df.iloc[1]
df.values
df.to_numpy()
18 changes: 16 additions & 2 deletions crates/ruff/src/rules/pandas_vet/fixes.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
use rustpython_parser::ast::{Expr, ExprKind, Keyword, Location};
use rustpython_parser::ast::{Expr, ExprContext, ExprKind, Keyword, Location};

use crate::ast::helpers;
use crate::ast::types::Range;
use crate::autofix::apply_fix;
use crate::autofix::helpers::remove_argument;
use crate::fix::Fix;
use crate::source_code::Locator;
use crate::source_code::{Locator, Stylist};

fn match_name(expr: &Expr) -> Option<&str> {
if let ExprKind::Call { func, .. } = &expr.node {
Expand Down Expand Up @@ -70,3 +70,17 @@ pub fn fix_inplace_argument(
None
}
}

/// Replace attribute with `attr`
pub fn fix_attr(attr: &str, value: &Expr, stylist: &Stylist) -> String {
let stmt = Expr::new(
Location::default(),
Location::default(),
ExprKind::Attribute {
value: Box::new(value.clone()),
attr: attr.to_string(),
ctx: ExprContext::Store,
},
);
helpers::unparse_expr(&stmt, stylist)
}
5 changes: 5 additions & 0 deletions crates/ruff/src/rules/pandas_vet/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,11 @@ mod tests {
}

#[test_case(Rule::UseOfInplaceArgument, Path::new("PD002.py"); "PD002")]
#[test_case(Rule::UseOfDotIsNull, Path::new("fixes.py"); "PD003")]
#[test_case(Rule::UseOfDotNotNull, Path::new("fixes.py"); "PD004")]
#[test_case(Rule::UseOfDotAt, Path::new("fixes.py"); "PD008")]
#[test_case(Rule::UseOfDotIat, Path::new("fixes.py"); "PD009")]
#[test_case(Rule::UseOfDotValues, Path::new("fixes.py"); "PD011")]
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!("{}_{}", rule_code.code(), path.to_string_lossy());
let diagnostics = test_path(
Expand Down
43 changes: 36 additions & 7 deletions crates/ruff/src/rules/pandas_vet/rules/check_attr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ use rustpython_parser::ast::{ExprKind, Located};

use crate::ast::types::{BindingKind, Range};
use crate::checkers::ast::Checker;
use crate::fix::Fix;
use crate::registry::{Diagnostic, DiagnosticKind, Rule};
use crate::rules::pandas_vet::fixes::fix_attr;
use crate::rules::pandas_vet::helpers::is_dataframe_candidate;
use crate::violation::Violation;
use crate::violation::{AlwaysAutofixableViolation, Violation};

define_violation!(
pub struct UseOfDotIx;
Expand All @@ -20,31 +22,43 @@ impl Violation for UseOfDotIx {
define_violation!(
pub struct UseOfDotAt;
);
impl Violation for UseOfDotAt {
impl AlwaysAutofixableViolation for UseOfDotAt {
#[derive_message_formats]
fn message(&self) -> String {
format!("Use `.loc` instead of `.at`. If speed is important, use numpy.")
}

fn autofix_title(&self) -> String {
format!("Replace `.at` with `.loc`")
}
}

define_violation!(
pub struct UseOfDotIat;
);
impl Violation for UseOfDotIat {
impl AlwaysAutofixableViolation for UseOfDotIat {
#[derive_message_formats]
fn message(&self) -> String {
format!("Use `.iloc` instead of `.iat`. If speed is important, use numpy.")
}

fn autofix_title(&self) -> String {
format!("Replace `.iat` with `.iloc`")
}
}

define_violation!(
pub struct UseOfDotValues;
);
impl Violation for UseOfDotValues {
impl AlwaysAutofixableViolation for UseOfDotValues {
#[derive_message_formats]
fn message(&self) -> String {
format!("Use `.to_numpy()` instead of `.values`")
}

fn autofix_title(&self) -> String {
format!("Replace `.values` with `.to_numpy()`")
}
}

pub fn check_attr(
Expand Down Expand Up @@ -94,7 +108,22 @@ pub fn check_attr(
}
}

checker
.diagnostics
.push(Diagnostic::new(violation, Range::from_located(attr_expr)));
let mut diagnostic = Diagnostic::new(violation, Range::from_located(attr_expr));
if checker.patch(diagnostic.kind.rule()) {
let replacement = match *diagnostic.kind.rule() {
Rule::UseOfDotAt => Some("loc"),
Rule::UseOfDotIat => Some("iloc"),
Rule::UseOfDotValues => Some("to_numpy()"),
_ => None,
};
if let Some(replacement) = replacement {
diagnostic.amend(Fix::replacement(
fix_attr(replacement, value, checker.stylist),
attr_expr.location,
attr_expr.end_location.unwrap(),
));
}
}

checker.diagnostics.push(diagnostic);
}
36 changes: 30 additions & 6 deletions crates/ruff/src/rules/pandas_vet/rules/check_call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,38 @@ use rustpython_parser::ast::{ExprKind, Located};

use crate::ast::types::{BindingKind, Range};
use crate::checkers::ast::Checker;
use crate::fix::Fix;
use crate::registry::{Diagnostic, DiagnosticKind, Rule};
use crate::rules::pandas_vet::fixes::fix_attr;
use crate::rules::pandas_vet::helpers::is_dataframe_candidate;
use crate::violation::Violation;
use crate::violation::{AlwaysAutofixableViolation, Violation};

define_violation!(
pub struct UseOfDotIsNull;
);
impl Violation for UseOfDotIsNull {
impl AlwaysAutofixableViolation for UseOfDotIsNull {
#[derive_message_formats]
fn message(&self) -> String {
format!("`.isna` is preferred to `.isnull`; functionality is equivalent")
}

fn autofix_title(&self) -> String {
format!("Replace `.isnull` with `.isna`")
}
}

define_violation!(
pub struct UseOfDotNotNull;
);
impl Violation for UseOfDotNotNull {
impl AlwaysAutofixableViolation for UseOfDotNotNull {
#[derive_message_formats]
fn message(&self) -> String {
format!("`.notna` is preferred to `.notnull`; functionality is equivalent")
}

fn autofix_title(&self) -> String {
format!("Replace `.notnull` with `.notna`")
}
}

define_violation!(
Expand Down Expand Up @@ -102,7 +112,21 @@ pub fn check_call(checker: &mut Checker, func: &Located<ExprKind>) {
}
}

checker
.diagnostics
.push(Diagnostic::new(violation, Range::from_located(func)));
let mut diagnostic = Diagnostic::new(violation, Range::from_located(func));
if checker.patch(diagnostic.kind.rule()) {
let replacement = match *diagnostic.kind.rule() {
Rule::UseOfDotIsNull => Some("isna"),
Rule::UseOfDotNotNull => Some("notna"),
_ => None,
};
if let Some(replacement) = replacement {
diagnostic.amend(Fix::replacement(
fix_attr(replacement, value, checker.stylist),
func.location,
func.end_location.unwrap(),
));
}
}

checker.diagnostics.push(diagnostic);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
source: crates/ruff/src/rules/pandas_vet/mod.rs
expression: diagnostics
---
- kind:
UseOfDotIsNull: ~
location:
row: 5
column: 0
end_location:
row: 5
column: 9
fix:
content:
- df.isna
location:
row: 5
column: 0
end_location:
row: 5
column: 9
parent: ~

Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
source: crates/ruff/src/rules/pandas_vet/mod.rs
expression: diagnostics
---
- kind:
UseOfDotNotNull: ~
location:
row: 7
column: 0
end_location:
row: 7
column: 10
fix:
content:
- df.notna
location:
row: 7
column: 0
end_location:
row: 7
column: 10
parent: ~

Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
source: crates/ruff/src/rules/pandas_vet/mod.rs
expression: diagnostics
---
- kind:
UseOfDotAt: ~
location:
row: 9
column: 0
end_location:
row: 9
column: 5
fix:
content:
- df.loc
location:
row: 9
column: 0
end_location:
row: 9
column: 5
parent: ~

Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
source: crates/ruff/src/rules/pandas_vet/mod.rs
expression: diagnostics
---
- kind:
UseOfDotIat: ~
location:
row: 11
column: 0
end_location:
row: 11
column: 6
fix:
content:
- df.iloc
location:
row: 11
column: 0
end_location:
row: 11
column: 6
parent: ~

Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
source: crates/ruff/src/rules/pandas_vet/mod.rs
expression: diagnostics
---
- kind:
UseOfDotValues: ~
location:
row: 13
column: 0
end_location:
row: 13
column: 9
fix:
content:
- df.to_numpy()
location:
row: 13
column: 0
end_location:
row: 13
column: 9
parent: ~

0 comments on commit a0dc90c

Please sign in to comment.