mirror of
https://github.com/astral-sh/ruff.git
synced 2025-11-03 05:13:00 +00:00
[ruff] Fix B004 to skip invalid hasattr/getattr calls (#20486)
## Summary Fixes #20440 Fix B004 to skip invalid hasattr/getattr calls - Add argument validation for `hasattr` and `getattr` - Skip B004 rule when function calls have invalid argument patterns
This commit is contained in:
parent
bd5b3e4f6e
commit
43cda2dfe9
4 changed files with 42 additions and 2 deletions
|
|
@ -52,3 +52,21 @@ class A:
|
|||
|
||||
assert hasattr(A(), "__call__")
|
||||
assert callable(A()) is False
|
||||
|
||||
# https://github.com/astral-sh/ruff/issues/20440
|
||||
def test_invalid_hasattr_calls():
|
||||
hasattr(0, "__call__", 0) # 3 args - invalid
|
||||
hasattr(0, "__call__", x=0) # keyword arg - invalid
|
||||
hasattr(0, "__call__", 0, x=0) # 3 args + keyword - invalid
|
||||
hasattr() # no args - invalid
|
||||
hasattr(0) # 1 arg - invalid
|
||||
hasattr(*(), "__call__", "extra") # unpacking - invalid
|
||||
hasattr(*()) # unpacking - invalid
|
||||
|
||||
def test_invalid_getattr_calls():
|
||||
getattr(0, "__call__", None, "extra") # 4 args - invalid
|
||||
getattr(0, "__call__", default=None) # keyword arg - invalid
|
||||
getattr() # no args - invalid
|
||||
getattr(0) # 1 arg - invalid
|
||||
getattr(*(), "__call__", None, "extra") # unpacking - invalid
|
||||
getattr(*()) # unpacking - invalid
|
||||
|
|
|
|||
|
|
@ -717,7 +717,9 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) {
|
|||
flake8_bugbear::rules::re_sub_positional_args(checker, call);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::UnreliableCallableCheck) {
|
||||
flake8_bugbear::rules::unreliable_callable_check(checker, expr, func, args);
|
||||
flake8_bugbear::rules::unreliable_callable_check(
|
||||
checker, expr, func, args, keywords,
|
||||
);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::StripWithMultiCharacters) {
|
||||
flake8_bugbear::rules::strip_with_multi_characters(checker, expr, func, args);
|
||||
|
|
|
|||
|
|
@ -90,7 +90,11 @@ pub(crate) fn unreliable_callable_check(
|
|||
expr: &Expr,
|
||||
func: &Expr,
|
||||
args: &[Expr],
|
||||
keywords: &[ast::Keyword],
|
||||
) {
|
||||
if !keywords.is_empty() {
|
||||
return;
|
||||
}
|
||||
let [obj, attr, ..] = args else {
|
||||
return;
|
||||
};
|
||||
|
|
@ -103,7 +107,21 @@ pub(crate) fn unreliable_callable_check(
|
|||
let Some(builtins_function) = checker.semantic().resolve_builtin_symbol(func) else {
|
||||
return;
|
||||
};
|
||||
if !matches!(builtins_function, "hasattr" | "getattr") {
|
||||
|
||||
// Validate function arguments based on function name
|
||||
let valid_args = match builtins_function {
|
||||
"hasattr" => {
|
||||
// hasattr should have exactly 2 positional arguments and no keywords
|
||||
args.len() == 2
|
||||
}
|
||||
"getattr" => {
|
||||
// getattr should have 2 or 3 positional arguments and no keywords
|
||||
args.len() == 2 || args.len() == 3
|
||||
}
|
||||
_ => return,
|
||||
};
|
||||
|
||||
if !valid_args {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -156,4 +156,6 @@ help: Replace with `callable()`
|
|||
- assert hasattr(A(), "__call__")
|
||||
53 + assert callable(A())
|
||||
54 | assert callable(A()) is False
|
||||
55 |
|
||||
56 | # https://github.com/astral-sh/ruff/issues/20440
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue