Expand the scope of useless-expression (B018) (#3455)

This commit is contained in:
Charlie Marsh 2023-03-23 18:33:58 -04:00 committed by GitHub
parent aea925a898
commit e8d17d23cb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 146 additions and 56 deletions

View file

@ -57,3 +57,9 @@ def foo3():
def foo4(): def foo4():
... ...
def foo5():
foo.bar # Attribute (raise)
object().__class__ # Attribute (raise)
"foo" + "bar" # BinOp (raise)

View file

@ -420,10 +420,6 @@ where
pyupgrade::rules::lru_cache_with_maxsize_none(self, decorator_list); pyupgrade::rules::lru_cache_with_maxsize_none(self, decorator_list);
} }
if self.settings.rules.enabled(Rule::UselessExpression) {
flake8_bugbear::rules::useless_expression(self, body);
}
if self.settings.rules.enabled(Rule::CachedInstanceMethod) { if self.settings.rules.enabled(Rule::CachedInstanceMethod) {
flake8_bugbear::rules::cached_instance_method(self, decorator_list); flake8_bugbear::rules::cached_instance_method(self, decorator_list);
} }
@ -756,10 +752,6 @@ where
} }
} }
if self.settings.rules.enabled(Rule::UselessExpression) {
flake8_bugbear::rules::useless_expression(self, body);
}
if !self.is_stub { if !self.is_stub {
if self.settings.rules.any_enabled(&[ if self.settings.rules.any_enabled(&[
Rule::AbstractBaseClassWithoutAbstractMethod, Rule::AbstractBaseClassWithoutAbstractMethod,
@ -1830,6 +1822,9 @@ where
if self.settings.rules.enabled(Rule::UselessComparison) { if self.settings.rules.enabled(Rule::UselessComparison) {
flake8_bugbear::rules::useless_comparison(self, value); flake8_bugbear::rules::useless_comparison(self, value);
} }
if self.settings.rules.enabled(Rule::UselessExpression) {
flake8_bugbear::rules::useless_expression(self, value);
}
if self if self
.settings .settings
.rules .rules

View file

@ -19,6 +19,7 @@ impl Violation for UselessComparison {
} }
} }
/// B015
pub fn useless_comparison(checker: &mut Checker, expr: &Expr) { pub fn useless_comparison(checker: &mut Checker, expr: &Expr) {
if matches!(expr.node, ExprKind::Compare { .. }) { if matches!(expr.node, ExprKind::Compare { .. }) {
checker checker

View file

@ -1,41 +1,77 @@
use rustpython_parser::ast::{Constant, ExprKind, Stmt, StmtKind}; use rustpython_parser::ast::{Constant, Expr, ExprKind};
use ruff_diagnostics::{Diagnostic, Violation}; use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation}; use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::helpers::contains_effect;
use ruff_python_ast::types::Range; use ruff_python_ast::types::Range;
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
#[derive(Debug, PartialEq, Eq)]
pub enum Kind {
Expression,
Attribute,
}
#[violation] #[violation]
pub struct UselessExpression; pub struct UselessExpression {
kind: Kind,
}
impl Violation for UselessExpression { impl Violation for UselessExpression {
#[derive_message_formats] #[derive_message_formats]
fn message(&self) -> String { fn message(&self) -> String {
match self.kind {
Kind::Expression => {
format!("Found useless expression. Either assign it to a variable or remove it.") format!("Found useless expression. Either assign it to a variable or remove it.")
} }
Kind::Attribute => {
format!(
"Found useless attribute access. Either assign it to a variable or remove it."
)
}
}
}
} }
/// B018 /// B018
pub fn useless_expression(checker: &mut Checker, body: &[Stmt]) { pub fn useless_expression(checker: &mut Checker, value: &Expr) {
for stmt in body { // Ignore comparisons, as they're handled by `useless_comparison`.
if let StmtKind::Expr { value } = &stmt.node { if matches!(value.node, ExprKind::Compare { .. }) {
match &value.node { return;
ExprKind::List { .. } | ExprKind::Dict { .. } | ExprKind::Set { .. } => {
checker
.diagnostics
.push(Diagnostic::new(UselessExpression, Range::from(value)));
} }
ExprKind::Constant { value: val, .. } => match &val {
Constant::Str { .. } | Constant::Ellipsis => {} // Ignore strings, to avoid false positives with docstrings.
_ => { if matches!(
checker value.node,
.diagnostics ExprKind::JoinedStr { .. }
.push(Diagnostic::new(UselessExpression, Range::from(value))); | ExprKind::Constant {
value: Constant::Str(..) | Constant::Ellipsis,
..
} }
) {
return;
}
// Ignore statements that have side effects.
if contains_effect(&checker.ctx, value) {
// Flag attributes as useless expressions, even if they're attached to calls or other
// expressions.
if matches!(value.node, ExprKind::Attribute { .. }) {
checker.diagnostics.push(Diagnostic::new(
UselessExpression {
kind: Kind::Attribute,
}, },
_ => {} Range::from(value),
} ));
} }
return;
} }
checker.diagnostics.push(Diagnostic::new(
UselessExpression {
kind: Kind::Expression,
},
Range::from(value),
));
} }

View file

@ -314,4 +314,43 @@ expression: diagnostics
column: 5 column: 5
fix: ~ fix: ~
parent: ~ parent: ~
- kind:
name: UselessExpression
body: Found useless expression. Either assign it to a variable or remove it.
suggestion: ~
fixable: false
location:
row: 63
column: 4
end_location:
row: 63
column: 11
fix: ~
parent: ~
- kind:
name: UselessExpression
body: Found useless attribute access. Either assign it to a variable or remove it.
suggestion: ~
fixable: false
location:
row: 64
column: 4
end_location:
row: 64
column: 22
fix: ~
parent: ~
- kind:
name: UselessExpression
body: Found useless expression. Either assign it to a variable or remove it.
suggestion: ~
fixable: false
location:
row: 65
column: 4
end_location:
row: 65
column: 17
fix: ~
parent: ~

View file

@ -42,26 +42,6 @@ expression: diagnostics
row: 15 row: 15
column: 21 column: 21
parent: ~ parent: ~
- kind:
name: IfElseBlockInsteadOfDictGet
body: "Use `var = a_dict.get(key, val1 + val2)` instead of an `if` block"
suggestion: "Replace with `var = a_dict.get(key, val1 + val2)`"
fixable: true
location:
row: 18
column: 0
end_location:
row: 21
column: 21
fix:
content: "var = a_dict.get(key, val1 + val2)"
location:
row: 18
column: 0
end_location:
row: 21
column: 21
parent: ~
- kind: - kind:
name: IfElseBlockInsteadOfDictGet name: IfElseBlockInsteadOfDictGet
body: "Use `var = a_dict.get(keys[idx], \"default\")` instead of an `if` block" body: "Use `var = a_dict.get(keys[idx], \"default\")` instead of an `if` block"

View file

@ -37,10 +37,10 @@ expression: diagnostics
content: "" content: ""
location: location:
row: 16 row: 16
column: 0 column: 4
end_location: end_location:
row: 17 row: 16
column: 0 column: 8
parent: ~ parent: ~
- kind: - kind:
name: UnusedVariable name: UnusedVariable

View file

@ -131,6 +131,39 @@ pub fn contains_effect(ctx: &Context, expr: &Expr) -> bool {
} }
} }
// Avoid false positive for overloaded operators.
if let ExprKind::BinOp { left, right, .. } = &expr.node {
if !matches!(
left.node,
ExprKind::Constant { .. }
| ExprKind::JoinedStr { .. }
| ExprKind::List { .. }
| ExprKind::Tuple { .. }
| ExprKind::Set { .. }
| ExprKind::Dict { .. }
| ExprKind::ListComp { .. }
| ExprKind::SetComp { .. }
| ExprKind::DictComp { .. }
) {
return true;
}
if !matches!(
right.node,
ExprKind::Constant { .. }
| ExprKind::JoinedStr { .. }
| ExprKind::List { .. }
| ExprKind::Tuple { .. }
| ExprKind::Set { .. }
| ExprKind::Dict { .. }
| ExprKind::ListComp { .. }
| ExprKind::SetComp { .. }
| ExprKind::DictComp { .. }
) {
return true;
}
return false;
}
// Otherwise, avoid all complex expressions. // Otherwise, avoid all complex expressions.
matches!( matches!(
expr.node, expr.node,