[pylint] Implement len-test (PLC1802) (#14309)

## Summary

This PR implements [`use-implicit-booleaness-not-len` /
`C1802`](https://pylint.pycqa.org/en/latest/user_guide/messages/convention/use-implicit-booleaness-not-len.html)
> For sequences, (strings, lists, tuples), use the fact that empty
sequences are false.

---------

Co-authored-by: xbrtnik1 <524841@mail.muni.cz>
Co-authored-by: xbrtnik1 <xbrtnik1@mail.muni.cz>
This commit is contained in:
Lokejoke 2024-11-26 20:30:17 +01:00 committed by GitHub
parent 9f446faa6c
commit 82c01aa662
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 871 additions and 0 deletions

View file

@ -0,0 +1,234 @@
if len('TEST'): # [PLC1802]
pass
if not len('TEST'): # [PLC1802]
pass
z = []
if z and len(['T', 'E', 'S', 'T']): # [PLC1802]
pass
if True or len('TEST'): # [PLC1802]
pass
if len('TEST') == 0: # Should be fine
pass
if len('TEST') < 1: # Should be fine
pass
if len('TEST') <= 0: # Should be fine
pass
if 1 > len('TEST'): # Should be fine
pass
if 0 >= len('TEST'): # Should be fine
pass
if z and len('TEST') == 0: # Should be fine
pass
if 0 == len('TEST') < 10: # Should be fine
pass
# Should be fine
if 0 < 1 <= len('TEST') < 10: # [comparison-of-constants]
pass
if 10 > len('TEST') != 0: # Should be fine
pass
if 10 > len('TEST') > 1 > 0: # Should be fine
pass
if 0 <= len('TEST') < 100: # Should be fine
pass
if z or 10 > len('TEST') != 0: # Should be fine
pass
if z:
pass
elif len('TEST'): # [PLC1802]
pass
if z:
pass
elif not len('TEST'): # [PLC1802]
pass
while len('TEST'): # [PLC1802]
pass
while not len('TEST'): # [PLC1802]
pass
while z and len('TEST'): # [PLC1802]
pass
while not len('TEST') and z: # [PLC1802]
pass
assert len('TEST') > 0 # Should be fine
x = 1 if len('TEST') != 0 else 2 # Should be fine
f_o_o = len('TEST') or 42 # Should be fine
a = x and len(x) # Should be fine
def some_func():
return len('TEST') > 0 # Should be fine
def github_issue_1325():
l = [1, 2, 3]
length = len(l) if l else 0 # Should be fine
return length
def github_issue_1331(*args):
assert False, len(args) # Should be fine
def github_issue_1331_v2(*args):
assert len(args), args # [PLC1802]
def github_issue_1331_v3(*args):
assert len(args) or z, args # [PLC1802]
def github_issue_1331_v4(*args):
assert z and len(args), args # [PLC1802]
def github_issue_1331_v5(**args):
assert z and len(args), args # [PLC1802]
b = bool(len(z)) # [PLC1802]
c = bool(len('TEST') or 42) # [PLC1802]
def github_issue_1879():
class ClassWithBool(list):
def __bool__(self):
return True
class ClassWithoutBool(list):
pass
class ChildClassWithBool(ClassWithBool):
pass
class ChildClassWithoutBool(ClassWithoutBool):
pass
assert len(ClassWithBool())
assert len(ChildClassWithBool())
assert len(ClassWithoutBool()) # unintuitive?, in pylint: [PLC1802]
assert len(ChildClassWithoutBool()) # unintuitive?, in pylint: [PLC1802]
assert len(range(0)) # [PLC1802]
assert len([t + 1 for t in []]) # [PLC1802]
# assert len(u + 1 for u in []) generator has no len
assert len({"1":(v + 1) for v in {}}) # [PLC1802]
assert len(set((w + 1) for w in set())) # [PLC1802]
import numpy
numpy_array = numpy.array([0])
if len(numpy_array) > 0:
print('numpy_array')
if len(numpy_array):
print('numpy_array')
if numpy_array:
print('b')
import pandas as pd
pandas_df = pd.DataFrame()
if len(pandas_df):
print("this works, but pylint tells me not to use len() without comparison")
if len(pandas_df) > 0:
print("this works and pylint likes it, but it's not the solution intended by PEP-8")
if pandas_df:
print("this does not work (truth value of dataframe is ambiguous)")
def function_returning_list(r):
if r==1:
return [1]
return [2]
def function_returning_int(r):
if r==1:
return 1
return 2
def function_returning_generator(r):
for i in [r, 1, 2, 3]:
yield i
def function_returning_comprehension(r):
return [x+1 for x in [r, 1, 2, 3]]
def function_returning_function(r):
return function_returning_generator(r)
assert len(function_returning_list(z)) # [PLC1802] differs from pylint
assert len(function_returning_int(z))
# This should raise a PLC1802 once astroid can infer it
# See https://github.com/pylint-dev/pylint/pull/3821#issuecomment-743771514
assert len(function_returning_generator(z))
assert len(function_returning_comprehension(z))
assert len(function_returning_function(z))
def github_issue_4215():
# Test undefined variables
# https://github.com/pylint-dev/pylint/issues/4215
if len(undefined_var): # [undefined-variable]
pass
if len(undefined_var2[0]): # [undefined-variable]
pass
def f(cond:bool):
x = [1,2,3]
if cond:
x = [4,5,6]
if len(x): # this should be addressed
print(x)
def g(cond:bool):
x = [1,2,3]
if cond:
x = [4,5,6]
if len(x): # this should be addressed
print(x)
del x
def h(cond:bool):
x = [1,2,3]
x = 123
if len(x): # ok
print(x)
def outer():
x = [1,2,3]
def inner(x:int):
return x+1
if len(x): # [PLC1802]
print(x)
def redefined():
x = 123
x = [1, 2, 3]
if len(x): # this should be addressed
print(x)
global_seq = [1, 2, 3]
def i():
global global_seq
if len(global_seq): # ok
print(global_seq)
def j():
if False:
x = [1, 2, 3]
if len(x): # [PLC1802] should be fine
print(x)

View file

@ -494,6 +494,9 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
if checker.enabled(Rule::SuperWithoutBrackets) {
pylint::rules::super_without_brackets(checker, func);
}
if checker.enabled(Rule::LenTest) {
pylint::rules::len_test(checker, call);
}
if checker.enabled(Rule::BitCount) {
refurb::rules::bit_count(checker, call);
}

View file

@ -192,6 +192,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Pylint, "C0208") => (RuleGroup::Stable, rules::pylint::rules::IterationOverSet),
(Pylint, "C0414") => (RuleGroup::Stable, rules::pylint::rules::UselessImportAlias),
(Pylint, "C0415") => (RuleGroup::Preview, rules::pylint::rules::ImportOutsideTopLevel),
(Pylint, "C1802") => (RuleGroup::Preview, rules::pylint::rules::LenTest),
(Pylint, "C1901") => (RuleGroup::Preview, rules::pylint::rules::CompareToEmptyString),
(Pylint, "C2401") => (RuleGroup::Stable, rules::pylint::rules::NonAsciiName),
(Pylint, "C2403") => (RuleGroup::Stable, rules::pylint::rules::NonAsciiImportName),

View file

@ -220,6 +220,7 @@ mod tests {
Rule::BadStaticmethodArgument,
Path::new("bad_staticmethod_argument.py")
)]
#[test_case(Rule::LenTest, Path::new("len_as_condition.py"))]
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
let diagnostics = test_path(

View file

@ -0,0 +1,191 @@
use crate::checkers::ast::Checker;
use crate::fix::snippet::SourceCodeSnippet;
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::{self as ast, Expr, ExprCall};
use ruff_python_semantic::analyze::type_inference::{PythonType, ResolvedPythonType};
use ruff_python_semantic::analyze::typing::find_binding_value;
use ruff_python_semantic::{BindingId, SemanticModel};
use ruff_text_size::Ranged;
/// ## What it does
/// Checks for usage of call of 'len' on sequences
/// in boolean test context.
///
/// ## Why is this bad?
/// Empty sequences are considered false in a boolean context.
/// You can either remove the call to 'len'
/// or compare the length against a scalar.
///
/// ## Example
/// ```python
/// fruits = ["orange", "apple"]
/// vegetables = []
///
/// if len(fruits):
/// print(fruits)
///
/// if not len(vegetables):
/// print(vegetables)
/// ```
///
/// Use instead:
/// ```python
/// fruits = ["orange", "apple"]
///
/// if fruits:
/// print(fruits)
///
/// if not vegetables:
/// print(vegetables)
/// ```
///
/// ## References
/// [PEP 8: Programming Recommendations](https://peps.python.org/pep-0008/#programming-recommendations)
#[violation]
pub struct LenTest {
expression: SourceCodeSnippet,
}
impl AlwaysFixableViolation for LenTest {
#[derive_message_formats]
fn message(&self) -> String {
if let Some(expression) = self.expression.full_display() {
format!("`len({expression})` used as condition without comparison")
} else {
"`len(SEQUENCE)` used as condition without comparison".to_string()
}
}
fn fix_title(&self) -> String {
"Remove `len`".to_string()
}
}
/// PLC1802
pub(crate) fn len_test(checker: &mut Checker, call: &ExprCall) {
let ExprCall {
func, arguments, ..
} = call;
let semantic = checker.semantic();
if !semantic.in_boolean_test() {
return;
}
if !semantic.match_builtin_expr(func, "len") {
return;
}
// Single argument and no keyword arguments
let [argument] = &*arguments.args else { return };
if !arguments.keywords.is_empty() {
return;
}
// Simple inferred sequence type (e.g., list, set, dict, tuple, string, bytes, varargs, kwargs).
if !is_sequence(argument, semantic) && !is_indirect_sequence(argument, semantic) {
return;
}
let replacement = checker.locator().slice(argument.range()).to_string();
checker.diagnostics.push(
Diagnostic::new(
LenTest {
expression: SourceCodeSnippet::new(replacement.clone()),
},
call.range(),
)
.with_fix(Fix::safe_edit(Edit::range_replacement(
replacement,
call.range(),
))),
);
}
fn is_indirect_sequence(expr: &Expr, semantic: &SemanticModel) -> bool {
let Expr::Name(ast::ExprName { id: name, .. }) = expr else {
return false;
};
let scope = semantic.current_scope();
let bindings: Vec<BindingId> = scope.get_all(name).collect();
let [binding_id] = bindings.as_slice() else {
return false;
};
let binding = semantic.binding(*binding_id);
// Attempt to find the binding's value
let Some(binding_value) = find_binding_value(binding, semantic) else {
// If the binding is not an argument, return false
if !binding.kind.is_argument() {
return false;
}
// Attempt to retrieve the function definition statement
let Some(function) = binding
.statement(semantic)
.and_then(|statement| statement.as_function_def_stmt())
else {
return false;
};
// If not find in non-default params, it must be varargs or kwargs
return function.parameters.find(name).is_none();
};
// If `binding_value` is found, check if it is a sequence
is_sequence(binding_value, semantic)
}
fn is_sequence(expr: &Expr, semantic: &SemanticModel) -> bool {
// Check if the expression type is a direct sequence match (dict, list, set, tuple, string or bytes)
if matches!(
ResolvedPythonType::from(expr),
ResolvedPythonType::Atom(
PythonType::Dict
| PythonType::List
| PythonType::Set
| PythonType::Tuple
| PythonType::String
| PythonType::Bytes
)
) {
return true;
}
// Check if the expression is a function call to a built-in sequence constructor
let Some(ExprCall { func, .. }) = expr.as_call_expr() else {
return false;
};
// Match against specific built-in constructors that return sequences
return semantic.resolve_builtin_symbol(func).is_some_and(|func| {
matches!(
func,
"chr"
| "format"
| "input"
| "repr"
| "str"
| "list"
| "dir"
| "locals"
| "globals"
| "vars"
| "dict"
| "set"
| "frozenset"
| "tuple"
| "range"
| "bin"
| "bytes"
| "bytearray"
| "hex"
| "memoryview"
| "oct"
)
});
}

View file

@ -39,6 +39,7 @@ pub(crate) use invalid_length_return::*;
pub(crate) use invalid_str_return::*;
pub(crate) use invalid_string_characters::*;
pub(crate) use iteration_over_set::*;
pub(crate) use len_test::*;
pub(crate) use literal_membership::*;
pub(crate) use load_before_global_declaration::*;
pub(crate) use logging::*;
@ -144,6 +145,7 @@ mod invalid_length_return;
mod invalid_str_return;
mod invalid_string_characters;
mod iteration_over_set;
mod len_test;
mod literal_membership;
mod load_before_global_declaration;
mod logging;

View file

@ -0,0 +1,436 @@
---
source: crates/ruff_linter/src/rules/pylint/mod.rs
snapshot_kind: text
---
len_as_condition.py:1:4: PLC1802 [*] `len('TEST')` used as condition without comparison
|
1 | if len('TEST'): # [PLC1802]
| ^^^^^^^^^^^ PLC1802
2 | pass
|
= help: Remove `len`
Safe fix
1 |-if len('TEST'): # [PLC1802]
1 |+if 'TEST': # [PLC1802]
2 2 | pass
3 3 |
4 4 | if not len('TEST'): # [PLC1802]
len_as_condition.py:4:8: PLC1802 [*] `len('TEST')` used as condition without comparison
|
2 | pass
3 |
4 | if not len('TEST'): # [PLC1802]
| ^^^^^^^^^^^ PLC1802
5 | pass
|
= help: Remove `len`
Safe fix
1 1 | if len('TEST'): # [PLC1802]
2 2 | pass
3 3 |
4 |-if not len('TEST'): # [PLC1802]
4 |+if not 'TEST': # [PLC1802]
5 5 | pass
6 6 |
7 7 | z = []
len_as_condition.py:8:10: PLC1802 [*] `len(['T', 'E', 'S', 'T'])` used as condition without comparison
|
7 | z = []
8 | if z and len(['T', 'E', 'S', 'T']): # [PLC1802]
| ^^^^^^^^^^^^^^^^^^^^^^^^^ PLC1802
9 | pass
|
= help: Remove `len`
Safe fix
5 5 | pass
6 6 |
7 7 | z = []
8 |-if z and len(['T', 'E', 'S', 'T']): # [PLC1802]
8 |+if z and ['T', 'E', 'S', 'T']: # [PLC1802]
9 9 | pass
10 10 |
11 11 | if True or len('TEST'): # [PLC1802]
len_as_condition.py:11:12: PLC1802 [*] `len('TEST')` used as condition without comparison
|
9 | pass
10 |
11 | if True or len('TEST'): # [PLC1802]
| ^^^^^^^^^^^ PLC1802
12 | pass
|
= help: Remove `len`
Safe fix
8 8 | if z and len(['T', 'E', 'S', 'T']): # [PLC1802]
9 9 | pass
10 10 |
11 |-if True or len('TEST'): # [PLC1802]
11 |+if True or 'TEST': # [PLC1802]
12 12 | pass
13 13 |
14 14 | if len('TEST') == 0: # Should be fine
len_as_condition.py:53:6: PLC1802 [*] `len('TEST')` used as condition without comparison
|
51 | if z:
52 | pass
53 | elif len('TEST'): # [PLC1802]
| ^^^^^^^^^^^ PLC1802
54 | pass
|
= help: Remove `len`
Safe fix
50 50 |
51 51 | if z:
52 52 | pass
53 |-elif len('TEST'): # [PLC1802]
53 |+elif 'TEST': # [PLC1802]
54 54 | pass
55 55 |
56 56 | if z:
len_as_condition.py:58:10: PLC1802 [*] `len('TEST')` used as condition without comparison
|
56 | if z:
57 | pass
58 | elif not len('TEST'): # [PLC1802]
| ^^^^^^^^^^^ PLC1802
59 | pass
|
= help: Remove `len`
Safe fix
55 55 |
56 56 | if z:
57 57 | pass
58 |-elif not len('TEST'): # [PLC1802]
58 |+elif not 'TEST': # [PLC1802]
59 59 | pass
60 60 |
61 61 | while len('TEST'): # [PLC1802]
len_as_condition.py:61:7: PLC1802 [*] `len('TEST')` used as condition without comparison
|
59 | pass
60 |
61 | while len('TEST'): # [PLC1802]
| ^^^^^^^^^^^ PLC1802
62 | pass
|
= help: Remove `len`
Safe fix
58 58 | elif not len('TEST'): # [PLC1802]
59 59 | pass
60 60 |
61 |-while len('TEST'): # [PLC1802]
61 |+while 'TEST': # [PLC1802]
62 62 | pass
63 63 |
64 64 | while not len('TEST'): # [PLC1802]
len_as_condition.py:64:11: PLC1802 [*] `len('TEST')` used as condition without comparison
|
62 | pass
63 |
64 | while not len('TEST'): # [PLC1802]
| ^^^^^^^^^^^ PLC1802
65 | pass
|
= help: Remove `len`
Safe fix
61 61 | while len('TEST'): # [PLC1802]
62 62 | pass
63 63 |
64 |-while not len('TEST'): # [PLC1802]
64 |+while not 'TEST': # [PLC1802]
65 65 | pass
66 66 |
67 67 | while z and len('TEST'): # [PLC1802]
len_as_condition.py:67:13: PLC1802 [*] `len('TEST')` used as condition without comparison
|
65 | pass
66 |
67 | while z and len('TEST'): # [PLC1802]
| ^^^^^^^^^^^ PLC1802
68 | pass
|
= help: Remove `len`
Safe fix
64 64 | while not len('TEST'): # [PLC1802]
65 65 | pass
66 66 |
67 |-while z and len('TEST'): # [PLC1802]
67 |+while z and 'TEST': # [PLC1802]
68 68 | pass
69 69 |
70 70 | while not len('TEST') and z: # [PLC1802]
len_as_condition.py:70:11: PLC1802 [*] `len('TEST')` used as condition without comparison
|
68 | pass
69 |
70 | while not len('TEST') and z: # [PLC1802]
| ^^^^^^^^^^^ PLC1802
71 | pass
|
= help: Remove `len`
Safe fix
67 67 | while z and len('TEST'): # [PLC1802]
68 68 | pass
69 69 |
70 |-while not len('TEST') and z: # [PLC1802]
70 |+while not 'TEST' and z: # [PLC1802]
71 71 | pass
72 72 |
73 73 | assert len('TEST') > 0 # Should be fine
len_as_condition.py:93:12: PLC1802 [*] `len(args)` used as condition without comparison
|
92 | def github_issue_1331_v2(*args):
93 | assert len(args), args # [PLC1802]
| ^^^^^^^^^ PLC1802
94 |
95 | def github_issue_1331_v3(*args):
|
= help: Remove `len`
Safe fix
90 90 | assert False, len(args) # Should be fine
91 91 |
92 92 | def github_issue_1331_v2(*args):
93 |- assert len(args), args # [PLC1802]
93 |+ assert args, args # [PLC1802]
94 94 |
95 95 | def github_issue_1331_v3(*args):
96 96 | assert len(args) or z, args # [PLC1802]
len_as_condition.py:96:12: PLC1802 [*] `len(args)` used as condition without comparison
|
95 | def github_issue_1331_v3(*args):
96 | assert len(args) or z, args # [PLC1802]
| ^^^^^^^^^ PLC1802
97 |
98 | def github_issue_1331_v4(*args):
|
= help: Remove `len`
Safe fix
93 93 | assert len(args), args # [PLC1802]
94 94 |
95 95 | def github_issue_1331_v3(*args):
96 |- assert len(args) or z, args # [PLC1802]
96 |+ assert args or z, args # [PLC1802]
97 97 |
98 98 | def github_issue_1331_v4(*args):
99 99 | assert z and len(args), args # [PLC1802]
len_as_condition.py:99:18: PLC1802 [*] `len(args)` used as condition without comparison
|
98 | def github_issue_1331_v4(*args):
99 | assert z and len(args), args # [PLC1802]
| ^^^^^^^^^ PLC1802
100 |
101 | def github_issue_1331_v5(**args):
|
= help: Remove `len`
Safe fix
96 96 | assert len(args) or z, args # [PLC1802]
97 97 |
98 98 | def github_issue_1331_v4(*args):
99 |- assert z and len(args), args # [PLC1802]
99 |+ assert z and args, args # [PLC1802]
100 100 |
101 101 | def github_issue_1331_v5(**args):
102 102 | assert z and len(args), args # [PLC1802]
len_as_condition.py:102:18: PLC1802 [*] `len(args)` used as condition without comparison
|
101 | def github_issue_1331_v5(**args):
102 | assert z and len(args), args # [PLC1802]
| ^^^^^^^^^ PLC1802
103 |
104 | b = bool(len(z)) # [PLC1802]
|
= help: Remove `len`
Safe fix
99 99 | assert z and len(args), args # [PLC1802]
100 100 |
101 101 | def github_issue_1331_v5(**args):
102 |- assert z and len(args), args # [PLC1802]
102 |+ assert z and args, args # [PLC1802]
103 103 |
104 104 | b = bool(len(z)) # [PLC1802]
105 105 | c = bool(len('TEST') or 42) # [PLC1802]
len_as_condition.py:104:10: PLC1802 [*] `len(z)` used as condition without comparison
|
102 | assert z and len(args), args # [PLC1802]
103 |
104 | b = bool(len(z)) # [PLC1802]
| ^^^^^^ PLC1802
105 | c = bool(len('TEST') or 42) # [PLC1802]
|
= help: Remove `len`
Safe fix
101 101 | def github_issue_1331_v5(**args):
102 102 | assert z and len(args), args # [PLC1802]
103 103 |
104 |-b = bool(len(z)) # [PLC1802]
104 |+b = bool(z) # [PLC1802]
105 105 | c = bool(len('TEST') or 42) # [PLC1802]
106 106 |
107 107 | def github_issue_1879():
len_as_condition.py:105:10: PLC1802 [*] `len('TEST')` used as condition without comparison
|
104 | b = bool(len(z)) # [PLC1802]
105 | c = bool(len('TEST') or 42) # [PLC1802]
| ^^^^^^^^^^^ PLC1802
106 |
107 | def github_issue_1879():
|
= help: Remove `len`
Safe fix
102 102 | assert z and len(args), args # [PLC1802]
103 103 |
104 104 | b = bool(len(z)) # [PLC1802]
105 |-c = bool(len('TEST') or 42) # [PLC1802]
105 |+c = bool('TEST' or 42) # [PLC1802]
106 106 |
107 107 | def github_issue_1879():
108 108 |
len_as_condition.py:126:12: PLC1802 [*] `len(range(0))` used as condition without comparison
|
124 | assert len(ClassWithoutBool()) # unintuitive?, in pylint: [PLC1802]
125 | assert len(ChildClassWithoutBool()) # unintuitive?, in pylint: [PLC1802]
126 | assert len(range(0)) # [PLC1802]
| ^^^^^^^^^^^^^ PLC1802
127 | assert len([t + 1 for t in []]) # [PLC1802]
128 | # assert len(u + 1 for u in []) generator has no len
|
= help: Remove `len`
Safe fix
123 123 | assert len(ChildClassWithBool())
124 124 | assert len(ClassWithoutBool()) # unintuitive?, in pylint: [PLC1802]
125 125 | assert len(ChildClassWithoutBool()) # unintuitive?, in pylint: [PLC1802]
126 |- assert len(range(0)) # [PLC1802]
126 |+ assert range(0) # [PLC1802]
127 127 | assert len([t + 1 for t in []]) # [PLC1802]
128 128 | # assert len(u + 1 for u in []) generator has no len
129 129 | assert len({"1":(v + 1) for v in {}}) # [PLC1802]
len_as_condition.py:127:12: PLC1802 [*] `len([t + 1 for t in []])` used as condition without comparison
|
125 | assert len(ChildClassWithoutBool()) # unintuitive?, in pylint: [PLC1802]
126 | assert len(range(0)) # [PLC1802]
127 | assert len([t + 1 for t in []]) # [PLC1802]
| ^^^^^^^^^^^^^^^^^^^^^^^^ PLC1802
128 | # assert len(u + 1 for u in []) generator has no len
129 | assert len({"1":(v + 1) for v in {}}) # [PLC1802]
|
= help: Remove `len`
Safe fix
124 124 | assert len(ClassWithoutBool()) # unintuitive?, in pylint: [PLC1802]
125 125 | assert len(ChildClassWithoutBool()) # unintuitive?, in pylint: [PLC1802]
126 126 | assert len(range(0)) # [PLC1802]
127 |- assert len([t + 1 for t in []]) # [PLC1802]
127 |+ assert [t + 1 for t in []] # [PLC1802]
128 128 | # assert len(u + 1 for u in []) generator has no len
129 129 | assert len({"1":(v + 1) for v in {}}) # [PLC1802]
130 130 | assert len(set((w + 1) for w in set())) # [PLC1802]
len_as_condition.py:129:12: PLC1802 [*] `len({"1":(v + 1) for v in {}})` used as condition without comparison
|
127 | assert len([t + 1 for t in []]) # [PLC1802]
128 | # assert len(u + 1 for u in []) generator has no len
129 | assert len({"1":(v + 1) for v in {}}) # [PLC1802]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLC1802
130 | assert len(set((w + 1) for w in set())) # [PLC1802]
|
= help: Remove `len`
Safe fix
126 126 | assert len(range(0)) # [PLC1802]
127 127 | assert len([t + 1 for t in []]) # [PLC1802]
128 128 | # assert len(u + 1 for u in []) generator has no len
129 |- assert len({"1":(v + 1) for v in {}}) # [PLC1802]
129 |+ assert {"1":(v + 1) for v in {}} # [PLC1802]
130 130 | assert len(set((w + 1) for w in set())) # [PLC1802]
131 131 |
132 132 |
len_as_condition.py:130:12: PLC1802 [*] `len(set((w + 1) for w in set()))` used as condition without comparison
|
128 | # assert len(u + 1 for u in []) generator has no len
129 | assert len({"1":(v + 1) for v in {}}) # [PLC1802]
130 | assert len(set((w + 1) for w in set())) # [PLC1802]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLC1802
|
= help: Remove `len`
Safe fix
127 127 | assert len([t + 1 for t in []]) # [PLC1802]
128 128 | # assert len(u + 1 for u in []) generator has no len
129 129 | assert len({"1":(v + 1) for v in {}}) # [PLC1802]
130 |- assert len(set((w + 1) for w in set())) # [PLC1802]
130 |+ assert set((w + 1) for w in set()) # [PLC1802]
131 131 |
132 132 |
133 133 | import numpy
len_as_condition.py:214:8: PLC1802 [*] `len(x)` used as condition without comparison
|
212 | def inner(x:int):
213 | return x+1
214 | if len(x): # [PLC1802]
| ^^^^^^ PLC1802
215 | print(x)
|
= help: Remove `len`
Safe fix
211 211 | x = [1,2,3]
212 212 | def inner(x:int):
213 213 | return x+1
214 |- if len(x): # [PLC1802]
214 |+ if x: # [PLC1802]
215 215 | print(x)
216 216 |
217 217 | def redefined():
len_as_condition.py:233:8: PLC1802 [*] `len(x)` used as condition without comparison
|
231 | if False:
232 | x = [1, 2, 3]
233 | if len(x): # [PLC1802] should be fine
| ^^^^^^ PLC1802
234 | print(x)
|
= help: Remove `len`
Safe fix
230 230 | def j():
231 231 | if False:
232 232 | x = [1, 2, 3]
233 |- if len(x): # [PLC1802] should be fine
233 |+ if x: # [PLC1802] should be fine
234 234 | print(x)

3
ruff.schema.json generated
View file

@ -3425,6 +3425,9 @@
"PLC0414",
"PLC0415",
"PLC1",
"PLC18",
"PLC180",
"PLC1802",
"PLC19",
"PLC190",
"PLC1901",