Skip to content

Commit

Permalink
Merge pull request #43 from robsdedude/add-multi-line-comprehension-v…
Browse files Browse the repository at this point in the history
…alue-exception

Allow redundant parens in comprehension values.
  • Loading branch information
robsdedude authored Mar 22, 2024
2 parents 171f07f + 9c57f27 commit b0fb0cb
Show file tree
Hide file tree
Showing 4 changed files with 122 additions and 6 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ Changelog
=========

## NEXT

**⭐ New**
* Exempt parentheses around multi-line values in comprehensions ([#43](https://github.com/robsdedude/flake8-picky-parentheses/pull/43)).

## 0.5.4
***
Expand Down
28 changes: 27 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ exceptions to this rule:
Even if these parentheses are redundant, they help to divide parts of
expressions and show sequence of actions.
3. Parts of slices.
4. Multi-line<sup>[1)](#footnotes)</sup> `if` and `for` parts in comprehensions.
4. Multi-line<sup>[1)](#footnotes)</sup> expression, `if` and `for` parts in comprehensions.
5. Multi-line<sup>[1)](#footnotes)</sup> keyword arguments or argument defaults.
6. String concatenation over several lines in lists and tuples .

Expand Down Expand Up @@ -269,6 +269,32 @@ a = (
a = (
b for b in (c + d)
)

# GOOD
a = (
(
1
+ b
)
for b in c
)

# BAD
a = (
(1 + b) for b in c
)

# GOOD
a = {
(
"foo%s"
% b
): (
b
* 2
)
for b in c
}
```

Exception type 5:
Expand Down
13 changes: 9 additions & 4 deletions src/flake8_picky_parentheses/_redundant_parentheses.py
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,9 @@ def _get_exceptions_from_ast(cls, sorted_parens_coords, tree, tokens):
ast.BinOp, ast.BoolOp, ast.UnaryOp, ast.Compare, ast.Await,
ast.IfExp
)
comprehension_exceptions = (
ast.ListComp, ast.SetComp, ast.DictComp, ast.GeneratorExp
)
nodes = list(cls._nodes_with_pos_and_parents(tree))
nodes.sort(key=lambda x: (x[1], len(x[3])))

Expand All @@ -283,7 +286,7 @@ def _get_exceptions_from_ast(cls, sorted_parens_coords, tree, tokens):
nodes_idx = 0
last_exception_node = None
rewrite_buffer = None
for _, parens_coord in enumerate(sorted_parens_coords):
for parens_coord in sorted_parens_coords:
node, pos, end, parents = nodes[nodes_idx]
while not cls._node_in_parens(
parens_coord, node, pos, end, tokens
Expand All @@ -299,23 +302,25 @@ def _get_exceptions_from_ast(cls, sorted_parens_coords, tree, tokens):

if (
parents
and isinstance(parents[0], ast.Slice)
and isinstance(parents[0], (ast.Slice, ast.Starred))
and isinstance(node, special_ops_pair_exceptions)
):
rewrite_buffer = ProblemRewrite(parens_coord.open_, None)
last_exception_node = node
continue
if (
parents
and isinstance(parents[0], special_ops_pair_exceptions)
and isinstance(parents[0], comprehension_exceptions)
and isinstance(node, special_ops_pair_exceptions)
and node in (getattr(parents[0], attr, None)
for attr in ("elt", "key", "value"))
):
rewrite_buffer = ProblemRewrite(parens_coord.open_, None)
last_exception_node = node
continue
if (
parents
and isinstance(parents[0], ast.Starred)
and isinstance(parents[0], special_ops_pair_exceptions)
and isinstance(node, special_ops_pair_exceptions)
):
rewrite_buffer = ProblemRewrite(parens_coord.open_, None)
Expand Down
84 changes: 84 additions & 0 deletions tests/test_redundant_parentheses.py
Original file line number Diff line number Diff line change
Expand Up @@ -1728,3 +1728,87 @@ def test_multi_line_dict_value_with_parens(plugin, dict_):
def test_multi_line_dict_value_without_parens(plugin, dict_):
s = f"foo = {dict_}"
assert no_lint(plugin(s))


@pytest.mark.parametrize("line_break", (" ", "\\\n"))
def test_ternary_operator_one_line(plugin, line_break):
s = f"""a = 1{line_break}if foo{line_break}else 2"""
assert no_lint(plugin(s))


@pytest.mark.parametrize("line_break", (" ", "\\\n"))
def test_ternary_operator_one_line_with_parens(plugin, line_break):
s = f"""a = (1{line_break}if foo{line_break}else 2)"""
print(s)
assert lint_codes(plugin(s), ["PAR001"])


@pytest.mark.parametrize("script", (
"""(
1
if foo
else 2
)""",
"""(
1 if foo else 2
)"""
))
@pytest.mark.parametrize(("template", "indent"), (
("%s", ""),
("(%s for foo in bar)", ""),
("""(
%s
for foo in bar
)""", " "),
))
def test_ternary_operator_multi_line_with_parens(
script, template, indent, plugin
):
script = "\n".join(indent + line for line in script.split("\n"))
script = "a = " + template % script
assert no_lint(plugin(script))


@pytest.mark.parametrize("comprehension_type", (
"()", "[]", "{}",
))
def test_multi_line_ternary_op_in_comprehension(comprehension_type, plugin):
s = f"""\
a = {comprehension_type[0]}
(
item.isoformat()
if isinstance(item, datetime.datetime)
else item
)
for item in data
{comprehension_type[1]}
"""
assert no_lint(plugin(s))


def test_multi_line_ternary_op_in_dict_comprehension_key(plugin):
s = """\
a = {
(
item.isoformat()
if isinstance(item, datetime.datetime)
else item
): "foo"
for item in data
}
"""
assert no_lint(plugin(s))


def test_multi_line_ternary_op_in_dict_comprehension_value(plugin):
s = """\
a = {
"foo": (
item.isoformat()
if isinstance(item, datetime.datetime)
else item
)
for item in data
}
"""
assert no_lint(plugin(s))

0 comments on commit b0fb0cb

Please sign in to comment.