projects/guix·blob python-team · refs

109 lines · 3.9 KiB

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