From 966f4e72f31f6b40cd7149e9907f072200f70297 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Tue, 1 Aug 2023 23:17:28 +0200 Subject: [PATCH 1/3] gh-104683: Argument Clinic: Refactor and simplify 'add docstring' states Introduce docstring_append() helper, and use it for both parameter and function docstrings. Remove docstring fixup from do_post_block_processing_cleanup(); instead, make sure no fixup is needed. --- Tools/clinic/clinic.py | 52 ++++++++++++++++-------------------------- 1 file changed, 20 insertions(+), 32 deletions(-) diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index f9409c7f088d64..65365aa392691e 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -4617,15 +4617,18 @@ def parse(self, block: Block) -> None: fail("'preserve' only works for blocks that don't produce any output!") block.output = self.saved_output - @staticmethod - def valid_line(line: str) -> bool: + def in_docstring(self) -> bool: + """Return true if we are processing a docstring.""" + return "docstring" in self.state.__name__ + + def valid_line(self, line: str) -> bool: # ignore comment-only lines if line.lstrip().startswith('#'): return False # Ignore empty lines too # (but not in docstring sections!) - if not line.strip(): + if not self.in_docstring() and not line.strip(): return False return True @@ -5262,12 +5265,20 @@ def state_parameter_docstring_start(self, line: str) -> None: assert self.indent.depth == 3 return self.next(self.state_parameter_docstring, line) + def docstring_append(self, obj: Function | Parameter, line: str) -> None: + """Add a rstripped line to the current docstring.""" + docstring = obj.docstring + if docstring: + docstring += "\n" + if stripped := line.rstrip(): + docstring += self.indent.dedent(stripped) + obj.docstring = docstring + # every line of the docstring must start with at least F spaces, # where F > P. # these F spaces will be stripped. def state_parameter_docstring(self, line: str) -> None: - stripped = line.strip() - if stripped.startswith('#'): + if not self.valid_line(line): return indent = self.indent.measure(line) @@ -5281,16 +5292,8 @@ def state_parameter_docstring(self, line: str) -> None: return self.next(self.state_function_docstring, line) assert self.function and self.function.parameters - last_parameter = next(reversed(list(self.function.parameters.values()))) - - new_docstring = last_parameter.docstring - - if new_docstring: - new_docstring += '\n' - if stripped: - new_docstring += self.indent.dedent(line) - - last_parameter.docstring = new_docstring + last_param = list(self.function.parameters.values())[-1] + self.docstring_append(last_param, line) # the final stanza of the DSL is the docstring. def state_function_docstring(self, line: str) -> None: @@ -5299,19 +5302,10 @@ def state_function_docstring(self, line: str) -> None: if self.group: fail("Function " + self.function.name + " has a ] without a matching [.") - stripped = line.strip() - if stripped.startswith('#'): + if not self.valid_line(line): return - new_docstring = self.function.docstring - if new_docstring: - new_docstring += "\n" - if stripped: - line = self.indent.dedent(line).rstrip() - else: - line = '' - new_docstring += line - self.function.docstring = new_docstring + self.docstring_append(self.function, line) def format_docstring(self) -> str: f = self.function @@ -5580,12 +5574,6 @@ def do_post_block_processing_cleanup(self) -> None: if no_parameter_after_star: fail("Function " + self.function.name + " specifies '*' without any parameters afterwards.") - # remove trailing whitespace from all parameter docstrings - for name, value in self.function.parameters.items(): - if not value: - continue - value.docstring = value.docstring.rstrip() - self.function.docstring = self.format_docstring() From 83af46d7c2eabcb23cb31a315c7dbb043a312461 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Wed, 2 Aug 2023 00:07:15 +0200 Subject: [PATCH 2/3] Apply suggestions from code review Co-authored-by: Alex Waygood --- Tools/clinic/clinic.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index 65365aa392691e..642fb7e791f059 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -4619,7 +4619,10 @@ def parse(self, block: Block) -> None: def in_docstring(self) -> bool: """Return true if we are processing a docstring.""" - return "docstring" in self.state.__name__ + return self.state in { + self.state_parameter_docstring, + self.state_function_docstring, + } def valid_line(self, line: str) -> bool: # ignore comment-only lines @@ -5292,7 +5295,7 @@ def state_parameter_docstring(self, line: str) -> None: return self.next(self.state_function_docstring, line) assert self.function and self.function.parameters - last_param = list(self.function.parameters.values())[-1] + last_param = next(reversed(self.function.parameters.values())) self.docstring_append(last_param, line) # the final stanza of the DSL is the docstring. From 5e9ceba59057b7dbee2515a92b5317a2de002a26 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Wed, 2 Aug 2023 00:55:48 +0200 Subject: [PATCH 3/3] Add test --- Lib/test/test_clinic.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/Lib/test/test_clinic.py b/Lib/test/test_clinic.py index 6100444e6da797..6bdc571dd4d5ac 100644 --- a/Lib/test/test_clinic.py +++ b/Lib/test/test_clinic.py @@ -648,6 +648,28 @@ def test_function_docstring(self): Path to be examined """) + def test_docstring_trailing_whitespace(self): + function = self.parse_function( + "module t\n" + "t.s\n" + " a: object\n" + " Param docstring with trailing whitespace \n" + "Func docstring summary with trailing whitespace \n" + " \n" + "Func docstring body with trailing whitespace \n" + ) + self.checkDocstring(function, """ + s($module, /, a) + -- + + Func docstring summary with trailing whitespace + + a + Param docstring with trailing whitespace + + Func docstring body with trailing whitespace + """) + def test_explicit_parameters_in_docstring(self): function = self.parse_function(dedent(""" module foo