Repository: psf/black

Issue:
Black removes leading and trailing spaces in multiline docstrings but fails to remove them from one-line docstrings
With the file `test.py` containing
```
def foo(a):
    """ This is fine. """

    return a + 1


def bar(a):
    """As is this."""

    return a + 1


def baz(a):
    """ This is not.

    Since it uses more than one line. """

    return a + 1
```
Running `black --diff test.py` results in a diff of:
```
@@ -9,10 +9,10 @@
 
     return a + 1
 
 
 def baz(a):
-    """ This is not.
+    """This is not.
 
-    Since it uses more than one line. """
+    Since it uses more than one line."""
 
     return a + 1
```

**Expected behavior**

Black should also complain that the docstring of foo contains a leading / trailing space and remove these. Note that all one-line examples in PEP 257 https://www.python.org/dev/peps/pep-0257/#one-line-docstrings have no leading / trailing spaces.

**Environment (please complete the following information):**

- Version: master
- OS: Linux (Ubuntu 20.04)
- Python Version: Python 3.8.2

**Does this bug also happen on master?**

This also happens on master.
Black removes leading and trailing spaces in multiline docstrings but fails to remove them from one-line docstrings
With the file `test.py` containing
```
def foo(a):
    """ This is fine. """

    return a + 1


def bar(a):
    """As is this."""

    return a + 1


def baz(a):
    """ This is not.

    Since it uses more than one line. """

    return a + 1
```
Running `black --diff test.py` results in a diff of:
```
@@ -9,10 +9,10 @@
 
     return a + 1
 
 
 def baz(a):
-    """ This is not.
+    """This is not.
 
-    Since it uses more than one line. """
+    Since it uses more than one line."""
 
     return a + 1
```

**Expected behavior**

Black should also complain that the docstring of foo contains a leading / trailing space and remove these. Note that all one-line examples in PEP 257 https://www.python.org/dev/peps/pep-0257/#one-line-docstrings have no leading / trailing spaces.

**Environment (please complete the following information):**

- Version: master
- OS: Linux (Ubuntu 20.04)
- Python Version: Python 3.8.2

**Does this bug also happen on master?**

This also happens on master.


Discussion:

Resolving patch:
diff --git a/src/black/__init__.py b/src/black/__init__.py
--- a/src/black/__init__.py
+++ b/src/black/__init__.py
@@ -2153,16 +2153,35 @@ def visit_STRING(self, leaf: Leaf) -> Iterator[Line]:
             # We're ignoring docstrings with backslash newline escapes because changing
             # indentation of those changes the AST representation of the code.
             prefix = get_string_prefix(leaf.value)
-            lead_len = len(prefix) + 3
-            tail_len = -3
-            indent = " " * 4 * self.current_line.depth
-            docstring = fix_docstring(leaf.value[lead_len:tail_len], indent)
+            docstring = leaf.value[len(prefix) :]  # Remove the prefix
+            quote_char = docstring[0]
+            # A natural way to remove the outer quotes is to do:
+            #   docstring = docstring.strip(quote_char)
+            # but that breaks on """""x""" (which is '""x').
+            # So we actually need to remove the first character and the next two
+            # characters but only if they are the same as the first.
+            quote_len = 1 if docstring[1] != quote_char else 3
+            docstring = docstring[quote_len:-quote_len]
+
+            if is_multiline_string(leaf):
+                indent = " " * 4 * self.current_line.depth
+                docstring = fix_docstring(docstring, indent)
+            else:
+                docstring = docstring.strip()
+
             if docstring:
-                if leaf.value[lead_len - 1] == docstring[0]:
+                # Add some padding if the docstring starts / ends with a quote mark.
+                if docstring[0] == quote_char:
                     docstring = " " + docstring
-                if leaf.value[tail_len + 1] == docstring[-1]:
+                if docstring[-1] == quote_char:
                     docstring = docstring + " "
-            leaf.value = leaf.value[0:lead_len] + docstring + leaf.value[tail_len:]
+            else:
+                # Add some padding if the docstring is empty.
+                docstring = " "
+
+            # We could enforce triple quotes at this point.
+            quote = quote_char * quote_len
+            leaf.value = prefix + quote + docstring + quote
 
         yield from self.visit_default(leaf)
 
@@ -6113,7 +6132,7 @@ def get_imports_from_children(children: List[LN]) -> Generator[str, None, None]:
 
 @lru_cache()
 def get_gitignore(root: Path) -> PathSpec:
-    """ Return a PathSpec matching gitignore content if present."""
+    """Return a PathSpec matching gitignore content if present."""
     gitignore = root / ".gitignore"
     lines: List[str] = []
     if gitignore.is_file():
@@ -6953,11 +6972,6 @@ def patched_main() -> None:
 
 
 def is_docstring(leaf: Leaf) -> bool:
-    if not is_multiline_string(leaf):
-        # For the purposes of docstring re-indentation, we don't need to do anything
-        # with single-line docstrings.
-        return False
-
     if prev_siblings_are(
         leaf.parent, [None, token.NEWLINE, token.INDENT, syms.simple_stmt]
     ):


Test patch:
diff --git a/tests/data/docstring.py b/tests/data/docstring.py
--- a/tests/data/docstring.py
+++ b/tests/data/docstring.py
@@ -102,6 +102,23 @@ def and_this():
   "hey yah"'''
 
 
+def empty():
+    '''
+    
+    
+    
+    
+    '''
+
+
+def oneline_empty():
+    '''      '''
+
+
+def single_quotes():
+    'testing'
+
+
 def believe_it_or_not_this_is_in_the_py_stdlib(): ''' 
 "hey yah"'''
 
@@ -110,6 +127,8 @@ def ignored_docstring():
     """a => \
 b"""  
 
+def single_line_docstring_with_whitespace():
+    """   This should be stripped """
 
 def docstring_with_inline_tabs_and_space_indentation():
     """hey
@@ -134,7 +153,6 @@ def docstring_with_inline_tabs_and_tab_indentation():
 	line ends with some tabs		
 	"""
 	pass
-        
 
 # output
 
@@ -241,6 +259,18 @@ def and_this():
     "hey yah"'''
 
 
+def empty():
+    """ """
+
+
+def oneline_empty():
+    """ """
+
+
+def single_quotes():
+    "testing"
+
+
 def believe_it_or_not_this_is_in_the_py_stdlib():
     '''
     "hey yah"'''
@@ -251,6 +281,10 @@ def ignored_docstring():
 b"""
 
 
+def single_line_docstring_with_whitespace():
+    """This should be stripped"""
+
+
 def docstring_with_inline_tabs_and_space_indentation():
     """hey