[pylint] Implement PLR0916 (too-many-boolean-expressions) (#7975)

## Summary

Add
[R0916](https://pylint.readthedocs.io/en/latest/user_guide/messages/refactor/too-many-boolean-expressions.html),
no autofix available.

See: #970 

## Test Plan

`cargo test` and manually.
This commit is contained in:
Steve C 2023-10-17 00:44:25 -04:00 committed by GitHub
parent 5da0f9111e
commit 21ea290d6a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 398 additions and 0 deletions

View file

@ -0,0 +1,54 @@
if a:
...
elif (a and b):
...
elif (a and b) and c:
...
elif (a and b) and c and d:
...
elif (a and b) and c and d and e:
...
elif (a and b) and c and d and e and f:
...
elif (a and b) and c and d and e and f and g:
...
elif (a and b) and c and d and e and f and g and h:
...
elif (a and b) and c and d and e and f and g and h and i:
...
elif (a and b) and c and d and e and f and g and h and i and j:
...
elif (a and b) and c and d and e and f and g and h and i and j and k:
...
elif (a and b) and c and d and e and f and g and h and i and j and k and l:
...
elif (a and b) and c and d and e and f and g and h and i and j and k and l and m:
...
elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n:
...
elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o:
...
elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p:
...
elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q:
...
elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r:
...
elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s:
...
elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t:
...
elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t and u:
...
elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t and u and v:
...
elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t and u and v and w:
...
elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t and u and v and w and x:
...
elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t and u and v and w and x and y:
...
elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t and u and v and w and x and y and z:
...
else:
...

View file

@ -1070,6 +1070,9 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
if checker.enabled(Rule::CheckAndRemoveFromSet) {
refurb::rules::check_and_remove_from_set(checker, if_);
}
if checker.enabled(Rule::TooManyBooleanExpressions) {
pylint::rules::too_many_boolean_expressions(checker, if_);
}
if checker.source_type.is_stub() {
if checker.any_enabled(&[
Rule::UnrecognizedVersionInfoCheck,

View file

@ -247,6 +247,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Pylint, "R0912") => (RuleGroup::Stable, rules::pylint::rules::TooManyBranches),
(Pylint, "R0913") => (RuleGroup::Stable, rules::pylint::rules::TooManyArguments),
(Pylint, "R0915") => (RuleGroup::Stable, rules::pylint::rules::TooManyStatements),
(Pylint, "R0916") => (RuleGroup::Preview, rules::pylint::rules::TooManyBooleanExpressions),
(Pylint, "R1701") => (RuleGroup::Stable, rules::pylint::rules::RepeatedIsinstanceCalls),
(Pylint, "R1711") => (RuleGroup::Stable, rules::pylint::rules::UselessReturn),
(Pylint, "R1714") => (RuleGroup::Stable, rules::pylint::rules::RepeatedEqualityComparison),

View file

@ -231,6 +231,22 @@ mod tests {
Ok(())
}
#[test]
fn max_boolean_expressions() -> Result<()> {
let diagnostics = test_path(
Path::new("pylint/too_many_boolean_expressions.py"),
&LinterSettings {
pylint: pylint::settings::Settings {
max_bool_expr: 5,
..pylint::settings::Settings::default()
},
..LinterSettings::for_rule(Rule::TooManyBooleanExpressions)
},
)?;
assert_messages!(diagnostics);
Ok(())
}
#[test]
fn max_statements() -> Result<()> {
let diagnostics = test_path(

View file

@ -45,6 +45,7 @@ pub(crate) use subprocess_popen_preexec_fn::*;
pub(crate) use subprocess_run_without_check::*;
pub(crate) use sys_exit_alias::*;
pub(crate) use too_many_arguments::*;
pub(crate) use too_many_boolean_expressions::*;
pub(crate) use too_many_branches::*;
pub(crate) use too_many_public_methods::*;
pub(crate) use too_many_return_statements::*;
@ -108,6 +109,7 @@ mod subprocess_popen_preexec_fn;
mod subprocess_run_without_check;
mod sys_exit_alias;
mod too_many_arguments;
mod too_many_boolean_expressions;
mod too_many_branches;
mod too_many_public_methods;
mod too_many_return_statements;

View file

@ -0,0 +1,89 @@
use ast::{Expr, StmtIf};
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast as ast;
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
/// ## What it does
/// Checks for too many Boolean expressions in an `if` statement.
///
/// By default, this rule allows up to 5 expressions. This can be configured
/// using the [`pylint.max-bool-expr`] option.
///
/// ## Why is this bad?
/// `if` statements with many Boolean expressions are harder to understand
/// and maintain. Consider assigning the result of the Boolean expression,
/// or any of its sub-expressions, to a variable.
///
/// ## Example
/// ```python
/// if a and b and c and d and e and f and g and h:
/// ...
/// ```
///
/// ## Options
/// - `pylint.max-bool-expr`
#[violation]
pub struct TooManyBooleanExpressions {
expressions: usize,
max_expressions: usize,
}
impl Violation for TooManyBooleanExpressions {
#[derive_message_formats]
fn message(&self) -> String {
let TooManyBooleanExpressions {
expressions,
max_expressions,
} = self;
format!("Too many Boolean expressions ({expressions} > {max_expressions})")
}
}
/// PLR0916
pub(crate) fn too_many_boolean_expressions(checker: &mut Checker, stmt: &StmtIf) {
if let Some(bool_op) = stmt.test.as_bool_op_expr() {
let expressions = count_bools(bool_op);
if expressions > checker.settings.pylint.max_bool_expr {
checker.diagnostics.push(Diagnostic::new(
TooManyBooleanExpressions {
expressions,
max_expressions: checker.settings.pylint.max_bool_expr,
},
bool_op.range(),
));
}
}
for elif in &stmt.elif_else_clauses {
if let Some(bool_op) = elif.test.as_ref().and_then(Expr::as_bool_op_expr) {
let expressions = count_bools(bool_op);
if expressions > checker.settings.pylint.max_bool_expr {
checker.diagnostics.push(Diagnostic::new(
TooManyBooleanExpressions {
expressions,
max_expressions: checker.settings.pylint.max_bool_expr,
},
bool_op.range(),
));
}
}
}
}
/// Count the number of Boolean expressions in a `bool_op` expression.
fn count_bools(bool_op: &ast::ExprBoolOp) -> usize {
bool_op
.values
.iter()
.map(|expr| {
if let Expr::BoolOp(bool_op) = expr {
count_bools(bool_op)
} else {
1
}
})
.sum::<usize>()
}

View file

@ -40,6 +40,7 @@ pub struct Settings {
pub allow_magic_value_types: Vec<ConstantType>,
pub max_args: usize,
pub max_returns: usize,
pub max_bool_expr: usize,
pub max_branches: usize,
pub max_statements: usize,
pub max_public_methods: usize,
@ -51,6 +52,7 @@ impl Default for Settings {
allow_magic_value_types: vec![ConstantType::Str, ConstantType::Bytes],
max_args: 5,
max_returns: 6,
max_bool_expr: 5,
max_branches: 12,
max_statements: 50,
max_public_methods: 20,

View file

@ -0,0 +1,214 @@
---
source: crates/ruff_linter/src/rules/pylint/mod.rs
---
too_many_boolean_expressions.py:11:6: PLR0916 Too many Boolean expressions (6 > 5)
|
9 | elif (a and b) and c and d and e:
10 | ...
11 | elif (a and b) and c and d and e and f:
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR0916
12 | ...
13 | elif (a and b) and c and d and e and f and g:
|
too_many_boolean_expressions.py:13:6: PLR0916 Too many Boolean expressions (7 > 5)
|
11 | elif (a and b) and c and d and e and f:
12 | ...
13 | elif (a and b) and c and d and e and f and g:
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR0916
14 | ...
15 | elif (a and b) and c and d and e and f and g and h:
|
too_many_boolean_expressions.py:15:6: PLR0916 Too many Boolean expressions (8 > 5)
|
13 | elif (a and b) and c and d and e and f and g:
14 | ...
15 | elif (a and b) and c and d and e and f and g and h:
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR0916
16 | ...
17 | elif (a and b) and c and d and e and f and g and h and i:
|
too_many_boolean_expressions.py:17:6: PLR0916 Too many Boolean expressions (9 > 5)
|
15 | elif (a and b) and c and d and e and f and g and h:
16 | ...
17 | elif (a and b) and c and d and e and f and g and h and i:
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR0916
18 | ...
19 | elif (a and b) and c and d and e and f and g and h and i and j:
|
too_many_boolean_expressions.py:19:6: PLR0916 Too many Boolean expressions (10 > 5)
|
17 | elif (a and b) and c and d and e and f and g and h and i:
18 | ...
19 | elif (a and b) and c and d and e and f and g and h and i and j:
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR0916
20 | ...
21 | elif (a and b) and c and d and e and f and g and h and i and j and k:
|
too_many_boolean_expressions.py:21:6: PLR0916 Too many Boolean expressions (11 > 5)
|
19 | elif (a and b) and c and d and e and f and g and h and i and j:
20 | ...
21 | elif (a and b) and c and d and e and f and g and h and i and j and k:
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR0916
22 | ...
23 | elif (a and b) and c and d and e and f and g and h and i and j and k and l:
|
too_many_boolean_expressions.py:23:6: PLR0916 Too many Boolean expressions (12 > 5)
|
21 | elif (a and b) and c and d and e and f and g and h and i and j and k:
22 | ...
23 | elif (a and b) and c and d and e and f and g and h and i and j and k and l:
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR0916
24 | ...
25 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m:
|
too_many_boolean_expressions.py:25:6: PLR0916 Too many Boolean expressions (13 > 5)
|
23 | elif (a and b) and c and d and e and f and g and h and i and j and k and l:
24 | ...
25 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m:
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR0916
26 | ...
27 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n:
|
too_many_boolean_expressions.py:27:6: PLR0916 Too many Boolean expressions (14 > 5)
|
25 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m:
26 | ...
27 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n:
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR0916
28 | ...
29 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o:
|
too_many_boolean_expressions.py:29:6: PLR0916 Too many Boolean expressions (15 > 5)
|
27 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n:
28 | ...
29 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o:
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR0916
30 | ...
31 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p:
|
too_many_boolean_expressions.py:31:6: PLR0916 Too many Boolean expressions (16 > 5)
|
29 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o:
30 | ...
31 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p:
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR0916
32 | ...
33 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q:
|
too_many_boolean_expressions.py:33:6: PLR0916 Too many Boolean expressions (17 > 5)
|
31 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p:
32 | ...
33 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q:
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR0916
34 | ...
35 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r:
|
too_many_boolean_expressions.py:35:6: PLR0916 Too many Boolean expressions (18 > 5)
|
33 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q:
34 | ...
35 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r:
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR0916
36 | ...
37 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s:
|
too_many_boolean_expressions.py:37:6: PLR0916 Too many Boolean expressions (19 > 5)
|
35 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r:
36 | ...
37 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s:
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR0916
38 | ...
39 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t:
|
too_many_boolean_expressions.py:39:6: PLR0916 Too many Boolean expressions (20 > 5)
|
37 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s:
38 | ...
39 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t:
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR0916
40 | ...
41 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t and u:
|
too_many_boolean_expressions.py:41:6: PLR0916 Too many Boolean expressions (21 > 5)
|
39 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t:
40 | ...
41 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t and u:
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR0916
42 | ...
43 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t and u and v:
|
too_many_boolean_expressions.py:43:6: PLR0916 Too many Boolean expressions (22 > 5)
|
41 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t and u:
42 | ...
43 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t and u and v:
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR0916
44 | ...
45 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t and u and v and w:
|
too_many_boolean_expressions.py:45:6: PLR0916 Too many Boolean expressions (23 > 5)
|
43 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t and u and v:
44 | ...
45 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t and u and v and w:
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR0916
46 | ...
47 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t and u and v and w and x:
|
too_many_boolean_expressions.py:47:6: PLR0916 Too many Boolean expressions (24 > 5)
|
45 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t and u and v and w:
46 | ...
47 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t and u and v and w and x:
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR0916
48 | ...
49 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t and u and v and w and x and y:
|
too_many_boolean_expressions.py:49:6: PLR0916 Too many Boolean expressions (25 > 5)
|
47 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t and u and v and w and x:
48 | ...
49 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t and u and v and w and x and y:
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR0916
50 | ...
51 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t and u and v and w and x and y and z:
|
too_many_boolean_expressions.py:51:6: PLR0916 Too many Boolean expressions (26 > 5)
|
49 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t and u and v and w and x and y:
50 | ...
51 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t and u and v and w and x and y and z:
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR0916
52 | ...
53 | else:
|

View file

@ -2379,6 +2379,11 @@ pub struct PylintOptions {
example = r"max-public-methods = 20"
)]
pub max_public_methods: Option<usize>,
/// Maximum number of Boolean expressions allowed within a single `if` statement
/// (see: `PLR0916`).
#[option(default = r"5", value_type = "int", example = r"max-bool-expr = 5")]
pub max_bool_expr: Option<usize>,
}
impl PylintOptions {
@ -2389,6 +2394,7 @@ impl PylintOptions {
.allow_magic_value_types
.unwrap_or(defaults.allow_magic_value_types),
max_args: self.max_args.unwrap_or(defaults.max_args),
max_bool_expr: self.max_bool_expr.unwrap_or(defaults.max_bool_expr),
max_returns: self.max_returns.unwrap_or(defaults.max_returns),
max_branches: self.max_branches.unwrap_or(defaults.max_branches),
max_statements: self.max_statements.unwrap_or(defaults.max_statements),

10
ruff.schema.json generated
View file

@ -2214,6 +2214,15 @@
"format": "uint",
"minimum": 0.0
},
"max-bool-expr": {
"description": "Maximum number of Boolean expressions allowed within a single `if` statement (see: `PLR0916`).",
"type": [
"integer",
"null"
],
"format": "uint",
"minimum": 0.0
},
"max-branches": {
"description": "Maximum number of branches allowed for a function or method body (see: `PLR0912`).",
"type": [
@ -2949,6 +2958,7 @@
"PLR0912",
"PLR0913",
"PLR0915",
"PLR0916",
"PLR1",
"PLR17",
"PLR170",

View file

@ -68,6 +68,7 @@ KNOWN_FORMATTING_VIOLATIONS = [
"surrounding-whitespace",
"tab-indentation",
"too-few-spaces-before-inline-comment",
"too-many-boolean-expressions",
"trailing-comma-on-bare-tuple",
"triple-single-quotes",
"under-indentation",