Repository: ambv/black
Pull request #642 (2018-12-15 08:10:25)

Add support for special comments in multiline functions
This is a starter diff to deal with #282, and I'm happy to iterate on it.

We want to ensure that type comments (https://www.python.org/dev/peps/pep-0484/) for Python 2 are formatted in a valid way, so that we can use Black in our organization.

coveralls: ## Pull Request Test Coverage Report for [Build 839](https://coveralls.io/builds/20650521)

* **17** of **17**   **(100.0%)**  changed or added relevant lines in **2** files are covered.
* No unchanged relevant lines lost coverage.
* Overall coverage increased (+**0.04%**) to **91.658%**

---



|  Totals | [![Coverage Status](https://coveralls.io/builds/20650521/badge)](https://coveralls.io/builds/20650521) |
| :-- | --: |
| Change from base [Build 838](https://coveralls.io/builds/20638476): |  0.04% |
| Covered Lines: | 2791 |
| Relevant Lines: | 3045 |

---
##### 💛  - [Coveralls](https://coveralls.io)

coveralls: ## Pull Request Test Coverage Report for [Build 839](https://coveralls.io/builds/20650521)

* **17** of **17**   **(100.0%)**  changed or added relevant lines in **2** files are covered.
* No unchanged relevant lines lost coverage.
* Overall coverage increased (+**0.04%**) to **91.658%**

---



|  Totals | [![Coverage Status](https://coveralls.io/builds/20650521/badge)](https://coveralls.io/builds/20650521) |
| :-- | --: |
| Change from base [Build 838](https://coveralls.io/builds/20638476): |  0.04% |
| Covered Lines: | 2791 |
| Relevant Lines: | 3045 |

---
##### 💛  - [Coveralls](https://coveralls.io)

coveralls: ## Pull Request Test Coverage Report for [Build 839](https://coveralls.io/builds/20650521)

* **17** of **17**   **(100.0%)**  changed or added relevant lines in **2** files are covered.
* No unchanged relevant lines lost coverage.
* Overall coverage increased (+**0.04%**) to **91.658%**

---



|  Totals | [![Coverage Status](https://coveralls.io/builds/20650521/badge)](https://coveralls.io/builds/20650521) |
| :-- | --: |
| Change from base [Build 838](https://coveralls.io/builds/20638476): |  0.04% |
| Covered Lines: | 2791 |
| Relevant Lines: | 3045 |

---
##### 💛  - [Coveralls](https://coveralls.io)


Patch:
diff --git a/README.md b/README.md
index 7be8093..c548ce4 100644
--- a/README.md
+++ b/README.md
@@ -1346,7 +1346,7 @@ Multiple contributions by:
 * [Luka Sterbic](mailto:luka.sterbic@gmail.com)
 * [Miguel Gaiowski](mailto:miggaiowski@gmail.com)
 * [Miroslav Shubernetskiy](mailto:miroslav@miki725.com)
-* [Neraste](neraste.herr10@gmail.com)
+* [Neraste](mailto:neraste.herr10@gmail.com)
 * [Osaetin Daniel](mailto:osaetindaniel@gmail.com)
 * [Peter Bengtsson](mailto:mail@peterbe.com)
 * [Stavros Korokithakis](mailto:hi@stavros.io)
diff --git a/black.py b/black.py
index 8855c37..fb8e474 100644
--- a/black.py
+++ b/black.py
@@ -160,7 +160,9 @@ def read_pyproject_toml(
         pyproject_toml = toml.load(value)
         config = pyproject_toml.get("tool", {}).get("black", {})
     except (toml.TomlDecodeError, OSError) as e:
-        raise click.BadOptionUsage(f"Error reading configuration file: {e}", ctx)
+        raise click.FileError(
+            filename=value, hint=f"Error reading configuration file: {e}"
+        )
 
     if not config:
         return None
@@ -2110,8 +2112,19 @@ def split_line(
         return
 
     line_str = str(line).strip("\n")
-    if not line.should_explode and is_line_short_enough(
-        line, line_length=line_length, line_str=line_str
+
+    # we don't want to split special comments like type annotations
+    # https://github.com/python/typing/issues/186
+    has_special_comment = False
+    for leaf in line.leaves:
+        for comment in line.comments_after(leaf):
+            if leaf.type == token.COMMA and is_special_comment(comment):
+                has_special_comment = True
+
+    if (
+        not has_special_comment
+        and not line.should_explode
+        and is_line_short_enough(line, line_length=line_length, line_str=line_str)
     ):
         yield line
         return
@@ -2460,6 +2473,16 @@ def is_import(leaf: Leaf) -> bool:
     )
 
 
+def is_special_comment(leaf: Leaf) -> bool:
+    """Return True if the given leaf is a special comment.
+    Only returns true for type comments for now."""
+    t = leaf.type
+    v = leaf.value
+    return bool(
+        (t == token.COMMENT or t == STANDALONE_COMMENT) and (v.startswith("# type:"))
+    )
+
+
 def normalize_prefix(leaf: Leaf, *, inside_brackets: bool) -> None:
     """Leave existing extra newlines if not `inside_brackets`. Remove everything
     else.
@@ -2949,6 +2972,7 @@ def ensure_visible(leaf: Leaf) -> None:
 
 def should_explode(line: Line, opening_bracket: Leaf) -> bool:
     """Should `line` immediately be split with `delimiter_split()` after RHS?"""
+
     if not (
         opening_bracket.parent
         and opening_bracket.parent.type in {syms.atom, syms.import_from}


Test patch:
diff --git a/tests/data/comments6.py b/tests/data/comments6.py
new file mode 100644
index 0000000..770e3cc
--- /dev/null
+++ b/tests/data/comments6.py
@@ -0,0 +1,68 @@
+from typing import Any, Tuple
+
+
+def f(
+    a,  # type: int
+):
+    pass
+
+
+# test type comments
+def f(a, b, c, d, e, f, g, h, i):
+    # type: (int, int, int, int, int, int, int, int, int) -> None
+    pass
+
+
+def f(
+    a,  # type: int
+    b,  # type: int
+    c,  # type: int
+    d,  # type: int
+    e,  # type: int
+    f,  # type: int
+    g,  # type: int
+    h,  # type: int
+    i,  # type: int
+):
+    # type: (...) -> None
+    pass
+
+
+def f(
+    arg,  # type: int
+    *args,  # type: *Any
+    default=False,  # type: bool
+    **kwargs,  # type: **Any
+):
+    # type: (...) -> None
+    pass
+
+
+def f(
+    a,  # type: int
+    b,  # type: int
+    c,  # type: int
+    d,  # type: int
+):
+    # type: (...) -> None
+
+    element = 0  # type: int
+    another_element = 1  # type: float
+    another_element_with_long_name = 2  # type: int
+    another_really_really_long_element_with_a_unnecessarily_long_name_to_describe_what_it_does_enterprise_style = (
+        3
+    )  # type: int
+
+    tup = (
+        another_element,  # type: int
+        another_really_really_long_element_with_a_unnecessarily_long_name_to_describe_what_it_does_enterprise_style,  # type: int
+    )  # type: Tuple[int, int]
+
+    a = (
+        element
+        + another_element
+        + another_element_with_long_name
+        + element
+        + another_element
+        + another_element_with_long_name
+    )  # type: int
diff --git a/tests/test_black.py b/tests/test_black.py
index 1d759f2..078e8d8 100644
--- a/tests/test_black.py
+++ b/tests/test_black.py
@@ -362,6 +362,14 @@ def test_comments5(self) -> None:
         black.assert_equivalent(source, actual)
         black.assert_stable(source, actual, line_length=ll)
 
+    @patch("black.dump_to_file", dump_to_stderr)
+    def test_comments6(self) -> None:
+        source, expected = read_data("comments6")
+        actual = fs(source)
+        self.assertFormatEqual(expected, actual)
+        black.assert_equivalent(source, actual)
+        black.assert_stable(source, actual, line_length=ll)
+
     @patch("black.dump_to_file", dump_to_stderr)
     def test_cantfit(self) -> None:
         source, expected = read_data("cantfit")