]> git.madduck.net Git - etc/vim.git/commitdiff

madduck's git repository

Every one of the projects in this repository is available at the canonical URL git://git.madduck.net/madduck/pub/<projectpath> — see each project's metadata for the exact URL.

All patches and comments are welcome. Please squash your changes to logical commits before using git-format-patch and git-send-email to patches@git.madduck.net. If you'd read over the Git project's submission guidelines and adhered to them, I'd be especially grateful.

SSH access, as well as push access can be individually arranged.

If you use my repositories frequently, consider adding the following snippet to ~/.gitconfig and using the third clone URL listed for each project:

[url "git://git.madduck.net/madduck/"]
  insteadOf = madduck:

Handle unnecessarily escaped strings (#128)
authorZsolt Dollenstein <zsol.zsol@gmail.com>
Fri, 13 Apr 2018 18:31:23 +0000 (19:31 +0100)
committerŁukasz Langa <lukasz@langa.pl>
Fri, 13 Apr 2018 18:31:23 +0000 (11:31 -0700)
README.md
black.py
docs/reference/reference_functions.rst
tests/string_quotes.py

index 65eda2accff3fd7ac27cb8b0a5d4e303898e381d..cff4bdf59dbae0f0080890cc8a42ff2c48231bd3 100644 (file)
--- a/README.md
+++ b/README.md
@@ -499,6 +499,9 @@ More details can be found in [CONTRIBUTING](CONTRIBUTING.md).
 
 * Vim plugin now works on Windows, too
 
 
 * Vim plugin now works on Windows, too
 
+* fixed unstable formatting when encountering unnecessarily escaped quotes
+  in a string (#120)
+
 
 ### 18.4a1
 
 
 ### 18.4a1
 
index 587d9b35acc48642a9981423e6a81a10bddc3c37..ccc1e942cc8ae40071ed89c3533d8269c4c4436b 100644 (file)
--- a/black.py
+++ b/black.py
@@ -24,6 +24,7 @@ from typing import (
     Iterator,
     List,
     Optional,
     Iterator,
     List,
     Optional,
+    Pattern,
     Set,
     Tuple,
     Type,
     Set,
     Tuple,
     Type,
@@ -1984,9 +1985,10 @@ def normalize_string_quotes(leaf: Leaf) -> None:
         return  # There's an internal error
 
     prefix = leaf.value[:first_quote_pos]
         return  # There's an internal error
 
     prefix = leaf.value[:first_quote_pos]
-    body = leaf.value[first_quote_pos + len(orig_quote):-len(orig_quote)]
     unescaped_new_quote = re.compile(rf"(([^\\]|^)(\\\\)*){new_quote}")
     unescaped_new_quote = re.compile(rf"(([^\\]|^)(\\\\)*){new_quote}")
-    escaped_orig_quote = re.compile(rf"\\(\\\\)*{orig_quote}")
+    escaped_new_quote = re.compile(rf"([^\\]|^)\\(\\\\)*{new_quote}")
+    escaped_orig_quote = re.compile(rf"([^\\]|^)\\(\\\\)*{orig_quote}")
+    body = leaf.value[first_quote_pos + len(orig_quote):-len(orig_quote)]
     if "r" in prefix.casefold():
         if unescaped_new_quote.search(body):
             # There's at least one unescaped new_quote in this raw string
     if "r" in prefix.casefold():
         if unescaped_new_quote.search(body):
             # There's at least one unescaped new_quote in this raw string
@@ -1996,11 +1998,14 @@ def normalize_string_quotes(leaf: Leaf) -> None:
         # Do not introduce or remove backslashes in raw strings
         new_body = body
     else:
         # Do not introduce or remove backslashes in raw strings
         new_body = body
     else:
-        new_body = escaped_orig_quote.sub(rf"\1{orig_quote}", body)
-        new_body = unescaped_new_quote.sub(rf"\1\\{new_quote}", new_body)
-        # Add escapes again for consecutive occurences of new_quote (sub
-        # doesn't match overlapping substrings).
-        new_body = unescaped_new_quote.sub(rf"\1\\{new_quote}", new_body)
+        # remove unnecessary quotes
+        new_body = sub_twice(escaped_new_quote, rf"\1\2{new_quote}", body)
+        if body != new_body:
+            # Consider the string without unnecessary quotes as the original
+            body = new_body
+            leaf.value = f"{prefix}{orig_quote}{body}{orig_quote}"
+        new_body = sub_twice(escaped_orig_quote, rf"\1\2{orig_quote}", new_body)
+        new_body = sub_twice(unescaped_new_quote, rf"\1\\{new_quote}", new_body)
     if new_quote == '"""' and new_body[-1] == '"':
         # edge case:
         new_body = new_body[:-1] + '\\"'
     if new_quote == '"""' and new_body[-1] == '"':
         # edge case:
         new_body = new_body[:-1] + '\\"'
@@ -2374,5 +2379,14 @@ def shutdown(loop: BaseEventLoop) -> None:
         loop.close()
 
 
         loop.close()
 
 
+def sub_twice(regex: Pattern[str], replacement: str, original: str) -> str:
+    """Replace `regex` with `replacement` twice on `original`.
+
+    This is used by string normalization to perform replaces on
+    overlapping matches.
+    """
+    return regex.sub(replacement, regex.sub(replacement, original))
+
+
 if __name__ == "__main__":
     main()
 if __name__ == "__main__":
     main()
index 2b8c08e0042a94505b9822a2b9314fef7bcc10a5..19128ba7098bb2226d81a4e7026b41318a6150cf 100644 (file)
@@ -91,4 +91,6 @@ Utilities
 
 .. autofunction:: black.preceding_leaf
 
 
 .. autofunction:: black.preceding_leaf
 
+.. autofunction:: black.sub_twice
+
 .. autofunction:: black.whitespace
 .. autofunction:: black.whitespace
index 8ccd041b1aac5ff9f569d5d5dcdc1177bc7dc9ec..1ac6b06a56e4249b79c00c98a7e060a6f08bafb9 100644 (file)
@@ -38,6 +38,10 @@ re.compile(r'[\\"]')
 "x = '''; y = \"\"\"\""
 "x = ''''; y = \"\"\"\"\""
 "x = '' ''; y = \"\"\"\"\""
 "x = '''; y = \"\"\"\""
 "x = ''''; y = \"\"\"\"\""
 "x = '' ''; y = \"\"\"\"\""
+'unnecessary \"\"escaping'
+"unnecessary \'\'escaping"
+'\\""'
+"\\''"
 
 # output
 
 
 # output
 
@@ -81,3 +85,7 @@ re.compile(r'[\\"]')
 'x = \'\'\'; y = """"'
 'x = \'\'\'\'; y = """""'
 'x = \'\' \'\'; y = """""'
 'x = \'\'\'; y = """"'
 'x = \'\'\'\'; y = """""'
 'x = \'\' \'\'; y = """""'
+'unnecessary ""escaping'
+"unnecessary ''escaping"
+'\\""'
+"\\''"