Repository: google/yapf
Pull request #819 (2020-02-29 03:06:31)

Add space inside brackets
For issue #811, add a style option "SPACE_INSIDE_BRACKETS".

### SPACE_INSIDE_BRACKETS
Use spaces inside brackets and parens.  For example:

```python
        method_call( 1 )
        my_dict[ 3 ][ 1 ][ get_index( *args, **kwargs ) ]
        my_set = { 1, 2, 3 }
```

This Bool option (False by default) inserts spaces after opening, and
before closing brackets (parens, brackets, and braces, to be precise).

Added a new test case to cover the changes in reformatter_pep8_test.py.
Slight overall increase in test coverage +3 total, +1 in unwrapped_line.

coveralls: 
[![Coverage Status](https://coveralls.io/builds/29042364/badge)](https://coveralls.io/builds/29042364)

Coverage decreased (-0.06%) to 96.332% when pulling **1e83b0c880ecd6df378fa9adc5b3173f5e070c11 on wwade:spaces-inside-brackets** into **6c6f029846105315a0d354dabcc6e8e2807ae7d5 on google:master**.

wwade: @gwelymernans @davidbrownell this is a bit of an update on PR #657, but I was using it independently as a patch before I saw PR #657.

The `coveralls` failure is not obvious to me - at least in a local run, the coverage actually increased (against python3.7), and I see other PRs coming in with the same coverage count as this one.  Any guidance here?

Patch:
diff --git a/CHANGELOG b/CHANGELOG
index d0c0b80..e1feffb 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -11,6 +11,8 @@
 - Don't over-indent a parameter list when not needed. But make sure it is
   properly indented so that it doesn't collide with the lines afterwards.
 - Don't split between two-word comparison operators: "is not", "not in", etc.
+- New knob `SPACE_INSIDE_BRACKETS` to add spaces inside brackets, braces, and
+  parentheses.
 
 ## [0.29.0] 2019-11-28
 ### Added
diff --git a/README.rst b/README.rst
index 31f6d28..1fd04ea 100644
--- a/README.rst
+++ b/README.rst
@@ -594,6 +594,15 @@ Knobs
 ``SPACE_BETWEEN_ENDING_COMMA_AND_CLOSING_BRACKET``
     Insert a space between the ending comma and closing bracket of a list, etc.
 
+``SPACE_INSIDE_BRACKETS``
+    Use spaces inside brackets, braces, and parentheses.  For example:
+
+    .. code-block:: python
+
+        method_call( 1 )
+        my_dict[ 3 ][ 1 ][ get_index( *args, **kwargs ) ]
+        my_set = { 1, 2, 3 }
+
 ``SPLIT_ARGUMENTS_WHEN_COMMA_TERMINATED``
     Split before arguments if the argument list is terminated by a comma.
 
@@ -813,7 +822,7 @@ I still get non Pep8 compliant code! Why?
 YAPF tries very hard to be fully PEP 8 compliant. However, it is paramount
 to not risk altering the semantics of your code. Thus, YAPF tries to be as
 safe as possible and does not change the token stream
-(e.g., by adding parenthesis).
+(e.g., by adding parentheses).
 All these cases however, can be easily fixed manually. For instance,
 
 .. code-block:: python
@@ -822,7 +831,7 @@ All these cases however, can be easily fixed manually. For instance,
 
     FOO = my_variable_1 + my_variable_2 + my_variable_3 + my_variable_4 + my_variable_5 + my_variable_6 + my_variable_7 + my_variable_8
 
-won't be split, but you can easily get it right by just adding parenthesis:
+won't be split, but you can easily get it right by just adding parentheses:
 
 .. code-block:: python
 
diff --git a/yapf/yapflib/style.py b/yapf/yapflib/style.py
index 87cab02..5f2483f 100644
--- a/yapf/yapflib/style.py
+++ b/yapf/yapflib/style.py
@@ -208,6 +208,13 @@ _STYLE_HELP = dict(
     SPACE_BETWEEN_ENDING_COMMA_AND_CLOSING_BRACKET=textwrap.dedent("""\
       Insert a space between the ending comma and closing bracket of a list,
       etc."""),
+    SPACE_INSIDE_BRACKETS=textwrap.dedent("""\
+      Use spaces inside brackets, braces, and parentheses.  For example:
+
+        method_call( 1 )
+        my_dict[ 3 ][ 1 ][ get_index( *args, **kwargs ) ]
+        my_set = { 1, 2, 3 }
+      """),
     SPACES_AROUND_POWER_OPERATOR=textwrap.dedent("""\
       Use spaces around the power operator."""),
     SPACES_AROUND_DEFAULT_OR_NAMED_ASSIGN=textwrap.dedent("""\
@@ -384,6 +391,7 @@ def CreatePEP8Style():
       JOIN_MULTIPLE_LINES=True,
       NO_SPACES_AROUND_SELECTED_BINARY_OPERATORS=set(),
       SPACE_BETWEEN_ENDING_COMMA_AND_CLOSING_BRACKET=True,
+      SPACE_INSIDE_BRACKETS=False,
       SPACES_AROUND_POWER_OPERATOR=False,
       SPACES_AROUND_DEFAULT_OR_NAMED_ASSIGN=False,
       SPACES_BEFORE_COMMENT=2,
@@ -566,6 +574,7 @@ _STYLE_OPTION_VALUE_CONVERTER = dict(
     JOIN_MULTIPLE_LINES=_BoolConverter,
     NO_SPACES_AROUND_SELECTED_BINARY_OPERATORS=_StringSetConverter,
     SPACE_BETWEEN_ENDING_COMMA_AND_CLOSING_BRACKET=_BoolConverter,
+    SPACE_INSIDE_BRACKETS=_BoolConverter,
     SPACES_AROUND_POWER_OPERATOR=_BoolConverter,
     SPACES_AROUND_DEFAULT_OR_NAMED_ASSIGN=_BoolConverter,
     SPACES_BEFORE_COMMENT=_IntOrIntListConverter,
diff --git a/yapf/yapflib/unwrapped_line.py b/yapf/yapflib/unwrapped_line.py
index c75d285..e4cb428 100644
--- a/yapf/yapflib/unwrapped_line.py
+++ b/yapf/yapflib/unwrapped_line.py
@@ -287,6 +287,12 @@ def _SpaceRequiredBetween(left, right):
   if lval == ',' and rval == ':':
     # We do want a space between a comma and colon.
     return True
+  if style.Get('SPACE_INSIDE_BRACKETS'):
+    # Supersede the "no space before a colon or comma" check.
+    if lval in pytree_utils.OPENING_BRACKETS and rval == ':':
+      return True
+    if rval in pytree_utils.CLOSING_BRACKETS and lval == ':':
+      return True
   if rval in ':,':
     # Otherwise, we never want a space before a colon or comma.
     return False
@@ -328,6 +334,11 @@ def _SpaceRequiredBetween(left, right):
       # A string followed by something other than a subscript, closing bracket,
       # dot, or a binary op should have a space after it.
       return True
+    if rval in pytree_utils.CLOSING_BRACKETS:
+      # A string followed by closing brackets should have a space after it
+      # depending on SPACE_INSIDE_BRACKETS.  A string followed by opening
+      # brackets, however, should not.
+      return style.Get('SPACE_INSIDE_BRACKETS')
     if format_token.Subtype.SUBSCRIPT_BRACKET in right.subtypes:
       # It's legal to do this in Python: 'hello'[a]
       return False
@@ -393,44 +404,50 @@ def _SpaceRequiredBetween(left, right):
     return False
   if (lval in pytree_utils.OPENING_BRACKETS and
       rval in pytree_utils.OPENING_BRACKETS):
-    # Nested objects' opening brackets shouldn't be separated.
-    return False
+    # Nested objects' opening brackets shouldn't be separated, unless enabled
+    # by SPACE_INSIDE_BRACKETS.
+    return style.Get('SPACE_INSIDE_BRACKETS')
   if (lval in pytree_utils.CLOSING_BRACKETS and
       rval in pytree_utils.CLOSING_BRACKETS):
-    # Nested objects' closing brackets shouldn't be separated.
-    return False
+    # Nested objects' closing brackets shouldn't be separated, unless enabled
+    # by SPACE_INSIDE_BRACKETS.
+    return style.Get('SPACE_INSIDE_BRACKETS')
   if lval in pytree_utils.CLOSING_BRACKETS and rval in '([':
     # A call, set, dictionary, or subscript that has a call or subscript after
     # it shouldn't have a space between them.
     return False
   if lval in pytree_utils.OPENING_BRACKETS and _IsIdNumberStringToken(right):
-    # Don't separate the opening bracket from the first item.
-    return False
+    # Don't separate the opening bracket from the first item, unless enabled
+    # by SPACE_INSIDE_BRACKETS.
+    return style.Get('SPACE_INSIDE_BRACKETS')
   if left.is_name and rval in '([':
     # Don't separate a call or array access from the name.
     return False
   if rval in pytree_utils.CLOSING_BRACKETS:
-    # Don't separate the closing bracket from the last item.
+    # Don't separate the closing bracket from the last item, unless enabled
+    # by SPACE_INSIDE_BRACKETS.
     # FIXME(morbo): This might be too permissive.
-    return False
+    return style.Get('SPACE_INSIDE_BRACKETS')
   if lval == 'print' and rval == '(':
     # Special support for the 'print' function.
     return False
   if lval in pytree_utils.OPENING_BRACKETS and _IsUnaryOperator(right):
-    # Don't separate a unary operator from the opening bracket.
-    return False
+    # Don't separate a unary operator from the opening bracket, unless enabled
+    # by SPACE_INSIDE_BRACKETS.
+    return style.Get('SPACE_INSIDE_BRACKETS')
   if (lval in pytree_utils.OPENING_BRACKETS and
       (format_token.Subtype.VARARGS_STAR in right.subtypes or
        format_token.Subtype.KWARGS_STAR_STAR in right.subtypes)):
-    # Don't separate a '*' or '**' from the opening bracket.
-    return False
+    # Don't separate a '*' or '**' from the opening bracket, unless enabled
+    # by SPACE_INSIDE_BRACKETS.
+    return style.Get('SPACE_INSIDE_BRACKETS')
   if rval == ';':
     # Avoid spaces before a semicolon. (Why is there a semicolon?!)
     return False
   if lval == '(' and rval == 'await':
     # Special support for the 'await' keyword. Don't separate the 'await'
-    # keyword from an opening paren.
-    return False
+    # keyword from an opening paren, unless enabled by SPACE_INSIDE_BRACKETS.
+    return style.Get('SPACE_INSIDE_BRACKETS')
   return True
 
 


Test patch:
diff --git a/yapftests/reformatter_pep8_test.py b/yapftests/reformatter_pep8_test.py
index 28798e3..8f336e1 100644
--- a/yapftests/reformatter_pep8_test.py
+++ b/yapftests/reformatter_pep8_test.py
@@ -699,5 +699,149 @@ class _():
     self.assertCodeEqual(expected_formatted_code, reformatted_code)
 
 
+class TestsForSpacesInsideBrackets(yapf_test_helper.YAPFTest):
+  """Test the SPACE_INSIDE_BRACKETS style option."""
+  unformatted_code = textwrap.dedent("""\
+    foo()
+    foo(1)
+    foo(1,2)
+    foo((1,))
+    foo((1, 2))
+    foo((1, 2,))
+    foo(bar['baz'][0])
+    set1 = {1, 2, 3}
+    dict1 = {1: 1, foo: 2, 3: bar}
+    dict2 = {
+        1: 1,
+        foo: 2,
+        3: bar,
+    }
+    dict3[3][1][get_index(*args,**kwargs)]
+    dict4[3][1][get_index(**kwargs)]
+    x = dict5[4](foo(*args))
+    a = list1[:]
+    b = list2[slice_start:]
+    c = list3[slice_start:slice_end]
+    d = list4[slice_start:slice_end:]
+    e = list5[slice_start:slice_end:slice_step]
+    # Print gets special handling
+    print(set2)
+    compound = ((10+3)/(5-2**(6+x)))
+    string_idx = "mystring"[3]
+    """)
+
+  def testEnabled(self):
+    style.SetGlobalStyle(
+        style.CreateStyleFromConfig('{space_inside_brackets: True}'))
+
+    expected_formatted_code = textwrap.dedent("""\
+      foo()
+      foo( 1 )
+      foo( 1, 2 )
+      foo( ( 1, ) )
+      foo( ( 1, 2 ) )
+      foo( (
+          1,
+          2,
+      ) )
+      foo( bar[ 'baz' ][ 0 ] )
+      set1 = { 1, 2, 3 }
+      dict1 = { 1: 1, foo: 2, 3: bar }
+      dict2 = {
+          1: 1,
+          foo: 2,
+          3: bar,
+      }
+      dict3[ 3 ][ 1 ][ get_index( *args, **kwargs ) ]
+      dict4[ 3 ][ 1 ][ get_index( **kwargs ) ]
+      x = dict5[ 4 ]( foo( *args ) )
+      a = list1[ : ]
+      b = list2[ slice_start: ]
+      c = list3[ slice_start:slice_end ]
+      d = list4[ slice_start:slice_end: ]
+      e = list5[ slice_start:slice_end:slice_step ]
+      # Print gets special handling
+      print( set2 )
+      compound = ( ( 10 + 3 ) / ( 5 - 2**( 6 + x ) ) )
+      string_idx = "mystring"[ 3 ]
+      """)
+
+    uwlines = yapf_test_helper.ParseAndUnwrap(self.unformatted_code)
+    self.assertCodeEqual(expected_formatted_code, reformatter.Reformat(uwlines))
+
+  def testDefault(self):
+    style.SetGlobalStyle(style.CreatePEP8Style())
+
+    expected_formatted_code = textwrap.dedent("""\
+      foo()
+      foo(1)
+      foo(1, 2)
+      foo((1, ))
+      foo((1, 2))
+      foo((
+          1,
+          2,
+      ))
+      foo(bar['baz'][0])
+      set1 = {1, 2, 3}
+      dict1 = {1: 1, foo: 2, 3: bar}
+      dict2 = {
+          1: 1,
+          foo: 2,
+          3: bar,
+      }
+      dict3[3][1][get_index(*args, **kwargs)]
+      dict4[3][1][get_index(**kwargs)]
+      x = dict5[4](foo(*args))
+      a = list1[:]
+      b = list2[slice_start:]
+      c = list3[slice_start:slice_end]
+      d = list4[slice_start:slice_end:]
+      e = list5[slice_start:slice_end:slice_step]
+      # Print gets special handling
+      print(set2)
+      compound = ((10 + 3) / (5 - 2**(6 + x)))
+      string_idx = "mystring"[3]
+      """)
+
+    uwlines = yapf_test_helper.ParseAndUnwrap(self.unformatted_code)
+    self.assertCodeEqual(expected_formatted_code, reformatter.Reformat(uwlines))
+
+  @unittest.skipUnless(py3compat.PY36, 'Requires Python 3.6')
+  def testAwait(self):
+    style.SetGlobalStyle(
+        style.CreateStyleFromConfig('{space_inside_brackets: True}'))
+    unformatted_code = textwrap.dedent("""\
+      import asyncio
+      import time
+
+      @print_args
+      async def slow_operation():
+        await asyncio.sleep(1)
+        # print("Slow operation {} complete".format(n))
+        async def main():
+          start = time.time()
+          if (await get_html()):
+            pass
+      """)
+    expected_formatted_code = textwrap.dedent("""\
+      import asyncio
+      import time
+
+
+      @print_args
+      async def slow_operation():
+          await asyncio.sleep( 1 )
+
+          # print("Slow operation {} complete".format(n))
+          async def main():
+              start = time.time()
+              if ( await get_html() ):
+                  pass
+      """)
+    uwlines = yapf_test_helper.ParseAndUnwrap(unformatted_code)
+    self.assertCodeEqual(expected_formatted_code, reformatter.Reformat(uwlines))
+
+
 if __name__ == '__main__':
   unittest.main()