The changes in this commit were generated by running: for f in $(find src -name '*.rs'); do sed -Ei 's/use crate::registry::.*;/\0use crate::violations;/g' $f; done for f in $(find src -name '*.rs'); do sed -Ei 's/CheckKind::([A-Z])/violations::\1/g' $f; done git checkout src/registry.rs src/lib.rs src/lib_wasm.rs src/violations.rs cargo +nightly fmt
115 lines
2.9 KiB
Rust
115 lines
2.9 KiB
Rust
use rustpython_ast::{Arguments, ExprKind};
|
|
use rustpython_parser::ast::{Constant, Expr};
|
|
|
|
use crate::ast::types::Range;
|
|
use crate::checkers::ast::Checker;
|
|
use crate::registry::{Check, CheckKind};
|
|
use crate::violations;
|
|
|
|
const FUNC_NAME_ALLOWLIST: &[&str] = &[
|
|
"assertEqual",
|
|
"assertEquals",
|
|
"assertNotEqual",
|
|
"assertNotEquals",
|
|
"failIfEqual",
|
|
"failUnlessEqual",
|
|
"fromkeys",
|
|
"get",
|
|
"getattr",
|
|
"index",
|
|
"pop",
|
|
"setattr",
|
|
"setdefault",
|
|
];
|
|
|
|
/// 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
|
|
/// be either the first or second argument in the call.
|
|
fn allow_boolean_trap(func: &Expr) -> bool {
|
|
if let ExprKind::Attribute { attr, .. } = &func.node {
|
|
return FUNC_NAME_ALLOWLIST.contains(&attr.as_ref());
|
|
}
|
|
|
|
if let ExprKind::Name { id, .. } = &func.node {
|
|
return FUNC_NAME_ALLOWLIST.contains(&id.as_ref());
|
|
}
|
|
|
|
false
|
|
}
|
|
|
|
fn is_boolean_arg(arg: &Expr) -> bool {
|
|
matches!(
|
|
&arg.node,
|
|
ExprKind::Constant {
|
|
value: Constant::Bool(_),
|
|
..
|
|
}
|
|
)
|
|
}
|
|
|
|
fn add_if_boolean(checker: &mut Checker, arg: &Expr, kind: CheckKind) {
|
|
if is_boolean_arg(arg) {
|
|
checker
|
|
.checks
|
|
.push(Check::new(kind, Range::from_located(arg)));
|
|
}
|
|
}
|
|
|
|
pub fn check_positional_boolean_in_def(checker: &mut Checker, arguments: &Arguments) {
|
|
for arg in arguments.posonlyargs.iter().chain(arguments.args.iter()) {
|
|
if arg.node.annotation.is_none() {
|
|
continue;
|
|
}
|
|
let Some(expr) = &arg.node.annotation else {
|
|
continue;
|
|
};
|
|
|
|
// check for both bool (python class) and 'bool' (string annotation)
|
|
let hint = match &expr.node {
|
|
ExprKind::Name { id, .. } => id == "bool",
|
|
ExprKind::Constant {
|
|
value: Constant::Str(value),
|
|
..
|
|
} => value == "bool",
|
|
_ => false,
|
|
};
|
|
if !hint {
|
|
continue;
|
|
}
|
|
checker.checks.push(Check::new(
|
|
violations::BooleanPositionalArgInFunctionDefinition,
|
|
Range::from_located(arg),
|
|
));
|
|
}
|
|
}
|
|
|
|
pub fn check_boolean_default_value_in_function_definition(
|
|
checker: &mut Checker,
|
|
arguments: &Arguments,
|
|
) {
|
|
for arg in &arguments.defaults {
|
|
add_if_boolean(
|
|
checker,
|
|
arg,
|
|
violations::BooleanDefaultValueInFunctionDefinition,
|
|
);
|
|
}
|
|
}
|
|
|
|
pub fn check_boolean_positional_value_in_function_call(
|
|
checker: &mut Checker,
|
|
args: &[Expr],
|
|
func: &Expr,
|
|
) {
|
|
for arg in args {
|
|
if allow_boolean_trap(func) {
|
|
continue;
|
|
}
|
|
add_if_boolean(
|
|
checker,
|
|
arg,
|
|
violations::BooleanPositionalValueInFunctionCall,
|
|
);
|
|
}
|
|
}
|