Repository: psf/black

Issue:
Use parentheses for method access on integer literals
**Describe the style change**

When code tries to access a method on integer literals, the "space before dot" way should be reformatted to the "surround the integer literal with parentheses" way.

**Examples in the current _Black_ style** 

```python
count = 5 .bit_count()
```

**Desired style**

```python
count = (5).bit_count()
```

**Additional context** 

This is Twitter Driven Development btw :laughing: 

![designrequestviatwitter](https://user-images.githubusercontent.com/63936253/110869384-55bcab80-8298-11eb-8363-8a06f2ee5de5.png)




Discussion:

Resolving patch:
diff --git a/src/black/linegen.py b/src/black/linegen.py
--- a/src/black/linegen.py
+++ b/src/black/linegen.py
@@ -197,6 +197,28 @@ def visit_decorators(self, node: Node) -> Iterator[Line]:
             yield from self.line()
             yield from self.visit(child)
 
+    def visit_power(self, node: Node) -> Iterator[Line]:
+        for idx, leaf in enumerate(node.children[:-1]):
+            next_leaf = node.children[idx + 1]
+
+            if not isinstance(leaf, Leaf):
+                continue
+
+            value = leaf.value.lower()
+            if (
+                leaf.type == token.NUMBER
+                and next_leaf.type == syms.trailer
+                # Ensure that we are in an attribute trailer
+                and next_leaf.children[0].type == token.DOT
+                # It shouldn't wrap hexadecimal, binary and octal literals
+                and not value.startswith(("0x", "0b", "0o"))
+                # It shouldn't wrap complex literals
+                and "j" not in value
+            ):
+                wrap_in_parentheses(node, leaf)
+
+        yield from self.visit_default(node)
+
     def visit_SEMI(self, leaf: Leaf) -> Iterator[Line]:
         """Remove a semicolon and put the other statement on a separate line."""
         yield from self.line()
diff --git a/src/black/nodes.py b/src/black/nodes.py
--- a/src/black/nodes.py
+++ b/src/black/nodes.py
@@ -306,12 +306,7 @@ def whitespace(leaf: Leaf, *, complex_subscript: bool) -> str:  # noqa: C901
             return NO
 
         if not prev:
-            if t == token.DOT:
-                prevp = preceding_leaf(p)
-                if not prevp or prevp.type != token.NUMBER:
-                    return NO
-
-            elif t == token.LSQB:
+            if t == token.DOT or t == token.LSQB:
                 return NO
 
         elif prev.type != token.COMMA:


Test patch:
diff --git a/tests/data/attribute_access_on_number_literals.py b/tests/data/attribute_access_on_number_literals.py
new file mode 100644
--- /dev/null
+++ b/tests/data/attribute_access_on_number_literals.py
@@ -0,0 +1,47 @@
+x = 123456789 .bit_count()
+x = (123456).__abs__()
+x = .1.is_integer()
+x = 1. .imag
+x = 1E+1.imag
+x = 1E-1.real
+x = 123456789.123456789.hex()
+x = 123456789.123456789E123456789 .real
+x = 123456789E123456789 .conjugate()
+x = 123456789J.real
+x = 123456789.123456789J.__add__(0b1011.bit_length())
+x = 0XB1ACC.conjugate()
+x = 0B1011 .conjugate()
+x = 0O777 .real
+x = 0.000000006  .hex()
+x = -100.0000J
+
+if 10 .real:
+    ...
+
+y = 100[no]
+y = 100(no)
+
+# output
+
+x = (123456789).bit_count()
+x = (123456).__abs__()
+x = (0.1).is_integer()
+x = (1.0).imag
+x = (1e1).imag
+x = (1e-1).real
+x = (123456789.123456789).hex()
+x = (123456789.123456789e123456789).real
+x = (123456789e123456789).conjugate()
+x = 123456789j.real
+x = 123456789.123456789j.__add__(0b1011.bit_length())
+x = 0xB1ACC.conjugate()
+x = 0b1011.conjugate()
+x = 0o777.real
+x = (0.000000006).hex()
+x = -100.0000j
+
+if (10).real:
+    ...
+
+y = 100[no]
+y = 100(no)
diff --git a/tests/data/expression.diff b/tests/data/expression.diff
--- a/tests/data/expression.diff
+++ b/tests/data/expression.diff
@@ -11,7 +11,7 @@
  True
  False
  1
-@@ -21,71 +21,104 @@
+@@ -21,99 +21,135 @@
  Name1 or (Name2 and Name3) or Name4
  Name1 or Name2 and Name3 or Name4
  v1 << 2
@@ -144,8 +144,11 @@
  call(**self.screen_kwargs)
  call(b, **self.screen_kwargs)
  lukasz.langa.pl
-@@ -94,26 +127,29 @@
- 1.0 .real
+ call.me(maybe)
+-1 .real
+-1.0 .real
++(1).real
++(1.0).real
  ....__class__
  list[str]
  dict[str, int]
diff --git a/tests/data/expression.py b/tests/data/expression.py
--- a/tests/data/expression.py
+++ b/tests/data/expression.py
@@ -382,8 +382,8 @@ async def f():
 call(b, **self.screen_kwargs)
 lukasz.langa.pl
 call.me(maybe)
-1 .real
-1.0 .real
+(1).real
+(1.0).real
 ....__class__
 list[str]
 dict[str, int]
diff --git a/tests/data/expression_skip_magic_trailing_comma.diff b/tests/data/expression_skip_magic_trailing_comma.diff
--- a/tests/data/expression_skip_magic_trailing_comma.diff
+++ b/tests/data/expression_skip_magic_trailing_comma.diff
@@ -11,7 +11,7 @@
  True
  False
  1
-@@ -21,71 +21,92 @@
+@@ -21,99 +21,118 @@
  Name1 or (Name2 and Name3) or Name4
  Name1 or Name2 and Name3 or Name4
  v1 << 2
@@ -132,8 +132,11 @@
  call(**self.screen_kwargs)
  call(b, **self.screen_kwargs)
  lukasz.langa.pl
-@@ -94,26 +115,24 @@
- 1.0 .real
+ call.me(maybe)
+-1 .real
+-1.0 .real
++(1).real
++(1.0).real
  ....__class__
  list[str]
  dict[str, int]
diff --git a/tests/test_format.py b/tests/test_format.py
--- a/tests/test_format.py
+++ b/tests/test_format.py
@@ -15,6 +15,7 @@
 )
 
 SIMPLE_CASES: List[str] = [
+    "attribute_access_on_number_literals",
     "beginning_backslash",
     "bracketmatch",
     "class_blank_parentheses",