diff --git a/crates/ruff_linter/resources/test/fixtures/pylint/len_as_condition.py b/crates/ruff_linter/resources/test/fixtures/pylint/len_as_condition.py new file mode 100644 index 0000000000000..c310b4a57354f --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/pylint/len_as_condition.py @@ -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) diff --git a/crates/ruff_linter/src/checkers/ast/analyze/expression.rs b/crates/ruff_linter/src/checkers/ast/analyze/expression.rs index 7c27f1a1f01d1..0c2f4e235c087 100644 --- a/crates/ruff_linter/src/checkers/ast/analyze/expression.rs +++ b/crates/ruff_linter/src/checkers/ast/analyze/expression.rs @@ -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); } diff --git a/crates/ruff_linter/src/codes.rs b/crates/ruff_linter/src/codes.rs index a14bc7e23a58d..1c0fb68f73279 100644 --- a/crates/ruff_linter/src/codes.rs +++ b/crates/ruff_linter/src/codes.rs @@ -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), diff --git a/crates/ruff_linter/src/rules/pylint/mod.rs b/crates/ruff_linter/src/rules/pylint/mod.rs index 68b10132c0686..9a374357b8d46 100644 --- a/crates/ruff_linter/src/rules/pylint/mod.rs +++ b/crates/ruff_linter/src/rules/pylint/mod.rs @@ -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( diff --git a/crates/ruff_linter/src/rules/pylint/rules/len_test.rs b/crates/ruff_linter/src/rules/pylint/rules/len_test.rs new file mode 100644 index 0000000000000..e4892b35fb9e6 --- /dev/null +++ b/crates/ruff_linter/src/rules/pylint/rules/len_test.rs @@ -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 = 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" + ) + }); +} diff --git a/crates/ruff_linter/src/rules/pylint/rules/mod.rs b/crates/ruff_linter/src/rules/pylint/rules/mod.rs index 08e6e868d0b2a..5d63275f03a43 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/mod.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/mod.rs @@ -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; diff --git a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLC1802_len_as_condition.py.snap b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLC1802_len_as_condition.py.snap new file mode 100644 index 0000000000000..d765194e899bb --- /dev/null +++ b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLC1802_len_as_condition.py.snap @@ -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) diff --git a/ruff.schema.json b/ruff.schema.json index f45b698932d92..ff9810758261b 100644 --- a/ruff.schema.json +++ b/ruff.schema.json @@ -3425,6 +3425,9 @@ "PLC0414", "PLC0415", "PLC1", + "PLC18", + "PLC180", + "PLC1802", "PLC19", "PLC190", "PLC1901",