Skip to content

Commit

Permalink
Expand the scope of useless-expression (B018)
Browse files Browse the repository at this point in the history
  • Loading branch information
charliermarsh committed Mar 17, 2023
1 parent f5e5caa commit bcad23d
Show file tree
Hide file tree
Showing 8 changed files with 126 additions and 52 deletions.
6 changes: 6 additions & 0 deletions crates/ruff/resources/test/fixtures/flake8_bugbear/B018.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,9 @@ def foo3():

def foo4():
...


def foo5():
foo.bar # Attribute (raise)
object().__class__ # Attribute (raise)
"foo" + "bar" # BinOp (raise)
11 changes: 3 additions & 8 deletions crates/ruff/src/checkers/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -417,10 +417,6 @@ where
pyupgrade::rules::functools_cache(self, decorator_list);
}

if self.settings.rules.enabled(Rule::UselessExpression) {
flake8_bugbear::rules::useless_expression(self, body);
}

if self.settings.rules.enabled(Rule::CachedInstanceMethod) {
flake8_bugbear::rules::cached_instance_method(self, decorator_list);
}
Expand Down Expand Up @@ -750,10 +746,6 @@ where
}
}

if self.settings.rules.enabled(Rule::UselessExpression) {
flake8_bugbear::rules::useless_expression(self, body);
}

if !self.is_stub {
if self
.settings
Expand Down Expand Up @@ -1861,6 +1853,9 @@ where
if self.settings.rules.enabled(Rule::UselessComparison) {
flake8_bugbear::rules::useless_comparison(self, value);
}
if self.settings.rules.enabled(Rule::UselessExpression) {
flake8_bugbear::rules::useless_expression(self, value);
}
if self
.settings
.rules
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ impl Violation for UselessComparison {
}
}

/// B015
pub fn useless_comparison(checker: &mut Checker, expr: &Expr) {
if matches!(expr.node, ExprKind::Compare { .. }) {
checker
Expand Down
78 changes: 57 additions & 21 deletions crates/ruff/src/rules/flake8_bugbear/rules/useless_expression.rs
Original file line number Diff line number Diff line change
@@ -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_macros::{derive_message_formats, violation};
use ruff_python_ast::helpers::contains_effect;
use ruff_python_ast::types::Range;

use crate::checkers::ast::Checker;

#[derive(Debug, PartialEq, Eq)]
pub enum Kind {
Expression,
Attribute,
}

#[violation]
pub struct UselessExpression;
pub struct UselessExpression {
kind: Kind,
}

impl Violation for UselessExpression {
#[derive_message_formats]
fn message(&self) -> String {
format!("Found useless expression. Either assign it to a variable or remove it.")
match self.kind {
Kind::Expression => {
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
pub fn useless_expression(checker: &mut Checker, body: &[Stmt]) {
for stmt in body {
if let StmtKind::Expr { value } = &stmt.node {
match &value.node {
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 => {}
_ => {
checker
.diagnostics
.push(Diagnostic::new(UselessExpression, Range::from(value)));
}
},
_ => {}
pub fn useless_expression(checker: &mut Checker, value: &Expr) {
// Ignore comparisons, as they're handled by `useless_comparison`.
if matches!(value.node, ExprKind::Compare { .. }) {
return;
}

// Ignore strings, to avoid false positives with docstrings.
if matches!(
value.node,
ExprKind::JoinedStr { .. }
| 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),
));
}
Original file line number Diff line number Diff line change
Expand Up @@ -314,4 +314,43 @@ expression: diagnostics
column: 5
fix: ~
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: ~

Original file line number Diff line number Diff line change
Expand Up @@ -42,26 +42,6 @@ expression: diagnostics
row: 15
column: 21
parent: ~
- kind:
name: DictGetWithDefault
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:
name: DictGetWithDefault
body: "Use `var = a_dict.get(keys[idx], \"default\")` instead of an `if` block"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,10 @@ expression: diagnostics
content: ""
location:
row: 16
column: 0
column: 4
end_location:
row: 17
column: 0
row: 16
column: 8
parent: ~
- kind:
name: UnusedVariable
Expand Down
17 changes: 17 additions & 0 deletions crates/ruff_python_ast/src/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,23 @@ 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 { .. }
) {
return true;
}
if !matches!(
right.node,
ExprKind::Constant { .. } | ExprKind::JoinedStr { .. }
) {
return true;
}
return false;
}

// Otherwise, avoid all complex expressions.
matches!(
expr.node,
Expand Down

0 comments on commit bcad23d

Please sign in to comment.