From: Markus Armbruster <armbru@redhat.com>
To: Vladimir Sementsov-Ogievskiy <vsementsov@yandex-team.ru>
Cc: michael.roth@amd.com, qemu-devel@nongnu.org
Subject: Re: [PATCH v5] qapi: Add documentation format validation
Date: Tue, 04 Nov 2025 12:46:06 +0100 [thread overview]
Message-ID: <87wm46t401.fsf@pond.sub.org> (raw)
In-Reply-To: <f1e50de9-912d-430e-ac0d-8341e003be13@yandex-team.ru> (Vladimir Sementsov-Ogievskiy's message of "Tue, 4 Nov 2025 13:55:51 +0300")
Vladimir Sementsov-Ogievskiy <vsementsov@yandex-team.ru> writes:
> On 04.11.25 13:07, Markus Armbruster wrote:
>> Vladimir Sementsov-Ogievskiy <vsementsov@yandex-team.ru> writes:
>>
>>> Add explicit validation for QAPI documentation formatting rules:
>>>
>>> 1. Lines must not exceed 70 columns in width (including '# ' prefix)
>>> 2. Sentences must be separated by two spaces
>>>
>>> Example sections and literal :: blocks (seldom case) are excluded, we
>>> don't require them to be <= 70, that would be too restrictive. Anyway,
>>> they share common 80-columns recommendations (not requirements).
>>>
>>> Add two simple tests, illustrating the change.
>>>
>>> Signed-off-by: Vladimir Sementsov-Ogievskiy <vsementsov@yandex-team.ru>
>>> ---
>>>
>>> Hi all!
>>>
>>> v5: - break "literal" block at any decreasing the indent,
>>> not only at no-indent
>>> - add two simple tests
>>>
>>> This is based on
>>> [PATCH 0/8] A QAPI schema doc markup fix, and style cleanup
>>> Based-on: <20251031094751.2817932-1-armbru@redhat.com>
>>>
>>> scripts/qapi/parser.py | 52 +++++++++++++++++++++-
>>> tests/qapi-schema/doc-bad-long-line.err | 1 +
>>> tests/qapi-schema/doc-bad-long-line.json | 6 +++
>>> tests/qapi-schema/doc-bad-long-line.out | 0
>>> tests/qapi-schema/doc-bad-whitespaces.err | 2 +
>>> tests/qapi-schema/doc-bad-whitespaces.json | 6 +++
>>> tests/qapi-schema/doc-bad-whitespaces.out | 0
>>> tests/qapi-schema/meson.build | 2 +
>>> 8 files changed, 68 insertions(+), 1 deletion(-)
>>> create mode 100644 tests/qapi-schema/doc-bad-long-line.err
>>> create mode 100644 tests/qapi-schema/doc-bad-long-line.json
>>> create mode 100644 tests/qapi-schema/doc-bad-long-line.out
>>> create mode 100644 tests/qapi-schema/doc-bad-whitespaces.err
>>> create mode 100644 tests/qapi-schema/doc-bad-whitespaces.json
>>> create mode 100644 tests/qapi-schema/doc-bad-whitespaces.out
>>>
>>> diff --git a/scripts/qapi/parser.py b/scripts/qapi/parser.py
>>> index 9fbf80a541..ffb149850d 100644
>>> --- a/scripts/qapi/parser.py
>>> +++ b/scripts/qapi/parser.py
>>> @@ -108,6 +108,11 @@ def __init__(self,
>>> self.exprs: List[QAPIExpression] = []
>>> self.docs: List[QAPIDoc] = []
>>>
>>> + # State for tracking qmp-example blocks and simple
>>> + # :: literal blocks.
>>> + self._literal_mode = False
>>> + self._literal_mode_indent = 0
>>> +
>>> # Showtime!
>>> self._parse()
>>>
>>> @@ -423,12 +428,57 @@ def get_doc_line(self) -> Optional[str]:
>>> if self.val != '##':
>>> raise QAPIParseError(
>>> self, "junk after '##' at end of documentation comment")
>>> + self._literal_mode = False
>>> return None
>>> if self.val == '#':
>>> return ''
>>> if self.val[1] != ' ':
>>> raise QAPIParseError(self, "missing space after #")
>>> - return self.val[2:].rstrip()
>>> +
>>> + line = self.val[2:].rstrip()
>>> +
>>> + if re.match(r'(\.\. +qmp-example)? *::$', line):
>>
>> After a closer reading of ReST docs and some testing: this isn't quite
>> right, although it works okay for what we have.
>>
>> A directive's '::' need not be at the end of a line. The regexp fails
>> to match a qmp-example directive with text after '::'. No such
>> directives exist right now.
>>
>> A literal block starts after a '::' at the end of a paragraph,
>> i.e. after '::' and a blank line. The regexp only matches '::' on its
>> own line, not at the end of a line of text. It matches it even when
>> it's not followed by a blank line.
>>
>> In review of v4, I claimed we don't use the contracted form "text::".
>> Not true. For instance, in block-core.json:
>>
>> # @bins: list of io request counts corresponding to histogram
>> # intervals, one more element than @boundaries has. For the
>> # example above, @bins may be something like [3, 1, 5, 2], and
>> # corresponding histogram looks like::
>> #
>> # 5| *
>> # 4| *
>> # 3| * *
>> # 2| * * *
>> # 1| * * * *
>> # +------------------
>> # 10 50 100
>> #
>
> Ow, my old histograms)
Haha!
>> # Since: 4.0
>> ##
>>
>> The literal block starts after "like::" and ends before "Since:'.
>>
>>> + self._literal_mode = True
>>> + self._literal_mode_indent = 0
>>> + elif self._literal_mode and line:
>>> + indent = re.match(r'^ *', line).end()
>>> + if self._literal_mode_indent == 0:
>>> + self._literal_mode_indent = indent
>>> + elif indent < self._literal_mode_indent:
>>> + # ReST directives stop at decreasing indentation
>>> + self._literal_mode = False
>>
>> This isn't quite right, either. We need to stop when indentation of
>> non-blank lines drops below the indentation of the line containing the
>> '::'.
>>
>> Perhaps it's easier for both of us if I fix this on top. Thoughts?
>
> No objections, good for me!
I can merge this with a brief note, like so:
Signed-off-by: Vladimir Sementsov-Ogievskiy <vsementsov@yandex-team.ru>
The detection of example and literal blocks isn't quite correct, but
it works well enough, and we can improve on top.
Reviewed-by: Markus Armbruster <armbru@redhat.com>
>>> +
>>> + if not self._literal_mode:
>>> + self._validate_doc_line_format(line)
>>> +
>>> + return line
>>> +
>>> + def _validate_doc_line_format(self, line: str) -> None:
>>> + """
>>> + Validate documentation format rules for a single line:
>>> + 1. Lines should not exceed 70 columns
>>> + 2. Sentences should be separated by two spaces
>>> + """
>>> + full_line_length = len(line) + 2 # "# " = 2 characters
>>> + if full_line_length > 70:
>>> + # Skip URL lines - they can't be broken
>>> + if re.match(r' *(https?|ftp)://[^ ]*$', line):
>>> + pass
>>> + else:
>>> + raise QAPIParseError(
>>> + self, "documentation line exceeds 70 columns"
>>> + )
>>> +
>>> + single_space_pattern = r'(\be\.g\.|^ *\d\.|([.!?])) [A-Z0-9(]'
>>> + for m in list(re.finditer(single_space_pattern, line)):
>>> + if not m.group(2):
>>> + continue
>>> + # HACK so the error message points to the offending spot
>>> + self.pos = self.line_pos + 2 + m.start(2) + 1
>>> + raise QAPIParseError(
>>> + self, "Use two spaces between sentences\n"
>>> + "If this not the end of a sentence, please report the bug",
>>> + )
>>>
>>> @staticmethod
>>> def _match_at_name_colon(string: str) -> Optional[Match[str]]:
>>> diff --git a/tests/qapi-schema/doc-bad-long-line.err b/tests/qapi-schema/doc-bad-long-line.err
>>> new file mode 100644
>>> index 0000000000..611a3b1fef
>>> --- /dev/null
>>> +++ b/tests/qapi-schema/doc-bad-long-line.err
>>> @@ -0,0 +1 @@
>>> +doc-bad-long-line.json:4:1: documentation line exceeds 70 columns
>>> diff --git a/tests/qapi-schema/doc-bad-long-line.json b/tests/qapi-schema/doc-bad-long-line.json
>>> new file mode 100644
>>> index 0000000000..d7f887694d
>>> --- /dev/null
>>> +++ b/tests/qapi-schema/doc-bad-long-line.json
>>> @@ -0,0 +1,6 @@
>>> +##
>>> +# @foo:
>>> +#
>>> +# This line has exactly 71 characters, including spaces and punctuation!
>>
>> Really?
>
> Oh, it's 72 characters actually! AI tricked me. Didn't I check it out?
>
> Maybe:
>
> # This line has exactly 71 chars, including the leading hash and space.
Works!
>>> +##
>>> +{ 'command': 'foo' }
>>> diff --git a/tests/qapi-schema/doc-bad-long-line.out b/tests/qapi-schema/doc-bad-long-line.out
>>> new file mode 100644
>>> index 0000000000..e69de29bb2
>>> diff --git a/tests/qapi-schema/doc-bad-whitespaces.err b/tests/qapi-schema/doc-bad-whitespaces.err
>>> new file mode 100644
>>> index 0000000000..5cca1954c0
>>> --- /dev/null
>>> +++ b/tests/qapi-schema/doc-bad-whitespaces.err
>>> @@ -0,0 +1,2 @@
>>> +doc-bad-whitespaces.json:4:48: Use two spaces between sentences
>>> +If this not the end of a sentence, please report the bug
>>> diff --git a/tests/qapi-schema/doc-bad-whitespaces.json b/tests/qapi-schema/doc-bad-whitespaces.json
>>> new file mode 100644
>>> index 0000000000..b0c318c670
>>> --- /dev/null
>>> +++ b/tests/qapi-schema/doc-bad-whitespaces.json
>>> @@ -0,0 +1,6 @@
>>> +##
>>> +# @foo:
>>> +#
>>> +# Sentences should be split by two whitespaces. But here is only one.
>>
>> two spaces
>>
>>> +##
>>> +{ 'command': 'foo' }
>>> diff --git a/tests/qapi-schema/doc-bad-whitespaces.out b/tests/qapi-schema/doc-bad-whitespaces.out
>>> new file mode 100644
>>> index 0000000000..e69de29bb2
>>> diff --git a/tests/qapi-schema/meson.build b/tests/qapi-schema/meson.build
>>> index c47025d16d..b24b27db21 100644
>>> --- a/tests/qapi-schema/meson.build
>>> +++ b/tests/qapi-schema/meson.build
>>> @@ -61,8 +61,10 @@ schemas = [
>>> 'doc-bad-event-arg.json',
>>> 'doc-bad-feature.json',
>>> 'doc-bad-indent.json',
>>> + 'doc-bad-long-line.json',
>>> 'doc-bad-symbol.json',
>>> 'doc-bad-union-member.json',
>>> + 'doc-bad-whitespaces.json',
>>> 'doc-before-include.json',
>>> 'doc-before-pragma.json',
>>> 'doc-duplicate-features.json',
Thanks!
next prev parent reply other threads:[~2025-11-04 11:46 UTC|newest]
Thread overview: 6+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-10-31 18:31 [PATCH v5] qapi: Add documentation format validation Vladimir Sementsov-Ogievskiy
2025-11-04 10:07 ` Markus Armbruster
2025-11-04 10:55 ` Vladimir Sementsov-Ogievskiy
2025-11-04 11:46 ` Markus Armbruster [this message]
2025-11-04 14:41 ` Vladimir Sementsov-Ogievskiy
2025-11-04 12:58 ` Markus Armbruster
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=87wm46t401.fsf@pond.sub.org \
--to=armbru@redhat.com \
--cc=michael.roth@amd.com \
--cc=qemu-devel@nongnu.org \
--cc=vsementsov@yandex-team.ru \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.