We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
SyntaxWarning on regexp on first run of black
When running `black` on the following code:

```
text = re.sub(
    "([_a-zA-Z0-9-+]+)(\.[_a-zA-Z0-9-+]+)*"
    "@([a-zA-Z0-9-]+)(\.[a-zA-Z0-9-]+)*(\.[a-zA-Z]{2,4})",
    '<a href="mailto:\g<0>">\g<0></a>',
    text,
)
text = re.sub(
    "(ftp|http|https):\/\/(\w+:{0,1}\w*@)?"
    "(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?",
    '<a href="\g<0>">\g<0></a>',
    text,
)
```

I get the following warnings (written twice):

```
<unknown>:2: SyntaxWarning: invalid escape sequence '\.'
<unknown>:3: SyntaxWarning: invalid escape sequence '\.'
<unknown>:4: SyntaxWarning: invalid escape sequence '\g'
<unknown>:8: SyntaxWarning: invalid escape sequence '\/'
<unknown>:9: SyntaxWarning: invalid escape sequence '\S'
<unknown>:10: SyntaxWarning: invalid escape sequence '\g'
<unknown>:2: SyntaxWarning: invalid escape sequence '\.'
<unknown>:3: SyntaxWarning: invalid escape sequence '\.'
<unknown>:4: SyntaxWarning: invalid escape sequence '\g'
<unknown>:8: SyntaxWarning: invalid escape sequence '\/'
<unknown>:9: SyntaxWarning: invalid escape sequence '\S'
<unknown>:10: SyntaxWarning: invalid escape sequence '\g'
```

When re-running `black` on the same file, the warnings are not shown again.  I have to modify the lines (adding a space for instance) to see the warnings again.

Are these warnings normal?  (The syntax is normally correct according to the documentation of the `re` module.)
If they are normal, should they really appear twice in the output?  And why don't they appear again when running a second time `black`?

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `src/black/parsing.py`
Content:
```
1 """
2 Parse Python code and perform AST validation.
3 """
4 
5 import ast
6 import sys
7 from typing import Iterable, Iterator, List, Set, Tuple
8 
9 from black.mode import VERSION_TO_FEATURES, Feature, TargetVersion, supports_feature
10 from black.nodes import syms
11 from blib2to3 import pygram
12 from blib2to3.pgen2 import driver
13 from blib2to3.pgen2.grammar import Grammar
14 from blib2to3.pgen2.parse import ParseError
15 from blib2to3.pgen2.tokenize import TokenError
16 from blib2to3.pytree import Leaf, Node
17 
18 
19 class InvalidInput(ValueError):
20     """Raised when input source code fails all parse attempts."""
21 
22 
23 def get_grammars(target_versions: Set[TargetVersion]) -> List[Grammar]:
24     if not target_versions:
25         # No target_version specified, so try all grammars.
26         return [
27             # Python 3.7-3.9
28             pygram.python_grammar_async_keywords,
29             # Python 3.0-3.6
30             pygram.python_grammar,
31             # Python 3.10+
32             pygram.python_grammar_soft_keywords,
33         ]
34 
35     grammars = []
36     # If we have to parse both, try to parse async as a keyword first
37     if not supports_feature(
38         target_versions, Feature.ASYNC_IDENTIFIERS
39     ) and not supports_feature(target_versions, Feature.PATTERN_MATCHING):
40         # Python 3.7-3.9
41         grammars.append(pygram.python_grammar_async_keywords)
42     if not supports_feature(target_versions, Feature.ASYNC_KEYWORDS):
43         # Python 3.0-3.6
44         grammars.append(pygram.python_grammar)
45     if any(Feature.PATTERN_MATCHING in VERSION_TO_FEATURES[v] for v in target_versions):
46         # Python 3.10+
47         grammars.append(pygram.python_grammar_soft_keywords)
48 
49     # At least one of the above branches must have been taken, because every Python
50     # version has exactly one of the two 'ASYNC_*' flags
51     return grammars
52 
53 
54 def lib2to3_parse(src_txt: str, target_versions: Iterable[TargetVersion] = ()) -> Node:
55     """Given a string with source, return the lib2to3 Node."""
56     if not src_txt.endswith("\n"):
57         src_txt += "\n"
58 
59     grammars = get_grammars(set(target_versions))
60     errors = {}
61     for grammar in grammars:
62         drv = driver.Driver(grammar)
63         try:
64             result = drv.parse_string(src_txt, True)
65             break
66 
67         except ParseError as pe:
68             lineno, column = pe.context[1]
69             lines = src_txt.splitlines()
70             try:
71                 faulty_line = lines[lineno - 1]
72             except IndexError:
73                 faulty_line = "<line number missing in source>"
74             errors[grammar.version] = InvalidInput(
75                 f"Cannot parse: {lineno}:{column}: {faulty_line}"
76             )
77 
78         except TokenError as te:
79             # In edge cases these are raised; and typically don't have a "faulty_line".
80             lineno, column = te.args[1]
81             errors[grammar.version] = InvalidInput(
82                 f"Cannot parse: {lineno}:{column}: {te.args[0]}"
83             )
84 
85     else:
86         # Choose the latest version when raising the actual parsing error.
87         assert len(errors) >= 1
88         exc = errors[max(errors)]
89         raise exc from None
90 
91     if isinstance(result, Leaf):
92         result = Node(syms.file_input, [result])
93     return result
94 
95 
96 def matches_grammar(src_txt: str, grammar: Grammar) -> bool:
97     drv = driver.Driver(grammar)
98     try:
99         drv.parse_string(src_txt, True)
100     except (ParseError, TokenError, IndentationError):
101         return False
102     else:
103         return True
104 
105 
106 def lib2to3_unparse(node: Node) -> str:
107     """Given a lib2to3 node, return its string representation."""
108     code = str(node)
109     return code
110 
111 
112 def parse_single_version(
113     src: str, version: Tuple[int, int], *, type_comments: bool
114 ) -> ast.AST:
115     filename = "<unknown>"
116     return ast.parse(
117         src, filename, feature_version=version, type_comments=type_comments
118     )
119 
120 
121 def parse_ast(src: str) -> ast.AST:
122     # TODO: support Python 4+ ;)
123     versions = [(3, minor) for minor in range(3, sys.version_info[1] + 1)]
124 
125     first_error = ""
126     for version in sorted(versions, reverse=True):
127         try:
128             return parse_single_version(src, version, type_comments=True)
129         except SyntaxError as e:
130             if not first_error:
131                 first_error = str(e)
132 
133     # Try to parse without type comments
134     for version in sorted(versions, reverse=True):
135         try:
136             return parse_single_version(src, version, type_comments=False)
137         except SyntaxError:
138             pass
139 
140     raise SyntaxError(first_error)
141 
142 
143 def _normalize(lineend: str, value: str) -> str:
144     # To normalize, we strip any leading and trailing space from
145     # each line...
146     stripped: List[str] = [i.strip() for i in value.splitlines()]
147     normalized = lineend.join(stripped)
148     # ...and remove any blank lines at the beginning and end of
149     # the whole string
150     return normalized.strip()
151 
152 
153 def stringify_ast(node: ast.AST, depth: int = 0) -> Iterator[str]:
154     """Simple visitor generating strings to compare ASTs by content."""
155 
156     if (
157         isinstance(node, ast.Constant)
158         and isinstance(node.value, str)
159         and node.kind == "u"
160     ):
161         # It's a quirk of history that we strip the u prefix over here. We used to
162         # rewrite the AST nodes for Python version compatibility and we never copied
163         # over the kind
164         node.kind = None
165 
166     yield f"{'  ' * depth}{node.__class__.__name__}("
167 
168     for field in sorted(node._fields):  # noqa: F402
169         # TypeIgnore has only one field 'lineno' which breaks this comparison
170         if isinstance(node, ast.TypeIgnore):
171             break
172 
173         try:
174             value: object = getattr(node, field)
175         except AttributeError:
176             continue
177 
178         yield f"{'  ' * (depth + 1)}{field}="
179 
180         if isinstance(value, list):
181             for item in value:
182                 # Ignore nested tuples within del statements, because we may insert
183                 # parentheses and they change the AST.
184                 if (
185                     field == "targets"
186                     and isinstance(node, ast.Delete)
187                     and isinstance(item, ast.Tuple)
188                 ):
189                     for elt in item.elts:
190                         yield from stringify_ast(elt, depth + 2)
191 
192                 elif isinstance(item, ast.AST):
193                     yield from stringify_ast(item, depth + 2)
194 
195         elif isinstance(value, ast.AST):
196             yield from stringify_ast(value, depth + 2)
197 
198         else:
199             normalized: object
200             if (
201                 isinstance(node, ast.Constant)
202                 and field == "value"
203                 and isinstance(value, str)
204             ):
205                 # Constant strings may be indented across newlines, if they are
206                 # docstrings; fold spaces after newlines when comparing. Similarly,
207                 # trailing and leading space may be removed.
208                 normalized = _normalize("\n", value)
209             elif field == "type_comment" and isinstance(value, str):
210                 # Trailing whitespace in type comments is removed.
211                 normalized = value.rstrip()
212             else:
213                 normalized = value
214             yield f"{'  ' * (depth + 2)}{normalized!r},  # {value.__class__.__name__}"
215 
216     yield f"{'  ' * depth})  # /{node.__class__.__name__}"
217
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/src/black/parsing.py b/src/black/parsing.py
--- a/src/black/parsing.py
+++ b/src/black/parsing.py
@@ -4,6 +4,7 @@
 
 import ast
 import sys
+import warnings
 from typing import Iterable, Iterator, List, Set, Tuple
 
 from black.mode import VERSION_TO_FEATURES, Feature, TargetVersion, supports_feature
@@ -109,13 +110,16 @@
     return code
 
 
-def parse_single_version(
+def _parse_single_version(
     src: str, version: Tuple[int, int], *, type_comments: bool
 ) -> ast.AST:
     filename = "<unknown>"
-    return ast.parse(
-        src, filename, feature_version=version, type_comments=type_comments
-    )
+    with warnings.catch_warnings():
+        warnings.simplefilter("ignore", SyntaxWarning)
+        warnings.simplefilter("ignore", DeprecationWarning)
+        return ast.parse(
+            src, filename, feature_version=version, type_comments=type_comments
+        )
 
 
 def parse_ast(src: str) -> ast.AST:
@@ -125,7 +129,7 @@
     first_error = ""
     for version in sorted(versions, reverse=True):
         try:
-            return parse_single_version(src, version, type_comments=True)
+            return _parse_single_version(src, version, type_comments=True)
         except SyntaxError as e:
             if not first_error:
                 first_error = str(e)
@@ -133,7 +137,7 @@
     # Try to parse without type comments
     for version in sorted(versions, reverse=True):
         try:
-            return parse_single_version(src, version, type_comments=False)
+            return _parse_single_version(src, version, type_comments=False)
         except SyntaxError:
             pass

{"golden_diff": "diff --git a/src/black/parsing.py b/src/black/parsing.py\n--- a/src/black/parsing.py\n+++ b/src/black/parsing.py\n@@ -4,6 +4,7 @@\n \n import ast\n import sys\n+import warnings\n from typing import Iterable, Iterator, List, Set, Tuple\n \n from black.mode import VERSION_TO_FEATURES, Feature, TargetVersion, supports_feature\n@@ -109,13 +110,16 @@\n     return code\n \n \n-def parse_single_version(\n+def _parse_single_version(\n     src: str, version: Tuple[int, int], *, type_comments: bool\n ) -> ast.AST:\n     filename = \"<unknown>\"\n-    return ast.parse(\n-        src, filename, feature_version=version, type_comments=type_comments\n-    )\n+    with warnings.catch_warnings():\n+        warnings.simplefilter(\"ignore\", SyntaxWarning)\n+        warnings.simplefilter(\"ignore\", DeprecationWarning)\n+        return ast.parse(\n+            src, filename, feature_version=version, type_comments=type_comments\n+        )\n \n \n def parse_ast(src: str) -> ast.AST:\n@@ -125,7 +129,7 @@\n     first_error = \"\"\n     for version in sorted(versions, reverse=True):\n         try:\n-            return parse_single_version(src, version, type_comments=True)\n+            return _parse_single_version(src, version, type_comments=True)\n         except SyntaxError as e:\n             if not first_error:\n                 first_error = str(e)\n@@ -133,7 +137,7 @@\n     # Try to parse without type comments\n     for version in sorted(versions, reverse=True):\n         try:\n-            return parse_single_version(src, version, type_comments=False)\n+            return _parse_single_version(src, version, type_comments=False)\n         except SyntaxError:\n             pass\n", "issue": "SyntaxWarning on regexp on first run of black\nWhen running `black` on the following code:\r\n\r\n```\r\ntext = re.sub(\r\n    \"([_a-zA-Z0-9-+]+)(\\.[_a-zA-Z0-9-+]+)*\"\r\n    \"@([a-zA-Z0-9-]+)(\\.[a-zA-Z0-9-]+)*(\\.[a-zA-Z]{2,4})\",\r\n    '<a href=\"mailto:\\g<0>\">\\g<0></a>',\r\n    text,\r\n)\r\ntext = re.sub(\r\n    \"(ftp|http|https):\\/\\/(\\w+:{0,1}\\w*@)?\"\r\n    \"(\\S+)(:[0-9]+)?(\\/|\\/([\\w#!:.?+=&%@!\\-\\/]))?\",\r\n    '<a href=\"\\g<0>\">\\g<0></a>',\r\n    text,\r\n)\r\n```\r\n\r\nI get the following warnings (written twice):\r\n\r\n```\r\n<unknown>:2: SyntaxWarning: invalid escape sequence '\\.'\r\n<unknown>:3: SyntaxWarning: invalid escape sequence '\\.'\r\n<unknown>:4: SyntaxWarning: invalid escape sequence '\\g'\r\n<unknown>:8: SyntaxWarning: invalid escape sequence '\\/'\r\n<unknown>:9: SyntaxWarning: invalid escape sequence '\\S'\r\n<unknown>:10: SyntaxWarning: invalid escape sequence '\\g'\r\n<unknown>:2: SyntaxWarning: invalid escape sequence '\\.'\r\n<unknown>:3: SyntaxWarning: invalid escape sequence '\\.'\r\n<unknown>:4: SyntaxWarning: invalid escape sequence '\\g'\r\n<unknown>:8: SyntaxWarning: invalid escape sequence '\\/'\r\n<unknown>:9: SyntaxWarning: invalid escape sequence '\\S'\r\n<unknown>:10: SyntaxWarning: invalid escape sequence '\\g'\r\n```\r\n\r\nWhen re-running `black` on the same file, the warnings are not shown again.  I have to modify the lines (adding a space for instance) to see the warnings again.\r\n\r\nAre these warnings normal?  (The syntax is normally correct according to the documentation of the `re` module.)\r\nIf they are normal, should they really appear twice in the output?  And why don't they appear again when running a second time `black`?\n", "before_files": [{"content": "\"\"\"\nParse Python code and perform AST validation.\n\"\"\"\n\nimport ast\nimport sys\nfrom typing import Iterable, Iterator, List, Set, Tuple\n\nfrom black.mode import VERSION_TO_FEATURES, Feature, TargetVersion, supports_feature\nfrom black.nodes import syms\nfrom blib2to3 import pygram\nfrom blib2to3.pgen2 import driver\nfrom blib2to3.pgen2.grammar import Grammar\nfrom blib2to3.pgen2.parse import ParseError\nfrom blib2to3.pgen2.tokenize import TokenError\nfrom blib2to3.pytree import Leaf, Node\n\n\nclass InvalidInput(ValueError):\n    \"\"\"Raised when input source code fails all parse attempts.\"\"\"\n\n\ndef get_grammars(target_versions: Set[TargetVersion]) -> List[Grammar]:\n    if not target_versions:\n        # No target_version specified, so try all grammars.\n        return [\n            # Python 3.7-3.9\n            pygram.python_grammar_async_keywords,\n            # Python 3.0-3.6\n            pygram.python_grammar,\n            # Python 3.10+\n            pygram.python_grammar_soft_keywords,\n        ]\n\n    grammars = []\n    # If we have to parse both, try to parse async as a keyword first\n    if not supports_feature(\n        target_versions, Feature.ASYNC_IDENTIFIERS\n    ) and not supports_feature(target_versions, Feature.PATTERN_MATCHING):\n        # Python 3.7-3.9\n        grammars.append(pygram.python_grammar_async_keywords)\n    if not supports_feature(target_versions, Feature.ASYNC_KEYWORDS):\n        # Python 3.0-3.6\n        grammars.append(pygram.python_grammar)\n    if any(Feature.PATTERN_MATCHING in VERSION_TO_FEATURES[v] for v in target_versions):\n        # Python 3.10+\n        grammars.append(pygram.python_grammar_soft_keywords)\n\n    # At least one of the above branches must have been taken, because every Python\n    # version has exactly one of the two 'ASYNC_*' flags\n    return grammars\n\n\ndef lib2to3_parse(src_txt: str, target_versions: Iterable[TargetVersion] = ()) -> Node:\n    \"\"\"Given a string with source, return the lib2to3 Node.\"\"\"\n    if not src_txt.endswith(\"\\n\"):\n        src_txt += \"\\n\"\n\n    grammars = get_grammars(set(target_versions))\n    errors = {}\n    for grammar in grammars:\n        drv = driver.Driver(grammar)\n        try:\n            result = drv.parse_string(src_txt, True)\n            break\n\n        except ParseError as pe:\n            lineno, column = pe.context[1]\n            lines = src_txt.splitlines()\n            try:\n                faulty_line = lines[lineno - 1]\n            except IndexError:\n                faulty_line = \"<line number missing in source>\"\n            errors[grammar.version] = InvalidInput(\n                f\"Cannot parse: {lineno}:{column}: {faulty_line}\"\n            )\n\n        except TokenError as te:\n            # In edge cases these are raised; and typically don't have a \"faulty_line\".\n            lineno, column = te.args[1]\n            errors[grammar.version] = InvalidInput(\n                f\"Cannot parse: {lineno}:{column}: {te.args[0]}\"\n            )\n\n    else:\n        # Choose the latest version when raising the actual parsing error.\n        assert len(errors) >= 1\n        exc = errors[max(errors)]\n        raise exc from None\n\n    if isinstance(result, Leaf):\n        result = Node(syms.file_input, [result])\n    return result\n\n\ndef matches_grammar(src_txt: str, grammar: Grammar) -> bool:\n    drv = driver.Driver(grammar)\n    try:\n        drv.parse_string(src_txt, True)\n    except (ParseError, TokenError, IndentationError):\n        return False\n    else:\n        return True\n\n\ndef lib2to3_unparse(node: Node) -> str:\n    \"\"\"Given a lib2to3 node, return its string representation.\"\"\"\n    code = str(node)\n    return code\n\n\ndef parse_single_version(\n    src: str, version: Tuple[int, int], *, type_comments: bool\n) -> ast.AST:\n    filename = \"<unknown>\"\n    return ast.parse(\n        src, filename, feature_version=version, type_comments=type_comments\n    )\n\n\ndef parse_ast(src: str) -> ast.AST:\n    # TODO: support Python 4+ ;)\n    versions = [(3, minor) for minor in range(3, sys.version_info[1] + 1)]\n\n    first_error = \"\"\n    for version in sorted(versions, reverse=True):\n        try:\n            return parse_single_version(src, version, type_comments=True)\n        except SyntaxError as e:\n            if not first_error:\n                first_error = str(e)\n\n    # Try to parse without type comments\n    for version in sorted(versions, reverse=True):\n        try:\n            return parse_single_version(src, version, type_comments=False)\n        except SyntaxError:\n            pass\n\n    raise SyntaxError(first_error)\n\n\ndef _normalize(lineend: str, value: str) -> str:\n    # To normalize, we strip any leading and trailing space from\n    # each line...\n    stripped: List[str] = [i.strip() for i in value.splitlines()]\n    normalized = lineend.join(stripped)\n    # ...and remove any blank lines at the beginning and end of\n    # the whole string\n    return normalized.strip()\n\n\ndef stringify_ast(node: ast.AST, depth: int = 0) -> Iterator[str]:\n    \"\"\"Simple visitor generating strings to compare ASTs by content.\"\"\"\n\n    if (\n        isinstance(node, ast.Constant)\n        and isinstance(node.value, str)\n        and node.kind == \"u\"\n    ):\n        # It's a quirk of history that we strip the u prefix over here. We used to\n        # rewrite the AST nodes for Python version compatibility and we never copied\n        # over the kind\n        node.kind = None\n\n    yield f\"{'  ' * depth}{node.__class__.__name__}(\"\n\n    for field in sorted(node._fields):  # noqa: F402\n        # TypeIgnore has only one field 'lineno' which breaks this comparison\n        if isinstance(node, ast.TypeIgnore):\n            break\n\n        try:\n            value: object = getattr(node, field)\n        except AttributeError:\n            continue\n\n        yield f\"{'  ' * (depth + 1)}{field}=\"\n\n        if isinstance(value, list):\n            for item in value:\n                # Ignore nested tuples within del statements, because we may insert\n                # parentheses and they change the AST.\n                if (\n                    field == \"targets\"\n                    and isinstance(node, ast.Delete)\n                    and isinstance(item, ast.Tuple)\n                ):\n                    for elt in item.elts:\n                        yield from stringify_ast(elt, depth + 2)\n\n                elif isinstance(item, ast.AST):\n                    yield from stringify_ast(item, depth + 2)\n\n        elif isinstance(value, ast.AST):\n            yield from stringify_ast(value, depth + 2)\n\n        else:\n            normalized: object\n            if (\n                isinstance(node, ast.Constant)\n                and field == \"value\"\n                and isinstance(value, str)\n            ):\n                # Constant strings may be indented across newlines, if they are\n                # docstrings; fold spaces after newlines when comparing. Similarly,\n                # trailing and leading space may be removed.\n                normalized = _normalize(\"\\n\", value)\n            elif field == \"type_comment\" and isinstance(value, str):\n                # Trailing whitespace in type comments is removed.\n                normalized = value.rstrip()\n            else:\n                normalized = value\n            yield f\"{'  ' * (depth + 2)}{normalized!r},  # {value.__class__.__name__}\"\n\n    yield f\"{'  ' * depth})  # /{node.__class__.__name__}\"\n", "path": "src/black/parsing.py"}], "after_files": [{"content": "\"\"\"\nParse Python code and perform AST validation.\n\"\"\"\n\nimport ast\nimport sys\nimport warnings\nfrom typing import Iterable, Iterator, List, Set, Tuple\n\nfrom black.mode import VERSION_TO_FEATURES, Feature, TargetVersion, supports_feature\nfrom black.nodes import syms\nfrom blib2to3 import pygram\nfrom blib2to3.pgen2 import driver\nfrom blib2to3.pgen2.grammar import Grammar\nfrom blib2to3.pgen2.parse import ParseError\nfrom blib2to3.pgen2.tokenize import TokenError\nfrom blib2to3.pytree import Leaf, Node\n\n\nclass InvalidInput(ValueError):\n    \"\"\"Raised when input source code fails all parse attempts.\"\"\"\n\n\ndef get_grammars(target_versions: Set[TargetVersion]) -> List[Grammar]:\n    if not target_versions:\n        # No target_version specified, so try all grammars.\n        return [\n            # Python 3.7-3.9\n            pygram.python_grammar_async_keywords,\n            # Python 3.0-3.6\n            pygram.python_grammar,\n            # Python 3.10+\n            pygram.python_grammar_soft_keywords,\n        ]\n\n    grammars = []\n    # If we have to parse both, try to parse async as a keyword first\n    if not supports_feature(\n        target_versions, Feature.ASYNC_IDENTIFIERS\n    ) and not supports_feature(target_versions, Feature.PATTERN_MATCHING):\n        # Python 3.7-3.9\n        grammars.append(pygram.python_grammar_async_keywords)\n    if not supports_feature(target_versions, Feature.ASYNC_KEYWORDS):\n        # Python 3.0-3.6\n        grammars.append(pygram.python_grammar)\n    if any(Feature.PATTERN_MATCHING in VERSION_TO_FEATURES[v] for v in target_versions):\n        # Python 3.10+\n        grammars.append(pygram.python_grammar_soft_keywords)\n\n    # At least one of the above branches must have been taken, because every Python\n    # version has exactly one of the two 'ASYNC_*' flags\n    return grammars\n\n\ndef lib2to3_parse(src_txt: str, target_versions: Iterable[TargetVersion] = ()) -> Node:\n    \"\"\"Given a string with source, return the lib2to3 Node.\"\"\"\n    if not src_txt.endswith(\"\\n\"):\n        src_txt += \"\\n\"\n\n    grammars = get_grammars(set(target_versions))\n    errors = {}\n    for grammar in grammars:\n        drv = driver.Driver(grammar)\n        try:\n            result = drv.parse_string(src_txt, True)\n            break\n\n        except ParseError as pe:\n            lineno, column = pe.context[1]\n            lines = src_txt.splitlines()\n            try:\n                faulty_line = lines[lineno - 1]\n            except IndexError:\n                faulty_line = \"<line number missing in source>\"\n            errors[grammar.version] = InvalidInput(\n                f\"Cannot parse: {lineno}:{column}: {faulty_line}\"\n            )\n\n        except TokenError as te:\n            # In edge cases these are raised; and typically don't have a \"faulty_line\".\n            lineno, column = te.args[1]\n            errors[grammar.version] = InvalidInput(\n                f\"Cannot parse: {lineno}:{column}: {te.args[0]}\"\n            )\n\n    else:\n        # Choose the latest version when raising the actual parsing error.\n        assert len(errors) >= 1\n        exc = errors[max(errors)]\n        raise exc from None\n\n    if isinstance(result, Leaf):\n        result = Node(syms.file_input, [result])\n    return result\n\n\ndef matches_grammar(src_txt: str, grammar: Grammar) -> bool:\n    drv = driver.Driver(grammar)\n    try:\n        drv.parse_string(src_txt, True)\n    except (ParseError, TokenError, IndentationError):\n        return False\n    else:\n        return True\n\n\ndef lib2to3_unparse(node: Node) -> str:\n    \"\"\"Given a lib2to3 node, return its string representation.\"\"\"\n    code = str(node)\n    return code\n\n\ndef _parse_single_version(\n    src: str, version: Tuple[int, int], *, type_comments: bool\n) -> ast.AST:\n    filename = \"<unknown>\"\n    with warnings.catch_warnings():\n        warnings.simplefilter(\"ignore\", SyntaxWarning)\n        warnings.simplefilter(\"ignore\", DeprecationWarning)\n        return ast.parse(\n            src, filename, feature_version=version, type_comments=type_comments\n        )\n\n\ndef parse_ast(src: str) -> ast.AST:\n    # TODO: support Python 4+ ;)\n    versions = [(3, minor) for minor in range(3, sys.version_info[1] + 1)]\n\n    first_error = \"\"\n    for version in sorted(versions, reverse=True):\n        try:\n            return _parse_single_version(src, version, type_comments=True)\n        except SyntaxError as e:\n            if not first_error:\n                first_error = str(e)\n\n    # Try to parse without type comments\n    for version in sorted(versions, reverse=True):\n        try:\n            return _parse_single_version(src, version, type_comments=False)\n        except SyntaxError:\n            pass\n\n    raise SyntaxError(first_error)\n\n\ndef _normalize(lineend: str, value: str) -> str:\n    # To normalize, we strip any leading and trailing space from\n    # each line...\n    stripped: List[str] = [i.strip() for i in value.splitlines()]\n    normalized = lineend.join(stripped)\n    # ...and remove any blank lines at the beginning and end of\n    # the whole string\n    return normalized.strip()\n\n\ndef stringify_ast(node: ast.AST, depth: int = 0) -> Iterator[str]:\n    \"\"\"Simple visitor generating strings to compare ASTs by content.\"\"\"\n\n    if (\n        isinstance(node, ast.Constant)\n        and isinstance(node.value, str)\n        and node.kind == \"u\"\n    ):\n        # It's a quirk of history that we strip the u prefix over here. We used to\n        # rewrite the AST nodes for Python version compatibility and we never copied\n        # over the kind\n        node.kind = None\n\n    yield f\"{'  ' * depth}{node.__class__.__name__}(\"\n\n    for field in sorted(node._fields):  # noqa: F402\n        # TypeIgnore has only one field 'lineno' which breaks this comparison\n        if isinstance(node, ast.TypeIgnore):\n            break\n\n        try:\n            value: object = getattr(node, field)\n        except AttributeError:\n            continue\n\n        yield f\"{'  ' * (depth + 1)}{field}=\"\n\n        if isinstance(value, list):\n            for item in value:\n                # Ignore nested tuples within del statements, because we may insert\n                # parentheses and they change the AST.\n                if (\n                    field == \"targets\"\n                    and isinstance(node, ast.Delete)\n                    and isinstance(item, ast.Tuple)\n                ):\n                    for elt in item.elts:\n                        yield from stringify_ast(elt, depth + 2)\n\n                elif isinstance(item, ast.AST):\n                    yield from stringify_ast(item, depth + 2)\n\n        elif isinstance(value, ast.AST):\n            yield from stringify_ast(value, depth + 2)\n\n        else:\n            normalized: object\n            if (\n                isinstance(node, ast.Constant)\n                and field == \"value\"\n                and isinstance(value, str)\n            ):\n                # Constant strings may be indented across newlines, if they are\n                # docstrings; fold spaces after newlines when comparing. Similarly,\n                # trailing and leading space may be removed.\n                normalized = _normalize(\"\\n\", value)\n            elif field == \"type_comment\" and isinstance(value, str):\n                # Trailing whitespace in type comments is removed.\n                normalized = value.rstrip()\n            else:\n                normalized = value\n            yield f\"{'  ' * (depth + 2)}{normalized!r},  # {value.__class__.__name__}\"\n\n    yield f\"{'  ' * depth})  # /{node.__class__.__name__}\"\n", "path": "src/black/parsing.py"}]}