From b0a9f51753b30ea88434805d1ade572956265b3c Mon Sep 17 00:00:00 2001 From: Dan Date: Tue, 14 Oct 2025 21:40:05 -0400 Subject: [PATCH 1/4] fix-20874 --- .../ruff_linter/src/rules/pandas_vet/mod.rs | 39 +++++++++++++++++++ .../src/rules/pandas_vet/rules/attr.rs | 39 ++++++++++++++++++- ...PD011_pass_numpy_typed_unique_inverse.snap | 4 ++ ...t__tests__PD011_pass_numpy_unique_all.snap | 4 ++ ...tests__PD011_pass_numpy_unique_counts.snap | 4 ++ ...ests__PD011_pass_numpy_unique_inverse.snap | 4 ++ 6 files changed, 93 insertions(+), 1 deletion(-) create mode 100644 crates/ruff_linter/src/rules/pandas_vet/snapshots/ruff_linter__rules__pandas_vet__tests__PD011_pass_numpy_typed_unique_inverse.snap create mode 100644 crates/ruff_linter/src/rules/pandas_vet/snapshots/ruff_linter__rules__pandas_vet__tests__PD011_pass_numpy_unique_all.snap create mode 100644 crates/ruff_linter/src/rules/pandas_vet/snapshots/ruff_linter__rules__pandas_vet__tests__PD011_pass_numpy_unique_counts.snap create mode 100644 crates/ruff_linter/src/rules/pandas_vet/snapshots/ruff_linter__rules__pandas_vet__tests__PD011_pass_numpy_unique_inverse.snap diff --git a/crates/ruff_linter/src/rules/pandas_vet/mod.rs b/crates/ruff_linter/src/rules/pandas_vet/mod.rs index 982eedbde5..3e96328c69 100644 --- a/crates/ruff_linter/src/rules/pandas_vet/mod.rs +++ b/crates/ruff_linter/src/rules/pandas_vet/mod.rs @@ -269,6 +269,45 @@ mod tests { ", "PD011_pass_node_name" )] + #[test_case( + r" + import pandas as pd + import numpy as np + unique = np.unique_inverse([1, 2, 3, 2, 1]) + result = unique.values + ", + "PD011_pass_numpy_unique_inverse" + )] + #[test_case( + r" + import pandas as pd + import numpy as np + unique = np.unique_all([1, 2, 3, 2, 1]) + result = unique.values + ", + "PD011_pass_numpy_unique_all" + )] + #[test_case( + r" + import pandas as pd + import numpy as np + unique = np.unique_counts([1, 2, 3, 2, 1]) + result = unique.values + ", + "PD011_pass_numpy_unique_counts" + )] + #[test_case( + r" + import pandas as pd + from typing import TYPE_CHECKING + if TYPE_CHECKING: + from numpy.lib._arraysetops_impl import UniqueInverseResult + import numpy as np + unique: UniqueInverseResult[np.uint64] = np.unique_inverse([1, 2, 3, 2, 1]) + result = unique.values + ", + "PD011_pass_numpy_typed_unique_inverse" + )] #[test_case( r#" import pandas as pd diff --git a/crates/ruff_linter/src/rules/pandas_vet/rules/attr.rs b/crates/ruff_linter/src/rules/pandas_vet/rules/attr.rs index ab6b55104b..c5f34c94ed 100644 --- a/crates/ruff_linter/src/rules/pandas_vet/rules/attr.rs +++ b/crates/ruff_linter/src/rules/pandas_vet/rules/attr.rs @@ -1,6 +1,6 @@ use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr}; -use ruff_python_semantic::Modules; +use ruff_python_semantic::{Modules, analyze::typing::find_binding_value}; use ruff_text_size::Ranged; use crate::Violation; @@ -43,6 +43,38 @@ impl Violation for PandasUseOfDotValues { } } +/// Check if a binding comes from a NumPy function that returns a `NamedTuple` with a `.values` field. +fn is_numpy_namedtuple_binding( + expr: &Expr, + semantic: &ruff_python_semantic::SemanticModel, +) -> bool { + let Expr::Name(name) = expr else { + return false; + }; + + let Some(binding_id) = semantic.resolve_name(name) else { + return false; + }; + let binding = semantic.binding(binding_id); + + let Some(assigned_value) = find_binding_value(binding, semantic) else { + return false; + }; + + let Some(call_expr) = assigned_value.as_call_expr() else { + return false; + }; + + let Some(qualified_name) = semantic.resolve_qualified_name(&call_expr.func) else { + return false; + }; + + matches!( + qualified_name.segments(), + ["numpy", "unique_inverse" | "unique_all" | "unique_counts"] + ) +} + /// PD011 pub(crate) fn attr(checker: &Checker, attribute: &ast::ExprAttribute) { if !checker.semantic().seen_module(Modules::PANDAS) { @@ -77,5 +109,10 @@ pub(crate) fn attr(checker: &Checker, attribute: &ast::ExprAttribute) { return; } + // Avoid flagging on NumPy `NamedTuples` that have a legitimate `.values` field + if is_numpy_namedtuple_binding(attribute.value.as_ref(), checker.semantic()) { + return; + } + checker.report_diagnostic(PandasUseOfDotValues, attribute.range()); } diff --git a/crates/ruff_linter/src/rules/pandas_vet/snapshots/ruff_linter__rules__pandas_vet__tests__PD011_pass_numpy_typed_unique_inverse.snap b/crates/ruff_linter/src/rules/pandas_vet/snapshots/ruff_linter__rules__pandas_vet__tests__PD011_pass_numpy_typed_unique_inverse.snap new file mode 100644 index 0000000000..07173f8f1b --- /dev/null +++ b/crates/ruff_linter/src/rules/pandas_vet/snapshots/ruff_linter__rules__pandas_vet__tests__PD011_pass_numpy_typed_unique_inverse.snap @@ -0,0 +1,4 @@ +--- +source: crates/ruff_linter/src/rules/pandas_vet/mod.rs +--- + diff --git a/crates/ruff_linter/src/rules/pandas_vet/snapshots/ruff_linter__rules__pandas_vet__tests__PD011_pass_numpy_unique_all.snap b/crates/ruff_linter/src/rules/pandas_vet/snapshots/ruff_linter__rules__pandas_vet__tests__PD011_pass_numpy_unique_all.snap new file mode 100644 index 0000000000..07173f8f1b --- /dev/null +++ b/crates/ruff_linter/src/rules/pandas_vet/snapshots/ruff_linter__rules__pandas_vet__tests__PD011_pass_numpy_unique_all.snap @@ -0,0 +1,4 @@ +--- +source: crates/ruff_linter/src/rules/pandas_vet/mod.rs +--- + diff --git a/crates/ruff_linter/src/rules/pandas_vet/snapshots/ruff_linter__rules__pandas_vet__tests__PD011_pass_numpy_unique_counts.snap b/crates/ruff_linter/src/rules/pandas_vet/snapshots/ruff_linter__rules__pandas_vet__tests__PD011_pass_numpy_unique_counts.snap new file mode 100644 index 0000000000..07173f8f1b --- /dev/null +++ b/crates/ruff_linter/src/rules/pandas_vet/snapshots/ruff_linter__rules__pandas_vet__tests__PD011_pass_numpy_unique_counts.snap @@ -0,0 +1,4 @@ +--- +source: crates/ruff_linter/src/rules/pandas_vet/mod.rs +--- + diff --git a/crates/ruff_linter/src/rules/pandas_vet/snapshots/ruff_linter__rules__pandas_vet__tests__PD011_pass_numpy_unique_inverse.snap b/crates/ruff_linter/src/rules/pandas_vet/snapshots/ruff_linter__rules__pandas_vet__tests__PD011_pass_numpy_unique_inverse.snap new file mode 100644 index 0000000000..07173f8f1b --- /dev/null +++ b/crates/ruff_linter/src/rules/pandas_vet/snapshots/ruff_linter__rules__pandas_vet__tests__PD011_pass_numpy_unique_inverse.snap @@ -0,0 +1,4 @@ +--- +source: crates/ruff_linter/src/rules/pandas_vet/mod.rs +--- + From 76ac3465e9fbec5450ba4177bcc9025c5669a6a5 Mon Sep 17 00:00:00 2001 From: Dan Date: Wed, 15 Oct 2025 20:03:37 -0400 Subject: [PATCH 2/4] Improve pandas detection --- .../src/rules/pandas_vet/helpers.rs | 66 ++++++++++++++++++- .../ruff_linter/src/rules/pandas_vet/mod.rs | 8 +++ .../src/rules/pandas_vet/rules/attr.rs | 39 +---------- ...__tests__PD011_pass_simple_non_pandas.snap | 4 ++ 4 files changed, 76 insertions(+), 41 deletions(-) create mode 100644 crates/ruff_linter/src/rules/pandas_vet/snapshots/ruff_linter__rules__pandas_vet__tests__PD011_pass_simple_non_pandas.snap diff --git a/crates/ruff_linter/src/rules/pandas_vet/helpers.rs b/crates/ruff_linter/src/rules/pandas_vet/helpers.rs index 2f32e53feb..3b42617ce7 100644 --- a/crates/ruff_linter/src/rules/pandas_vet/helpers.rs +++ b/crates/ruff_linter/src/rules/pandas_vet/helpers.rs @@ -1,5 +1,7 @@ use ruff_python_ast::Expr; -use ruff_python_semantic::{BindingKind, Imported, SemanticModel}; +use ruff_python_semantic::{ + BindingKind, Imported, SemanticModel, analyze::typing::find_binding_value, +}; #[derive(Debug)] pub(super) enum Resolution { @@ -34,7 +36,8 @@ pub(super) fn test_expression(expr: &Expr, semantic: &SemanticModel) -> Resoluti semantic .resolve_name(name) .map_or(Resolution::IrrelevantBinding, |id| { - match &semantic.binding(id).kind { + let binding = semantic.binding(id); + match &binding.kind { BindingKind::Argument => { // Avoid, e.g., `self.values`. if matches!(name.id.as_str(), "self" | "cls") { @@ -48,7 +51,21 @@ pub(super) fn test_expression(expr: &Expr, semantic: &SemanticModel) -> Resoluti | BindingKind::NamedExprAssignment | BindingKind::LoopVar | BindingKind::Global(_) - | BindingKind::Nonlocal(_, _) => Resolution::RelevantLocal, + | BindingKind::Nonlocal(_, _) => { + // Check if this binding comes from pandas or another relevant source + if let Some(assigned_value) = find_binding_value(binding, semantic) { + // Check if the assigned value comes from pandas + if is_pandas_related_value(assigned_value, semantic) { + Resolution::RelevantLocal + } else { + // This is a non-pandas binding (e.g., literal, numpy, etc.) + Resolution::IrrelevantBinding + } + } else { + // If we can't determine the source, be conservative and treat as relevant + Resolution::RelevantLocal + } + } BindingKind::Import(import) if matches!(import.qualified_name().segments(), ["pandas"]) => { @@ -61,3 +78,46 @@ pub(super) fn test_expression(expr: &Expr, semantic: &SemanticModel) -> Resoluti _ => Resolution::RelevantLocal, } } + +/// Check if an expression value is related to pandas (e.g., comes from pandas module or operations). +fn is_pandas_related_value(expr: &Expr, semantic: &SemanticModel) -> bool { + match expr { + // Literals are definitely not pandas-related + Expr::StringLiteral(_) + | Expr::BytesLiteral(_) + | Expr::NumberLiteral(_) + | Expr::BooleanLiteral(_) + | Expr::NoneLiteral(_) + | Expr::EllipsisLiteral(_) + | Expr::Tuple(_) + | Expr::List(_) + | Expr::Set(_) + | Expr::Dict(_) => false, + + // Direct pandas module access + Expr::Name(name) => { + if let Some(binding_id) = semantic.resolve_name(name) { + let binding = semantic.binding(binding_id); + if let BindingKind::Import(import) = &binding.kind { + return matches!(import.qualified_name().segments(), ["pandas"]); + } + } + false + } + // Method calls on pandas objects + Expr::Attribute(attr) => { + // Check if the object being accessed is pandas-related + is_pandas_related_value(attr.value.as_ref(), semantic) + } + // Function calls - check if they're pandas functions + Expr::Call(call) => { + if let Some(qualified_name) = semantic.resolve_qualified_name(&call.func) { + return qualified_name.segments().starts_with(&["pandas"]); + } + false + } + // For other expressions, we can't easily determine if they're pandas-related + // so we return false to be conservative (treat as non-pandas) + _ => false, + } +} diff --git a/crates/ruff_linter/src/rules/pandas_vet/mod.rs b/crates/ruff_linter/src/rules/pandas_vet/mod.rs index 3e96328c69..46c1f439f9 100644 --- a/crates/ruff_linter/src/rules/pandas_vet/mod.rs +++ b/crates/ruff_linter/src/rules/pandas_vet/mod.rs @@ -308,6 +308,14 @@ mod tests { ", "PD011_pass_numpy_typed_unique_inverse" )] + #[test_case( + r" + import pandas as pd + p = 1 + result = p.values + ", + "PD011_pass_simple_non_pandas" + )] #[test_case( r#" import pandas as pd diff --git a/crates/ruff_linter/src/rules/pandas_vet/rules/attr.rs b/crates/ruff_linter/src/rules/pandas_vet/rules/attr.rs index c5f34c94ed..ab6b55104b 100644 --- a/crates/ruff_linter/src/rules/pandas_vet/rules/attr.rs +++ b/crates/ruff_linter/src/rules/pandas_vet/rules/attr.rs @@ -1,6 +1,6 @@ use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{self as ast, Expr}; -use ruff_python_semantic::{Modules, analyze::typing::find_binding_value}; +use ruff_python_semantic::Modules; use ruff_text_size::Ranged; use crate::Violation; @@ -43,38 +43,6 @@ impl Violation for PandasUseOfDotValues { } } -/// Check if a binding comes from a NumPy function that returns a `NamedTuple` with a `.values` field. -fn is_numpy_namedtuple_binding( - expr: &Expr, - semantic: &ruff_python_semantic::SemanticModel, -) -> bool { - let Expr::Name(name) = expr else { - return false; - }; - - let Some(binding_id) = semantic.resolve_name(name) else { - return false; - }; - let binding = semantic.binding(binding_id); - - let Some(assigned_value) = find_binding_value(binding, semantic) else { - return false; - }; - - let Some(call_expr) = assigned_value.as_call_expr() else { - return false; - }; - - let Some(qualified_name) = semantic.resolve_qualified_name(&call_expr.func) else { - return false; - }; - - matches!( - qualified_name.segments(), - ["numpy", "unique_inverse" | "unique_all" | "unique_counts"] - ) -} - /// PD011 pub(crate) fn attr(checker: &Checker, attribute: &ast::ExprAttribute) { if !checker.semantic().seen_module(Modules::PANDAS) { @@ -109,10 +77,5 @@ pub(crate) fn attr(checker: &Checker, attribute: &ast::ExprAttribute) { return; } - // Avoid flagging on NumPy `NamedTuples` that have a legitimate `.values` field - if is_numpy_namedtuple_binding(attribute.value.as_ref(), checker.semantic()) { - return; - } - checker.report_diagnostic(PandasUseOfDotValues, attribute.range()); } diff --git a/crates/ruff_linter/src/rules/pandas_vet/snapshots/ruff_linter__rules__pandas_vet__tests__PD011_pass_simple_non_pandas.snap b/crates/ruff_linter/src/rules/pandas_vet/snapshots/ruff_linter__rules__pandas_vet__tests__PD011_pass_simple_non_pandas.snap new file mode 100644 index 0000000000..07173f8f1b --- /dev/null +++ b/crates/ruff_linter/src/rules/pandas_vet/snapshots/ruff_linter__rules__pandas_vet__tests__PD011_pass_simple_non_pandas.snap @@ -0,0 +1,4 @@ +--- +source: crates/ruff_linter/src/rules/pandas_vet/mod.rs +--- + From bc87b705ea71f81fde728e0ec7ec1e07dbe6f0d2 Mon Sep 17 00:00:00 2001 From: Dan Date: Sat, 1 Nov 2025 13:31:05 -0400 Subject: [PATCH 3/4] Improve PD011 detection to exclude non-pandas .values usage Refactored pandas_vet helpers to more accurately distinguish between pandas and non-pandas sources for .values attribute access, explicitly excluding numpy and other irrelevant bindings. Added a new test fixture and snapshot for PD011, and updated test cases to use the fixture file. --- .../test/fixtures/pandas_vet/PD011.py | 38 +++++++ .../src/rules/pandas_vet/helpers.rs | 102 ++++++++---------- .../ruff_linter/src/rules/pandas_vet/mod.rs | 48 +-------- ...es__pandas_vet__tests__PD011_PD011.py.snap | 11 ++ 4 files changed, 97 insertions(+), 102 deletions(-) create mode 100644 crates/ruff_linter/resources/test/fixtures/pandas_vet/PD011.py create mode 100644 crates/ruff_linter/src/rules/pandas_vet/snapshots/ruff_linter__rules__pandas_vet__tests__PD011_PD011.py.snap diff --git a/crates/ruff_linter/resources/test/fixtures/pandas_vet/PD011.py b/crates/ruff_linter/resources/test/fixtures/pandas_vet/PD011.py new file mode 100644 index 0000000000..bd9be741a3 --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/pandas_vet/PD011.py @@ -0,0 +1,38 @@ +import pandas as pd +import numpy as np + + +def test_numpy_unique_inverse(): + unique = np.unique_inverse([1, 2, 3, 2, 1]) + result = unique.values + + +def test_numpy_unique_all(): + unique = np.unique_all([1, 2, 3, 2, 1]) + result = unique.values + + +def test_numpy_unique_counts(): + unique = np.unique_counts([1, 2, 3, 2, 1]) + result = unique.values + + +def test_numpy_typed_unique_inverse(): + from typing import TYPE_CHECKING + if TYPE_CHECKING: + from numpy.lib._arraysetops_impl import UniqueInverseResult + unique: UniqueInverseResult[np.uint64] = np.unique_inverse([1, 2, 3, 2, 1]) + result = unique.values + + +def test_simple_non_pandas(): + p = 1 + result = p.values + + +def test_pandas_dataframe_values(): + """This should trigger PD011 - pandas DataFrame .values usage""" + import pandas as pd + x = pd.DataFrame() + result = x.values + diff --git a/crates/ruff_linter/src/rules/pandas_vet/helpers.rs b/crates/ruff_linter/src/rules/pandas_vet/helpers.rs index 3b42617ce7..45d7871392 100644 --- a/crates/ruff_linter/src/rules/pandas_vet/helpers.rs +++ b/crates/ruff_linter/src/rules/pandas_vet/helpers.rs @@ -18,6 +18,7 @@ pub(super) enum Resolution { /// Test an [`Expr`] for relevance to Pandas-related operations. pub(super) fn test_expression(expr: &Expr, semantic: &SemanticModel) -> Resolution { match expr { + // Literals in the expression itself are definitely not pandas-related Expr::StringLiteral(_) | Expr::BytesLiteral(_) | Expr::NumberLiteral(_) @@ -43,6 +44,7 @@ pub(super) fn test_expression(expr: &Expr, semantic: &SemanticModel) -> Resoluti if matches!(name.id.as_str(), "self" | "cls") { Resolution::IrrelevantBinding } else { + // Function arguments are treated as relevant unless proven otherwise Resolution::RelevantLocal } } @@ -52,72 +54,62 @@ pub(super) fn test_expression(expr: &Expr, semantic: &SemanticModel) -> Resoluti | BindingKind::LoopVar | BindingKind::Global(_) | BindingKind::Nonlocal(_, _) => { - // Check if this binding comes from pandas or another relevant source + // Check if this binding comes from a definitively non-pandas source if let Some(assigned_value) = find_binding_value(binding, semantic) { - // Check if the assigned value comes from pandas - if is_pandas_related_value(assigned_value, semantic) { - Resolution::RelevantLocal - } else { - // This is a non-pandas binding (e.g., literal, numpy, etc.) - Resolution::IrrelevantBinding + // Recurse to check the assigned value + match test_expression(assigned_value, semantic) { + // If the assigned value is definitively not pandas (literals, etc.) + Resolution::IrrelevantExpression => { + Resolution::IrrelevantBinding + } + // If it's clearly pandas-related, treat as relevant + Resolution::RelevantLocal | Resolution::PandasModule => { + Resolution::RelevantLocal + } + // If we got IrrelevantBinding, it means we traced it back to a + // non-pandas source (e.g., numpy import), so keep it as irrelevant + Resolution::IrrelevantBinding => Resolution::IrrelevantBinding, } } else { - // If we can't determine the source, be conservative and treat as relevant + // If we can't determine the source, be liberal and treat as relevant + // to avoid false negatives (e.g., function parameters with annotations) Resolution::RelevantLocal } } - BindingKind::Import(import) - if matches!(import.qualified_name().segments(), ["pandas"]) => - { - Resolution::PandasModule + BindingKind::Import(import) => { + let segments = import.qualified_name().segments(); + if matches!(segments, ["pandas"]) { + Resolution::PandasModule + } else if matches!(segments, ["numpy"]) { + // Explicitly exclude numpy imports + Resolution::IrrelevantBinding + } else { + Resolution::IrrelevantBinding + } } _ => Resolution::IrrelevantBinding, } }) } + // Recurse for attribute access (e.g., df.values -> check df) + Expr::Attribute(attr) => test_expression(attr.value.as_ref(), semantic), + // Recurse for call expressions (e.g., pd.DataFrame() -> check pd) + Expr::Call(call) => { + // Check if this is a pandas function call + if let Some(qualified_name) = semantic.resolve_qualified_name(&call.func) { + let segments = qualified_name.segments(); + if segments.starts_with(&["pandas"]) { + return Resolution::RelevantLocal; + } + // Explicitly exclude numpy function calls + if segments.starts_with(&["numpy"]) || segments.starts_with(&["np"]) { + return Resolution::IrrelevantBinding; + } + } + // For other calls, recurse on the function expression + test_expression(&call.func, semantic) + } + // For other expressions, default to relevant to avoid false negatives _ => Resolution::RelevantLocal, } } - -/// Check if an expression value is related to pandas (e.g., comes from pandas module or operations). -fn is_pandas_related_value(expr: &Expr, semantic: &SemanticModel) -> bool { - match expr { - // Literals are definitely not pandas-related - Expr::StringLiteral(_) - | Expr::BytesLiteral(_) - | Expr::NumberLiteral(_) - | Expr::BooleanLiteral(_) - | Expr::NoneLiteral(_) - | Expr::EllipsisLiteral(_) - | Expr::Tuple(_) - | Expr::List(_) - | Expr::Set(_) - | Expr::Dict(_) => false, - - // Direct pandas module access - Expr::Name(name) => { - if let Some(binding_id) = semantic.resolve_name(name) { - let binding = semantic.binding(binding_id); - if let BindingKind::Import(import) = &binding.kind { - return matches!(import.qualified_name().segments(), ["pandas"]); - } - } - false - } - // Method calls on pandas objects - Expr::Attribute(attr) => { - // Check if the object being accessed is pandas-related - is_pandas_related_value(attr.value.as_ref(), semantic) - } - // Function calls - check if they're pandas functions - Expr::Call(call) => { - if let Some(qualified_name) = semantic.resolve_qualified_name(&call.func) { - return qualified_name.segments().starts_with(&["pandas"]); - } - false - } - // For other expressions, we can't easily determine if they're pandas-related - // so we return false to be conservative (treat as non-pandas) - _ => false, - } -} diff --git a/crates/ruff_linter/src/rules/pandas_vet/mod.rs b/crates/ruff_linter/src/rules/pandas_vet/mod.rs index 46c1f439f9..7e6582d30c 100644 --- a/crates/ruff_linter/src/rules/pandas_vet/mod.rs +++ b/crates/ruff_linter/src/rules/pandas_vet/mod.rs @@ -269,53 +269,6 @@ mod tests { ", "PD011_pass_node_name" )] - #[test_case( - r" - import pandas as pd - import numpy as np - unique = np.unique_inverse([1, 2, 3, 2, 1]) - result = unique.values - ", - "PD011_pass_numpy_unique_inverse" - )] - #[test_case( - r" - import pandas as pd - import numpy as np - unique = np.unique_all([1, 2, 3, 2, 1]) - result = unique.values - ", - "PD011_pass_numpy_unique_all" - )] - #[test_case( - r" - import pandas as pd - import numpy as np - unique = np.unique_counts([1, 2, 3, 2, 1]) - result = unique.values - ", - "PD011_pass_numpy_unique_counts" - )] - #[test_case( - r" - import pandas as pd - from typing import TYPE_CHECKING - if TYPE_CHECKING: - from numpy.lib._arraysetops_impl import UniqueInverseResult - import numpy as np - unique: UniqueInverseResult[np.uint64] = np.unique_inverse([1, 2, 3, 2, 1]) - result = unique.values - ", - "PD011_pass_numpy_typed_unique_inverse" - )] - #[test_case( - r" - import pandas as pd - p = 1 - result = p.values - ", - "PD011_pass_simple_non_pandas" - )] #[test_case( r#" import pandas as pd @@ -426,6 +379,7 @@ mod tests { )] #[test_case(Rule::PandasUseOfInplaceArgument, Path::new("PD002.py"))] #[test_case(Rule::PandasNuniqueConstantSeriesCheck, Path::new("PD101.py"))] + #[test_case(Rule::PandasUseOfDotValues, Path::new("PD011.py"))] fn paths(rule_code: Rule, path: &Path) -> Result<()> { let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy()); let diagnostics = test_path( diff --git a/crates/ruff_linter/src/rules/pandas_vet/snapshots/ruff_linter__rules__pandas_vet__tests__PD011_PD011.py.snap b/crates/ruff_linter/src/rules/pandas_vet/snapshots/ruff_linter__rules__pandas_vet__tests__PD011_PD011.py.snap new file mode 100644 index 0000000000..ccd01f2903 --- /dev/null +++ b/crates/ruff_linter/src/rules/pandas_vet/snapshots/ruff_linter__rules__pandas_vet__tests__PD011_PD011.py.snap @@ -0,0 +1,11 @@ +--- +source: crates/ruff_linter/src/rules/pandas_vet/mod.rs +--- +PD011 Use `.to_numpy()` instead of `.values` + --> PD011.py:37:14 + | +35 | import pandas as pd +36 | x = pd.DataFrame() +37 | result = x.values + | ^^^^^^^^ + | From 19a34591aced4fb3483da66d8b1233243d992068 Mon Sep 17 00:00:00 2001 From: Dan Date: Sat, 1 Nov 2025 13:52:50 -0400 Subject: [PATCH 4/4] Remove obsolete PD011 passing snapshot files Deleted unused snapshot files for PD011 passing tests in pandas_vet. These files are no longer needed, due to test refactoring or removal. --- ...das_vet__tests__PD011_pass_numpy_typed_unique_inverse.snap | 4 ---- ...rules__pandas_vet__tests__PD011_pass_numpy_unique_all.snap | 4 ---- ...es__pandas_vet__tests__PD011_pass_numpy_unique_counts.snap | 4 ---- ...s__pandas_vet__tests__PD011_pass_numpy_unique_inverse.snap | 4 ---- ...ules__pandas_vet__tests__PD011_pass_simple_non_pandas.snap | 4 ---- 5 files changed, 20 deletions(-) delete mode 100644 crates/ruff_linter/src/rules/pandas_vet/snapshots/ruff_linter__rules__pandas_vet__tests__PD011_pass_numpy_typed_unique_inverse.snap delete mode 100644 crates/ruff_linter/src/rules/pandas_vet/snapshots/ruff_linter__rules__pandas_vet__tests__PD011_pass_numpy_unique_all.snap delete mode 100644 crates/ruff_linter/src/rules/pandas_vet/snapshots/ruff_linter__rules__pandas_vet__tests__PD011_pass_numpy_unique_counts.snap delete mode 100644 crates/ruff_linter/src/rules/pandas_vet/snapshots/ruff_linter__rules__pandas_vet__tests__PD011_pass_numpy_unique_inverse.snap delete mode 100644 crates/ruff_linter/src/rules/pandas_vet/snapshots/ruff_linter__rules__pandas_vet__tests__PD011_pass_simple_non_pandas.snap diff --git a/crates/ruff_linter/src/rules/pandas_vet/snapshots/ruff_linter__rules__pandas_vet__tests__PD011_pass_numpy_typed_unique_inverse.snap b/crates/ruff_linter/src/rules/pandas_vet/snapshots/ruff_linter__rules__pandas_vet__tests__PD011_pass_numpy_typed_unique_inverse.snap deleted file mode 100644 index 07173f8f1b..0000000000 --- a/crates/ruff_linter/src/rules/pandas_vet/snapshots/ruff_linter__rules__pandas_vet__tests__PD011_pass_numpy_typed_unique_inverse.snap +++ /dev/null @@ -1,4 +0,0 @@ ---- -source: crates/ruff_linter/src/rules/pandas_vet/mod.rs ---- - diff --git a/crates/ruff_linter/src/rules/pandas_vet/snapshots/ruff_linter__rules__pandas_vet__tests__PD011_pass_numpy_unique_all.snap b/crates/ruff_linter/src/rules/pandas_vet/snapshots/ruff_linter__rules__pandas_vet__tests__PD011_pass_numpy_unique_all.snap deleted file mode 100644 index 07173f8f1b..0000000000 --- a/crates/ruff_linter/src/rules/pandas_vet/snapshots/ruff_linter__rules__pandas_vet__tests__PD011_pass_numpy_unique_all.snap +++ /dev/null @@ -1,4 +0,0 @@ ---- -source: crates/ruff_linter/src/rules/pandas_vet/mod.rs ---- - diff --git a/crates/ruff_linter/src/rules/pandas_vet/snapshots/ruff_linter__rules__pandas_vet__tests__PD011_pass_numpy_unique_counts.snap b/crates/ruff_linter/src/rules/pandas_vet/snapshots/ruff_linter__rules__pandas_vet__tests__PD011_pass_numpy_unique_counts.snap deleted file mode 100644 index 07173f8f1b..0000000000 --- a/crates/ruff_linter/src/rules/pandas_vet/snapshots/ruff_linter__rules__pandas_vet__tests__PD011_pass_numpy_unique_counts.snap +++ /dev/null @@ -1,4 +0,0 @@ ---- -source: crates/ruff_linter/src/rules/pandas_vet/mod.rs ---- - diff --git a/crates/ruff_linter/src/rules/pandas_vet/snapshots/ruff_linter__rules__pandas_vet__tests__PD011_pass_numpy_unique_inverse.snap b/crates/ruff_linter/src/rules/pandas_vet/snapshots/ruff_linter__rules__pandas_vet__tests__PD011_pass_numpy_unique_inverse.snap deleted file mode 100644 index 07173f8f1b..0000000000 --- a/crates/ruff_linter/src/rules/pandas_vet/snapshots/ruff_linter__rules__pandas_vet__tests__PD011_pass_numpy_unique_inverse.snap +++ /dev/null @@ -1,4 +0,0 @@ ---- -source: crates/ruff_linter/src/rules/pandas_vet/mod.rs ---- - diff --git a/crates/ruff_linter/src/rules/pandas_vet/snapshots/ruff_linter__rules__pandas_vet__tests__PD011_pass_simple_non_pandas.snap b/crates/ruff_linter/src/rules/pandas_vet/snapshots/ruff_linter__rules__pandas_vet__tests__PD011_pass_simple_non_pandas.snap deleted file mode 100644 index 07173f8f1b..0000000000 --- a/crates/ruff_linter/src/rules/pandas_vet/snapshots/ruff_linter__rules__pandas_vet__tests__PD011_pass_simple_non_pandas.snap +++ /dev/null @@ -1,4 +0,0 @@ ---- -source: crates/ruff_linter/src/rules/pandas_vet/mod.rs ---- -