1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110
|
From: Alfred Wingate <parona@protonmail.com>
Date: Wed, 1 Nov 2023 11:12:08 +0200
Subject: Add support for PEP701
* fstrings are broken into several distinct tokens in py3.12, reattach
them together as a singular string to preserve previous behavior.
Closes: https://github.com/PyCQA/pydocstyle/issues/646
Signed-off-by: Alfred Wingate <parona@protonmail.com>
Origin: https://github.com/PyCQA/pydocstyle/pull/656
---
docs/release_notes.rst | 8 ++++++++
src/pydocstyle/parser.py | 23 +++++++++++++++++++++++
src/tests/parser_test.py | 29 +++++++++++++++++++++++++++++
3 files changed, 60 insertions(+)
diff --git a/docs/release_notes.rst b/docs/release_notes.rst
index 5ab5e11..ed721d9 100644
--- a/docs/release_notes.rst
+++ b/docs/release_notes.rst
@@ -4,6 +4,14 @@ Release Notes
**pydocstyle** version numbers follow the
`Semantic Versioning <http://semver.org/>`_ specification.
+
+Current development version
+---------------------------
+
+Bug Fixes
+
+* Add support for PEP-701 fixing fstring parsing in python3.12 (#656).
+
6.3.0 - January 17th, 2023
--------------------------
diff --git a/src/pydocstyle/parser.py b/src/pydocstyle/parser.py
index 95bd0a1..875f769 100644
--- a/src/pydocstyle/parser.py
+++ b/src/pydocstyle/parser.py
@@ -479,6 +479,29 @@ class Parser:
)
self.stream.move()
return docstring
+ if (sys.version_info.major, sys.version_info.minor) >= (
+ 3,
+ 12,
+ ) and self.current.kind == tk.FSTRING_START:
+
+ def fstring(string):
+ """Recursively parse fstring tokens to output it as one string."""
+ while self.current.kind != tk.FSTRING_END:
+ self.stream.move()
+ string += self.current.value
+ if self.current.kind == tk.FSTRING_START:
+ string = fstring(string)
+ self.stream.move()
+ string += self.current.value
+ return string
+
+ # Reattach fstring tokens together into a string to deal with PEP 701 in python3.12
+ start = self.current.start[0]
+ string = fstring(self.current.value)
+ end = self.current.end[0]
+ docstring = Docstring(string, start, end)
+ self.stream.move()
+ return docstring
return None
def parse_decorators(self):
diff --git a/src/tests/parser_test.py b/src/tests/parser_test.py
index 582c6cd..2c0bbac 100644
--- a/src/tests/parser_test.py
+++ b/src/tests/parser_test.py
@@ -114,6 +114,35 @@ def test_fstring_with_args():
assert str(function) == 'in public function `do_something`'
+def test_nested_fstring():
+ """Test parsing fstring with nested fstrings."""
+ parser = Parser()
+ code = CodeSnippet("""\
+ def do_something(pos_param0, pos_param1, kw_param0="default"):
+ f\"""Do something. {f"This is a nested fstring."}\"""
+ return None
+ """)
+ module = parser.parse(code, 'file_path')
+ assert module.is_public
+ assert module.dunder_all is None
+
+ function, = module.children
+ assert function.name == 'do_something'
+ assert function.decorators == []
+ assert function.children == []
+ assert function.docstring == 'f"""Do something. {f"This is a nested fstring."}"""'
+ assert function.docstring.start == 2
+ assert function.docstring.end == 2
+ assert function.kind == 'function'
+ assert function.parent == module
+ assert function.start == 1
+ assert function.end == 3
+ assert function.error_lineno == 2
+ assert function.source == code.getvalue()
+ assert function.is_public
+ assert str(function) == 'in public function `do_something`'
+
+
def test_decorated_function():
"""Test parsing of a simple function with a decorator."""
parser = Parser()
|