Repository: psf/black

Issue:
skip string normalization no longer ignores triple quote strings, like docstrings
**Describe the bug** 

previously running with `--skip-string-normalization` would ignore triple-quoted strings that used single quote character `'`

**To Reproduce** Steps to reproduce the behavior:

1. Take this file '...'

```
def test_function():
    '''this is my docstring'''
    pass
```
2. Run _Black_ on it with these arguments '....'
`--skip-string-normalization`
3. See error
it modifies the triple-quoted strings to double-quote character

do the same steps after installing `19.10.0b`

**Expected behavior** 

skip string normalization should ignore triple quoted strings

**Environment (please complete the following information):**

- Version: 20.8b1
- OS and Python version: linux python3.6

**Does this bug also happen on master?** 
yes

**Additional context** 
n/a



Discussion:
Hello @zdelagrange,

I cannot reproduce the bug:
```console
(black-rHKUX7ap) R:\Programming\black>type temp.py
def test_function():
    '''this is my docstring'''
    pass

(black-rHKUX7ap) R:\Programming\black>black temp.py --diff --color -S
All done! ✨ 🍰 ✨
1 file would be left unchanged.
```

**Environment:**
* Black version: `master` (20f74c20f7efe80f0b0199153934dddd80e21d8e)
* Python version: `CPython 3.8.1`
* OS version: `Windows 10 Home Edition Build 18363`
alrighty, here's a play-by-play of my console

```
black_test ❯ pip freeze | grep black
black==19.10b0

~/src/black_test master*
black_test ❯ cat black.py
def function_one(arg1, arg2, arg3):
    '''
    docstring
    '''
    pass

~/src/black_test master*
black_test ❯ black --skip-string-normalization black.py
All done! ✨ 🍰 ✨
1 file left unchanged.

~/src/black_test master*
black_test ❯ pip install black --upgrade
Collecting black
  Using cached https://files.pythonhosted.org/packages/8a/7c/a6cdd4c69e1377d917e0b9fe44acb8f42d340ac35bcf66020136cd00c62a/black-20.8b1-py3-none-any.whl
Requirement already satisfied, skipping upgrade: typed-ast>=1.4.0 in /Users/zacdelagrange/envs/black_test/lib/python3.6/site-packages (from black) (1.4.1)
Requirement already satisfied, skipping upgrade: pathspec<1,>=0.6 in /Users/zacdelagrange/envs/black_test/lib/python3.6/site-packages (from black) (0.8.0)
Requirement already satisfied, skipping upgrade: appdirs in /Users/zacdelagrange/envs/black_test/lib/python3.6/site-packages (from black) (1.4.4)
Requirement already satisfied, skipping upgrade: click>=7.1.2 in /Users/zacdelagrange/envs/black_test/lib/python3.6/site-packages (from black) (7.1.2)
Requirement already satisfied, skipping upgrade: toml>=0.10.1 in /Users/zacdelagrange/envs/black_test/lib/python3.6/site-packages (from black) (0.10.1)
Requirement already satisfied, skipping upgrade: regex>=2020.1.8 in /Users/zacdelagrange/envs/black_test/lib/python3.6/site-packages (from black) (2020.7.14)
Requirement already satisfied, skipping upgrade: mypy-extensions>=0.4.3 in /Users/zacdelagrange/envs/black_test/lib/python3.6/site-packages (from black) (0.4.3)
Requirement already satisfied, skipping upgrade: dataclasses>=0.6; python_version < "3.7" in /Users/zacdelagrange/envs/black_test/lib/python3.6/site-packages (from black) (0.7)
Requirement already satisfied, skipping upgrade: typing-extensions>=3.7.4 in /Users/zacdelagrange/envs/black_test/lib/python3.6/site-packages (from black) (3.7.4.3)
Installing collected packages: black
  Found existing installation: black 19.10b0
    Uninstalling black-19.10b0:
      Successfully uninstalled black-19.10b0
Successfully installed black-20.8b1
You are using pip version 18.1, however version 20.2.2 is available.
You should consider upgrading via the 'pip install --upgrade pip' command.

~/src/black_test master*
black_test ❯ cat black.py
def function_one(arg1, arg2, arg3):
    '''
    docstring
    '''
    pass

~/src/black_test master*
black_test ❯ black --skip-string-normalization black.py
reformatted black.py
All done! ✨ 🍰 ✨
1 file reformatted.

~/src/black_test master*
black_test ❯ cat black.py
def function_one(arg1, arg2, arg3):
    """
    docstring
    """
    pass

~/src/black_test master*
black_test ❯
```
Oh my that is a surprising bug! I can reproduce using the [same environment as I posted above](https://github.com/psf/black/issues/1634#issuecomment-681085013). 

Git `bisect` points to commit a4c11a75e12300abfbe4c36854e450d42bdd1ee7, from "Re-indent the contents of docstrings" (PR #1053). 

Resolving patch:
diff --git a/src/black/__init__.py b/src/black/__init__.py
--- a/src/black/__init__.py
+++ b/src/black/__init__.py
@@ -2050,7 +2050,6 @@ def visit_STRING(self, leaf: Leaf) -> Iterator[Line]:
                 if leaf.value[tail_len + 1] == docstring[-1]:
                     docstring = docstring + " "
             leaf.value = leaf.value[0:lead_len] + docstring + leaf.value[tail_len:]
-            normalize_string_quotes(leaf)
 
         yield from self.visit_default(leaf)
 


Test patch:
diff --git a/tests/data/docstring_no_string_normalization.py b/tests/data/docstring_no_string_normalization.py
new file mode 100644
--- /dev/null
+++ b/tests/data/docstring_no_string_normalization.py
@@ -0,0 +1,209 @@
+class ALonelyClass:
+    '''
+    A multiline class docstring.
+    '''
+    def AnEquallyLonelyMethod(self):
+        '''
+        A multiline method docstring'''
+        pass
+
+
+def one_function():
+    '''This is a docstring with a single line of text.'''
+    pass
+
+
+def shockingly_the_quotes_are_normalized():
+    '''This is a multiline docstring.
+    This is a multiline docstring.
+    This is a multiline docstring.
+    '''
+    pass
+
+
+def foo():
+  """This is a docstring with             
+  some lines of text here
+  """
+  return
+
+
+def baz():
+  '''"This" is a string with some
+  embedded "quotes"'''
+  return
+
+
+def poit():
+  """
+  Lorem ipsum dolor sit amet.       
+
+  Consectetur adipiscing elit:
+   - sed do eiusmod tempor incididunt ut labore
+   - dolore magna aliqua
+     - enim ad minim veniam
+     - quis nostrud exercitation ullamco laboris nisi
+   - aliquip ex ea commodo consequat
+  """
+  pass
+
+
+def under_indent():
+  """
+  These lines are indented in a way that does not
+make sense.
+  """
+  pass
+
+
+def over_indent():
+  """
+  This has a shallow indent
+    - But some lines are deeper
+    - And the closing quote is too deep
+    """
+  pass
+
+
+def single_line():
+    """But with a newline after it!
+
+    """
+    pass
+
+
+def this():
+    r"""
+    'hey ho'
+    """
+
+
+def that():
+  """ "hey yah" """
+
+
+def and_that():
+  """
+  "hey yah" """
+
+
+def and_this():
+  ''' 
+  "hey yah"'''
+
+
+def believe_it_or_not_this_is_in_the_py_stdlib(): ''' 
+"hey yah"'''
+
+
+def shockingly_the_quotes_are_normalized_v2():
+    '''
+    Docstring Docstring Docstring
+    '''
+    pass
+
+# output
+
+class ALonelyClass:
+    '''
+    A multiline class docstring.
+    '''
+
+    def AnEquallyLonelyMethod(self):
+        '''
+        A multiline method docstring'''
+        pass
+
+
+def one_function():
+    '''This is a docstring with a single line of text.'''
+    pass
+
+
+def shockingly_the_quotes_are_normalized():
+    '''This is a multiline docstring.
+    This is a multiline docstring.
+    This is a multiline docstring.
+    '''
+    pass
+
+
+def foo():
+    """This is a docstring with
+    some lines of text here
+    """
+    return
+
+
+def baz():
+    '''"This" is a string with some
+    embedded "quotes"'''
+    return
+
+
+def poit():
+    """
+    Lorem ipsum dolor sit amet.
+
+    Consectetur adipiscing elit:
+     - sed do eiusmod tempor incididunt ut labore
+     - dolore magna aliqua
+       - enim ad minim veniam
+       - quis nostrud exercitation ullamco laboris nisi
+     - aliquip ex ea commodo consequat
+    """
+    pass
+
+
+def under_indent():
+    """
+      These lines are indented in a way that does not
+    make sense.
+    """
+    pass
+
+
+def over_indent():
+    """
+    This has a shallow indent
+      - But some lines are deeper
+      - And the closing quote is too deep
+    """
+    pass
+
+
+def single_line():
+    """But with a newline after it!"""
+    pass
+
+
+def this():
+    r"""
+    'hey ho'
+    """
+
+
+def that():
+    """ "hey yah" """
+
+
+def and_that():
+    """
+    "hey yah" """
+
+
+def and_this():
+    '''
+    "hey yah"'''
+
+
+def believe_it_or_not_this_is_in_the_py_stdlib():
+    '''
+    "hey yah"'''
+
+
+def shockingly_the_quotes_are_normalized_v2():
+    '''
+    Docstring Docstring Docstring
+    '''
+    pass
diff --git a/tests/test_black.py b/tests/test_black.py
--- a/tests/test_black.py
+++ b/tests/test_black.py
@@ -517,11 +517,16 @@ def test_docstring(self) -> None:
         self.assertFormatEqual(expected, actual)
         black.assert_equivalent(source, actual)
         black.assert_stable(source, actual, DEFAULT_MODE)
+
+    @patch("black.dump_to_file", dump_to_stderr)
+    def test_docstring_no_string_normalization(self) -> None:
+        """Like test_docstring but with string normalization off."""
+        source, expected = read_data("docstring_no_string_normalization")
         mode = replace(DEFAULT_MODE, string_normalization=False)
-        not_normalized = fs(source, mode=mode)
-        self.assertFormatEqual(expected, not_normalized)
-        black.assert_equivalent(source, not_normalized)
-        black.assert_stable(source, not_normalized, mode=mode)
+        actual = fs(source, mode=mode)
+        self.assertFormatEqual(expected, actual)
+        black.assert_equivalent(source, actual)
+        black.assert_stable(source, actual, mode)
 
     def test_long_strings(self) -> None:
         """Tests for splitting long strings."""