Skip to content

Commit

Permalink
Allow parens around multi-line str as function argument
Browse files Browse the repository at this point in the history
  • Loading branch information
robsdedude committed Jan 16, 2025
1 parent d4d81cd commit 23de152
Show file tree
Hide file tree
Showing 4 changed files with 197 additions and 38 deletions.
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,20 @@ Changelog
This is an almost empty patch as no breaking changes of Python 3.13 affect this plugin.
Only documentation, project metadata as well as some tooling needed adjustments.

**🔧 Fixes**
* Make multi-line string exemptions more lenient ([#47](https://github.com/robsdedude/flake8-picky-parentheses/pull/47)).
Allow redundant parenthesis around multi-line string in function calls.
For example:
```python
# GOOD
func(
(
"a"
"b"
),
"c",
)
```

## 0.5.5
***
Expand Down
12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ exceptions to this rule:
3. Parts of slices.
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 .
6. String concatenation over several lines in lists, tuples, and function arguments.


Exception type 1:
Expand Down Expand Up @@ -345,6 +345,16 @@ Exception type 6:
"a" "b"
),
]

# This also applies to function calls:
# GOOD
func(
(
"a"
"b"
),
"c",
)
```

### Footnotes:
Expand Down
24 changes: 23 additions & 1 deletion src/flake8_picky_parentheses/_redundant_parentheses.py
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,7 @@ def _get_exceptions_from_ast(cls, sorted_parens_coords, tree, tokens):

nodes_idx = 0
last_exception_node = None
skip_node = False
rewrite_buffer = None
for parens_coord in sorted_parens_coords:
node, pos, end, parents = nodes[nodes_idx]
Expand All @@ -301,6 +302,12 @@ def _get_exceptions_from_ast(cls, sorted_parens_coords, tree, tokens):
if nodes_idx >= len(nodes):
return
node, pos, end, parents = nodes[nodes_idx]
if skip_node:
if last_exception_node is not node:
skip_node = False
rewrite_buffer = None
else:
continue
if rewrite_buffer is not None and last_exception_node is not node:
# moved to the next node => emmit the exception
yield rewrite_buffer
Expand Down Expand Up @@ -388,7 +395,10 @@ def _get_exceptions_from_ast(cls, sorted_parens_coords, tree, tokens):
continue
if (
parents
and isinstance(parents[0], (ast.Tuple, ast.List))
and isinstance(
parents[0],
(ast.Tuple, ast.List, ast.Call, ast.keyword),
)
and isinstance(node, AstStr)
):
tokens_slice = slice(parens_coord.token_indexes[0] + 1,
Expand All @@ -402,6 +412,18 @@ def _get_exceptions_from_ast(cls, sorted_parens_coords, tree, tokens):
if string_tokens[0].start[0] != string_tokens[-1].start[0]:
rewrite_buffer = ProblemRewrite(parens_coord.open_, None)
last_exception_node = node

if isinstance(parents[0], ast.Call):
prev_token = tokens[parens_coord.token_indexes[0] - 1]
if prev_token.type == tokenize.NAME:
# For function calls, we want the multi-line string
# to provide an exception for the outermost
# parenthesis pair if that is the one enclosing the
# function call's arguments.
# The innermost parenthesis pair is already covered
# by _get_exceptions_for_neighboring_parens
yield ProblemRewrite(parens_coord.open_, None)
skip_node = True
continue

if rewrite_buffer is not None:
Expand Down
185 changes: 149 additions & 36 deletions tests/test_redundant_parentheses.py
Original file line number Diff line number Diff line change
Expand Up @@ -462,45 +462,150 @@ def test_single_line_strings(plugin, value, quote):
assert lint_codes(plugin(s), ["PAR001"])


# GOOD (multi-line strings in list/tuple)
# GOOD (multi-line strings in list/tuple/func arg)
# https://github.com/robsdedude/flake8-picky-parentheses/issues/32
@pytest.mark.parametrize("value", (
'[("a"\n"b")]',
'[("a"\n"b"),]',
'[("a"\n"c"), "b"]',
'["a", ("b" \n"c")]',
'[("a"\n"c"), "b",]',
'["a", ("b"\n"c"),]',
'[(\n"a"\n"c"), "b"]',
'["a", (\n"b" \n"c")]',
'[(\n"a"\n"c"), "b",]',
'["a", (\n"b"\n"c"),]',
'[("a"\n"c"\n), "b"]',
'["a", ("b" \n"c"\n)]',
'[("a"\n"c"\n), "b",]',
'["a", ("b"\n"c"\n),]',
'(("a"\n"b"),)',
'(("a"\n"c"), "b")',
'("a", ("b" \n"c"))',
'(("a"\n"c"), "b",)',
'("a", ("b"\n"c"),)',
'((\n"a"\n"c"), "b")',
'("a", (\n"b" \n"c"))',
'((\n"a"\n"c"), "b",)',
'("a", (\n"b"\n"c"),)',
'(("a"\n"c"\n), "b")',
'("a", ("b" \n"c"\n))',
'(("a"\n"c"\n), "b",)',
'("a", ("b"\n"c"\n),)',
# lists
's = [("a"\n"b")]\n',
's = [("a"\n"b"),]\n',
's = [("a"\n"c"), "b"]\n',
's = ["a", ("b" \n"c")]\n',
's = [("a"\n"c"), "b",]\n',
's = ["a", ("b"\n"c"),]\n',
's = [(\n"a"\n"c"), "b"]\n',
's = ["a", (\n"b" \n"c")]\n',
's = [(\n"a"\n"c"), "b",]\n',
's = ["a", (\n"b"\n"c"),]\n',
's = [("a"\n"c"\n), "b"]\n',
's = ["a", ("b" \n"c"\n)]\n',
's = [("a"\n"c"\n), "b",]\n',
's = ["a", ("b"\n"c"\n),]\n',
# tuples
's = (("a"\n"b"),)\n',
's = (("a"\n"c"), "b")\n',
's = ("a", ("b" \n"c"))\n',
's = (("a"\n"c"), "b",)\n',
's = ("a", ("b"\n"c"),)\n',
's = ((\n"a"\n"c"), "b")\n',
's = ("a", (\n"b" \n"c"))\n',
's = ((\n"a"\n"c"), "b",)\n',
's = ("a", (\n"b"\n"c"),)\n',
's = (("a"\n"c"\n), "b")\n',
's = ("a", ("b" \n"c"\n))\n',
's = (("a"\n"c"\n), "b",)\n',
's = ("a", ("b"\n"c"\n),)\n',
# positional arguments
'func(("a"\n"b"))\n',
'func(("a"\n"b"),)\n',
'func(("a"\n"c"), "b")\n',
'func("a", ("b" \n"c"))\n',
'func(("a"\n"c"), "b",)\n',
'func("a", ("b"\n"c"),)\n',
'func((\n"a"\n"c"), "b")\n',
'func("a", (\n"b" \n"c"))\n',
'func((\n"a"\n"c"), "b",)\n',
'func("a", (\n"b"\n"c"),)\n',
'func(("a"\n"c"\n), "b")\n',
'func("a", ("b" \n"c"\n))\n',
'func(("a"\n"c"\n), "b",)\n',
'func("a", ("b"\n"c"\n),)\n',
# keyword arguments
'func(a=("a"\n"b"))\n',
'func(a=("a"\n"b"),)\n',
'func(a=("a"\n"c"), b="b")\n',
'func(a="a", b=("b" \n"c"))\n',
'func(a=("a"\n"c"), b="b",)\n',
'func(a="a", b=("b"\n"c"),)\n',
'func(a=(\n"a"\n"c"), b="b")\n',
'func(a="a", b=(\n"b" \n"c"))\n',
'func(a=(\n"a"\n"c"), b="b",)\n',
'func(a="a", b=(\n"b"\n"c"),)\n',
'func(a=("a"\n"c"\n), b="b")\n',
'func(a="a", b=("b" \n"c"\n))\n',
'func(a=("a"\n"c"\n), b="b",)\n',
'func(a="a", b=("b"\n"c"\n),)\n',
))
@pytest.mark.parametrize("quote", ("'", '"')[1:])
def test_grouped_single_line_strings(plugin, value, quote):
value = value.replace('"', quote)
s = f"a = {value}\n"
s = value.replace('"', quote)
assert no_lint(plugin(s))


# BAD (multi-line strings in list/tuple with double redundant parentheses)
# https://github.com/robsdedude/flake8-picky-parentheses/issues/32
@pytest.mark.parametrize("value", (
# lists
's = [(("a"\n"b"))]\n',
's = [(("a"\n"b")),]\n',
's = [(("a"\n"c")), "b"]\n',
's = ["a", (("b" \n"c"))]\n',
's = [(("a"\n"c")), "b",]\n',
's = ["a", (("b"\n"c")),]\n',
's = [((\n"a"\n"c")), "b"]\n',
's = ["a", ((\n"b" \n"c"))]\n',
's = [((\n"a"\n"c")), "b",]\n',
's = ["a", ((\n"b"\n"c")),]\n',
's = [(("a"\n"c"\n)), "b"]\n',
's = ["a", (("b" \n"c"\n))]\n',
's = [(("a"\n"c"\n)), "b",]\n',
's = ["a", (("b"\n"c"\n)),]\n',
# tuples
's = ((("a"\n"b")),)\n',
's = ((("a"\n"c")), "b")\n',
's = ("a", (("b" \n"c")))\n',
's = ((("a"\n"c")), "b",)\n',
's = ("a", (("b"\n"c")),)\n',
's = (((\n"a"\n"c")), "b")\n',
's = ("a", ((\n"b" \n"c")))\n',
's = (((\n"a"\n"c")), "b",)\n',
's = ("a", ((\n"b"\n"c")),)\n',
's = ((("a"\n"c"\n)), "b")\n',
's = ("a", (("b" \n"c"\n)))\n',
's = ((("a"\n"c"\n)), "b",)\n',
's = ("a", (("b"\n"c"\n)),)\n',
# positional arguments
'func((("a"\n"b")))\n',
'func((("a"\n"b")),)\n',
'func((("a"\n"c")), "b")\n',
'func("a", (("b" \n"c")))\n',
'func((("a"\n"c")), "b",)\n',
'func("a", (("b"\n"c")),)\n',
'func(((\n"a"\n"c")), "b")\n',
'func("a", ((\n"b" \n"c")))\n',
'func(((\n"a"\n"c")), "b",)\n',
'func("a", ((\n"b"\n"c")),)\n',
'func((("a"\n"c"\n)), "b")\n',
'func("a", (("b" \n"c"\n)))\n',
'func((("a"\n"c"\n)), "b",)\n',
'func("a", (("b"\n"c"\n)),)\n',
# keyword arguments
'func(a=(("a"\n"b")))\n',
'func(a=(("a"\n"b")),)\n',
'func(a=(("a"\n"c")), b="b")\n',
'func(a="a", b=(("b" \n"c")))\n',
'func(a=(("a"\n"c")), b="b",)\n',
'func(a="a", b=(("b"\n"c")),)\n',
'func(a=((\n"a"\n"c")), b="b")\n',
'func(a="a", b=((\n"b" \n"c")))\n',
'func(a=((\n"a"\n"c")), b="b",)\n',
'func(a="a", b=((\n"b"\n"c")),)\n',
'func(a=(("a"\n"c"\n)), b="b")\n',
'func(a="a", b=(("b" \n"c"\n)))\n',
'func(a=(("a"\n"c"\n)), b="b",)\n',
'func(a="a", b=(("b"\n"c"\n)),)\n',
))
@pytest.mark.parametrize("quote", ("'", '"')[1:])
def test_grouped_single_line_strings_double_parens(plugin, value, quote):
s = value.replace('"', quote)
assert lint_codes(plugin(s), ["PAR001"])


# GOOD (function call)
def test_function_call(plugin):
s = """foo("a")
Expand Down Expand Up @@ -615,7 +720,7 @@ def test_unnecessary_parens(plugin):
assert lint_codes(plugin(s), ["PAR001"])


# BAD (one pair of parenthesis is enough)
# BAD (one pair of parentheses is enough)
def test_bin_op_example_double_parens_1(plugin):
s = """a = 1 * ((2 + 3))
"""
Expand All @@ -624,28 +729,28 @@ def test_bin_op_example_double_parens_1(plugin):
assert lints[0].startswith("1:9 ") or lints[0].startswith("1:10 ")


# BAD (one pair of parenthesis is enough)
# BAD (one pair of parentheses is enough)
def test_bin_op_example_double_parens_2(plugin):
s = """a = ((1 * 2)) + 3
"""
assert lint_codes(plugin(s), ["PAR001"])


# BAD (one pair of parenthesis is enough)
# BAD (one pair of parentheses is enough)
def test_bin_op_example_double_parens_3(plugin):
s = """a = 1 + ((2 * 3))
"""
assert lint_codes(plugin(s), ["PAR001"])


# BAD (one pair of parenthesis is enough)
# BAD (one pair of parentheses is enough)
def test_bin_op_example_double_parens_4(plugin):
s = """a = ((1 + 2)) * 3
"""
assert lint_codes(plugin(s), ["PAR001"])


# BAD (redundant parenthesis around 1)
# BAD (redundant parentheses around 1)
def test_redundant_parens_around_tuple(plugin):
s = """a = ((1),)
"""
Expand Down Expand Up @@ -1812,3 +1917,11 @@ def test_multi_line_ternary_op_in_dict_comprehension_value(plugin):
}
"""
assert no_lint(plugin(s))


def test_multiple_exceptions(plugin):
s = """\
a = (1 + 2) + 3
b = ((1 + 2) + 3) + 4
"""
assert no_lint(plugin(s))

0 comments on commit 23de152

Please sign in to comment.