diff --git a/README.md b/README.md index c1ef70d..78682e8 100644 --- a/README.md +++ b/README.md @@ -77,6 +77,7 @@ Simplifying usage of dictionaries: * [`SIM401`](https://github.com/MartinThoma/flake8-simplify/issues/72): Use 'a_dict.get(key, "default_value")' instead of an if-block ([example](#SIM401)) * [`SIM118`](https://github.com/MartinThoma/flake8-simplify/issues/40): Use 'key in dict' instead of 'key in dict.keys()' ([example](#SIM118)) +* `SIM119` Reserved for [SIM911](#sim911) once it's stable General Code Style: @@ -106,6 +107,7 @@ Current experimental rules: * [`SIM908`](https://github.com/MartinThoma/flake8-simplify/issues/50): Use dict.get(key) ([example](#SIM908)) * [`SIM909`](https://github.com/MartinThoma/flake8-simplify/issues/114): Avoid reflexive assignments ([example](#SIM909)) * [`SIM910`](https://github.com/MartinThoma/flake8-simplify/issues/171): Avoid to use `dict.get(key, None)` ([example](#SIM910)) +* [`SIM911`](https://github.com/MartinThoma/flake8-simplify/issues/161): Avoid using `zip(dict.keys(), dict.values())` ([example](#SIM911)) ## Disabling Rules diff --git a/flake8_simplify/__init__.py b/flake8_simplify/__init__.py index 3fd1f1e..0f728d6 100644 --- a/flake8_simplify/__init__.py +++ b/flake8_simplify/__init__.py @@ -20,6 +20,7 @@ get_sim905, get_sim906, get_sim910, + get_sim911, ) from flake8_simplify.rules.ast_classdef import get_sim120 from flake8_simplify.rules.ast_compare import get_sim118, get_sim300 @@ -76,6 +77,7 @@ def visit_Call(self, node: ast.Call) -> Any: self.errors += get_sim905(node) self.errors += get_sim906(node) self.errors += get_sim910(Call(node)) + self.errors += get_sim911(node) self.generic_visit(node) def visit_With(self, node: ast.With) -> Any: diff --git a/flake8_simplify/rules/ast_call.py b/flake8_simplify/rules/ast_call.py index 60947f6..0e0d7f7 100644 --- a/flake8_simplify/rules/ast_call.py +++ b/flake8_simplify/rules/ast_call.py @@ -233,3 +233,67 @@ def get_sim910(node: Call) -> List[Tuple[int, int, str]]: ) ) return errors + + +def get_sim911(node: ast.AST) -> List[Tuple[int, int, str]]: + """ + Find nodes representing the expression "zip(_.keys(), _.values())". + + Returns a list of tuples containing the line number and column offset + of each identified node. + + Expr( + value=Call( + func=Name(id='zip', ctx=Load()), + args=[ + Call( + func=Attribute( + value=Name(id='_', ctx=Load()), + attr='keys', + ctx=Load()), + args=[], + keywords=[]), + Call( + func=Attribute( + value=Name(id='_', ctx=Load()), + attr='values', + ctx=Load()), + args=[], + keywords=[])], + keywords=[ + keyword( + arg='strict', + value=Constant(value=False)) + ] + ) + ) + """ + RULE = "SIM911 Use '{name}.items()' instead of 'zip({name}.keys(),{name}.values())'" + errors: List[Tuple[int, int, str]] = [] + + if isinstance(node, ast.Call): + if ( + isinstance(node.func, ast.Name) + and node.func.id == "zip" + and len(node.args) == 2 + ): + first_arg, second_arg = node.args + if ( + isinstance(first_arg, ast.Call) + and isinstance(first_arg.func, ast.Attribute) + and isinstance(first_arg.func.value, ast.Name) + and first_arg.func.attr == "keys" + and isinstance(second_arg, ast.Call) + and isinstance(second_arg.func, ast.Attribute) + and isinstance(second_arg.func.value, ast.Name) + and second_arg.func.attr == "values" + and first_arg.func.value.id == second_arg.func.value.id + ): + errors.append( + ( + node.lineno, + node.col_offset, + RULE.format(name=first_arg.func.value.id), + ) + ) + return errors diff --git a/tests/test_900_rules.py b/tests/test_900_rules.py index 3f1d17b..34b3368 100644 --- a/tests/test_900_rules.py +++ b/tests/test_900_rules.py @@ -204,3 +204,34 @@ def test_sim910(s, msg): expected = {msg} if msg is not None else set() results = _results(s) assert results == expected + + +@pytest.mark.parametrize( + ("s", "expected"), + ( + ( + "zip(d.keys(), d.values())", + { + "1:0 SIM911 Use 'd.items()' instead of 'zip(d.keys(), d.values())'" + }, + ), + ( + "zip(d.keys(), d.keys())", + None, + ), + ( + "zip(d1.keys(), d2.values())", + None, + ), + ( + "zip(d1.keys(), values)", + None, + ), + ( + "zip(keys, values)", + None, + ), + ), +) +def test_sim911(s, expected): + assert _results(s) == (expected or set())