Implement autofix for B009 (#684)

This commit is contained in:
Harutaka Kawamura 2022-11-12 01:06:47 +09:00 committed by GitHub
parent bd3b40688f
commit e727c24f79
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 111 additions and 69 deletions

View file

@ -501,7 +501,7 @@ For more, see [flake8-bugbear](https://pypi.org/project/flake8-bugbear/22.10.27/
| B006 | MutableArgumentDefault | Do not use mutable data structures for argument defaults. | | | B006 | MutableArgumentDefault | Do not use mutable data structures for argument defaults. | |
| B007 | UnusedLoopControlVariable | Loop control variable `i` not used within the loop body. | 🛠 | | B007 | UnusedLoopControlVariable | Loop control variable `i` not used within the loop body. | 🛠 |
| B008 | FunctionCallArgumentDefault | Do not perform function calls in argument defaults. | | | B008 | FunctionCallArgumentDefault | Do not perform function calls in argument defaults. | |
| B009 | GetAttrWithConstant | Do not call `getattr` with a constant attribute value, it is not any safer than normal property access. | | | B009 | GetAttrWithConstant | Do not call `getattr` with a constant attribute value, it is not any safer than normal property access. | 🛠 |
| B010 | SetAttrWithConstant | Do not call setattr with a constant attribute value, it is not any safer than normal property access. | | | B010 | SetAttrWithConstant | Do not call setattr with a constant attribute value, it is not any safer than normal property access. | |
| B011 | DoNotAssertFalse | Do not `assert False` (`python -O` removes these calls), raise `AssertionError()` | 🛠 | | B011 | DoNotAssertFalse | Do not `assert False` (`python -O` removes these calls), raise `AssertionError()` | 🛠 |
| B013 | RedundantTupleInExceptionHandler | A length-one tuple literal is redundant. Write `except ValueError:` instead of `except (ValueError,):`. | | | B013 | RedundantTupleInExceptionHandler | A length-one tuple literal is redundant. Write `except ValueError:` instead of `except (ValueError,):`. | |

View file

@ -1,7 +1,7 @@
""" """
Should emit: Should emit:
B009 - Line 17, 18, 19, 44 B009 - Line 18, 19, 20, 21, 22
B010 - Line 28, 29, 30 B010 - Line 33, 34, 35, 36
""" """
# Valid getattr usage # Valid getattr usage
@ -11,37 +11,26 @@ getattr(foo, "bar{foo}".format(foo="a"), None)
getattr(foo, "bar{foo}".format(foo="a")) getattr(foo, "bar{foo}".format(foo="a"))
getattr(foo, bar, None) getattr(foo, bar, None)
getattr(foo, "123abc") getattr(foo, "123abc")
getattr(foo, r"123\abc")
getattr(foo, "except") getattr(foo, "except")
# Invalid usage # Invalid usage
getattr(foo, "bar") getattr(foo, "bar")
getattr(foo, "_123abc") getattr(foo, "_123abc")
getattr(foo, "abc123") getattr(foo, "abc123")
getattr(foo, r"abc123")
_ = lambda x: getattr(x, "bar")
# Valid setattr usage # Valid setattr usage
setattr(foo, bar, None) setattr(foo, bar, None)
setattr(foo, "bar{foo}".format(foo="a"), None) setattr(foo, "bar{foo}".format(foo="a"), None)
setattr(foo, "123abc", None) setattr(foo, "123abc", None)
setattr(foo, r"123\abc", None)
setattr(foo, "except", None) setattr(foo, "except", None)
_ = lambda x: setattr(x, "bar", 1)
# Invalid usage # Invalid usage
setattr(foo, "bar", None) setattr(foo, "bar", None)
setattr(foo, "_123abc", None) setattr(foo, "_123abc", None)
setattr(foo, "abc123", None) setattr(foo, "abc123", None)
setattr(foo, r"abc123", None)
# Allow use of setattr within lambda expression
# since assignment is not valid in this context.
c = lambda x: setattr(x, "some_attr", 1)
class FakeCookieStore:
def __init__(self, has_setter):
self.cookie_filter = None
if has_setter:
self.setCookieFilter = lambda func: setattr(self, "cookie_filter", func)
# getattr is still flagged within lambda though
c = lambda x: getattr(x, "some_attr")
# should be replaced with
c = lambda x: x.some_attr

View file

@ -1075,11 +1075,11 @@ where
flake8_bugbear::plugins::getattr_with_constant(self, expr, func, args); flake8_bugbear::plugins::getattr_with_constant(self, expr, func, args);
} }
if self.settings.enabled.contains(&CheckCode::B010) { if self.settings.enabled.contains(&CheckCode::B010) {
if !self if self
.scopes .scopes
.iter() .iter()
.rev() .rev()
.any(|scope| matches!(scope.kind, ScopeKind::Lambda)) .all(|scope| !matches!(scope.kind, ScopeKind::Lambda))
{ {
flake8_bugbear::plugins::setattr_with_constant(self, expr, func, args); flake8_bugbear::plugins::setattr_with_constant(self, expr, func, args);
} }
@ -1473,7 +1473,6 @@ where
} }
self.push_scope(Scope::new(ScopeKind::Lambda)) self.push_scope(Scope::new(ScopeKind::Lambda))
} }
ExprKind::ListComp { elt, generators } | ExprKind::SetComp { elt, generators } => { ExprKind::ListComp { elt, generators } | ExprKind::SetComp { elt, generators } => {
if self.settings.enabled.contains(&CheckCode::C416) { if self.settings.enabled.contains(&CheckCode::C416) {
if let Some(check) = flake8_comprehensions::checks::unnecessary_comprehension( if let Some(check) = flake8_comprehensions::checks::unnecessary_comprehension(
@ -1489,7 +1488,6 @@ where
} }
self.push_scope(Scope::new(ScopeKind::Generator)) self.push_scope(Scope::new(ScopeKind::Generator))
} }
ExprKind::GeneratorExp { .. } | ExprKind::DictComp { .. } => { ExprKind::GeneratorExp { .. } | ExprKind::DictComp { .. } => {
self.push_scope(Scope::new(ScopeKind::Generator)) self.push_scope(Scope::new(ScopeKind::Generator))
} }

View file

@ -1743,6 +1743,7 @@ impl CheckKind {
| CheckKind::DeprecatedUnittestAlias(_, _) | CheckKind::DeprecatedUnittestAlias(_, _)
| CheckKind::DoNotAssertFalse | CheckKind::DoNotAssertFalse
| CheckKind::DuplicateHandlerException(_) | CheckKind::DuplicateHandlerException(_)
| CheckKind::GetAttrWithConstant
| CheckKind::IsLiteral | CheckKind::IsLiteral
| CheckKind::NewLineAfterLastParagraph | CheckKind::NewLineAfterLastParagraph
| CheckKind::NewLineAfterSectionName(_) | CheckKind::NewLineAfterSectionName(_)

View file

@ -1,29 +1,53 @@
use rustpython_ast::{Constant, Expr, ExprKind}; use rustpython_ast::{Constant, Expr, ExprContext, ExprKind};
use crate::ast::types::Range; use crate::ast::types::Range;
use crate::autofix::Fix;
use crate::check_ast::Checker; use crate::check_ast::Checker;
use crate::checks::{Check, CheckKind}; use crate::checks::{Check, CheckKind};
use crate::code_gen::SourceGenerator;
use crate::flake8_bugbear::constants::IDENTIFIER_REGEX; use crate::flake8_bugbear::constants::IDENTIFIER_REGEX;
use crate::python::keyword::KWLIST; use crate::python::keyword::KWLIST;
/// B009 fn attribute(value: &Expr, attr: &str) -> Expr {
Expr::new(
Default::default(),
Default::default(),
ExprKind::Attribute {
value: Box::new(value.clone()),
attr: attr.to_string(),
ctx: ExprContext::Load,
},
)
}
pub fn getattr_with_constant(checker: &mut Checker, expr: &Expr, func: &Expr, args: &[Expr]) { pub fn getattr_with_constant(checker: &mut Checker, expr: &Expr, func: &Expr, args: &[Expr]) {
if let ExprKind::Name { id, .. } = &func.node { if let ExprKind::Name { id, .. } = &func.node {
if id == "getattr" { if id == "getattr" {
if let [_, arg] = args { if let [obj, arg] = args {
if let ExprKind::Constant { if let ExprKind::Constant {
value: Constant::Str(value), value: Constant::Str(value),
.. ..
} = &arg.node } = &arg.node
{ {
if IDENTIFIER_REGEX.is_match(value) && !KWLIST.contains(&value.as_str()) { if IDENTIFIER_REGEX.is_match(value) && !KWLIST.contains(&value.as_str()) {
checker.add_check(Check::new( let mut check =
CheckKind::GetAttrWithConstant, Check::new(CheckKind::GetAttrWithConstant, Range::from_located(expr));
Range::from_located(expr), if checker.patch() {
let mut generator = SourceGenerator::new();
if let Ok(()) = generator.unparse_expr(&attribute(obj, value), 0) {
if let Ok(content) = generator.generate() {
check.amend(Fix::replacement(
content,
expr.location,
expr.end_location.unwrap(),
)); ));
} }
} }
} }
checker.add_check(check);
}
}
}
} }
} }
} }

View file

@ -18,7 +18,7 @@ pub fn setattr_with_constant(checker: &mut Checker, expr: &Expr, func: &Expr, ar
{ {
if IDENTIFIER_REGEX.is_match(value) && !KWLIST.contains(&value.as_str()) { if IDENTIFIER_REGEX.is_match(value) && !KWLIST.contains(&value.as_str()) {
checker.add_check(Check::new( checker.add_check(Check::new(
CheckKind::GetAttrWithConstant, CheckKind::SetAttrWithConstant,
Range::from_located(expr), Range::from_located(expr),
)); ));
} }

View file

@ -4,34 +4,87 @@ expression: checks
--- ---
- kind: GetAttrWithConstant - kind: GetAttrWithConstant
location: location:
row: 17 row: 18
column: 0 column: 0
end_location: end_location:
row: 17 row: 18
column: 19 column: 19
fix: ~ fix:
- kind: GetAttrWithConstant patch:
content: foo.bar
location: location:
row: 18 row: 18
column: 0 column: 0
end_location: end_location:
row: 18 row: 18
column: 19
applied: false
- kind: GetAttrWithConstant
location:
row: 19
column: 0
end_location:
row: 19
column: 23 column: 23
fix: ~ fix:
- kind: GetAttrWithConstant patch:
content: foo._123abc
location: location:
row: 19 row: 19
column: 0 column: 0
end_location: end_location:
row: 19 row: 19
column: 22 column: 23
fix: ~ applied: false
- kind: GetAttrWithConstant - kind: GetAttrWithConstant
location: location:
row: 45 row: 20
column: 0
end_location:
row: 20
column: 22
fix:
patch:
content: foo.abc123
location:
row: 20
column: 0
end_location:
row: 20
column: 22
applied: false
- kind: GetAttrWithConstant
location:
row: 21
column: 0
end_location:
row: 21
column: 23
fix:
patch:
content: foo.abc123
location:
row: 21
column: 0
end_location:
row: 21
column: 23
applied: false
- kind: GetAttrWithConstant
location:
row: 22
column: 14 column: 14
end_location: end_location:
row: 45 row: 22
column: 37 column: 31
fix: ~ fix:
patch:
content: x.bar
location:
row: 22
column: 14
end_location:
row: 22
column: 31
applied: false

View file

@ -2,28 +2,5 @@
source: src/linter.rs source: src/linter.rs
expression: checks expression: checks
--- ---
- kind: GetAttrWithConstant []
location:
row: 28
column: 0
end_location:
row: 28
column: 25
fix: ~
- kind: GetAttrWithConstant
location:
row: 29
column: 0
end_location:
row: 29
column: 29
fix: ~
- kind: GetAttrWithConstant
location:
row: 30
column: 0
end_location:
row: 30
column: 28
fix: ~