qapi: Divorce QAPIDoc from QAPIParseError

QAPIDoc stores a reference to QAPIParser just to pass it to
QAPIParseError.  The resulting error position depends on the state of
the parser.  It happens to be the current comment line.  Servicable,
but action at a distance.

The commit before previous moved most uses of QAPIParseError from
QAPIDoc to QAPIParser.  There are just three left.  Convert them to
QAPISemError.  This involves passing info to a few methods.  Then drop
the reference to QAPIParser.

The three errors lose the column number.  Not really interesting here:
it's the comment line's indentation.

Signed-off-by: Markus Armbruster <armbru@redhat.com>
Message-ID: <20240216145841.2099240-17-armbru@redhat.com>
Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
This commit is contained in:
Markus Armbruster 2024-02-16 15:58:40 +01:00
parent 629c5075aa
commit adb0193b90
5 changed files with 32 additions and 42 deletions

View File

@ -494,7 +494,7 @@ class QAPISchemaParser:
symbol = line[1:-1] symbol = line[1:-1]
if not symbol: if not symbol:
raise QAPIParseError(self, "name required after '@'") raise QAPIParseError(self, "name required after '@'")
doc = QAPIDoc(self, info, symbol) doc = QAPIDoc(info, symbol)
self.accept(False) self.accept(False)
line = self.get_doc_line() line = self.get_doc_line()
no_more_args = False no_more_args = False
@ -518,7 +518,7 @@ class QAPISchemaParser:
line = self.get_doc_line() line = self.get_doc_line()
while (line is not None while (line is not None
and (match := self._match_at_name_colon(line))): and (match := self._match_at_name_colon(line))):
doc.new_feature(match.group(1)) doc.new_feature(self.info, match.group(1))
text = line[match.end():] text = line[match.end():]
if text: if text:
doc.append_line(text) doc.append_line(text)
@ -536,7 +536,7 @@ class QAPISchemaParser:
% match.group(1)) % match.group(1))
while (line is not None while (line is not None
and (match := self._match_at_name_colon(line))): and (match := self._match_at_name_colon(line))):
doc.new_argument(match.group(1)) doc.new_argument(self.info, match.group(1))
text = line[match.end():] text = line[match.end():]
if text: if text:
doc.append_line(text) doc.append_line(text)
@ -546,7 +546,7 @@ class QAPISchemaParser:
r'(Returns|Since|Notes?|Examples?|TODO): *', r'(Returns|Since|Notes?|Examples?|TODO): *',
line): line):
# tagged section # tagged section
doc.new_tagged_section(match.group(1)) doc.new_tagged_section(self.info, match.group(1))
text = line[match.end():] text = line[match.end():]
if text: if text:
doc.append_line(text) doc.append_line(text)
@ -558,13 +558,13 @@ class QAPISchemaParser:
"unexpected '=' markup in definition documentation") "unexpected '=' markup in definition documentation")
else: else:
# tag-less paragraph # tag-less paragraph
doc.ensure_untagged_section() doc.ensure_untagged_section(self.info)
doc.append_line(line) doc.append_line(line)
line = self.get_doc_paragraph(doc) line = self.get_doc_paragraph(doc)
else: else:
# Free-form documentation # Free-form documentation
doc = QAPIDoc(self, info) doc = QAPIDoc(info)
doc.ensure_untagged_section() doc.ensure_untagged_section(self.info)
first = True first = True
while line is not None: while line is not None:
if match := self._match_at_name_colon(line): if match := self._match_at_name_colon(line):
@ -607,12 +607,10 @@ class QAPIDoc:
""" """
class Section: class Section:
def __init__(self, parser: QAPISchemaParser, def __init__(self, info: QAPISourceInfo,
tag: Optional[str] = None): tag: Optional[str] = None):
# section source info, i.e. where it begins # section source info, i.e. where it begins
self.info = parser.info self.info = info
# parser, for error messages about indentation
self._parser = parser
# section tag, if any ('Returns', '@name', ...) # section tag, if any ('Returns', '@name', ...)
self.tag = tag self.tag = tag
# section text without tag # section text without tag
@ -622,27 +620,20 @@ class QAPIDoc:
self.text += line + '\n' self.text += line + '\n'
class ArgSection(Section): class ArgSection(Section):
def __init__(self, parser: QAPISchemaParser, def __init__(self, info: QAPISourceInfo, tag: str):
tag: str): super().__init__(info, tag)
super().__init__(parser, tag)
self.member: Optional['QAPISchemaMember'] = None self.member: Optional['QAPISchemaMember'] = None
def connect(self, member: 'QAPISchemaMember') -> None: def connect(self, member: 'QAPISchemaMember') -> None:
self.member = member self.member = member
def __init__(self, parser: QAPISchemaParser, info: QAPISourceInfo, def __init__(self, info: QAPISourceInfo, symbol: Optional[str] = None):
symbol: Optional[str] = None):
# self._parser is used to report errors with QAPIParseError. The
# resulting error position depends on the state of the parser.
# It happens to be the beginning of the comment. More or less
# servicable, but action at a distance.
self._parser = parser
# info points to the doc comment block's first line # info points to the doc comment block's first line
self.info = info self.info = info
# definition doc's symbol, None for free-form doc # definition doc's symbol, None for free-form doc
self.symbol: Optional[str] = symbol self.symbol: Optional[str] = symbol
# the sections in textual order # the sections in textual order
self.all_sections: List[QAPIDoc.Section] = [QAPIDoc.Section(parser)] self.all_sections: List[QAPIDoc.Section] = [QAPIDoc.Section(info)]
# the body section # the body section
self.body: Optional[QAPIDoc.Section] = self.all_sections[0] self.body: Optional[QAPIDoc.Section] = self.all_sections[0]
# dicts mapping parameter/feature names to their description # dicts mapping parameter/feature names to their description
@ -658,44 +649,43 @@ class QAPIDoc:
raise QAPISemError( raise QAPISemError(
section.info, "text required after '%s:'" % section.tag) section.info, "text required after '%s:'" % section.tag)
def ensure_untagged_section(self) -> None: def ensure_untagged_section(self, info: QAPISourceInfo) -> None:
if self.all_sections and not self.all_sections[-1].tag: if self.all_sections and not self.all_sections[-1].tag:
# extend current section # extend current section
self.all_sections[-1].text += '\n' self.all_sections[-1].text += '\n'
return return
# start new section # start new section
section = self.Section(self._parser) section = self.Section(info)
self.sections.append(section) self.sections.append(section)
self.all_sections.append(section) self.all_sections.append(section)
def new_tagged_section(self, tag: str) -> None: def new_tagged_section(self, info: QAPISourceInfo, tag: str) -> None:
if tag in ('Returns', 'Since'): if tag in ('Returns', 'Since'):
for section in self.all_sections: for section in self.all_sections:
if isinstance(section, self.ArgSection): if isinstance(section, self.ArgSection):
continue continue
if section.tag == tag: if section.tag == tag:
raise QAPIParseError( raise QAPISemError(
self._parser, "duplicated '%s' section" % tag) info, "duplicated '%s' section" % tag)
section = self.Section(self._parser, tag) section = self.Section(info, tag)
self.sections.append(section) self.sections.append(section)
self.all_sections.append(section) self.all_sections.append(section)
def _new_description(self, name: str, def _new_description(self, info: QAPISourceInfo, name: str,
desc: Dict[str, ArgSection]) -> None: desc: Dict[str, ArgSection]) -> None:
if not name: if not name:
raise QAPIParseError(self._parser, "invalid parameter name") raise QAPISemError(info, "invalid parameter name")
if name in desc: if name in desc:
raise QAPIParseError(self._parser, raise QAPISemError(info, "'%s' parameter name duplicated" % name)
"'%s' parameter name duplicated" % name) section = self.ArgSection(info, '@' + name)
section = self.ArgSection(self._parser, '@' + name)
self.all_sections.append(section) self.all_sections.append(section)
desc[name] = section desc[name] = section
def new_argument(self, name: str) -> None: def new_argument(self, info: QAPISourceInfo, name: str) -> None:
self._new_description(name, self.args) self._new_description(info, name, self.args)
def new_feature(self, name: str) -> None: def new_feature(self, info: QAPISourceInfo, name: str) -> None:
self._new_description(name, self.features) self._new_description(info, name, self.features)
def append_line(self, line: str) -> None: def append_line(self, line: str) -> None:
self.all_sections[-1].append_line(line) self.all_sections[-1].append_line(line)
@ -707,7 +697,7 @@ class QAPIDoc:
"%s '%s' lacks documentation" "%s '%s' lacks documentation"
% (member.role, member.name)) % (member.role, member.name))
self.args[member.name] = QAPIDoc.ArgSection( self.args[member.name] = QAPIDoc.ArgSection(
self._parser, '@' + member.name) self.info, '@' + member.name)
self.args[member.name].connect(member) self.args[member.name].connect(member)
def connect_feature(self, feature: 'QAPISchemaFeature') -> None: def connect_feature(self, feature: 'QAPISchemaFeature') -> None:

View File

@ -1 +1 @@
doc-duplicated-arg.json:6:1: 'a' parameter name duplicated doc-duplicated-arg.json:6: 'a' parameter name duplicated

View File

@ -1 +1 @@
doc-duplicated-return.json:8:1: duplicated 'Returns' section doc-duplicated-return.json:8: duplicated 'Returns' section

View File

@ -1 +1 @@
doc-duplicated-since.json:8:1: duplicated 'Since' section doc-duplicated-since.json:8: duplicated 'Since' section

View File

@ -1 +1 @@
doc-empty-arg.json:5:1: invalid parameter name doc-empty-arg.json:5: invalid parameter name