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 diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index f9409c7f088d64..642fb7e791f059 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -4617,15 +4617,21 @@ 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 self.state in { + self.state_parameter_docstring, + self.state_function_docstring, + } + + 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 +5268,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 +5295,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 = next(reversed(self.function.parameters.values())) + 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 +5305,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 +5577,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()