]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-104683: Argument Clinic: Refactor and simplify 'add docstring' states (#107550)
authorErlend E. Aasland <erlend@python.org>
Tue, 1 Aug 2023 23:32:27 +0000 (01:32 +0200)
committerGitHub <noreply@github.com>
Tue, 1 Aug 2023 23:32:27 +0000 (23:32 +0000)
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.

Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
Lib/test/test_clinic.py
Tools/clinic/clinic.py

index 6100444e6da7971229027ee6856518e3498b76b1..6bdc571dd4d5ac460d6f39818a6d8d2221fd0450 100644 (file)
@@ -648,6 +648,28 @@ class ClinicParserTest(_ParserBase):
                 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
index f9409c7f088d648633474bbc10c6d500484542a8..642fb7e791f059678de0e9c0c6618daf29066cd3 100755 (executable)
@@ -4617,15 +4617,21 @@ class DSLParser:
                 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 @@ class DSLParser:
         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 @@ class DSLParser:
             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 @@ class DSLParser:
         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 @@ class DSLParser:
             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()