Repository: psf/black

Issue:
(🐞) Can't parse single quoted doc string longer than line length
```py
def f():
    "lorem ipsum ddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd         sus"
```
```
> black --version
black, 22.6.0 (compiled: yes)
Python (CPython) 3.10.5                   
> black test.py
error: cannot format ...
```

I have `preview` enabled.




Discussion:
OK, so Black is producing invalid code and it's also being misreported as a parse failure when really the root issue Black moved the ending quote to its own line...
```python
def f():
    "lorem ipsum ddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd         sus
    "
```
I really hope we can get rid of the two-pass hack soon or at least make the error handling a bit better.
Bisected regression to 20d8ccb54253f8a66321f6708d53e2a05a54079b.
Technically we could resolve this by enforcing triple quotes for docstrings so the buggy logic introduced in https://github.com/psf/black/commit/20d8ccb54253f8a66321f6708d53e2a05a54079b doesn't have worry about single quotes breaking things. Funnily enough there's a comment mentioning this possibility:

https://github.com/psf/black/blob/05b63c4bccbfb292a92d3ec962ce9b8fa4ebcfd5/src/black/linegen.py#L330-L331

... but I'll defer the decision to whether we want to enforce triple quotes for docstrings as docstring handling is a touchy area so I'd rather not make decision too quickly. See https://github.com/psf/black/issues/982 for this discussion. 

---

For the time being, I'll fix the code introduced in 20d8ccb54253f8a66321f6708d53e2a05a54079b and maybe improve Black's error handling around here too. 

Resolving patch:
diff --git a/src/black/linegen.py b/src/black/linegen.py
--- a/src/black/linegen.py
+++ b/src/black/linegen.py
@@ -330,13 +330,14 @@ def visit_STRING(self, leaf: Leaf) -> Iterator[Line]:
             # We could enforce triple quotes at this point.
             quote = quote_char * quote_len
 
-            if Preview.long_docstring_quotes_on_newline in self.mode:
+            # It's invalid to put closing single-character quotes on a new line.
+            if Preview.long_docstring_quotes_on_newline in self.mode and quote_len == 3:
                 # We need to find the length of the last line of the docstring
                 # to find if we can add the closing quotes to the line without
                 # exceeding the maximum line length.
                 # If docstring is one line, then we need to add the length
-                # of the indent, prefix, and starting quotes. Ending quote are
-                # handled later
+                # of the indent, prefix, and starting quotes. Ending quotes are
+                # handled later.
                 lines = docstring.splitlines()
                 last_line_length = len(lines[-1]) if docstring else 0
 


Test patch:
diff --git a/tests/data/preview/docstring_preview.py b/tests/data/preview/docstring_preview.py
--- a/tests/data/preview/docstring_preview.py
+++ b/tests/data/preview/docstring_preview.py
@@ -42,6 +42,14 @@ def multiline_docstring_at_line_limit_with_prefix():
     second line----------------------------------------------------------------------"""
 
 
+def single_quote_docstring_over_line_limit():
+    "We do not want to put the closing quote on a new line as that is invalid (see GH-3141)."
+
+
+def single_quote_docstring_over_line_limit2():
+    'We do not want to put the closing quote on a new line as that is invalid (see GH-3141).'
+
+
 # output
 
 
@@ -87,3 +95,11 @@ def multiline_docstring_at_line_limit_with_prefix():
     f"""first line----------------------------------------------------------------------
 
     second line----------------------------------------------------------------------"""
+
+
+def single_quote_docstring_over_line_limit():
+    "We do not want to put the closing quote on a new line as that is invalid (see GH-3141)."
+
+
+def single_quote_docstring_over_line_limit2():
+    "We do not want to put the closing quote on a new line as that is invalid (see GH-3141)."