]> git.madduck.net Git - etc/vim.git/commitdiff

madduck's git repository

Every one of the projects in this repository is available at the canonical URL git://git.madduck.net/madduck/pub/<projectpath> — see each project's metadata for the exact URL.

All patches and comments are welcome. Please squash your changes to logical commits before using git-format-patch and git-send-email to patches@git.madduck.net. If you'd read over the Git project's submission guidelines and adhered to them, I'd be especially grateful.

SSH access, as well as push access can be individually arranged.

If you use my repositories frequently, consider adding the following snippet to ~/.gitconfig and using the third clone URL listed for each project:

[url "git://git.madduck.net/madduck/"]
  insteadOf = madduck:

Hug power operators if its operands are "simple" (#2726)
authorRichard Si <63936253+ichard26@users.noreply.github.com>
Tue, 25 Jan 2022 03:13:34 +0000 (22:13 -0500)
committerGitHub <noreply@github.com>
Tue, 25 Jan 2022 03:13:34 +0000 (19:13 -0800)
Since power operators almost always have the highest binding power in expressions, it's often more readable to hug it with its operands. The main exception to this is when its operands are non-trivial in which case the power operator will not hug, the rule for this is the following:

> For power ops, an operand is considered "simple" if it's only a NAME, numeric CONSTANT, or attribute access (chained attribute access is allowed), with or without a preceding unary operator.

Fixes GH-538.
Closes GH-2095.

diff-shades results: https://gist.github.com/ichard26/ca6c6ad4bd1de5152d95418c8645354b

Co-authored-by: Diego <dpalma@evernote.com>
Co-authored-by: Felix Hildén <felix.hilden@gmail.com>
Co-authored-by: Jelle Zijlstra <jelle.zijlstra@gmail.com>
13 files changed:
CHANGES.md
docs/the_black_code_style/current_style.md
src/black/linegen.py
src/black/trans.py
src/black_primer/primer.json
tests/data/expression.diff
tests/data/expression.py
tests/data/expression_skip_magic_trailing_comma.diff
tests/data/pep_572.py
tests/data/pep_572_py39.py
tests/data/power_op_spacing.py [new file with mode: 0644]
tests/data/slices.py
tests/test_format.py

index 458d48cd2c1bfb98368bb52b2497a62bedb6707d..d203896a801fba9fcd5d84a54294efef26eecfef 100644 (file)
@@ -20,6 +20,7 @@
 - Tuple unpacking on `return` and `yield` constructs now implies 3.8+ (#2700)
 - Unparenthesized tuples on annotated assignments (e.g
   `values: Tuple[int, ...] = 1, 2, 3`) now implies 3.8+ (#2708)
+- Remove spaces around power operators if both operands are simple (#2726)
 - Allow setting custom cache directory on all platforms with environment variable
   `BLACK_CACHE_DIR` (#2739).
 - Text coloring added in the final statistics (#2712)
index 1d1e42e75c85ea06442258e4facf9d11cae84ff0..5be7ba6dbdbdccb4880251424c3a6cae3eb7ed9c 100644 (file)
@@ -284,6 +284,26 @@ multiple lines. This is so that _Black_ is compliant with the recent changes in
 [PEP 8](https://www.python.org/dev/peps/pep-0008/#should-a-line-break-before-or-after-a-binary-operator)
 style guide, which emphasizes that this approach improves readability.
 
+Almost all operators will be surrounded by single spaces, the only exceptions are unary
+operators (`+`, `-`, and `~`), and power operators when both operands are simple. For
+powers, an operand is considered simple if it's only a NAME, numeric CONSTANT, or
+attribute access (chained attribute access is allowed), with or without a preceding
+unary operator.
+
+```python
+# For example, these won't be surrounded by whitespace
+a = x**y
+b = config.base**5.2
+c = config.base**runtime.config.exponent
+d = 2**5
+e = 2**~5
+
+# ... but these will be surrounded by whitespace
+f = 2 ** get_exponent()
+g = get_x() ** get_y()
+h = config['base'] ** 2
+```
+
 ### Slices
 
 PEP 8
index 9ee42aaaf7259e4441edb1c9f25c5bd1f9e89e47..9fbdfadba6adcdd348efc7039a5a987b88efc0fb 100644 (file)
@@ -21,8 +21,8 @@ from black.comments import generate_comments, list_comments, FMT_OFF
 from black.numerics import normalize_numeric_literal
 from black.strings import get_string_prefix, fix_docstring
 from black.strings import normalize_string_prefix, normalize_string_quotes
-from black.trans import Transformer, CannotTransform, StringMerger
-from black.trans import StringSplitter, StringParenWrapper, StringParenStripper
+from black.trans import Transformer, CannotTransform, StringMerger, StringSplitter
+from black.trans import StringParenWrapper, StringParenStripper, hug_power_op
 from black.mode import Mode, Feature, Preview
 
 from blib2to3.pytree import Node, Leaf
@@ -404,6 +404,9 @@ def transform_line(
                 transformers = [delimiter_split, standalone_comment_split, rhs]
             else:
                 transformers = [rhs]
+    # It's always safe to attempt hugging of power operations and pretty much every line
+    # could match.
+    transformers.append(hug_power_op)
 
     for transform in transformers:
         # We are accumulating lines in `result` because we might want to abort
index cb41c1be487bdc1788e42340e408cd80d9d58d09..74d052fe2dcbd0854d11bee16417777e28ed26eb 100644 (file)
@@ -24,9 +24,9 @@ from typing import (
 import sys
 
 if sys.version_info < (3, 8):
-    from typing_extensions import Final
+    from typing_extensions import Literal, Final
 else:
-    from typing import Final
+    from typing import Literal, Final
 
 from mypy_extensions import trait
 
@@ -71,6 +71,88 @@ def TErr(err_msg: str) -> Err[CannotTransform]:
     return Err(cant_transform)
 
 
+def hug_power_op(line: Line, features: Collection[Feature]) -> Iterator[Line]:
+    """A transformer which normalizes spacing around power operators."""
+
+    # Performance optimization to avoid unnecessary Leaf clones and other ops.
+    for leaf in line.leaves:
+        if leaf.type == token.DOUBLESTAR:
+            break
+    else:
+        raise CannotTransform("No doublestar token was found in the line.")
+
+    def is_simple_lookup(index: int, step: Literal[1, -1]) -> bool:
+        # Brackets and parentheses indicate calls, subscripts, etc. ...
+        # basically stuff that doesn't count as "simple". Only a NAME lookup
+        # or dotted lookup (eg. NAME.NAME) is OK.
+        if step == -1:
+            disallowed = {token.RPAR, token.RSQB}
+        else:
+            disallowed = {token.LPAR, token.LSQB}
+
+        while 0 <= index < len(line.leaves):
+            current = line.leaves[index]
+            if current.type in disallowed:
+                return False
+            if current.type not in {token.NAME, token.DOT} or current.value == "for":
+                # If the current token isn't disallowed, we'll assume this is simple as
+                # only the disallowed tokens are semantically attached to this lookup
+                # expression we're checking. Also, stop early if we hit the 'for' bit
+                # of a comprehension.
+                return True
+
+            index += step
+
+        return True
+
+    def is_simple_operand(index: int, kind: Literal["base", "exponent"]) -> bool:
+        # An operand is considered "simple" if's a NAME, a numeric CONSTANT, a simple
+        # lookup (see above), with or without a preceding unary operator.
+        start = line.leaves[index]
+        if start.type in {token.NAME, token.NUMBER}:
+            return is_simple_lookup(index, step=(1 if kind == "exponent" else -1))
+
+        if start.type in {token.PLUS, token.MINUS, token.TILDE}:
+            if line.leaves[index + 1].type in {token.NAME, token.NUMBER}:
+                # step is always one as bases with a preceding unary op will be checked
+                # for simplicity starting from the next token (so it'll hit the check
+                # above).
+                return is_simple_lookup(index + 1, step=1)
+
+        return False
+
+    leaves: List[Leaf] = []
+    should_hug = False
+    for idx, leaf in enumerate(line.leaves):
+        new_leaf = leaf.clone()
+        if should_hug:
+            new_leaf.prefix = ""
+            should_hug = False
+
+        should_hug = (
+            (0 < idx < len(line.leaves) - 1)
+            and leaf.type == token.DOUBLESTAR
+            and is_simple_operand(idx - 1, kind="base")
+            and line.leaves[idx - 1].value != "lambda"
+            and is_simple_operand(idx + 1, kind="exponent")
+        )
+        if should_hug:
+            new_leaf.prefix = ""
+
+        leaves.append(new_leaf)
+
+    yield Line(
+        mode=line.mode,
+        depth=line.depth,
+        leaves=leaves,
+        comments=line.comments,
+        bracket_tracker=line.bracket_tracker,
+        inside_brackets=line.inside_brackets,
+        should_split_rhs=line.should_split_rhs,
+        magic_trailing_comma=line.magic_trailing_comma,
+    )
+
+
 class StringTransformer(ABC):
     """
     An implementation of the Transformer protocol that relies on its
index a8d8fc9e21fd58a013ce423dc08f0465a2070886..a6bfd4a2feca00dab035472006ace7e2058ad732 100644 (file)
@@ -81,7 +81,7 @@
     },
     "flake8-bugbear": {
       "cli_arguments": ["--experimental-string-processing"],
-      "expect_formatting_changes": false,
+      "expect_formatting_changes": true,
       "git_clone_url": "https://github.com/PyCQA/flake8-bugbear.git",
       "long_checkout": false,
       "py_versions": ["all"]
index 721a07d2141ab71fc2d898302e7e7eccf44bd276..5f29a18dc7f64cd31213ec54bbad70e834993ac0 100644 (file)
  True
  False
  1
-@@ -29,63 +29,96 @@
+@@ -21,71 +21,104 @@
+ Name1 or (Name2 and Name3) or Name4
+ Name1 or Name2 and Name3 or Name4
+ v1 << 2
+ 1 >> v2
+ 1 % finished
+-1 + v2 - v3 * 4 ^ 5 ** v6 / 7 // 8
+-((1 + v2) - (v3 * 4)) ^ (((5 ** v6) / 7) // 8)
++1 + v2 - v3 * 4 ^ 5**v6 / 7 // 8
++((1 + v2) - (v3 * 4)) ^ (((5**v6) / 7) // 8)
+ not great
  ~great
  +value
  -1
@@ -19,7 +29,7 @@
  (~int) and (not ((v1 ^ (123 + v2)) | True))
 -+really ** -confusing ** ~operator ** -precedence
 -flags & ~ select.EPOLLIN and waiters.write_task is not None
-++(really ** -(confusing ** ~(operator ** -precedence)))
+++(really ** -(confusing ** ~(operator**-precedence)))
 +flags & ~select.EPOLLIN and waiters.write_task is not None
  lambda arg: None
  lambda a=True: a
 +    *more,
 +]
  {i for i in (1, 2, 3)}
- {(i ** 2) for i in (1, 2, 3)}
+-{(i ** 2) for i in (1, 2, 3)}
 -{(i ** 2) for i, _ in ((1, 'a'), (2, 'b'), (3, 'c'))}
-+{(i ** 2) for i, _ in ((1, "a"), (2, "b"), (3, "c"))}
- {((i ** 2) + j) for i in (1, 2, 3) for j in (1, 2, 3)}
+-{((i ** 2) + j) for i in (1, 2, 3) for j in (1, 2, 3)}
++{(i**2) for i in (1, 2, 3)}
++{(i**2) for i, _ in ((1, "a"), (2, "b"), (3, "c"))}
++{((i**2) + j) for i in (1, 2, 3) for j in (1, 2, 3)}
  [i for i in (1, 2, 3)]
- [(i ** 2) for i in (1, 2, 3)]
+-[(i ** 2) for i in (1, 2, 3)]
 -[(i ** 2) for i, _ in ((1, 'a'), (2, 'b'), (3, 'c'))]
-+[(i ** 2) for i, _ in ((1, "a"), (2, "b"), (3, "c"))]
- [((i ** 2) + j) for i in (1, 2, 3) for j in (1, 2, 3)]
+-[((i ** 2) + j) for i in (1, 2, 3) for j in (1, 2, 3)]
++[(i**2) for i in (1, 2, 3)]
++[(i**2) for i, _ in ((1, "a"), (2, "b"), (3, "c"))]
++[((i**2) + j) for i in (1, 2, 3) for j in (1, 2, 3)]
  {i: 0 for i in (1, 2, 3)}
 -{i: j for i, j in ((1, 'a'), (2, 'b'), (3, 'c'))}
 +{i: j for i, j in ((1, "a"), (2, "b"), (3, "c"))}
  SomeName
  (Good, Bad, Ugly)
  (i for i in (1, 2, 3))
- ((i ** 2) for i in (1, 2, 3))
+-((i ** 2) for i in (1, 2, 3))
 -((i ** 2) for i, _ in ((1, 'a'), (2, 'b'), (3, 'c')))
-+((i ** 2) for i, _ in ((1, "a"), (2, "b"), (3, "c")))
- (((i ** 2) + j) for i in (1, 2, 3) for j in (1, 2, 3))
+-(((i ** 2) + j) for i in (1, 2, 3) for j in (1, 2, 3))
++((i**2) for i in (1, 2, 3))
++((i**2) for i, _ in ((1, "a"), (2, "b"), (3, "c")))
++(((i**2) + j) for i in (1, 2, 3) for j in (1, 2, 3))
  (*starred,)
 -{"id": "1","type": "type","started_at": now(),"ended_at": now() + timedelta(days=10),"priority": 1,"import_session_id": 1,**kwargs}
 +{
 +    return True
 +if (
 +    ~aaaa.a + aaaa.b - aaaa.c * aaaa.d / aaaa.e
-+    | aaaa.f & aaaa.g % aaaa.h ^ aaaa.i << aaaa.k >> aaaa.l ** aaaa.m // aaaa.n
++    | aaaa.f & aaaa.g % aaaa.h ^ aaaa.i << aaaa.k >> aaaa.l**aaaa.m // aaaa.n
 +):
 +    return True
 +if (
 +    ~aaaaaaaa.a + aaaaaaaa.b - aaaaaaaa.c @ aaaaaaaa.d / aaaaaaaa.e
 +    | aaaaaaaa.f & aaaaaaaa.g % aaaaaaaa.h
-+    ^ aaaaaaaa.i << aaaaaaaa.k >> aaaaaaaa.l ** aaaaaaaa.m // aaaaaaaa.n
++    ^ aaaaaaaa.i << aaaaaaaa.k >> aaaaaaaa.l**aaaaaaaa.m // aaaaaaaa.n
 +):
 +    return True
 +if (
 +    | aaaaaaaaaaaaaaaa.f & aaaaaaaaaaaaaaaa.g % aaaaaaaaaaaaaaaa.h
 +    ^ aaaaaaaaaaaaaaaa.i
 +    << aaaaaaaaaaaaaaaa.k
-+    >> aaaaaaaaaaaaaaaa.l ** aaaaaaaaaaaaaaaa.m // aaaaaaaaaaaaaaaa.n
++    >> aaaaaaaaaaaaaaaa.l**aaaaaaaaaaaaaaaa.m // aaaaaaaaaaaaaaaa.n
 +):
 +    return True
 +(
index d13450cda68e81beba962c42e4c14d4c0f0938eb..b056841027da189fcc23f33512684e1c9d994240 100644 (file)
@@ -282,15 +282,15 @@ Name1 or Name2 and Name3 or Name4
 v1 << 2
 1 >> v2
 1 % finished
-1 + v2 - v3 * 4 ^ 5 ** v6 / 7 // 8
-((1 + v2) - (v3 * 4)) ^ (((5 ** v6) / 7) // 8)
+1 + v2 - v3 * 4 ^ 5**v6 / 7 // 8
+((1 + v2) - (v3 * 4)) ^ (((5**v6) / 7) // 8)
 not great
 ~great
 +value
 -1
 ~int and not v1 ^ 123 + v2 | True
 (~int) and (not ((v1 ^ (123 + v2)) | True))
-+(really ** -(confusing ** ~(operator ** -precedence)))
++(really ** -(confusing ** ~(operator**-precedence)))
 flags & ~select.EPOLLIN and waiters.write_task is not None
 lambda arg: None
 lambda a=True: a
@@ -347,13 +347,13 @@ str or None if (1 if True else 2) else str or bytes or None
     *more,
 ]
 {i for i in (1, 2, 3)}
-{(i ** 2) for i in (1, 2, 3)}
-{(i ** 2) for i, _ in ((1, "a"), (2, "b"), (3, "c"))}
-{((i ** 2) + j) for i in (1, 2, 3) for j in (1, 2, 3)}
+{(i**2) for i in (1, 2, 3)}
+{(i**2) for i, _ in ((1, "a"), (2, "b"), (3, "c"))}
+{((i**2) + j) for i in (1, 2, 3) for j in (1, 2, 3)}
 [i for i in (1, 2, 3)]
-[(i ** 2) for i in (1, 2, 3)]
-[(i ** 2) for i, _ in ((1, "a"), (2, "b"), (3, "c"))]
-[((i ** 2) + j) for i in (1, 2, 3) for j in (1, 2, 3)]
+[(i**2) for i in (1, 2, 3)]
+[(i**2) for i, _ in ((1, "a"), (2, "b"), (3, "c"))]
+[((i**2) + j) for i in (1, 2, 3) for j in (1, 2, 3)]
 {i: 0 for i in (1, 2, 3)}
 {i: j for i, j in ((1, "a"), (2, "b"), (3, "c"))}
 {a: b * 2 for a, b in dictionary.items()}
@@ -441,9 +441,9 @@ numpy[np.newaxis, :]
 SomeName
 (Good, Bad, Ugly)
 (i for i in (1, 2, 3))
-((i ** 2) for i in (1, 2, 3))
-((i ** 2) for i, _ in ((1, "a"), (2, "b"), (3, "c")))
-(((i ** 2) + j) for i in (1, 2, 3) for j in (1, 2, 3))
+((i**2) for i in (1, 2, 3))
+((i**2) for i, _ in ((1, "a"), (2, "b"), (3, "c")))
+(((i**2) + j) for i in (1, 2, 3) for j in (1, 2, 3))
 (*starred,)
 {
     "id": "1",
@@ -588,13 +588,13 @@ if (
     return True
 if (
     ~aaaa.a + aaaa.b - aaaa.c * aaaa.d / aaaa.e
-    | aaaa.f & aaaa.g % aaaa.h ^ aaaa.i << aaaa.k >> aaaa.l ** aaaa.m // aaaa.n
+    | aaaa.f & aaaa.g % aaaa.h ^ aaaa.i << aaaa.k >> aaaa.l**aaaa.m // aaaa.n
 ):
     return True
 if (
     ~aaaaaaaa.a + aaaaaaaa.b - aaaaaaaa.c @ aaaaaaaa.d / aaaaaaaa.e
     | aaaaaaaa.f & aaaaaaaa.g % aaaaaaaa.h
-    ^ aaaaaaaa.i << aaaaaaaa.k >> aaaaaaaa.l ** aaaaaaaa.m // aaaaaaaa.n
+    ^ aaaaaaaa.i << aaaaaaaa.k >> aaaaaaaa.l**aaaaaaaa.m // aaaaaaaa.n
 ):
     return True
 if (
@@ -604,7 +604,7 @@ if (
     | aaaaaaaaaaaaaaaa.f & aaaaaaaaaaaaaaaa.g % aaaaaaaaaaaaaaaa.h
     ^ aaaaaaaaaaaaaaaa.i
     << aaaaaaaaaaaaaaaa.k
-    >> aaaaaaaaaaaaaaaa.l ** aaaaaaaaaaaaaaaa.m // aaaaaaaaaaaaaaaa.n
+    >> aaaaaaaaaaaaaaaa.l**aaaaaaaaaaaaaaaa.m // aaaaaaaaaaaaaaaa.n
 ):
     return True
 (
index 4a8a95c72371b70213f8eb65589aada239ca1414..5b722c91352e0d3fb597c3a7a7b4085a08c404e2 100644 (file)
  True
  False
  1
-@@ -29,63 +29,84 @@
+@@ -21,71 +21,92 @@
+ Name1 or (Name2 and Name3) or Name4
+ Name1 or Name2 and Name3 or Name4
+ v1 << 2
+ 1 >> v2
+ 1 % finished
+-1 + v2 - v3 * 4 ^ 5 ** v6 / 7 // 8
+-((1 + v2) - (v3 * 4)) ^ (((5 ** v6) / 7) // 8)
++1 + v2 - v3 * 4 ^ 5**v6 / 7 // 8
++((1 + v2) - (v3 * 4)) ^ (((5**v6) / 7) // 8)
+ not great
  ~great
  +value
  -1
@@ -19,7 +29,7 @@
  (~int) and (not ((v1 ^ (123 + v2)) | True))
 -+really ** -confusing ** ~operator ** -precedence
 -flags & ~ select.EPOLLIN and waiters.write_task is not None
-++(really ** -(confusing ** ~(operator ** -precedence)))
+++(really ** -(confusing ** ~(operator**-precedence)))
 +flags & ~select.EPOLLIN and waiters.write_task is not None
  lambda arg: None
  lambda a=True: a
 +    *more,
 +]
  {i for i in (1, 2, 3)}
- {(i ** 2) for i in (1, 2, 3)}
+-{(i ** 2) for i in (1, 2, 3)}
 -{(i ** 2) for i, _ in ((1, 'a'), (2, 'b'), (3, 'c'))}
-+{(i ** 2) for i, _ in ((1, "a"), (2, "b"), (3, "c"))}
- {((i ** 2) + j) for i in (1, 2, 3) for j in (1, 2, 3)}
+-{((i ** 2) + j) for i in (1, 2, 3) for j in (1, 2, 3)}
++{(i**2) for i in (1, 2, 3)}
++{(i**2) for i, _ in ((1, "a"), (2, "b"), (3, "c"))}
++{((i**2) + j) for i in (1, 2, 3) for j in (1, 2, 3)}
  [i for i in (1, 2, 3)]
- [(i ** 2) for i in (1, 2, 3)]
+-[(i ** 2) for i in (1, 2, 3)]
 -[(i ** 2) for i, _ in ((1, 'a'), (2, 'b'), (3, 'c'))]
-+[(i ** 2) for i, _ in ((1, "a"), (2, "b"), (3, "c"))]
- [((i ** 2) + j) for i in (1, 2, 3) for j in (1, 2, 3)]
+-[((i ** 2) + j) for i in (1, 2, 3) for j in (1, 2, 3)]
++[(i**2) for i in (1, 2, 3)]
++[(i**2) for i, _ in ((1, "a"), (2, "b"), (3, "c"))]
++[((i**2) + j) for i in (1, 2, 3) for j in (1, 2, 3)]
  {i: 0 for i in (1, 2, 3)}
 -{i: j for i, j in ((1, 'a'), (2, 'b'), (3, 'c'))}
 +{i: j for i, j in ((1, "a"), (2, "b"), (3, "c"))}
  SomeName
  (Good, Bad, Ugly)
  (i for i in (1, 2, 3))
- ((i ** 2) for i in (1, 2, 3))
+-((i ** 2) for i in (1, 2, 3))
 -((i ** 2) for i, _ in ((1, 'a'), (2, 'b'), (3, 'c')))
-+((i ** 2) for i, _ in ((1, "a"), (2, "b"), (3, "c")))
- (((i ** 2) + j) for i in (1, 2, 3) for j in (1, 2, 3))
+-(((i ** 2) + j) for i in (1, 2, 3) for j in (1, 2, 3))
++((i**2) for i in (1, 2, 3))
++((i**2) for i, _ in ((1, "a"), (2, "b"), (3, "c")))
++(((i**2) + j) for i in (1, 2, 3) for j in (1, 2, 3))
  (*starred,)
 -{"id": "1","type": "type","started_at": now(),"ended_at": now() + timedelta(days=10),"priority": 1,"import_session_id": 1,**kwargs}
 +{
 +    return True
 +if (
 +    ~aaaa.a + aaaa.b - aaaa.c * aaaa.d / aaaa.e
-+    | aaaa.f & aaaa.g % aaaa.h ^ aaaa.i << aaaa.k >> aaaa.l ** aaaa.m // aaaa.n
++    | aaaa.f & aaaa.g % aaaa.h ^ aaaa.i << aaaa.k >> aaaa.l**aaaa.m // aaaa.n
 +):
 +    return True
 +if (
 +    ~aaaaaaaa.a + aaaaaaaa.b - aaaaaaaa.c @ aaaaaaaa.d / aaaaaaaa.e
 +    | aaaaaaaa.f & aaaaaaaa.g % aaaaaaaa.h
-+    ^ aaaaaaaa.i << aaaaaaaa.k >> aaaaaaaa.l ** aaaaaaaa.m // aaaaaaaa.n
++    ^ aaaaaaaa.i << aaaaaaaa.k >> aaaaaaaa.l**aaaaaaaa.m // aaaaaaaa.n
 +):
 +    return True
 +if (
 +    | aaaaaaaaaaaaaaaa.f & aaaaaaaaaaaaaaaa.g % aaaaaaaaaaaaaaaa.h
 +    ^ aaaaaaaaaaaaaaaa.i
 +    << aaaaaaaaaaaaaaaa.k
-+    >> aaaaaaaaaaaaaaaa.l ** aaaaaaaaaaaaaaaa.m // aaaaaaaaaaaaaaaa.n
++    >> aaaaaaaaaaaaaaaa.l**aaaaaaaaaaaaaaaa.m // aaaaaaaaaaaaaaaa.n
 +):
 +    return True
 +(
index c6867f2625855693197e2d262120193788528b7c..d41805f1cb16d93eb0600ae30b69019973652522 100644 (file)
@@ -4,7 +4,7 @@ if (match := pattern.search(data)) is None:
     pass
 if match := pattern.search(data):
     pass
-[y := f(x), y ** 2, y ** 3]
+[y := f(x), y**2, y**3]
 filtered_data = [y for x in data if (y := f(x)) is None]
 (y := f(x))
 y0 = (y1 := f(x))
index 7bbd509119729979974c46e164aee0ea43dcb1bc..b8b081b8c4574d3638f6dcc9242cffcc096f6ea2 100644 (file)
@@ -1,7 +1,7 @@
 # Unparenthesized walruses are now allowed in set literals & set comprehensions
 # since Python 3.9
 {x := 1, 2, 3}
-{x4 := x ** 5 for x in range(7)}
+{x4 := x**5 for x in range(7)}
 # We better not remove the parentheses here (since it's a 3.10 feature)
 x[(a := 1)]
 x[(a := 1), (b := 3)]
diff --git a/tests/data/power_op_spacing.py b/tests/data/power_op_spacing.py
new file mode 100644 (file)
index 0000000..87dde7f
--- /dev/null
@@ -0,0 +1,103 @@
+def function(**kwargs):
+    t = a**2 + b**3
+    return t ** 2
+
+
+def function_replace_spaces(**kwargs):
+    t = a **2 + b** 3 + c ** 4
+
+
+def function_dont_replace_spaces():
+    {**a, **b, **c}
+
+
+a = 5**~4
+b = 5 ** f()
+c = -(5**2)
+d = 5 ** f["hi"]
+e = lazy(lambda **kwargs: 5)
+f = f() ** 5
+g = a.b**c.d
+h = 5 ** funcs.f()
+i = funcs.f() ** 5
+j = super().name ** 5
+k = [(2**idx, value) for idx, value in pairs]
+l = mod.weights_[0] == pytest.approx(0.95**100, abs=0.001)
+m = [([2**63], [1, 2**63])]
+n = count <= 10**5
+o = settings(max_examples=10**6)
+p = {(k, k**2): v**2 for k, v in pairs}
+q = [10**i for i in range(6)]
+r = x**y
+
+a = 5.0**~4.0
+b = 5.0 ** f()
+c = -(5.0**2.0)
+d = 5.0 ** f["hi"]
+e = lazy(lambda **kwargs: 5)
+f = f() ** 5.0
+g = a.b**c.d
+h = 5.0 ** funcs.f()
+i = funcs.f() ** 5.0
+j = super().name ** 5.0
+k = [(2.0**idx, value) for idx, value in pairs]
+l = mod.weights_[0] == pytest.approx(0.95**100, abs=0.001)
+m = [([2.0**63.0], [1.0, 2**63.0])]
+n = count <= 10**5.0
+o = settings(max_examples=10**6.0)
+p = {(k, k**2): v**2.0 for k, v in pairs}
+q = [10.5**i for i in range(6)]
+
+
+# output
+
+
+def function(**kwargs):
+    t = a**2 + b**3
+    return t**2
+
+
+def function_replace_spaces(**kwargs):
+    t = a**2 + b**3 + c**4
+
+
+def function_dont_replace_spaces():
+    {**a, **b, **c}
+
+
+a = 5**~4
+b = 5 ** f()
+c = -(5**2)
+d = 5 ** f["hi"]
+e = lazy(lambda **kwargs: 5)
+f = f() ** 5
+g = a.b**c.d
+h = 5 ** funcs.f()
+i = funcs.f() ** 5
+j = super().name ** 5
+k = [(2**idx, value) for idx, value in pairs]
+l = mod.weights_[0] == pytest.approx(0.95**100, abs=0.001)
+m = [([2**63], [1, 2**63])]
+n = count <= 10**5
+o = settings(max_examples=10**6)
+p = {(k, k**2): v**2 for k, v in pairs}
+q = [10**i for i in range(6)]
+r = x**y
+
+a = 5.0**~4.0
+b = 5.0 ** f()
+c = -(5.0**2.0)
+d = 5.0 ** f["hi"]
+e = lazy(lambda **kwargs: 5)
+f = f() ** 5.0
+g = a.b**c.d
+h = 5.0 ** funcs.f()
+i = funcs.f() ** 5.0
+j = super().name ** 5.0
+k = [(2.0**idx, value) for idx, value in pairs]
+l = mod.weights_[0] == pytest.approx(0.95**100, abs=0.001)
+m = [([2.0**63.0], [1.0, 2**63.0])]
+n = count <= 10**5.0
+o = settings(max_examples=10**6.0)
+p = {(k, k**2): v**2.0 for k, v in pairs}
+q = [10.5**i for i in range(6)]
index 7a42678f646fcfc523d82f3e1e3b5173b7daeceb..165117cdcb495be4470d8cbe41bb4c584432380c 100644 (file)
@@ -9,7 +9,7 @@ slice[::-1]
 slice[:c, c - 1]
 slice[c, c + 1, d::]
 slice[ham[c::d] :: 1]
-slice[ham[cheese ** 2 : -1] : 1 : 1, ham[1:2]]
+slice[ham[cheese**2 : -1] : 1 : 1, ham[1:2]]
 slice[:-1:]
 slice[lambda: None : lambda: None]
 slice[lambda x, y, *args, really=2, **kwargs: None :, None::]
index 3895a095e863c2ec503fa43e272add8062da9f8e..c6c811040dcac311a0a921ab5167c815fbb9b0dc 100644 (file)
@@ -48,6 +48,7 @@ SIMPLE_CASES: List[str] = [
     "function2",
     "function_trailing_comma",
     "import_spacing",
+    "power_op_spacing",
     "remove_parens",
     "slices",
     "string_prefixes",