diff --git a/CHANGELOG.md b/CHANGELOG.md index a4e8eb05c..a2714e7d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,8 @@ Semantic versioning in our case means: - Adds `KwargsUnpackingInClassDefinitionViolation` #1714 - `DirectMagicAttributeAccessViolation` now only flags instances for which a known alternative exists #2268 +- Forbids getting collection element of list by unpacking #1824 +- Now `WPS227` forbids returning tuples that are too long #1731 ### Bugfixes @@ -68,12 +70,6 @@ Semantic versioning in our case means: - Adds documentation (and tests) for how to run project on Jupyter Notebooks - Updates `mypy` to `0.902` and fixes type issues -## 0.16.0 - -### Features - -- Forbids getting first element of list by unpacking - ## 0.15.2 diff --git a/tests/fixtures/noqa/noqa.py b/tests/fixtures/noqa/noqa.py index b53a76afe..026efb1dc 100644 --- a/tests/fixtures/noqa/noqa.py +++ b/tests/fixtures/noqa/noqa.py @@ -813,3 +813,6 @@ class TestClass(object, **{}): # noqa: WPS470 secondary_slice = items[1:][:3] # noqa: WPS471 first, *_rest = some_collection # noqa: WPS472 + +def foo2_func(): + return (1, 2, 3, 4, 5, 6) # noqa: WPS227 diff --git a/tests/test_checker/test_noqa.py b/tests/test_checker/test_noqa.py index 7f4cd9324..c07f4c88b 100644 --- a/tests/test_checker/test_noqa.py +++ b/tests/test_checker/test_noqa.py @@ -102,7 +102,7 @@ 'WPS224': 0, # defined in version specific table. 'WPS225': 1, 'WPS226': 0, # defined in ignored violations. - 'WPS227': 1, + 'WPS227': 2, 'WPS228': 1, 'WPS229': 1, 'WPS230': 1, diff --git a/tests/test_visitors/test_ast/test_complexity/test_counts/test_yield_length.py b/tests/test_visitors/test_ast/test_complexity/test_counts/test_output_length.py similarity index 55% rename from tests/test_visitors/test_ast/test_complexity/test_counts/test_yield_length.py rename to tests/test_visitors/test_ast/test_complexity/test_counts/test_output_length.py index 90ef93b66..fe04cff7a 100644 --- a/tests/test_visitors/test_ast/test_complexity/test_counts/test_yield_length.py +++ b/tests/test_visitors/test_ast/test_complexity/test_counts/test_output_length.py @@ -1,18 +1,21 @@ import pytest -from wemake_python_styleguide.constants import MAX_LEN_YIELD_TUPLE +from wemake_python_styleguide.constants import MAX_LEN_TUPLE_OUTPUT from wemake_python_styleguide.violations.complexity import ( - TooLongYieldTupleViolation, + TooLongOutputTupleViolation, ) from wemake_python_styleguide.visitors.ast.complexity.counts import ( - YieldTupleVisitor, + ReturnLikeStatementTupleVisitor, ) +RETURN_LIKE_STATEMENTS = ('return', 'yield') + + generator = """ def function_name(): i = 0 while True: - yield {0} + {0} {1} i = i + 1 """ @@ -30,17 +33,19 @@ def function_name(): tuple_empty, tuple_fixed_short, ]) -def test_yield_length_normal( +@pytest.mark.parametrize('return_like', RETURN_LIKE_STATEMENTS) +def test_output_length_normal( assert_errors, parse_ast_tree, tuple_param, mode, default_options, + return_like, ): """Testing that classes and functions in a module work well.""" - tree = parse_ast_tree(mode(generator.format(tuple_param))) + tree = parse_ast_tree(mode(generator.format(return_like, tuple_param))) - visitor = YieldTupleVisitor(default_options, tree=tree) + visitor = ReturnLikeStatementTupleVisitor(default_options, tree=tree) visitor.run() assert_errors(visitor, []) @@ -50,19 +55,21 @@ def test_yield_length_normal( tuple_long, tuple_fixed_long, ]) -def test_yield_length_violation( +@pytest.mark.parametrize('return_like', RETURN_LIKE_STATEMENTS) +def test_output_length_violation( assert_errors, assert_error_text, parse_ast_tree, tuple_param, mode, default_options, + return_like, ): """Testing that classes and functions in a module work well.""" - tree = parse_ast_tree(mode(generator.format(tuple_param))) + tree = parse_ast_tree(mode(generator.format(return_like, tuple_param))) - visitor = YieldTupleVisitor(default_options, tree=tree) + visitor = ReturnLikeStatementTupleVisitor(default_options, tree=tree) visitor.run() - assert_errors(visitor, [TooLongYieldTupleViolation]) - assert_error_text(visitor, 6, MAX_LEN_YIELD_TUPLE) + assert_errors(visitor, [TooLongOutputTupleViolation]) + assert_error_text(visitor, 6, MAX_LEN_TUPLE_OUTPUT) diff --git a/wemake_python_styleguide/constants.py b/wemake_python_styleguide/constants.py index 60addbc2b..3afba6759 100644 --- a/wemake_python_styleguide/constants.py +++ b/wemake_python_styleguide/constants.py @@ -406,8 +406,8 @@ #: Maximum amount of ``pragma`` no-cover comments per module. MAX_NO_COVER_COMMENTS: Final = 5 -#: Maximum length of ``yield`` ``tuple`` expressions. -MAX_LEN_YIELD_TUPLE: Final = 5 +#: Maximum length of ``yield`` or ``return`` ``tuple`` expressions. +MAX_LEN_TUPLE_OUTPUT: Final = 5 #: Maximum number of compare nodes in a single expression. MAX_COMPARES: Final = 2 diff --git a/wemake_python_styleguide/presets/topics/complexity.py b/wemake_python_styleguide/presets/topics/complexity.py index bc1f12dd5..c953cc36d 100644 --- a/wemake_python_styleguide/presets/topics/complexity.py +++ b/wemake_python_styleguide/presets/topics/complexity.py @@ -31,7 +31,7 @@ counts.ConditionsVisitor, counts.ElifVisitor, counts.TryExceptVisitor, - counts.YieldTupleVisitor, + counts.ReturnLikeStatementTupleVisitor, counts.TupleUnpackVisitor, classes.ClassComplexityVisitor, diff --git a/wemake_python_styleguide/violations/complexity.py b/wemake_python_styleguide/violations/complexity.py index acfaf1b80..f62671380 100644 --- a/wemake_python_styleguide/violations/complexity.py +++ b/wemake_python_styleguide/violations/complexity.py @@ -47,7 +47,7 @@ TooManyForsInComprehensionViolation TooManyExceptCasesViolation OverusedStringViolation - TooLongYieldTupleViolation + TooLongOutputTupleViolation TooLongCompareViolation TooLongTryBodyViolation TooManyPublicAttributesViolation @@ -89,7 +89,7 @@ .. autoclass:: TooManyForsInComprehensionViolation .. autoclass:: TooManyExceptCasesViolation .. autoclass:: OverusedStringViolation -.. autoclass:: TooLongYieldTupleViolation +.. autoclass:: TooLongOutputTupleViolation .. autoclass:: TooLongCompareViolation .. autoclass:: TooLongTryBodyViolation .. autoclass:: TooManyPublicAttributesViolation @@ -884,22 +884,24 @@ class OverusedStringViolation(MaybeASTViolation): @final -class TooLongYieldTupleViolation(ASTViolation): +class TooLongOutputTupleViolation(ASTViolation): """ - Forbid yielding tuples that are too long. + Forbid returning or yielding tuples that are too long. Reasoning: - Long yield tuples complicate generator usage. + Long output tuples complicate function or generator usage. This rule helps to reduce complication. Solution: Use lists of similar type or wrapper objects. .. versionadded:: 0.10.0 + .. versionchanged:: 0.16.0 + """ - error_template = 'Found too long yield tuple: {0}' + error_template = 'Found too long function output tuple: {0}' code = 227 diff --git a/wemake_python_styleguide/visitors/ast/complexity/counts.py b/wemake_python_styleguide/visitors/ast/complexity/counts.py index 3214f4dae..172a73e5a 100644 --- a/wemake_python_styleguide/visitors/ast/complexity/counts.py +++ b/wemake_python_styleguide/visitors/ast/complexity/counts.py @@ -16,6 +16,7 @@ # Type aliases: _ConditionNodes = Union[ast.If, ast.While, ast.IfExp] _ModuleMembers = Union[AnyFunctionDef, ast.ClassDef] +_ReturnLikeStatement = Union[ast.Return, ast.Yield] @final @@ -205,22 +206,26 @@ def _check_try_body_length(self, node: ast.Try) -> None: @final -class YieldTupleVisitor(BaseNodeVisitor): - """Finds too long ``tuples`` in ``yield`` expressions.""" +@alias('visit_return_like', ( + 'visit_Return', + 'visit_Yield', +)) +class ReturnLikeStatementTupleVisitor(BaseNodeVisitor): + """Finds too long ``tuples`` in ``yield`` and ``return`` expressions.""" - def visit_Yield(self, node: ast.Yield) -> None: - """Helper to get all ``yield`` nodes in a function at once.""" - self._check_yield_values(node) + def visit_return_like(self, node: _ReturnLikeStatement) -> None: + """Helper to get all ``yield`` and ``return`` nodes in a function.""" + self._check_return_like_values(node) self.generic_visit(node) - def _check_yield_values(self, node: ast.Yield) -> None: + def _check_return_like_values(self, node: _ReturnLikeStatement) -> None: if isinstance(node.value, ast.Tuple): - if len(node.value.elts) > constants.MAX_LEN_YIELD_TUPLE: + if len(node.value.elts) > constants.MAX_LEN_TUPLE_OUTPUT: self.add_violation( - complexity.TooLongYieldTupleViolation( + complexity.TooLongOutputTupleViolation( node, text=str(len(node.value.elts)), - baseline=constants.MAX_LEN_YIELD_TUPLE, + baseline=constants.MAX_LEN_TUPLE_OUTPUT, ), )