diff --git a/README.md b/README.md index b833707..c7828e6 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,7 @@ Python-specific rules: * [`SIM117`](https://github.com/MartinThoma/flake8-simplify/issues/35): Merge with-statements that use the same scope ([example](#SIM117)) * [`SIM119`](https://github.com/MartinThoma/flake8-simplify/issues/37) ![](https://shields.io/badge/-legacyfix-inactive): Use dataclasses for data containers ([example](#SIM119)) * `SIM120` ![](https://shields.io/badge/-legacyfix-inactive): Use 'class FooBar:' instead of 'class FooBar(object):' ([example](#SIM120)) +* `SIM124`: Reserved for SIM904 once it's stable Simplifying Comparations: @@ -92,6 +93,7 @@ the code will change to another number. Current experimental rules: * `SIM901`: Use comparisons directly instead of wrapping them in a `bool(...)` call ([example](#SIM901)) +* `SIM904`: Assign values to dictionary directly at initialization ([example](#SIM904)) ## Disabling Rules @@ -545,3 +547,14 @@ bool(a == b) # Good a == b ``` + +### SIM904 + +```python +# Bad +a = {} +a["b"] = "c" + +# Good +a = {"b": "c"} +``` diff --git a/flake8_simplify.py b/flake8_simplify.py index 88eb2a8..f91318a 100644 --- a/flake8_simplify.py +++ b/flake8_simplify.py @@ -108,6 +108,7 @@ def __init__(self, orig: ast.Call) -> None: "instead of an if-block" ) SIM901 = "SIM901 Use '{better}' instead of '{current}'" +SIM904 = "SIM904 Initialize dictionary '{dict_name}' directly" # ast.Constant in Python 3.8, ast.NameConstant in Python 3.6 and 3.7 BOOL_CONST_TYPES = (ast.Constant, ast.NameConstant) @@ -1838,10 +1839,71 @@ def _get_sim901(node: ast.Call) -> List[Tuple[int, int, str]]: return errors +def _get_sim904(node: ast.Assign) -> List[Tuple[int, int, str]]: + """ + Assign values to dictionary directly at initialization. + + Example + ------- + Code: + # Bad + a = { } + a['b] = 'c' + + # Good + a = {'b': 'c'} + Bad AST: + [ + Assign( + targets=[Name(id='a', ctx=Store())], + value=Dict(keys=[], values=[]), + type_comment=None, + ), + Assign( + targets=[ + Subscript( + value=Name(id='a', ctx=Load()), + slice=Constant(value='b', kind=None), + ctx=Store(), + ), + ], + value=Constant(value='c', kind=None), + type_comment=None, + ), + ] + """ + errors: List[Tuple[int, int, str]] = [] + n2 = node.next_sibling # type: ignore + if not ( + isinstance(node.value, ast.Dict) + and isinstance(n2, ast.Assign) + and len(n2.targets) == 1 + and len(node.targets) == 1 + and isinstance(n2.targets[0], ast.Subscript) + and isinstance(n2.targets[0].value, ast.Name) + and isinstance(node.targets[0], ast.Name) + and n2.targets[0].value.id == node.targets[0].id + ): + return errors + dict_name = to_source(node.targets[0]) + errors.append( + ( + node.lineno, + node.col_offset, + SIM904.format(dict_name=dict_name), + ) + ) + return errors + + class Visitor(ast.NodeVisitor): def __init__(self) -> None: self.errors: List[Tuple[int, int, str]] = [] + def visit_Assign(self, node: ast.Assign) -> Any: + self.errors += _get_sim904(node) + self.generic_visit(node) + def visit_Call(self, node: ast.Call) -> Any: self.errors += _get_sim115(Call(node)) self.errors += _get_sim901(node) diff --git a/tests/test_simplify.py b/tests/test_simplify.py index 5443746..0304702 100644 --- a/tests/test_simplify.py +++ b/tests/test_simplify.py @@ -935,3 +935,11 @@ def test_sim401_positive_msg_check_issue89(): def test_sim901(): results = _results("bool(a == b)") assert results == {"1:0 SIM901 Use 'a == b' instead of 'bool(a == b)'"} + + +def test_sim904(): + results = _results( + """a = { } +a['b'] = 'c'""" + ) + assert results == {"1:0 SIM904 Initialize dictionary 'a' directly"}