diff --git a/CHANGELOG.md b/CHANGELOG.md index 52fc12093..e5207c7a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ Semantic versioning in our case means: - Fixes `ReassigningVariableToItselfViolation` to extract variables from unary operators #1874 - Fixes that `f'{some:,}'` was considered too complex #1921 +- Fixes that `range(len(x))` was not allowed even outside `for` loops #1883 ### Misc diff --git a/tests/test_visitors/test_ast/test_functions/test_call_context/test_range_len.py b/tests/test_visitors/test_ast/test_functions/test_call_context/test_range_len.py index 01762fc09..ecfc479d2 100644 --- a/tests/test_visitors/test_ast/test_functions/test_call_context/test_range_len.py +++ b/tests/test_visitors/test_ast/test_functions/test_call_context/test_range_len.py @@ -7,6 +7,21 @@ WrongFunctionCallContextVisitor, ) +for_template = """ +def function(): + for list_item in {0}: + ... +""" + +wrong_samples = ( + 'range(len())', + 'range(len(some))', + 'range(len([]))', + 'range(1, len(some))', + 'range(-1, len(some))', + 'range(0, len(some), -1)', +) + @pytest.mark.parametrize('code', [ 'range(10)', @@ -25,9 +40,10 @@ def test_correct_range_len( parse_ast_tree, code, default_options, + mode, ): """Testing that ``range()`` can be used.""" - tree = parse_ast_tree(code) + tree = parse_ast_tree(mode(for_template.format(code))) visitor = WrongFunctionCallContextVisitor(default_options, tree=tree) visitor.run() @@ -35,24 +51,34 @@ def test_correct_range_len( assert_errors(visitor, []) -@pytest.mark.parametrize('code', [ - 'range(len())', - 'range(len(some))', - 'range(len([]))', - 'range(1, len(some))', - 'range(-1, len(some))', - 'range(0, len(some), -1)', -]) +@pytest.mark.parametrize('code', wrong_samples) def test_range_len( assert_errors, parse_ast_tree, code, default_options, + mode, ): """Testing that ``range(len(...))`` cannot be used.""" - tree = parse_ast_tree(code) + tree = parse_ast_tree(mode(for_template.format(code))) visitor = WrongFunctionCallContextVisitor(default_options, tree=tree) visitor.run() assert_errors(visitor, [ImplicitEnumerateViolation]) + + +@pytest.mark.parametrize('code', wrong_samples) +def test_range_len_outside_for_loop( + assert_errors, + parse_ast_tree, + code, + default_options, +): + """Testing that ``range(len(...))`` can be used outside loops.""" + tree = parse_ast_tree(code) + + visitor = WrongFunctionCallContextVisitor(default_options, tree=tree) + visitor.run() + + assert_errors(visitor, []) diff --git a/wemake_python_styleguide/visitors/ast/functions.py b/wemake_python_styleguide/visitors/ast/functions.py index 53055af97..581bdfe5b 100644 --- a/wemake_python_styleguide/visitors/ast/functions.py +++ b/wemake_python_styleguide/visitors/ast/functions.py @@ -4,7 +4,11 @@ from typing_extensions import final -from wemake_python_styleguide.compat.aliases import FunctionNodes, TextNodes +from wemake_python_styleguide.compat.aliases import ( + ForNodes, + FunctionNodes, + TextNodes, +) from wemake_python_styleguide.compat.functions import get_posonlyargs from wemake_python_styleguide.constants import ( FUNCTIONS_BLACKLIST, @@ -240,6 +244,8 @@ def _check_type_compare(self, node: ast.Call) -> None: self.add_violation(TypeCompareViolation(node)) def _check_range_len(self, node: ast.Call) -> None: + if not isinstance(nodes.get_parent(node), ForNodes): + return if not functions.given_function_called(node, {'range'}): return