diff --git a/src/ast/helpers.rs b/src/ast/helpers.rs index 1fce821bba..37cec82701 100644 --- a/src/ast/helpers.rs +++ b/src/ast/helpers.rs @@ -212,6 +212,23 @@ pub fn is_constant_non_singleton(expr: &Expr) -> bool { is_constant(expr) && !is_singleton(expr) } +/// Return `true` if an `Expr` is not a reference to a variable (or something +/// that could resolve to a variable, like a function call). +pub fn is_non_variable(expr: &Expr) -> bool { + matches!( + expr.node, + ExprKind::Constant { .. } + | ExprKind::Tuple { .. } + | ExprKind::List { .. } + | ExprKind::Set { .. } + | ExprKind::Dict { .. } + | ExprKind::SetComp { .. } + | ExprKind::ListComp { .. } + | ExprKind::DictComp { .. } + | ExprKind::GeneratorExp { .. } + ) +} + /// Return the `Keyword` with the given name, if it's present in the list of /// `Keyword` arguments. pub fn find_keyword<'a>(keywords: &'a [Keyword], keyword_name: &str) -> Option<&'a Keyword> { diff --git a/src/checkers/ast.rs b/src/checkers/ast.rs index d0daa9ae13..b4b7bdaeaf 100644 --- a/src/checkers/ast.rs +++ b/src/checkers/ast.rs @@ -1588,7 +1588,7 @@ where pylint::plugins::used_prior_global_declaration(self, id, expr); } } - ExprKind::Attribute { attr, .. } => { + ExprKind::Attribute { attr, value, .. } => { // Ex) typing.List[...] if !self.in_deferred_string_type_definition && self.settings.enabled.contains(&CheckCode::UP006) @@ -1629,11 +1629,16 @@ where ] { if self.settings.enabled.contains(&code) { if attr == name { + // Avoid flagging on function calls (e.g., `df.values()`). if let Some(parent) = self.current_expr_parent() { if matches!(parent.0.node, ExprKind::Call { .. }) { continue; } } + // Avoid flagging on non-DataFrames (e.g., `{"a": 1}.values`). + if helpers::is_non_variable(value) { + continue; + } self.add_check(Check::new(code.kind(), Range::from_located(expr))); }; } diff --git a/src/pandas_vet/mod.rs b/src/pandas_vet/mod.rs index c26a476a00..1de9b4bd28 100644 --- a/src/pandas_vet/mod.rs +++ b/src/pandas_vet/mod.rs @@ -81,7 +81,8 @@ mod tests { #[test_case("result = df.to_array()", &[]; "PD011_pass_to_array")] #[test_case("result = df.array", &[]; "PD011_pass_array")] #[test_case("result = df.values", &[CheckCode::PD011]; "PD011_fail_values")] - #[test_case("result = {}.values()", &[]; "PD011_pass_values_call")] + #[test_case("result = df.values()", &[]; "PD011_pass_values_call")] + #[test_case("result = {}.values", &[]; "PD011_pass_values_dict")] #[test_case("result = values", &[]; "PD011_pass_node_name")] #[test_case("employees = pd.read_csv(input_file)", &[]; "PD012_pass_read_csv")] #[test_case("employees = pd.read_table(input_file)", &[CheckCode::PD012]; "PD012_fail_read_table")]