mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-29 21:35:58 +00:00
Allow Boolean positionals in setters (#9429)
## Summary Ignores Boolean trap enforcement for methods that appear to be setters (as in the Qt and pygame APIs). Closes https://github.com/astral-sh/ruff/issues/9287. Closes https://github.com/astral-sh/ruff/issues/8923.
This commit is contained in:
parent
94968fedd5
commit
f419af494f
7 changed files with 55 additions and 33 deletions
|
@ -71,6 +71,8 @@ foo.is_(True)
|
||||||
bar.is_not(False)
|
bar.is_not(False)
|
||||||
next(iter([]), False)
|
next(iter([]), False)
|
||||||
sa.func.coalesce(tbl.c.valid, False)
|
sa.func.coalesce(tbl.c.valid, False)
|
||||||
|
setVisible(True)
|
||||||
|
set_visible(True)
|
||||||
|
|
||||||
|
|
||||||
class Registry:
|
class Registry:
|
||||||
|
@ -114,3 +116,6 @@ from typing import override
|
||||||
@override
|
@override
|
||||||
def func(x: bool):
|
def func(x: bool):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
settings(True)
|
||||||
|
|
|
@ -724,7 +724,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if checker.enabled(Rule::BooleanPositionalValueInCall) {
|
if checker.enabled(Rule::BooleanPositionalValueInCall) {
|
||||||
flake8_boolean_trap::rules::boolean_positional_value_in_call(checker, args, func);
|
flake8_boolean_trap::rules::boolean_positional_value_in_call(checker, call);
|
||||||
}
|
}
|
||||||
if checker.enabled(Rule::Debugger) {
|
if checker.enabled(Rule::Debugger) {
|
||||||
flake8_debugger::rules::debugger_call(checker, expr, func);
|
flake8_debugger::rules::debugger_call(checker, expr, func);
|
||||||
|
|
|
@ -51,13 +51,29 @@ pub(super) fn is_allowed_func_def(name: &str) -> bool {
|
||||||
/// Returns `true` if an argument is allowed to use a boolean trap. To return
|
/// Returns `true` if an argument is allowed to use a boolean trap. To return
|
||||||
/// `true`, the function name must be explicitly allowed, and the argument must
|
/// `true`, the function name must be explicitly allowed, and the argument must
|
||||||
/// be either the first or second argument in the call.
|
/// be either the first or second argument in the call.
|
||||||
pub(super) fn allow_boolean_trap(func: &Expr) -> bool {
|
pub(super) fn allow_boolean_trap(call: &ast::ExprCall) -> bool {
|
||||||
if let Expr::Attribute(ast::ExprAttribute { attr, .. }) = func {
|
let func_name = match call.func.as_ref() {
|
||||||
return is_allowed_func_call(attr);
|
Expr::Attribute(ast::ExprAttribute { attr, .. }) => attr.as_str(),
|
||||||
|
Expr::Name(ast::ExprName { id, .. }) => id.as_str(),
|
||||||
|
_ => return false,
|
||||||
|
};
|
||||||
|
|
||||||
|
// If the function name is explicitly allowed, then the boolean trap is
|
||||||
|
// allowed.
|
||||||
|
if is_allowed_func_call(func_name) {
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Expr::Name(ast::ExprName { id, .. }) = func {
|
// If the function appears to be a setter (e.g., `set_visible` or `setVisible`), then the
|
||||||
return is_allowed_func_call(id);
|
// boolean trap is allowed. We want to avoid raising a violation for cases in which the argument
|
||||||
|
// is positional-only and third-party, and this tends to be the case for setters.
|
||||||
|
if call.arguments.args.len() == 1 {
|
||||||
|
if func_name
|
||||||
|
.strip_prefix("set")
|
||||||
|
.is_some_and(|suffix| suffix.starts_with(|c: char| c == '_' || c.is_ascii_uppercase()))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
false
|
false
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
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::Expr;
|
use ruff_python_ast as ast;
|
||||||
use ruff_text_size::Ranged;
|
use ruff_text_size::Ranged;
|
||||||
|
|
||||||
use crate::checkers::ast::Checker;
|
use crate::checkers::ast::Checker;
|
||||||
|
@ -45,11 +45,16 @@ impl Violation for BooleanPositionalValueInCall {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn boolean_positional_value_in_call(checker: &mut Checker, args: &[Expr], func: &Expr) {
|
pub(crate) fn boolean_positional_value_in_call(checker: &mut Checker, call: &ast::ExprCall) {
|
||||||
if allow_boolean_trap(func) {
|
if allow_boolean_trap(call) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
for arg in args.iter().filter(|arg| arg.is_boolean_literal_expr()) {
|
for arg in call
|
||||||
|
.arguments
|
||||||
|
.args
|
||||||
|
.iter()
|
||||||
|
.filter(|arg| arg.is_boolean_literal_expr())
|
||||||
|
{
|
||||||
checker
|
checker
|
||||||
.diagnostics
|
.diagnostics
|
||||||
.push(Diagnostic::new(BooleanPositionalValueInCall, arg.range()));
|
.push(Diagnostic::new(BooleanPositionalValueInCall, arg.range()));
|
||||||
|
|
|
@ -81,12 +81,12 @@ FBT.py:19:5: FBT001 Boolean-typed positional argument in function definition
|
||||||
21 | kwonly_nonvalued_nohint,
|
21 | kwonly_nonvalued_nohint,
|
||||||
|
|
|
|
||||||
|
|
||||||
FBT.py:89:19: FBT001 Boolean-typed positional argument in function definition
|
FBT.py:91:19: FBT001 Boolean-typed positional argument in function definition
|
||||||
|
|
|
|
||||||
88 | # FBT001: Boolean positional arg in function definition
|
90 | # FBT001: Boolean positional arg in function definition
|
||||||
89 | def foo(self, value: bool) -> None:
|
91 | def foo(self, value: bool) -> None:
|
||||||
| ^^^^^ FBT001
|
| ^^^^^ FBT001
|
||||||
90 | pass
|
92 | pass
|
||||||
|
|
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -28,14 +28,10 @@ FBT.py:57:17: FBT003 Boolean positional value in function call
|
||||||
59 | mylist.index(True)
|
59 | mylist.index(True)
|
||||||
|
|
|
|
||||||
|
|
||||||
FBT.py:69:38: FBT003 Boolean positional value in function call
|
FBT.py:121:10: FBT003 Boolean positional value in function call
|
||||||
|
|
|
|
||||||
67 | os.set_blocking(0, False)
|
121 | settings(True)
|
||||||
68 | g_action.set_enabled(True)
|
| ^^^^ FBT003
|
||||||
69 | settings.set_enable_developer_extras(True)
|
|
|
||||||
| ^^^^ FBT003
|
|
||||||
70 | foo.is_(True)
|
|
||||||
71 | bar.is_not(False)
|
|
||||||
|
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -81,26 +81,26 @@ FBT.py:19:5: FBT001 Boolean-typed positional argument in function definition
|
||||||
21 | kwonly_nonvalued_nohint,
|
21 | kwonly_nonvalued_nohint,
|
||||||
|
|
|
|
||||||
|
|
||||||
FBT.py:89:19: FBT001 Boolean-typed positional argument in function definition
|
FBT.py:91:19: FBT001 Boolean-typed positional argument in function definition
|
||||||
|
|
|
|
||||||
88 | # FBT001: Boolean positional arg in function definition
|
90 | # FBT001: Boolean positional arg in function definition
|
||||||
89 | def foo(self, value: bool) -> None:
|
91 | def foo(self, value: bool) -> None:
|
||||||
| ^^^^^ FBT001
|
| ^^^^^ FBT001
|
||||||
90 | pass
|
92 | pass
|
||||||
|
|
|
|
||||||
|
|
||||||
FBT.py:99:10: FBT001 Boolean-typed positional argument in function definition
|
FBT.py:101:10: FBT001 Boolean-typed positional argument in function definition
|
||||||
|
|
|
|
||||||
99 | def func(x: Union[list, Optional[int | str | float | bool]]):
|
101 | def func(x: Union[list, Optional[int | str | float | bool]]):
|
||||||
| ^ FBT001
|
| ^ FBT001
|
||||||
100 | pass
|
102 | pass
|
||||||
|
|
|
|
||||||
|
|
||||||
FBT.py:103:10: FBT001 Boolean-typed positional argument in function definition
|
FBT.py:105:10: FBT001 Boolean-typed positional argument in function definition
|
||||||
|
|
|
|
||||||
103 | def func(x: bool | str):
|
105 | def func(x: bool | str):
|
||||||
| ^ FBT001
|
| ^ FBT001
|
||||||
104 | pass
|
106 | pass
|
||||||
|
|
|
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue