Repository: google/yapf

Issue:
README documents the wrong programmatic API
Both `FormatCode` and `FormatFile` return two-item tuples, but the README suggests they just return a string.

https://github.com/google/yapf/blob/c6077954245bc3add82dafd853a1c7305a6ebd20/yapf/yapflib/yapf_api.py#L137-L139

Comments:

Patch:
diff --git a/README.rst b/README.rst
index e441317..56fb9d6 100644
--- a/README.rst
+++ b/README.rst
@@ -270,8 +270,11 @@ share several arguments which are described below:
 
     >>> from yapf.yapflib.yapf_api import FormatCode  # reformat a string of code
 
-    >>> FormatCode("f ( a = 1, b = 2 )")
+    >>> formatted_code, changed = FormatCode("f ( a = 1, b = 2 )")
+    >>> formatted_code
     'f(a=1, b=2)\n'
+    >>> changed
+    True
 
 A ``style_config`` argument: Either a style name or a path to a file that contains
 formatting style settings. If None is specified, use the default style
@@ -279,7 +282,7 @@ as set in ``style.DEFAULT_STYLE_FACTORY``.
 
 .. code-block:: python
 
-    >>> FormatCode("def g():\n  return True", style_config='pep8')
+    >>> FormatCode("def g():\n  return True", style_config='pep8')[0]
     'def g():\n    return True\n'
 
 A ``lines`` argument: A list of tuples of lines (ints), [start, end],
@@ -289,15 +292,15 @@ than a whole file.
 
 .. code-block:: python
 
-    >>> FormatCode("def g( ):\n    a=1\n    b = 2\n    return a==b", lines=[(1, 1), (2, 3)])
+    >>> FormatCode("def g( ):\n    a=1\n    b = 2\n    return a==b", lines=[(1, 1), (2, 3)])[0]
     'def g():\n    a = 1\n    b = 2\n    return a==b\n'
 
 A ``print_diff`` (bool): Instead of returning the reformatted source, return a
-diff that turns the formatted source into reformatter source.
+diff that turns the formatted source into reformatted source.
 
 .. code-block:: python
 
-    >>> print(FormatCode("a==b", filename="foo.py", print_diff=True))
+    >>> print(FormatCode("a==b", filename="foo.py", print_diff=True)[0])
     --- foo.py (original)
     +++ foo.py (reformatted)
     @@ -1 +1 @@
@@ -316,14 +319,19 @@ the diff, the default is ``<unknown>``.
     >>> print(open("foo.py").read())  # contents of file
     a==b
 
-    >>> FormatFile("foo.py")
-    ('a == b\n', 'utf-8')
+    >>> reformatted_code, encoding, changed = FormatFile("foo.py")
+    >>> formatted_code
+    'a == b\n'
+    >>> encoding
+    'utf-8'
+    >>> changed
+    True
 
 The ``in_place`` argument saves the reformatted code back to the file:
 
 .. code-block:: python
 
-    >>> FormatFile("foo.py", in_place=True)
+    >>> FormatFile("foo.py", in_place=True)[:2]
     (None, 'utf-8')
 
     >>> print(open("foo.py").read())  # contents of file (now fixed)
diff --git a/setup.py b/setup.py
index fe2a046..70e57da 100644
--- a/setup.py
+++ b/setup.py
@@ -17,7 +17,7 @@ import codecs
 import sys
 import unittest
 
-from setuptools import setup, Command
+from setuptools import find_packages, setup, Command
 
 import yapf
 
@@ -49,7 +49,7 @@ with codecs.open('README.rst', 'r', 'utf-8') as fd:
       author='Google Inc.',
       maintainer='Bill Wendling',
       maintainer_email='morbo@google.com',
-      packages=['yapf', 'yapf.yapflib', 'yapftests'],
+      packages=find_packages('.'),
       classifiers=[
           'Development Status :: 4 - Beta',
           'Environment :: Console',
diff --git a/yapf/yapflib/reformatter.py b/yapf/yapflib/reformatter.py
index 60386e5..d07be84 100644
--- a/yapf/yapflib/reformatter.py
+++ b/yapf/yapflib/reformatter.py
@@ -119,14 +119,14 @@ def _RetainRequiredVerticalSpacing(cur_uwline, prev_uwline, lines):
   if prev_uwline is not None:
     prev_tok = prev_uwline.last
 
+  if cur_uwline.disable:
+    # After the first token we are acting on a single line. So if it is
+    # disabled we must not reformat.
+    lines = set()
+
   for cur_tok in cur_uwline.tokens:
     _RetainRequiredVerticalSpacingBetweenTokens(cur_tok, prev_tok, lines)
-
     prev_tok = cur_tok
-    if cur_uwline.disable:
-      # After the first token we are acting on a single line. So if it is
-      # disabled we must not reformat.
-      lines = set()
 
 
 def _RetainRequiredVerticalSpacingBetweenTokens(cur_tok, prev_tok, lines):
@@ -491,10 +491,13 @@ def _AnalyzeSolutionSpace(initial_state):
     if count > 10000:
       node.state.ignore_stack_for_comparison = True
 
-    if node.state in seen:
-      continue
-
+    # Unconditionally add the state and check if it was present to avoid having to
+    # hash it twice in the common case (state hashing is expensive).
+    before_seen_count = len(seen)
     seen.add(node.state)
+    # If seen didn't change size, the state was already present.
+    if before_seen_count == len(seen):
+      continue
 
     # FIXME(morbo): Add a 'decision' element?
 
diff --git a/yapf/yapflib/unwrapped_line.py b/yapf/yapflib/unwrapped_line.py
index 475bd6e..0986314 100644
--- a/yapf/yapflib/unwrapped_line.py
+++ b/yapf/yapflib/unwrapped_line.py
@@ -323,6 +323,9 @@ def _SpaceRequiredBetween(left, right, is_line_disabled):
       format_token.Subtype.DEFAULT_OR_NAMED_ASSIGN not in left.subtypes):
     # Space between equal and '.' as in "X = ...".
     return True
+  if lval == ':' and rval == '.':
+    # Space between : and ...
+    return True
   if ((right.is_keyword or right.is_name) and
       (left.is_keyword or left.is_name)):
     # Don't merge two keywords/identifiers.
diff --git a/yapf/yapflib/yapf_api.py b/yapf/yapflib/yapf_api.py
index dde1df9..fdcce30 100644
--- a/yapf/yapflib/yapf_api.py
+++ b/yapf/yapflib/yapf_api.py
@@ -269,10 +269,9 @@ def _MarkLinesToFormat(uwlines, lines):
         index += 1
         while index < len(uwlines):
           uwline = uwlines[index]
-          if uwline.is_comment and _EnableYAPF(uwline.first.value.strip()):
-            if not re.search(DISABLE_PATTERN,
-                             uwline.first.value.strip().split('\n')[-1].strip(),
-                             re.IGNORECASE):
+          line = uwline.first.value.strip()
+          if uwline.is_comment and _EnableYAPF(line):
+            if not _DisableYAPF(line):
               break
           uwline.disable = True
           index += 1


Test patch:
diff --git a/yapftests/blank_line_calculator_test.py b/yapftests/blank_line_calculator_test.py
index 1ec0a5e..193f419 100644
--- a/yapftests/blank_line_calculator_test.py
+++ b/yapftests/blank_line_calculator_test.py
@@ -300,15 +300,12 @@ class BasicBlankLineCalculatorTest(yapf_test_helper.YAPFTest):
         def B():  # 4
           pass  # 5
 
-
         def C():
           pass
 
 
         def D():  # 9
           pass  # 10
-
-
         def E():
           pass
         """)
@@ -375,6 +372,8 @@ class BasicBlankLineCalculatorTest(yapf_test_helper.YAPFTest):
           pass  # 7
 
 
+
+
         def C():
           pass
         """)
@@ -410,6 +409,7 @@ class BasicBlankLineCalculatorTest(yapf_test_helper.YAPFTest):
 
 
 
+
         def C():
           pass
         """)
diff --git a/yapftests/reformatter_pep8_test.py b/yapftests/reformatter_pep8_test.py
index bdd074a..a5301f1 100644
--- a/yapftests/reformatter_pep8_test.py
+++ b/yapftests/reformatter_pep8_test.py
@@ -698,6 +698,17 @@ class _():
     reformatted_code = reformatter.Reformat(uwlines)
     self.assertCodeEqual(expected_formatted_code, reformatted_code)
 
+  @unittest.skipUnless(py3compat.PY36, 'Requires Python 3.6')
+  def testSpaceBetweenColonAndElipses(self):
+    style.SetGlobalStyle(style.CreatePEP8Style())
+    code = textwrap.dedent("""\
+      class MyClass(ABC):
+
+          place: ...
+    """)
+    uwlines = yapf_test_helper.ParseAndUnwrap(code)
+    self.assertCodeEqual(code, reformatter.Reformat(uwlines, verify=False))
+
 
 class TestsForSpacesInsideBrackets(yapf_test_helper.YAPFTest):
   """Test the SPACE_INSIDE_BRACKETS style option."""
diff --git a/yapftests/yapf_test.py b/yapftests/yapf_test.py
index e3e3df3..dc0d0a5 100644
--- a/yapftests/yapf_test.py
+++ b/yapftests/yapf_test.py
@@ -881,8 +881,6 @@ x = {
         """)
     expected_formatted_code = textwrap.dedent("""\
         a = line_to_format
-
-
         def f():
             x = y + 42; z = n * 42
             if True: a += 1 ; b += 1 ; c += 1
@@ -905,8 +903,6 @@ x = {
         ''')
     expected_formatted_code = textwrap.dedent('''\
         foo = 42
-
-
         def f():
             email_text += """<html>This is a really long docstring that goes over the column limit and is multi-line.<br><br>
         <b>Czar: </b>"""+despot["Nicholas"]+"""<br>
@@ -1031,6 +1027,7 @@ x = {
             def aaaaaaaaaaaaa(self):
                 pass
 
+
             def bbbbbbbbbbbbb(self):  # 5
                 pass
         """)
@@ -1170,13 +1167,12 @@ x = {
 
   def testPseudoParenSpaces(self):
     unformatted_code = textwrap.dedent("""\
-        def foo():
+        def   foo():
           def bar():
             return {msg_id: author for author, msg_id in reader}
         """)
     expected_formatted_code = textwrap.dedent("""\
         def foo():
-
           def bar():
             return {msg_id: author for author, msg_id in reader}
         """)
@@ -1482,6 +1478,30 @@ CONTINUATION_ALIGN_STYLE = valign-right
         expected_formatted_code,
         extra_options=['--style', 'yapf', '--lines', '1-1'])
 
+  def testDisableWithLinesOption(self):
+    unformatted_code = textwrap.dedent("""\
+        # yapf_lines_bug.py
+        # yapf: disable
+        def outer_func():
+            def inner_func():
+                return
+            return
+        # yapf: enable
+        """)
+    expected_formatted_code = textwrap.dedent("""\
+        # yapf_lines_bug.py
+        # yapf: disable
+        def outer_func():
+            def inner_func():
+                return
+            return
+        # yapf: enable
+        """)
+    self.assertYapfReformats(
+        unformatted_code,
+        expected_formatted_code,
+        extra_options=['--lines', '1-8'])
+
   @unittest.skipUnless(py3compat.PY36, 'Requires Python 3.6')
   def testNoSpacesAroundBinaryOperators(self):
     unformatted_code = """\