Enforce the relative ordering of "Intro", "Members", "Features",
"Errors" and "Returns" sections.

Signed-off-by: John Snow <[email protected]>
---
 docs/devel/qapi-code-gen.rst | 19 +++++++++++------
 scripts/qapi/parser.py       | 41 ++++++++++++++++++++++++++++++------
 2 files changed, 48 insertions(+), 12 deletions(-)

diff --git a/docs/devel/qapi-code-gen.rst b/docs/devel/qapi-code-gen.rst
index 3ac1c2dae18..29e9c5a3da6 100644
--- a/docs/devel/qapi-code-gen.rst
+++ b/docs/devel/qapi-code-gen.rst
@@ -985,12 +985,19 @@ When documentation is required (see pragma_ 
'doc-required'), every
 definition must have documentation.
 
 Definition documentation starts with a description naming the
-definition with an optional indented overview (the "intro"),
-:ref:`descriptions <doc-descrip>` of each argument (for commands and
-events), member (for structs and unions), branch (for alternates), or
-value (for enums), :ref:`feature descriptions <doc-feat>` of each
-feature (if any), and finally optional :ref:`tagged sections
-<doc-tagged>`.
+definition with an optional indented overview (the "intro").
+
+The introduction is followed by :ref:`descriptions <doc-descrip>` of
+each argument (for commands and events), member (for structs and
+unions), branch (for alternates), or value (for enums).
+
+:ref:`Tagged sections <doc-tagged>` follow in order: Returns (for
+commands), Errors (for commands), and lastly :ref:`feature
+descriptions <doc-feat>` if applicable.
+
+Definition documentation concludes with rST-formatted detail
+paragraphs (including examples, notes, &c), and finally an optional
+"Since" tagged section at the very end.
 
 .. _doc-descrip:
 
diff --git a/scripts/qapi/parser.py b/scripts/qapi/parser.py
index fe284a3f866..94a60a23482 100644
--- a/scripts/qapi/parser.py
+++ b/scripts/qapi/parser.py
@@ -545,6 +545,28 @@ def get_doc(self) -> 'QAPIDoc':
             doc.all_sections.append(QAPIDoc.Section(info, QAPIDoc.Kind.INTRO))
             line = self.get_doc_indented(doc)
             no_more_args = False
+            last_ordered_section = QAPIDoc.Kind.INTRO
+
+            def _tag_check(this: Union['QAPIDoc.Kind', str]) -> None:
+                nonlocal last_ordered_section
+                if isinstance(this, str):
+                    this = QAPIDoc.Kind.from_string(this)
+
+                if this in (QAPIDoc.Kind.TODO, QAPIDoc.Kind.SINCE,
+                            QAPIDoc.Kind.DETAILS):
+                    return
+
+                if this.value < last_ordered_section.value:
+                    raise QAPIParseError(
+                        self,
+                        f"'{this}' section cannot appear after "
+                        f"'{last_ordered_section}' section, please re-order "
+                        "the sections and adjust phrasing as necessary to "
+                        "ensure consistent documentation flow between the "
+                        "source code and the rendered HTML manual"
+                    )
+
+                last_ordered_section = this
 
             while line is not None:
                 # Blank lines
@@ -558,6 +580,7 @@ def get_doc(self) -> 'QAPIDoc':
                     if doc.has_features:
                         raise QAPIParseError(
                             self, "duplicated 'Features:' line")
+                    _tag_check(QAPIDoc.Kind.FEATURE)
                     self.accept(False)
                     line = self.get_doc_line()
                     while line == '':
@@ -581,6 +604,7 @@ def get_doc(self) -> 'QAPIDoc':
                             self,
                             "description of '@%s:' follows a section"
                             % match.group(1))
+                    _tag_check(QAPIDoc.Kind.MEMBER)
                     while (line is not None
                            and (match := self._match_at_name_colon(line))):
                         doc.new_argument(self.info, match.group(1))
@@ -621,6 +645,7 @@ def get_doc(self) -> 'QAPIDoc':
                         )
                         raise QAPIParseError(self, emsg)
 
+                    _tag_check(match.group(1))
                     doc.new_tagged_section(
                         self.info,
                         QAPIDoc.Kind.from_string(match.group(1))
@@ -632,6 +657,7 @@ def get_doc(self) -> 'QAPIDoc':
                     no_more_args = True
                 else:
                     # plain paragraph(s)
+                    _tag_check(QAPIDoc.Kind.DETAILS)
                     doc.ensure_untagged_section(self.info)
                     doc.append_line(line)
                     line = self.get_doc_paragraph(doc)
@@ -674,14 +700,17 @@ class QAPIDoc:
     """
 
     class Kind(enum.Enum):
-        DETAILS = 0
+        # The order here is the order in which sections must appear in
+        # source code; with the exception of 'TODO' which may appear
+        # anywhere but is treated as a comment.
+        INTRO = 0
         MEMBER = 1
-        FEATURE = 2
-        RETURNS = 3
-        ERRORS = 4
-        SINCE = 5
+        RETURNS = 2
+        ERRORS = 3
+        FEATURE = 4
+        DETAILS = 5
         TODO = 6
-        INTRO = 7
+        SINCE = 7
 
         @staticmethod
         def from_string(kind: str) -> 'QAPIDoc.Kind':
-- 
2.55.0


Reply via email to