Skip to content
This repository was archived by the owner on Nov 3, 2023. It is now read-only.

Commit 306c7c8

Browse files
committed
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: #646 Signed-off-by: Alfred Wingate <[email protected]>
1 parent f78f194 commit 306c7c8

File tree

3 files changed

+60
-0
lines changed

3 files changed

+60
-0
lines changed

docs/release_notes.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,14 @@ Release Notes
44
**pydocstyle** version numbers follow the
55
`Semantic Versioning <http://semver.org/>`_ specification.
66

7+
8+
Current development version
9+
---------------------------
10+
11+
Bug Fixes
12+
13+
* Add support for PEP-701 fixing fstring parsing in python3.12 (#656).
14+
715
6.3.0 - January 17th, 2023
816
--------------------------
917

src/pydocstyle/parser.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -479,6 +479,29 @@ def parse_docstring(self):
479479
)
480480
self.stream.move()
481481
return docstring
482+
if (sys.version_info.major, sys.version_info.minor) >= (
483+
3,
484+
12,
485+
) and self.current.kind == tk.FSTRING_START:
486+
487+
def fstring(string):
488+
"""Recursively parse fstring tokens to output it as one string."""
489+
while self.current.kind != tk.FSTRING_END:
490+
self.stream.move()
491+
string += self.current.value
492+
if self.current.kind == tk.FSTRING_START:
493+
string = fstring(string)
494+
self.stream.move()
495+
string += self.current.value
496+
return string
497+
498+
# Reattach fstring tokens together into a string to deal with PEP 701 in python3.12
499+
start = self.current.start[0]
500+
string = fstring(self.current.value)
501+
end = self.current.end[0]
502+
docstring = Docstring(string, start, end)
503+
self.stream.move()
504+
return docstring
482505
return None
483506

484507
def parse_decorators(self):

src/tests/parser_test.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,35 @@ def do_something(pos_param0, pos_param1, kw_param0="default"):
114114
assert str(function) == 'in public function `do_something`'
115115

116116

117+
def test_nested_fstring():
118+
"""Test parsing fstring with nested fstrings."""
119+
parser = Parser()
120+
code = CodeSnippet("""\
121+
def do_something(pos_param0, pos_param1, kw_param0="default"):
122+
f\"""Do something. {f"This is a nested fstring."}\"""
123+
return None
124+
""")
125+
module = parser.parse(code, 'file_path')
126+
assert module.is_public
127+
assert module.dunder_all is None
128+
129+
function, = module.children
130+
assert function.name == 'do_something'
131+
assert function.decorators == []
132+
assert function.children == []
133+
assert function.docstring == 'f"""Do something. {f"This is a nested fstring."}"""'
134+
assert function.docstring.start == 2
135+
assert function.docstring.end == 2
136+
assert function.kind == 'function'
137+
assert function.parent == module
138+
assert function.start == 1
139+
assert function.end == 3
140+
assert function.error_lineno == 2
141+
assert function.source == code.getvalue()
142+
assert function.is_public
143+
assert str(function) == 'in public function `do_something`'
144+
145+
117146
def test_decorated_function():
118147
"""Test parsing of a simple function with a decorator."""
119148
parser = Parser()

0 commit comments

Comments
 (0)