From d9e71a75ccfefa3d9156a64c03313a0d4ad981e5 Mon Sep 17 00:00:00 2001
From: "Michael J. Sullivan" <sully@msully.net>
Date: Wed, 2 Oct 2019 18:57:49 -0700
Subject: [PATCH] Don't break long lines when `type: ignore` is present (#1040)

Fixes #997.
---
 black.py                   | 33 +++++++++++++++++++++++++++---
 tests/data/comments6.py    |  4 ++++
 tests/data/comments7.py    | 42 ++++++++++++++++++++++++++++++++++++++
 tests/data/expression.diff | 17 +++++++--------
 tests/data/expression.py   |  4 +---
 5 files changed, 84 insertions(+), 16 deletions(-)

diff --git a/black.py b/black.py
index 957e51a..f283ffc 100644
--- a/black.py
+++ b/black.py
@@ -1325,6 +1325,28 @@ class Line:
 
         return False
 
+    def contains_unsplittable_type_ignore(self) -> bool:
+        if not self.leaves:
+            return False
+
+        # If a 'type: ignore' is attached to the end of a line, we
+        # can't split the line, because we can't know which of the
+        # subexpressions the ignore was meant to apply to.
+        #
+        # We only want this to apply to actual physical lines from the
+        # original source, though: we don't want the presence of a
+        # 'type: ignore' at the end of a multiline expression to
+        # justify pushing it all onto one line. Thus we
+        # (unfortunately) need to check the actual source lines and
+        # only report an unsplittable 'type: ignore' if this line was
+        # one line in the original code.
+        if self.leaves[0].lineno == self.leaves[-1].lineno:
+            for comment in self.comments.get(id(self.leaves[-1]), []):
+                if is_type_comment(comment, " ignore"):
+                    return True
+
+        return False
+
     def contains_multiline_strings(self) -> bool:
         for leaf in self.leaves:
             if is_multiline_string(leaf):
@@ -2332,7 +2354,10 @@ def split_line(
     if (
         not line.contains_uncollapsable_type_comments()
         and not line.should_explode
-        and is_line_short_enough(line, line_length=line_length, line_str=line_str)
+        and (
+            is_line_short_enough(line, line_length=line_length, line_str=line_str)
+            or line.contains_unsplittable_type_ignore()
+        )
     ):
         yield line
         return
@@ -2705,12 +2730,14 @@ def is_import(leaf: Leaf) -> bool:
     )
 
 
-def is_type_comment(leaf: Leaf) -> bool:
+def is_type_comment(leaf: Leaf, suffix: str = "") -> 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 t in {token.COMMENT, t == STANDALONE_COMMENT} and v.startswith("# type:")
+    return t in {token.COMMENT, t == STANDALONE_COMMENT} and v.startswith(
+        "# type:" + suffix
+    )
 
 
 def normalize_prefix(leaf: Leaf, *, inside_brackets: bool) -> None:
diff --git a/tests/data/comments6.py b/tests/data/comments6.py
index 6e34b74..7bc83ae 100644
--- a/tests/data/comments6.py
+++ b/tests/data/comments6.py
@@ -99,5 +99,9 @@ def func(
         a[-1],  # type: ignore
     )
 
+    c = call(
+        "aaaaaaaa", "aaaaaaaa", "aaaaaaaa", "aaaaaaaa", "aaaaaaaa", "aaaaaaaa", "aaaaaaaa"  # type: ignore
+    )
+
 
 result = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"  # aaa
diff --git a/tests/data/comments7.py b/tests/data/comments7.py
index f69863e..088dc99 100644
--- a/tests/data/comments7.py
+++ b/tests/data/comments7.py
@@ -36,6 +36,25 @@ result = (
 
 result = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"  # aaa
 
+
+def func():
+    c = call(
+        0.0123,
+        0.0456,
+        0.0789,
+        0.0123,
+        0.0789,
+        a[-1],  # type: ignore
+    )
+
+    # The type: ignore exception only applies to line length, not
+    # other types of formatting.
+    c = call(
+        "aaaaaaaa", "aaaaaaaa", "aaaaaaaa", "aaaaaaaa", "aaaaaaaa", "aaaaaaaa",  # type: ignore
+        "aaaaaaaa", "aaaaaaaa", "aaaaaaaa", "aaaaaaaa", "aaaaaaaa", "aaaaaaaa"
+    )
+
+
 # output
 
 from .config import (
@@ -71,3 +90,26 @@ result = 1  # look ma, no comment migration xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
 result = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"  # aaa
 
 result = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"  # aaa
+
+
+def func():
+    c = call(
+        0.0123, 0.0456, 0.0789, 0.0123, 0.0789, a[-1]  # type: ignore
+    )
+
+    # The type: ignore exception only applies to line length, not
+    # other types of formatting.
+    c = call(
+        "aaaaaaaa",
+        "aaaaaaaa",
+        "aaaaaaaa",
+        "aaaaaaaa",
+        "aaaaaaaa",
+        "aaaaaaaa",  # type: ignore
+        "aaaaaaaa",
+        "aaaaaaaa",
+        "aaaaaaaa",
+        "aaaaaaaa",
+        "aaaaaaaa",
+        "aaaaaaaa",
+    )
diff --git a/tests/data/expression.diff b/tests/data/expression.diff
index c3ee14d..eff98f9 100644
--- a/tests/data/expression.diff
+++ b/tests/data/expression.diff
@@ -118,7 +118,7 @@
  call(**self.screen_kwargs)
  call(b, **self.screen_kwargs)
  lukasz.langa.pl
-@@ -94,23 +115,25 @@
+@@ -94,23 +115,23 @@
  1.0 .real
  ....__class__
  list[str]
@@ -132,15 +132,12 @@
  xxxx_xxxxx_xxxx_xxx: Callable[..., List[SomeClass]] = classmethod(  # type: ignore
      sync(async_xxxx_xxx_xxxx_xxxxx_xxxx_xxx.__func__)
  )
--xxxx_xxx_xxxx_xxxxx_xxxx_xxx: Callable[..., List[SomeClass]] = classmethod(  # type: ignore
--    sync(async_xxxx_xxx_xxxx_xxxxx_xxxx_xxx.__func__)
--)
- xxxx_xxx_xxxx_xxxxx_xxxx_xxx: Callable[
-     ..., List[SomeClass]
+ xxxx_xxx_xxxx_xxxxx_xxxx_xxx: Callable[..., List[SomeClass]] = classmethod(  # type: ignore
+     sync(async_xxxx_xxx_xxxx_xxxxx_xxxx_xxx.__func__)
+ )
+-xxxx_xxx_xxxx_xxxxx_xxxx_xxx: Callable[
+-    ..., List[SomeClass]
 -] = classmethod(sync(async_xxxx_xxx_xxxx_xxxxx_xxxx_xxx.__func__))  # type: ignore
-+] = classmethod(  # type: ignore
-+    sync(async_xxxx_xxx_xxxx_xxxxx_xxxx_xxx.__func__)
-+)
 +xxxx_xxx_xxxx_xxxxx_xxxx_xxx: Callable[..., List[SomeClass]] = classmethod(
 +    sync(async_xxxx_xxx_xxxx_xxxxx_xxxx_xxx.__func__)
 +)  # type: ignore
@@ -149,7 +146,7 @@
  slice[0:1:2]
  slice[:]
  slice[:-1]
-@@ -134,113 +157,171 @@
+@@ -134,113 +155,171 @@
  numpy[-(c + 1) :, d]
  numpy[:, l[-2]]
  numpy[:, ::-1]
diff --git a/tests/data/expression.py b/tests/data/expression.py
index 912f76d..3851249 100644
--- a/tests/data/expression.py
+++ b/tests/data/expression.py
@@ -374,9 +374,7 @@ very_long_variable_name_filters: t.List[
 xxxx_xxxxx_xxxx_xxx: Callable[..., List[SomeClass]] = classmethod(  # type: ignore
     sync(async_xxxx_xxx_xxxx_xxxxx_xxxx_xxx.__func__)
 )
-xxxx_xxx_xxxx_xxxxx_xxxx_xxx: Callable[
-    ..., List[SomeClass]
-] = classmethod(  # type: ignore
+xxxx_xxx_xxxx_xxxxx_xxxx_xxx: Callable[..., List[SomeClass]] = classmethod(  # type: ignore
     sync(async_xxxx_xxx_xxxx_xxxxx_xxxx_xxx.__func__)
 )
 xxxx_xxx_xxxx_xxxxx_xxxx_xxx: Callable[..., List[SomeClass]] = classmethod(
-- 
2.39.5