Skip to content

Commit 1418339

Browse files
TLoufpicnixzAA-Turnerjakobandersen
authored
Allow configuration of trailing commas in multi-line signatures (sphinx-doc#12975)
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>
1 parent fe06909 commit 1418339

File tree

16 files changed

+451
-28
lines changed

16 files changed

+451
-28
lines changed

CHANGES.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,11 @@ Features added
5555
* #7630, #4824: autodoc: Use :file:`.pyi` type stub files
5656
to auto-document native modules.
5757
Patch by Adam Turner, partially based on work by Allie Fitter.
58+
* #12975: Enable configuration of trailing commas in multi-line signatures
59+
in the Python and Javascript domains, via the new
60+
:confval:`python_trailing_comma_in_multi_line_signatures` and
61+
:confval:`javascript_trailing_comma_in_multi_line_signatures`
62+
configuration options.
5863

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

9096
Testing
9197
-------

doc/usage/configuration.rst

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4092,6 +4092,14 @@ Options for the Javascript domain
40924092

40934093
.. versionadded:: 7.1
40944094

4095+
.. confval:: javascript_trailing_comma_in_multi_line_signatures
4096+
:type: :code-py:`bool`
4097+
:default: :code-py:`True`
4098+
4099+
Use a trailing comma in parameter lists spanning multiple lines, if true.
4100+
4101+
.. versionadded:: 8.2
4102+
40954103

40964104
Options for the Python domain
40974105
-----------------------------
@@ -4181,6 +4189,14 @@ Options for the Python domain
41814189
41824190
.. versionadded:: 7.1
41834191

4192+
.. confval:: python_trailing_comma_in_multi_line_signatures
4193+
:type: :code-py:`bool`
4194+
:default: :code-py:`True`
4195+
4196+
Use a trailing comma in parameter lists spanning multiple lines, if true.
4197+
4198+
.. versionadded:: 8.2
4199+
41844200
.. confval:: python_use_unqualified_type_names
41854201
:type: :code-py:`bool`
41864202
:default: :code-py:`False`

sphinx/addnodes.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,8 @@ class desc_parameterlist(nodes.Part, nodes.Inline, nodes.FixedTextElement):
259259
As default the parameter list is written in line with the rest of the signature.
260260
Set ``multi_line_parameter_list = True`` to describe a multi-line parameter list.
261261
In that case each parameter will then be written on its own, indented line.
262+
A trailing comma will be added on the last line
263+
if ``multi_line_trailing_comma`` is True.
262264
"""
263265

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

278282
child_text_separator = ', '

sphinx/domains/javascript.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,10 @@ def handle_signature(self, sig: str, signode: desc_signature) -> tuple[str, str]
109109
and (len(sig) > max_len > 0)
110110
)
111111

112+
trailing_comma = (
113+
self.env.config.javascript_trailing_comma_in_multi_line_signatures
114+
)
115+
112116
display_prefix = self.get_display_prefix()
113117
if display_prefix:
114118
signode += addnodes.desc_annotation('', '', *display_prefix)
@@ -129,7 +133,12 @@ def handle_signature(self, sig: str, signode: desc_signature) -> tuple[str, str]
129133
if not arglist:
130134
signode += addnodes.desc_parameterlist()
131135
else:
132-
_pseudo_parse_arglist(signode, arglist, multi_line_parameter_list)
136+
_pseudo_parse_arglist(
137+
signode,
138+
arglist,
139+
multi_line_parameter_list,
140+
trailing_comma,
141+
)
133142
return fullname, prefix
134143

135144
def _object_hierarchy_parts(self, sig_node: desc_signature) -> tuple[str, ...]:
@@ -564,6 +573,12 @@ def setup(app: Sphinx) -> ExtensionMetadata:
564573
'env',
565574
types=frozenset({int, type(None)}),
566575
)
576+
app.add_config_value(
577+
'javascript_trailing_comma_in_multi_line_signatures',
578+
True,
579+
'env',
580+
types=frozenset({bool}),
581+
)
567582
return {
568583
'version': 'builtin',
569584
'env_version': 3,

sphinx/domains/python/__init__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1078,6 +1078,12 @@ def setup(app: Sphinx) -> ExtensionMetadata:
10781078
'env',
10791079
types=frozenset({int, type(None)}),
10801080
)
1081+
app.add_config_value(
1082+
'python_trailing_comma_in_multi_line_signatures',
1083+
True,
1084+
'env',
1085+
types=frozenset({bool}),
1086+
)
10811087
app.add_config_value('python_display_short_literal_types', False, 'env')
10821088
app.connect('object-description-transform', filter_meta_fields)
10831089
app.connect('missing-reference', builtin_resolver, priority=900)

sphinx/domains/python/_annotations.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -400,11 +400,15 @@ def _pformat_token(self, tok: Token, native: bool = False) -> str:
400400

401401

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

463467

464468
def _parse_arglist(
465-
arglist: str, env: BuildEnvironment, multi_line_parameter_list: bool = False
469+
arglist: str,
470+
env: BuildEnvironment,
471+
multi_line_parameter_list: bool = False,
472+
trailing_comma: bool = True,
466473
) -> addnodes.desc_parameterlist:
467474
"""Parse a list of arguments using AST parser"""
468475
params = addnodes.desc_parameterlist(arglist)
469476
params['multi_line_parameter_list'] = multi_line_parameter_list
477+
params['multi_line_trailing_comma'] = trailing_comma
470478
sig = signature_from_str('(%s)' % arglist)
471479
last_kind = None
472480
for param in sig.parameters.values():
@@ -522,7 +530,10 @@ def _parse_arglist(
522530

523531

524532
def _pseudo_parse_arglist(
525-
signode: desc_signature, arglist: str, multi_line_parameter_list: bool = False
533+
signode: desc_signature,
534+
arglist: str,
535+
multi_line_parameter_list: bool = False,
536+
trailing_comma: bool = True,
526537
) -> None:
527538
"""'Parse' a list of arguments separated by commas.
528539
@@ -532,6 +543,7 @@ def _pseudo_parse_arglist(
532543
"""
533544
paramlist = addnodes.desc_parameterlist()
534545
paramlist['multi_line_parameter_list'] = multi_line_parameter_list
546+
paramlist['multi_line_trailing_comma'] = trailing_comma
535547
stack: list[Element] = [paramlist]
536548
try:
537549
for argument in arglist.split(','):

sphinx/domains/python/_object.py

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,7 @@ def handle_signature(self, sig: str, signode: desc_signature) -> tuple[str, str]
310310
and (sig_len - (arglist_span[1] - arglist_span[0])) > max_len > 0
311311
)
312312

313+
trailing_comma = self.env.config.python_trailing_comma_in_multi_line_signatures
313314
sig_prefix = self.get_signature_prefix(sig)
314315
if sig_prefix:
315316
if type(sig_prefix) is str:
@@ -332,7 +333,10 @@ def handle_signature(self, sig: str, signode: desc_signature) -> tuple[str, str]
332333
if tp_list:
333334
try:
334335
signode += _parse_type_list(
335-
tp_list, self.env, multi_line_type_parameter_list
336+
tp_list,
337+
self.env,
338+
multi_line_type_parameter_list,
339+
trailing_comma,
336340
)
337341
except Exception as exc:
338342
logger.warning(
@@ -341,19 +345,34 @@ def handle_signature(self, sig: str, signode: desc_signature) -> tuple[str, str]
341345

342346
if arglist:
343347
try:
344-
signode += _parse_arglist(arglist, self.env, multi_line_parameter_list)
348+
signode += _parse_arglist(
349+
arglist,
350+
self.env,
351+
multi_line_parameter_list,
352+
trailing_comma,
353+
)
345354
except SyntaxError:
346355
# fallback to parse arglist original parser
347356
# (this may happen if the argument list is incorrectly used
348357
# as a list of bases when documenting a class)
349358
# it supports to represent optional arguments (ex. "func(foo [, bar])")
350-
_pseudo_parse_arglist(signode, arglist, multi_line_parameter_list)
359+
_pseudo_parse_arglist(
360+
signode,
361+
arglist,
362+
multi_line_parameter_list,
363+
trailing_comma,
364+
)
351365
except (NotImplementedError, ValueError) as exc:
352366
# duplicated parameter names raise ValueError and not a SyntaxError
353367
logger.warning(
354368
'could not parse arglist (%r): %s', arglist, exc, location=signode
355369
)
356-
_pseudo_parse_arglist(signode, arglist, multi_line_parameter_list)
370+
_pseudo_parse_arglist(
371+
signode,
372+
arglist,
373+
multi_line_parameter_list,
374+
trailing_comma,
375+
)
357376
else:
358377
if self.needs_arglist():
359378
# for callables, add an empty parameter list

sphinx/writers/html5.py

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,7 @@ def _visit_sig_parameter_list(
174174
self.required_params_left = sum(self.list_is_required_param)
175175
self.param_separator = node.child_text_separator
176176
self.multi_line_parameter_list = node.get('multi_line_parameter_list', False)
177+
self.trailing_comma = node.get('multi_line_trailing_comma', False)
177178
if self.multi_line_parameter_list:
178179
self.body.append('\n\n')
179180
self.body.append(self.starttag(node, 'dl'))
@@ -238,7 +239,8 @@ def depart_desc_parameter(self, node: Element) -> None:
238239
or is_required
239240
and (is_last_group or next_is_required)
240241
):
241-
self.body.append(self.param_separator)
242+
if not is_last_group or opt_param_left_at_level or self.trailing_comma:
243+
self.body.append(self.param_separator)
242244
self.body.append('</dd>\n')
243245

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

282284
def depart_desc_optional(self, node: Element) -> None:
283285
self.optional_param_level -= 1
286+
level = self.optional_param_level
284287
if self.multi_line_parameter_list:
285-
# If it's the first time we go down one level, add the separator
286-
# before the bracket.
287-
if self.optional_param_level == self.max_optional_param_level - 1:
288+
max_level = self.max_optional_param_level
289+
len_lirp = len(self.list_is_required_param)
290+
is_last_group = self.param_group_index + 1 == len_lirp
291+
# If it's the first time we go down one level, add the separator before the
292+
# bracket, except if this is the last parameter and the parameter list
293+
# should not feature a trailing comma.
294+
if level == max_level - 1 and (
295+
not is_last_group or level > 0 or self.trailing_comma
296+
):
288297
self.body.append(self.param_separator)
289298
self.body.append('<span class="optional">]</span>')
290299
# End the line if we have just closed the last bracket of this
291300
# optional parameter group.
292-
if self.optional_param_level == 0:
301+
if level == 0:
293302
self.body.append('</dd>\n')
294303
else:
295304
self.body.append('<span class="optional">]</span>')
296-
if self.optional_param_level == 0:
305+
if level == 0:
297306
self.param_group_index += 1
298307

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

sphinx/writers/latex.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -954,6 +954,7 @@ def _visit_sig_parameter_list(
954954
self.required_params_left = sum(self.list_is_required_param)
955955
self.param_separator = r'\sphinxparamcomma '
956956
self.multi_line_parameter_list = node.get('multi_line_parameter_list', False)
957+
self.trailing_comma = node.get('multi_line_trailing_comma', False)
957958

958959
def visit_desc_parameterlist(self, node: Element) -> None:
959960
if self.has_tp_list:
@@ -1013,7 +1014,7 @@ def _depart_sig_parameter(self, node: Element) -> None:
10131014
if (
10141015
opt_param_left_at_level
10151016
or is_required
1016-
and (is_last_group or next_is_required)
1017+
and (next_is_required or self.trailing_comma)
10171018
):
10181019
self.body.append(self.param_separator)
10191020

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

10561057
def depart_desc_optional(self, node: Element) -> None:
10571058
self.optional_param_level -= 1
1059+
level = self.optional_param_level
10581060
if self.multi_line_parameter_list:
1061+
max_level = self.max_optional_param_level
1062+
len_lirp = len(self.list_is_required_param)
1063+
is_last_group = self.param_group_index + 1 == len_lirp
10591064
# If it's the first time we go down one level, add the separator before the
1060-
# bracket.
1061-
if self.optional_param_level == self.max_optional_param_level - 1:
1065+
# bracket, except if this is the last parameter and the parameter list
1066+
# should not feature a trailing comma.
1067+
if level == max_level - 1 and (
1068+
not is_last_group or level > 0 or self.trailing_comma
1069+
):
10621070
self.body.append(self.param_separator)
10631071
self.body.append('}')
1064-
if self.optional_param_level == 0:
1072+
if level == 0:
10651073
self.param_group_index += 1
10661074

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

sphinx/writers/text.py

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -648,6 +648,7 @@ def _visit_sig_parameter_list(
648648
self.required_params_left = sum(self.list_is_required_param)
649649
self.param_separator = ', '
650650
self.multi_line_parameter_list = node.get('multi_line_parameter_list', False)
651+
self.trailing_comma = node.get('multi_line_trailing_comma', False)
651652
if self.multi_line_parameter_list:
652653
self.param_separator = self.param_separator.rstrip()
653654
self.context.append(sig_close_paren)
@@ -699,7 +700,8 @@ def visit_desc_parameter(self, node: Element) -> None:
699700
or is_required
700701
and (is_last_group or next_is_required)
701702
):
702-
self.add_text(self.param_separator)
703+
if not is_last_group or opt_param_left_at_level or self.trailing_comma:
704+
self.add_text(self.param_separator)
703705
self.end_state(wrap=False, end=None)
704706

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

741743
def depart_desc_optional(self, node: Element) -> None:
742744
self.optional_param_level -= 1
745+
level = self.optional_param_level
743746
if self.multi_line_parameter_list:
747+
max_level = self.max_optional_param_level
748+
len_lirp = len(self.list_is_required_param)
749+
is_last_group = self.param_group_index + 1 == len_lirp
744750
# If it's the first time we go down one level, add the separator before the
745-
# bracket.
746-
if self.optional_param_level == self.max_optional_param_level - 1:
751+
# bracket, except if this is the last parameter and the parameter list
752+
# should not feature a trailing comma.
753+
if level == max_level - 1 and (
754+
not is_last_group or level > 0 or self.trailing_comma
755+
):
747756
self.add_text(self.param_separator)
748757
self.add_text(']')
749758
# End the line if we have just closed the last bracket of this group of
750759
# optional parameters.
751-
if self.optional_param_level == 0:
760+
if level == 0:
752761
self.end_state(wrap=False, end=None)
753762

754763
else:
755764
self.add_text(']')
756-
if self.optional_param_level == 0:
765+
if level == 0:
757766
self.param_group_index += 1
758767

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

0 commit comments

Comments
 (0)