From 306c7c8f2d863bdc098a65d2dadbd4703b9b16d5 Mon Sep 17 00:00:00 2001 From: Alfred Wingate Date: Wed, 1 Nov 2023 11:12:08 +0200 Subject: [PATCH] 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 --- 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 46e36562..3db4c189 100644 --- a/docs/release_notes.rst +++ b/docs/release_notes.rst @@ -4,6 +4,14 @@ Release Notes **pydocstyle** version numbers follow the `Semantic Versioning `_ 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 95bd0a10..875f769d 100644 --- a/src/pydocstyle/parser.py +++ b/src/pydocstyle/parser.py @@ -479,6 +479,29 @@ def parse_docstring(self): ) 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 582c6cde..2c0bbaca 100644 --- a/src/tests/parser_test.py +++ b/src/tests/parser_test.py @@ -114,6 +114,35 @@ def do_something(pos_param0, pos_param1, kw_param0="default"): 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()