diff --git a/src/black/__init__.py b/src/black/__init__.py index a7e89ccba61..4eef16903c7 100644 --- a/src/black/__init__.py +++ b/src/black/__init__.py @@ -2083,6 +2083,15 @@ def visit_STRING(self, leaf: Leaf) -> Iterator[Line]: ) and is_multiline_string(leaf): prefix = " " * self.current_line.depth docstring = fix_docstring(leaf.value[3:-3], prefix) + quote_char = leaf.value[0] + if len(docstring) > 0 and docstring[-1] == quote_char: + # This may have trimmed trailing whitespace, which may + # have produced four quotes in a row. Escape the last + # quote -- if it isn't already, by being preceded by + # an odd number of backslashes + docstring = re.compile(rf"(([^\\]|^)(\\\\)*){quote_char}$").sub( + rf"\1\\{quote_char}", docstring + ) leaf.value = leaf.value[0:3] + docstring + leaf.value[-3:] normalize_string_quotes(leaf) diff --git a/tests/data/docstring.py b/tests/data/docstring.py index f5adeb7bb7b..c88733ae980 100644 --- a/tests/data/docstring.py +++ b/tests/data/docstring.py @@ -73,6 +73,38 @@ def single_line(): """ pass + +def containing_quotes(): + """No quotes here + + "quotes here" """ + pass + + +def containing_unbalanced_quotes(): + """No quotes here + + quote here" """ + pass + + +def entirely_space(): + """ + + """ + pass + + +def just_quote(): + """ " """ + pass + + +def escaped_already(): + """ + foo\"""" + pass + # output class MyClass: @@ -148,3 +180,33 @@ def over_indent(): def single_line(): """But with a newline after it!""" pass + + +def containing_quotes(): + """No quotes here + + "quotes here\"""" + pass + + +def containing_unbalanced_quotes(): + """No quotes here + + quote here\"""" + pass + + +def entirely_space(): + """""" + pass + + +def just_quote(): + """ " """ + pass + + +def escaped_already(): + """ + foo\"""" + pass