Add flake8-boolean-trap (#790)
This commit is contained in:
10
README.md
10
README.md
@@ -512,6 +512,16 @@ For more, see [flake8-comprehensions](https://pypi.org/project/flake8-comprehens
|
||||
| C416 | UnnecessaryComprehension | Unnecessary `(list\|set)` comprehension (rewrite using `(list\|set)()`) | 🛠 |
|
||||
| C417 | UnnecessaryMap | Unnecessary `map` usage (rewrite using a `(list\|set\|dict)` comprehension) | |
|
||||
|
||||
### flake8-boolean-trap
|
||||
|
||||
For more, see [flake8-boolean-trap](https://pypi.org/project/flake8-boolean-trap/0.1.0/) on PyPI.
|
||||
|
||||
| Code | Name | Message | Fix |
|
||||
| ---- | ---- | ------- | --- |
|
||||
| FBT001 | BooleanPositionalArgInFunctionDefinition | Boolean positional arg in function definition | |
|
||||
| FBT002 | BooleanDefaultValueInFunctionDefinition | Boolean default value in function definition | |
|
||||
| FBT003 | BooleanPositionalValueInFunctionCall | Boolean positional value in function call | |
|
||||
|
||||
### flake8-bugbear
|
||||
|
||||
For more, see [flake8-bugbear](https://pypi.org/project/flake8-bugbear/22.10.27/) on PyPI.
|
||||
|
||||
42
resources/test/fixtures/FBT.py
vendored
Normal file
42
resources/test/fixtures/FBT.py
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
def function(
|
||||
posonly_nohint,
|
||||
posonly_nonboolhint: int,
|
||||
posonly_boolhint: bool,
|
||||
posonly_boolstrhint: "bool",
|
||||
/,
|
||||
offset,
|
||||
posorkw_nonvalued_nohint,
|
||||
posorkw_nonvalued_nonboolhint: int,
|
||||
posorkw_nonvalued_boolhint: bool,
|
||||
posorkw_nonvalued_boolstrhint: "bool",
|
||||
posorkw_boolvalued_nohint=True,
|
||||
posorkw_boolvalued_nonboolhint: int = True,
|
||||
posorkw_boolvalued_boolhint: bool = True,
|
||||
posorkw_boolvalued_boolstrhint: "bool" = True,
|
||||
posorkw_nonboolvalued_nohint=1,
|
||||
posorkw_nonboolvalued_nonboolhint: int = 2,
|
||||
posorkw_nonboolvalued_boolhint: bool = 3,
|
||||
posorkw_nonboolvalued_boolstrhint: "bool" = 4,
|
||||
*,
|
||||
kwonly_nonvalued_nohint,
|
||||
kwonly_nonvalued_nonboolhint: int,
|
||||
kwonly_nonvalued_boolhint: bool,
|
||||
kwonly_nonvalued_boolstrhint: "bool",
|
||||
kwonly_boolvalued_nohint=True,
|
||||
kwonly_boolvalued_nonboolhint: int = False,
|
||||
kwonly_boolvalued_boolhint: bool = True,
|
||||
kwonly_boolvalued_boolstrhint: "bool" = True,
|
||||
kwonly_nonboolvalued_nohint=5,
|
||||
kwonly_nonboolvalued_nonboolhint: int = 1,
|
||||
kwonly_nonboolvalued_boolhint: bool = 1,
|
||||
kwonly_nonboolvalued_boolstrhint: "bool" = 1,
|
||||
**kw,
|
||||
):
|
||||
...
|
||||
|
||||
|
||||
def used(do):
|
||||
return do
|
||||
|
||||
used("a", True)
|
||||
used(do=True)
|
||||
@@ -36,8 +36,8 @@ use crate::source_code_locator::SourceCodeLocator;
|
||||
use crate::visibility::{module_visibility, transition_scope, Modifier, Visibility, VisibleScope};
|
||||
use crate::{
|
||||
docstrings, flake8_2020, flake8_annotations, flake8_bandit, flake8_blind_except,
|
||||
flake8_bugbear, flake8_builtins, flake8_comprehensions, flake8_print, flake8_tidy_imports,
|
||||
mccabe, pep8_naming, pycodestyle, pydocstyle, pyflakes, pyupgrade,
|
||||
flake8_boolean_trap, flake8_bugbear, flake8_builtins, flake8_comprehensions, flake8_print,
|
||||
flake8_tidy_imports, mccabe, pep8_naming, pycodestyle, pydocstyle, pyflakes, pyupgrade,
|
||||
};
|
||||
|
||||
const GLOBAL_SCOPE_INDEX: usize = 0;
|
||||
@@ -1462,6 +1462,12 @@ where
|
||||
pyupgrade::plugins::type_of_primitive(self, expr, func, args);
|
||||
}
|
||||
|
||||
// flake8-boolean-trap
|
||||
if self.settings.enabled.contains(&CheckCode::FBT003) {
|
||||
flake8_boolean_trap::plugins::check_boolean_positional_value_in_function_call(
|
||||
self, args,
|
||||
);
|
||||
}
|
||||
if let ExprKind::Name { id, ctx } = &func.node {
|
||||
if id == "locals" && matches!(ctx, ExprContext::Load) {
|
||||
let scope = &mut self.scopes
|
||||
@@ -1955,6 +1961,16 @@ where
|
||||
flake8_bugbear::plugins::function_call_argument_default(self, arguments)
|
||||
}
|
||||
|
||||
// flake8-boolean-trap
|
||||
if self.settings.enabled.contains(&CheckCode::FBT001) {
|
||||
flake8_boolean_trap::plugins::check_positional_boolean_in_def(self, arguments);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::FBT002) {
|
||||
flake8_boolean_trap::plugins::check_boolean_default_value_in_function_definition(
|
||||
self, arguments,
|
||||
);
|
||||
}
|
||||
|
||||
// Bind, but intentionally avoid walking default expressions, as we handle them
|
||||
// upstream.
|
||||
for arg in &arguments.posonlyargs {
|
||||
|
||||
@@ -240,6 +240,10 @@ pub enum CheckCode {
|
||||
S105,
|
||||
S106,
|
||||
S107,
|
||||
// flake8-boolean-trap
|
||||
FBT001,
|
||||
FBT002,
|
||||
FBT003,
|
||||
// Ruff
|
||||
RUF001,
|
||||
RUF002,
|
||||
@@ -258,6 +262,7 @@ pub enum CheckCategory {
|
||||
PEP8Naming,
|
||||
Flake8Bandit,
|
||||
Flake8Comprehensions,
|
||||
Flake8BooleanTrap,
|
||||
Flake8Bugbear,
|
||||
Flake8Builtins,
|
||||
Flake8TidyImports,
|
||||
@@ -278,6 +283,7 @@ impl CheckCategory {
|
||||
CheckCategory::Pyflakes => "Pyflakes",
|
||||
CheckCategory::Isort => "isort",
|
||||
CheckCategory::Flake8Bandit => "flake8-bandit",
|
||||
CheckCategory::Flake8BooleanTrap => "flake8-boolean-trap",
|
||||
CheckCategory::Flake8Builtins => "flake8-builtins",
|
||||
CheckCategory::Flake8Bugbear => "flake8-bugbear",
|
||||
CheckCategory::Flake8Comprehensions => "flake8-comprehensions",
|
||||
@@ -327,6 +333,9 @@ impl CheckCategory {
|
||||
Some("https://pypi.org/project/flake8-blind-except/0.2.1/")
|
||||
}
|
||||
CheckCategory::McCabe => Some("https://pypi.org/project/mccabe/0.7.0/"),
|
||||
CheckCategory::Flake8BooleanTrap => {
|
||||
Some("https://pypi.org/project/flake8-boolean-trap/0.1.0/")
|
||||
}
|
||||
CheckCategory::Ruff => None,
|
||||
CheckCategory::Meta => None,
|
||||
}
|
||||
@@ -564,6 +573,10 @@ pub enum CheckKind {
|
||||
HardcodedPasswordDefault(String),
|
||||
// mccabe
|
||||
FunctionIsTooComplex(String, usize),
|
||||
// flake8-boolean-trap
|
||||
BooleanPositionalArgInFunctionDefinition,
|
||||
BooleanDefaultValueInFunctionDefinition,
|
||||
BooleanPositionalValueInFunctionCall,
|
||||
// Ruff
|
||||
AmbiguousUnicodeCharacterString(char, char),
|
||||
AmbiguousUnicodeCharacterDocstring(char, char),
|
||||
@@ -848,6 +861,10 @@ impl CheckCode {
|
||||
CheckCode::S106 => CheckKind::HardcodedPasswordFuncArg("...".to_string()),
|
||||
CheckCode::S107 => CheckKind::HardcodedPasswordDefault("...".to_string()),
|
||||
CheckCode::C901 => CheckKind::FunctionIsTooComplex("...".to_string(), 10),
|
||||
// flake8-boolean-trap
|
||||
CheckCode::FBT001 => CheckKind::BooleanPositionalArgInFunctionDefinition,
|
||||
CheckCode::FBT002 => CheckKind::BooleanDefaultValueInFunctionDefinition,
|
||||
CheckCode::FBT003 => CheckKind::BooleanPositionalValueInFunctionCall,
|
||||
// Ruff
|
||||
CheckCode::RUF001 => CheckKind::AmbiguousUnicodeCharacterString('𝐁', 'B'),
|
||||
CheckCode::RUF002 => CheckKind::AmbiguousUnicodeCharacterDocstring('𝐁', 'B'),
|
||||
@@ -1055,6 +1072,9 @@ impl CheckCode {
|
||||
CheckCode::S106 => CheckCategory::Flake8Bandit,
|
||||
CheckCode::S107 => CheckCategory::Flake8Bandit,
|
||||
CheckCode::C901 => CheckCategory::McCabe,
|
||||
CheckCode::FBT001 => CheckCategory::Flake8BooleanTrap,
|
||||
CheckCode::FBT002 => CheckCategory::Flake8BooleanTrap,
|
||||
CheckCode::FBT003 => CheckCategory::Flake8BooleanTrap,
|
||||
CheckCode::RUF001 => CheckCategory::Ruff,
|
||||
CheckCode::RUF002 => CheckCategory::Ruff,
|
||||
CheckCode::RUF003 => CheckCategory::Ruff,
|
||||
@@ -1280,6 +1300,10 @@ impl CheckKind {
|
||||
CheckKind::HardcodedPasswordDefault(..) => &CheckCode::S107,
|
||||
// McCabe
|
||||
CheckKind::FunctionIsTooComplex(..) => &CheckCode::C901,
|
||||
// flake8-boolean-trap
|
||||
CheckKind::BooleanPositionalArgInFunctionDefinition => &CheckCode::FBT001,
|
||||
CheckKind::BooleanDefaultValueInFunctionDefinition => &CheckCode::FBT002,
|
||||
CheckKind::BooleanPositionalValueInFunctionCall => &CheckCode::FBT003,
|
||||
// Ruff
|
||||
CheckKind::AmbiguousUnicodeCharacterString(..) => &CheckCode::RUF001,
|
||||
CheckKind::AmbiguousUnicodeCharacterDocstring(..) => &CheckCode::RUF002,
|
||||
@@ -1941,6 +1965,16 @@ impl CheckKind {
|
||||
CheckKind::FunctionIsTooComplex(name, complexity) => {
|
||||
format!("`{name}` is too complex ({complexity})")
|
||||
}
|
||||
// flake8-boolean-trap
|
||||
CheckKind::BooleanPositionalArgInFunctionDefinition => {
|
||||
"Boolean positional arg in function definition".to_string()
|
||||
}
|
||||
CheckKind::BooleanDefaultValueInFunctionDefinition => {
|
||||
"Boolean default value in function definition".to_string()
|
||||
}
|
||||
CheckKind::BooleanPositionalValueInFunctionCall => {
|
||||
"Boolean positional value in function call".to_string()
|
||||
}
|
||||
// Ruff
|
||||
CheckKind::AmbiguousUnicodeCharacterString(confusable, representant) => {
|
||||
format!(
|
||||
|
||||
@@ -217,6 +217,12 @@ pub enum CheckCodePrefix {
|
||||
F9,
|
||||
F90,
|
||||
F901,
|
||||
FBT,
|
||||
FBT0,
|
||||
FBT00,
|
||||
FBT001,
|
||||
FBT002,
|
||||
FBT003,
|
||||
I,
|
||||
I0,
|
||||
I00,
|
||||
@@ -943,6 +949,12 @@ impl CheckCodePrefix {
|
||||
CheckCodePrefix::F9 => vec![CheckCode::F901],
|
||||
CheckCodePrefix::F90 => vec![CheckCode::F901],
|
||||
CheckCodePrefix::F901 => vec![CheckCode::F901],
|
||||
CheckCodePrefix::FBT => vec![CheckCode::FBT001, CheckCode::FBT002, CheckCode::FBT003],
|
||||
CheckCodePrefix::FBT0 => vec![CheckCode::FBT001, CheckCode::FBT002, CheckCode::FBT003],
|
||||
CheckCodePrefix::FBT00 => vec![CheckCode::FBT001, CheckCode::FBT002, CheckCode::FBT003],
|
||||
CheckCodePrefix::FBT001 => vec![CheckCode::FBT001],
|
||||
CheckCodePrefix::FBT002 => vec![CheckCode::FBT002],
|
||||
CheckCodePrefix::FBT003 => vec![CheckCode::FBT003],
|
||||
CheckCodePrefix::I => vec![CheckCode::I252, CheckCode::I001],
|
||||
CheckCodePrefix::I0 => vec![CheckCode::I001],
|
||||
CheckCodePrefix::I00 => vec![CheckCode::I001],
|
||||
@@ -1403,6 +1415,12 @@ impl CheckCodePrefix {
|
||||
CheckCodePrefix::F9 => PrefixSpecificity::Hundreds,
|
||||
CheckCodePrefix::F90 => PrefixSpecificity::Tens,
|
||||
CheckCodePrefix::F901 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::FBT => PrefixSpecificity::Category,
|
||||
CheckCodePrefix::FBT0 => PrefixSpecificity::Hundreds,
|
||||
CheckCodePrefix::FBT00 => PrefixSpecificity::Tens,
|
||||
CheckCodePrefix::FBT001 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::FBT002 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::FBT003 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::I => PrefixSpecificity::Category,
|
||||
CheckCodePrefix::I0 => PrefixSpecificity::Hundreds,
|
||||
CheckCodePrefix::I00 => PrefixSpecificity::Tens,
|
||||
|
||||
1
src/flake8_boolean_trap/mod.rs
Normal file
1
src/flake8_boolean_trap/mod.rs
Normal file
@@ -0,0 +1 @@
|
||||
pub mod plugins;
|
||||
71
src/flake8_boolean_trap/plugins.rs
Normal file
71
src/flake8_boolean_trap/plugins.rs
Normal file
@@ -0,0 +1,71 @@
|
||||
use rustpython_ast::{Arguments, ExprKind};
|
||||
use rustpython_parser::ast::{Constant, Expr};
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use crate::check_ast::Checker;
|
||||
use crate::checks::{Check, CheckKind};
|
||||
|
||||
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.add_check(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;
|
||||
}
|
||||
|
||||
if let Some(expr) = &arg.node.annotation {
|
||||
// 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 {
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::BooleanPositionalArgInFunctionDefinition,
|
||||
Range::from_located(arg),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_boolean_default_value_in_function_definition(
|
||||
checker: &mut Checker,
|
||||
arguments: &Arguments,
|
||||
) {
|
||||
for arg in arguments.defaults.iter() {
|
||||
add_if_boolean(
|
||||
checker,
|
||||
arg,
|
||||
CheckKind::BooleanDefaultValueInFunctionDefinition,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_boolean_positional_value_in_function_call(checker: &mut Checker, args: &[Expr]) {
|
||||
for arg in args {
|
||||
add_if_boolean(
|
||||
checker,
|
||||
arg,
|
||||
CheckKind::BooleanPositionalValueInFunctionCall,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,7 @@ mod flake8_2020;
|
||||
pub mod flake8_annotations;
|
||||
pub mod flake8_bandit;
|
||||
mod flake8_blind_except;
|
||||
pub mod flake8_boolean_trap;
|
||||
pub mod flake8_bugbear;
|
||||
mod flake8_builtins;
|
||||
mod flake8_comprehensions;
|
||||
|
||||
@@ -521,6 +521,9 @@ mod tests {
|
||||
#[test_case(CheckCode::YTT301, Path::new("YTT301.py"); "YTT301")]
|
||||
#[test_case(CheckCode::YTT302, Path::new("YTT302.py"); "YTT302")]
|
||||
#[test_case(CheckCode::YTT303, Path::new("YTT303.py"); "YTT303")]
|
||||
#[test_case(CheckCode::FBT001, Path::new("FBT.py"); "FBT001")]
|
||||
#[test_case(CheckCode::FBT002, Path::new("FBT.py"); "FBT002")]
|
||||
#[test_case(CheckCode::FBT003, Path::new("FBT.py"); "FBT003")]
|
||||
fn checks(check_code: CheckCode, path: &Path) -> Result<()> {
|
||||
let snapshot = format!("{}_{}", check_code.as_ref(), path.to_string_lossy());
|
||||
let mut checks = test_path(
|
||||
|
||||
69
src/snapshots/ruff__linter__tests__FBT001_FBT.py.snap
Normal file
69
src/snapshots/ruff__linter__tests__FBT001_FBT.py.snap
Normal file
@@ -0,0 +1,69 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind: BooleanPositionalArgInFunctionDefinition
|
||||
location:
|
||||
row: 4
|
||||
column: 4
|
||||
end_location:
|
||||
row: 4
|
||||
column: 26
|
||||
fix: ~
|
||||
- kind: BooleanPositionalArgInFunctionDefinition
|
||||
location:
|
||||
row: 5
|
||||
column: 4
|
||||
end_location:
|
||||
row: 5
|
||||
column: 31
|
||||
fix: ~
|
||||
- kind: BooleanPositionalArgInFunctionDefinition
|
||||
location:
|
||||
row: 10
|
||||
column: 4
|
||||
end_location:
|
||||
row: 10
|
||||
column: 36
|
||||
fix: ~
|
||||
- kind: BooleanPositionalArgInFunctionDefinition
|
||||
location:
|
||||
row: 11
|
||||
column: 4
|
||||
end_location:
|
||||
row: 11
|
||||
column: 41
|
||||
fix: ~
|
||||
- kind: BooleanPositionalArgInFunctionDefinition
|
||||
location:
|
||||
row: 14
|
||||
column: 4
|
||||
end_location:
|
||||
row: 14
|
||||
column: 37
|
||||
fix: ~
|
||||
- kind: BooleanPositionalArgInFunctionDefinition
|
||||
location:
|
||||
row: 15
|
||||
column: 4
|
||||
end_location:
|
||||
row: 15
|
||||
column: 42
|
||||
fix: ~
|
||||
- kind: BooleanPositionalArgInFunctionDefinition
|
||||
location:
|
||||
row: 18
|
||||
column: 4
|
||||
end_location:
|
||||
row: 18
|
||||
column: 40
|
||||
fix: ~
|
||||
- kind: BooleanPositionalArgInFunctionDefinition
|
||||
location:
|
||||
row: 19
|
||||
column: 4
|
||||
end_location:
|
||||
row: 19
|
||||
column: 45
|
||||
fix: ~
|
||||
|
||||
37
src/snapshots/ruff__linter__tests__FBT002_FBT.py.snap
Normal file
37
src/snapshots/ruff__linter__tests__FBT002_FBT.py.snap
Normal file
@@ -0,0 +1,37 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind: BooleanDefaultValueInFunctionDefinition
|
||||
location:
|
||||
row: 12
|
||||
column: 30
|
||||
end_location:
|
||||
row: 12
|
||||
column: 34
|
||||
fix: ~
|
||||
- kind: BooleanDefaultValueInFunctionDefinition
|
||||
location:
|
||||
row: 13
|
||||
column: 42
|
||||
end_location:
|
||||
row: 13
|
||||
column: 46
|
||||
fix: ~
|
||||
- kind: BooleanDefaultValueInFunctionDefinition
|
||||
location:
|
||||
row: 14
|
||||
column: 40
|
||||
end_location:
|
||||
row: 14
|
||||
column: 44
|
||||
fix: ~
|
||||
- kind: BooleanDefaultValueInFunctionDefinition
|
||||
location:
|
||||
row: 15
|
||||
column: 45
|
||||
end_location:
|
||||
row: 15
|
||||
column: 49
|
||||
fix: ~
|
||||
|
||||
13
src/snapshots/ruff__linter__tests__FBT003_FBT.py.snap
Normal file
13
src/snapshots/ruff__linter__tests__FBT003_FBT.py.snap
Normal file
@@ -0,0 +1,13 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind: BooleanPositionalValueInFunctionCall
|
||||
location:
|
||||
row: 41
|
||||
column: 10
|
||||
end_location:
|
||||
row: 41
|
||||
column: 14
|
||||
fix: ~
|
||||
|
||||
Reference in New Issue
Block a user