Skip to content

Commit

Permalink
Allow configuration of trailing commas in multi-line signatures (#12975)
Browse files Browse the repository at this point in the history
Stop outputting trailing commas for C and C++, as it is invalid syntax.

Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com>
Co-authored-by: Adam Turner <9087854+aa-turner@users.noreply.github.com>
Co-authored-by: Jakob Lykke Andersen <jakobandersen@users.noreply.github.com>
4 people authored Jan 21, 2025

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
1 parent fe06909 commit 1418339
Showing 16 changed files with 451 additions and 28 deletions.
6 changes: 6 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
@@ -55,6 +55,11 @@ Features added
* #7630, #4824: autodoc: Use :file:`.pyi` type stub files
to auto-document native modules.
Patch by Adam Turner, partially based on work by Allie Fitter.
* #12975: Enable configuration of trailing commas in multi-line signatures
in the Python and Javascript domains, via the new
:confval:`python_trailing_comma_in_multi_line_signatures` and
:confval:`javascript_trailing_comma_in_multi_line_signatures`
configuration options.

Bugs fixed
----------
@@ -86,6 +91,7 @@ Bugs fixed
before static methods, which themselves are rendered before regular
methods and attributes.
Patch by Bénédikt Tran.
* #12975: Avoid rendering a trailing comma in C and C++ multi-line signatures.

Testing
-------
16 changes: 16 additions & 0 deletions doc/usage/configuration.rst
Original file line number Diff line number Diff line change
@@ -4092,6 +4092,14 @@ Options for the Javascript domain

.. versionadded:: 7.1

.. confval:: javascript_trailing_comma_in_multi_line_signatures
:type: :code-py:`bool`
:default: :code-py:`True`

Use a trailing comma in parameter lists spanning multiple lines, if true.

.. versionadded:: 8.2


Options for the Python domain
-----------------------------
@@ -4181,6 +4189,14 @@ Options for the Python domain
.. versionadded:: 7.1

.. confval:: python_trailing_comma_in_multi_line_signatures
:type: :code-py:`bool`
:default: :code-py:`True`

Use a trailing comma in parameter lists spanning multiple lines, if true.

.. versionadded:: 8.2

.. confval:: python_use_unqualified_type_names
:type: :code-py:`bool`
:default: :code-py:`False`
4 changes: 4 additions & 0 deletions sphinx/addnodes.py
Original file line number Diff line number Diff line change
@@ -259,6 +259,8 @@ class desc_parameterlist(nodes.Part, nodes.Inline, nodes.FixedTextElement):
As default the parameter list is written in line with the rest of the signature.
Set ``multi_line_parameter_list = True`` to describe a multi-line parameter list.
In that case each parameter will then be written on its own, indented line.
A trailing comma will be added on the last line
if ``multi_line_trailing_comma`` is True.
"""

child_text_separator = ', '
@@ -273,6 +275,8 @@ class desc_type_parameter_list(nodes.Part, nodes.Inline, nodes.FixedTextElement)
As default the type parameters list is written in line with the rest of the signature.
Set ``multi_line_parameter_list = True`` to describe a multi-line type parameters list.
In that case each type parameter will then be written on its own, indented line.
A trailing comma will be added on the last line
if ``multi_line_trailing_comma`` is True.
"""

child_text_separator = ', '
17 changes: 16 additions & 1 deletion sphinx/domains/javascript.py
Original file line number Diff line number Diff line change
@@ -109,6 +109,10 @@ def handle_signature(self, sig: str, signode: desc_signature) -> tuple[str, str]
and (len(sig) > max_len > 0)
)

trailing_comma = (
self.env.config.javascript_trailing_comma_in_multi_line_signatures
)

display_prefix = self.get_display_prefix()
if display_prefix:
signode += addnodes.desc_annotation('', '', *display_prefix)
@@ -129,7 +133,12 @@ def handle_signature(self, sig: str, signode: desc_signature) -> tuple[str, str]
if not arglist:
signode += addnodes.desc_parameterlist()
else:
_pseudo_parse_arglist(signode, arglist, multi_line_parameter_list)
_pseudo_parse_arglist(
signode,
arglist,
multi_line_parameter_list,
trailing_comma,
)
return fullname, prefix

def _object_hierarchy_parts(self, sig_node: desc_signature) -> tuple[str, ...]:
@@ -564,6 +573,12 @@ def setup(app: Sphinx) -> ExtensionMetadata:
'env',
types=frozenset({int, type(None)}),
)
app.add_config_value(
'javascript_trailing_comma_in_multi_line_signatures',
True,
'env',
types=frozenset({bool}),
)
return {
'version': 'builtin',
'env_version': 3,
6 changes: 6 additions & 0 deletions sphinx/domains/python/__init__.py
Original file line number Diff line number Diff line change
@@ -1078,6 +1078,12 @@ def setup(app: Sphinx) -> ExtensionMetadata:
'env',
types=frozenset({int, type(None)}),
)
app.add_config_value(
'python_trailing_comma_in_multi_line_signatures',
True,
'env',
types=frozenset({bool}),
)
app.add_config_value('python_display_short_literal_types', False, 'env')
app.connect('object-description-transform', filter_meta_fields)
app.connect('missing-reference', builtin_resolver, priority=900)
18 changes: 15 additions & 3 deletions sphinx/domains/python/_annotations.py
Original file line number Diff line number Diff line change
@@ -400,11 +400,15 @@ def _pformat_token(self, tok: Token, native: bool = False) -> str:


def _parse_type_list(
tp_list: str, env: BuildEnvironment, multi_line_parameter_list: bool = False
tp_list: str,
env: BuildEnvironment,
multi_line_parameter_list: bool = False,
trailing_comma: bool = True,
) -> addnodes.desc_type_parameter_list:
"""Parse a list of type parameters according to PEP 695."""
type_params = addnodes.desc_type_parameter_list(tp_list)
type_params['multi_line_parameter_list'] = multi_line_parameter_list
type_params['multi_line_trailing_comma'] = trailing_comma
# formal parameter names are interpreted as type parameter names and
# type annotations are interpreted as type parameter bound or constraints
parser = _TypeParameterListParser(tp_list)
@@ -462,11 +466,15 @@ def _parse_type_list(


def _parse_arglist(
arglist: str, env: BuildEnvironment, multi_line_parameter_list: bool = False
arglist: str,
env: BuildEnvironment,
multi_line_parameter_list: bool = False,
trailing_comma: bool = True,
) -> addnodes.desc_parameterlist:
"""Parse a list of arguments using AST parser"""
params = addnodes.desc_parameterlist(arglist)
params['multi_line_parameter_list'] = multi_line_parameter_list
params['multi_line_trailing_comma'] = trailing_comma
sig = signature_from_str('(%s)' % arglist)
last_kind = None
for param in sig.parameters.values():
@@ -522,7 +530,10 @@ def _parse_arglist(


def _pseudo_parse_arglist(
signode: desc_signature, arglist: str, multi_line_parameter_list: bool = False
signode: desc_signature,
arglist: str,
multi_line_parameter_list: bool = False,
trailing_comma: bool = True,
) -> None:
"""'Parse' a list of arguments separated by commas.
@@ -532,6 +543,7 @@ def _pseudo_parse_arglist(
"""
paramlist = addnodes.desc_parameterlist()
paramlist['multi_line_parameter_list'] = multi_line_parameter_list
paramlist['multi_line_trailing_comma'] = trailing_comma
stack: list[Element] = [paramlist]
try:
for argument in arglist.split(','):
27 changes: 23 additions & 4 deletions sphinx/domains/python/_object.py
Original file line number Diff line number Diff line change
@@ -310,6 +310,7 @@ def handle_signature(self, sig: str, signode: desc_signature) -> tuple[str, str]
and (sig_len - (arglist_span[1] - arglist_span[0])) > max_len > 0
)

trailing_comma = self.env.config.python_trailing_comma_in_multi_line_signatures
sig_prefix = self.get_signature_prefix(sig)
if sig_prefix:
if type(sig_prefix) is str:
@@ -332,7 +333,10 @@ def handle_signature(self, sig: str, signode: desc_signature) -> tuple[str, str]
if tp_list:
try:
signode += _parse_type_list(
tp_list, self.env, multi_line_type_parameter_list
tp_list,
self.env,
multi_line_type_parameter_list,
trailing_comma,
)
except Exception as exc:
logger.warning(
@@ -341,19 +345,34 @@ def handle_signature(self, sig: str, signode: desc_signature) -> tuple[str, str]

if arglist:
try:
signode += _parse_arglist(arglist, self.env, multi_line_parameter_list)
signode += _parse_arglist(
arglist,
self.env,
multi_line_parameter_list,
trailing_comma,
)
except SyntaxError:
# fallback to parse arglist original parser
# (this may happen if the argument list is incorrectly used
# as a list of bases when documenting a class)
# it supports to represent optional arguments (ex. "func(foo [, bar])")
_pseudo_parse_arglist(signode, arglist, multi_line_parameter_list)
_pseudo_parse_arglist(
signode,
arglist,
multi_line_parameter_list,
trailing_comma,
)
except (NotImplementedError, ValueError) as exc:
# duplicated parameter names raise ValueError and not a SyntaxError
logger.warning(
'could not parse arglist (%r): %s', arglist, exc, location=signode
)
_pseudo_parse_arglist(signode, arglist, multi_line_parameter_list)
_pseudo_parse_arglist(
signode,
arglist,
multi_line_parameter_list,
trailing_comma,
)
else:
if self.needs_arglist():
# for callables, add an empty parameter list
21 changes: 15 additions & 6 deletions sphinx/writers/html5.py
Original file line number Diff line number Diff line change
@@ -174,6 +174,7 @@ def _visit_sig_parameter_list(
self.required_params_left = sum(self.list_is_required_param)
self.param_separator = node.child_text_separator
self.multi_line_parameter_list = node.get('multi_line_parameter_list', False)
self.trailing_comma = node.get('multi_line_trailing_comma', False)
if self.multi_line_parameter_list:
self.body.append('\n\n')
self.body.append(self.starttag(node, 'dl'))
@@ -238,7 +239,8 @@ def depart_desc_parameter(self, node: Element) -> None:
or is_required
and (is_last_group or next_is_required)
):
self.body.append(self.param_separator)
if not is_last_group or opt_param_left_at_level or self.trailing_comma:
self.body.append(self.param_separator)
self.body.append('</dd>\n')

elif self.required_params_left:
@@ -281,19 +283,26 @@ def visit_desc_optional(self, node: Element) -> None:

def depart_desc_optional(self, node: Element) -> None:
self.optional_param_level -= 1
level = self.optional_param_level
if self.multi_line_parameter_list:
# If it's the first time we go down one level, add the separator
# before the bracket.
if self.optional_param_level == self.max_optional_param_level - 1:
max_level = self.max_optional_param_level
len_lirp = len(self.list_is_required_param)
is_last_group = self.param_group_index + 1 == len_lirp
# If it's the first time we go down one level, add the separator before the
# bracket, except if this is the last parameter and the parameter list
# should not feature a trailing comma.
if level == max_level - 1 and (
not is_last_group or level > 0 or self.trailing_comma
):
self.body.append(self.param_separator)
self.body.append('<span class="optional">]</span>')
# End the line if we have just closed the last bracket of this
# optional parameter group.
if self.optional_param_level == 0:
if level == 0:
self.body.append('</dd>\n')
else:
self.body.append('<span class="optional">]</span>')
if self.optional_param_level == 0:
if level == 0:
self.param_group_index += 1

def visit_desc_annotation(self, node: Element) -> None:
16 changes: 12 additions & 4 deletions sphinx/writers/latex.py
Original file line number Diff line number Diff line change
@@ -954,6 +954,7 @@ def _visit_sig_parameter_list(
self.required_params_left = sum(self.list_is_required_param)
self.param_separator = r'\sphinxparamcomma '
self.multi_line_parameter_list = node.get('multi_line_parameter_list', False)
self.trailing_comma = node.get('multi_line_trailing_comma', False)

def visit_desc_parameterlist(self, node: Element) -> None:
if self.has_tp_list:
@@ -1013,7 +1014,7 @@ def _depart_sig_parameter(self, node: Element) -> None:
if (
opt_param_left_at_level
or is_required
and (is_last_group or next_is_required)
and (next_is_required or self.trailing_comma)
):
self.body.append(self.param_separator)

@@ -1055,13 +1056,20 @@ def visit_desc_optional(self, node: Element) -> None:

def depart_desc_optional(self, node: Element) -> None:
self.optional_param_level -= 1
level = self.optional_param_level
if self.multi_line_parameter_list:
max_level = self.max_optional_param_level
len_lirp = len(self.list_is_required_param)
is_last_group = self.param_group_index + 1 == len_lirp
# If it's the first time we go down one level, add the separator before the
# bracket.
if self.optional_param_level == self.max_optional_param_level - 1:
# bracket, except if this is the last parameter and the parameter list
# should not feature a trailing comma.
if level == max_level - 1 and (
not is_last_group or level > 0 or self.trailing_comma
):
self.body.append(self.param_separator)
self.body.append('}')
if self.optional_param_level == 0:
if level == 0:
self.param_group_index += 1

def visit_desc_annotation(self, node: Element) -> None:
19 changes: 14 additions & 5 deletions sphinx/writers/text.py
Original file line number Diff line number Diff line change
@@ -648,6 +648,7 @@ def _visit_sig_parameter_list(
self.required_params_left = sum(self.list_is_required_param)
self.param_separator = ', '
self.multi_line_parameter_list = node.get('multi_line_parameter_list', False)
self.trailing_comma = node.get('multi_line_trailing_comma', False)
if self.multi_line_parameter_list:
self.param_separator = self.param_separator.rstrip()
self.context.append(sig_close_paren)
@@ -699,7 +700,8 @@ def visit_desc_parameter(self, node: Element) -> None:
or is_required
and (is_last_group or next_is_required)
):
self.add_text(self.param_separator)
if not is_last_group or opt_param_left_at_level or self.trailing_comma:
self.add_text(self.param_separator)
self.end_state(wrap=False, end=None)

elif self.required_params_left:
@@ -740,20 +742,27 @@ def visit_desc_optional(self, node: Element) -> None:

def depart_desc_optional(self, node: Element) -> None:
self.optional_param_level -= 1
level = self.optional_param_level
if self.multi_line_parameter_list:
max_level = self.max_optional_param_level
len_lirp = len(self.list_is_required_param)
is_last_group = self.param_group_index + 1 == len_lirp
# If it's the first time we go down one level, add the separator before the
# bracket.
if self.optional_param_level == self.max_optional_param_level - 1:
# bracket, except if this is the last parameter and the parameter list
# should not feature a trailing comma.
if level == max_level - 1 and (
not is_last_group or level > 0 or self.trailing_comma
):
self.add_text(self.param_separator)
self.add_text(']')
# End the line if we have just closed the last bracket of this group of
# optional parameters.
if self.optional_param_level == 0:
if level == 0:
self.end_state(wrap=False, end=None)

else:
self.add_text(']')
if self.optional_param_level == 0:
if level == 0:
self.param_group_index += 1

def visit_desc_annotation(self, node: Element) -> None:
Loading

0 comments on commit 1418339

Please sign in to comment.