Allow configuration of trailing commas in multi-line signatures (#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>
This commit is contained in:
Thomas Louf 2025-01-21 21:08:18 +01:00 committed by GitHub
parent fe06909e32
commit 1418339eb9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 451 additions and 28 deletions

View File

@ -55,6 +55,11 @@ Features added
* #7630, #4824: autodoc: Use :file:`.pyi` type stub files * #7630, #4824: autodoc: Use :file:`.pyi` type stub files
to auto-document native modules. to auto-document native modules.
Patch by Adam Turner, partially based on work by Allie Fitter. 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 Bugs fixed
---------- ----------
@ -86,6 +91,7 @@ Bugs fixed
before static methods, which themselves are rendered before regular before static methods, which themselves are rendered before regular
methods and attributes. methods and attributes.
Patch by Bénédikt Tran. Patch by Bénédikt Tran.
* #12975: Avoid rendering a trailing comma in C and C++ multi-line signatures.
Testing Testing
------- -------

View File

@ -4092,6 +4092,14 @@ Options for the Javascript domain
.. versionadded:: 7.1 .. 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 Options for the Python domain
----------------------------- -----------------------------
@ -4181,6 +4189,14 @@ Options for the Python domain
.. versionadded:: 7.1 .. 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 .. confval:: python_use_unqualified_type_names
:type: :code-py:`bool` :type: :code-py:`bool`
:default: :code-py:`False` :default: :code-py:`False`

View File

@ -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. 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. 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. 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 = ', ' 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. 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. 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. 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 = ', ' child_text_separator = ', '

View File

@ -109,6 +109,10 @@ class JSObject(ObjectDescription[tuple[str, str]]):
and (len(sig) > max_len > 0) and (len(sig) > max_len > 0)
) )
trailing_comma = (
self.env.config.javascript_trailing_comma_in_multi_line_signatures
)
display_prefix = self.get_display_prefix() display_prefix = self.get_display_prefix()
if display_prefix: if display_prefix:
signode += addnodes.desc_annotation('', '', *display_prefix) signode += addnodes.desc_annotation('', '', *display_prefix)
@ -129,7 +133,12 @@ class JSObject(ObjectDescription[tuple[str, str]]):
if not arglist: if not arglist:
signode += addnodes.desc_parameterlist() signode += addnodes.desc_parameterlist()
else: else:
_pseudo_parse_arglist(signode, arglist, multi_line_parameter_list) _pseudo_parse_arglist(
signode,
arglist,
multi_line_parameter_list,
trailing_comma,
)
return fullname, prefix return fullname, prefix
def _object_hierarchy_parts(self, sig_node: desc_signature) -> tuple[str, ...]: def _object_hierarchy_parts(self, sig_node: desc_signature) -> tuple[str, ...]:
@ -564,6 +573,12 @@ def setup(app: Sphinx) -> ExtensionMetadata:
'env', 'env',
types=frozenset({int, type(None)}), types=frozenset({int, type(None)}),
) )
app.add_config_value(
'javascript_trailing_comma_in_multi_line_signatures',
True,
'env',
types=frozenset({bool}),
)
return { return {
'version': 'builtin', 'version': 'builtin',
'env_version': 3, 'env_version': 3,

View File

@ -1078,6 +1078,12 @@ def setup(app: Sphinx) -> ExtensionMetadata:
'env', 'env',
types=frozenset({int, type(None)}), 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.add_config_value('python_display_short_literal_types', False, 'env')
app.connect('object-description-transform', filter_meta_fields) app.connect('object-description-transform', filter_meta_fields)
app.connect('missing-reference', builtin_resolver, priority=900) app.connect('missing-reference', builtin_resolver, priority=900)

View File

@ -400,11 +400,15 @@ class _TypeParameterListParser(TokenProcessor):
def _parse_type_list( 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: ) -> addnodes.desc_type_parameter_list:
"""Parse a list of type parameters according to PEP 695.""" """Parse a list of type parameters according to PEP 695."""
type_params = addnodes.desc_type_parameter_list(tp_list) type_params = addnodes.desc_type_parameter_list(tp_list)
type_params['multi_line_parameter_list'] = multi_line_parameter_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 # formal parameter names are interpreted as type parameter names and
# type annotations are interpreted as type parameter bound or constraints # type annotations are interpreted as type parameter bound or constraints
parser = _TypeParameterListParser(tp_list) parser = _TypeParameterListParser(tp_list)
@ -462,11 +466,15 @@ def _parse_type_list(
def _parse_arglist( 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: ) -> addnodes.desc_parameterlist:
"""Parse a list of arguments using AST parser""" """Parse a list of arguments using AST parser"""
params = addnodes.desc_parameterlist(arglist) params = addnodes.desc_parameterlist(arglist)
params['multi_line_parameter_list'] = multi_line_parameter_list params['multi_line_parameter_list'] = multi_line_parameter_list
params['multi_line_trailing_comma'] = trailing_comma
sig = signature_from_str('(%s)' % arglist) sig = signature_from_str('(%s)' % arglist)
last_kind = None last_kind = None
for param in sig.parameters.values(): for param in sig.parameters.values():
@ -522,7 +530,10 @@ def _parse_arglist(
def _pseudo_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: ) -> None:
"""'Parse' a list of arguments separated by commas. """'Parse' a list of arguments separated by commas.
@ -532,6 +543,7 @@ def _pseudo_parse_arglist(
""" """
paramlist = addnodes.desc_parameterlist() paramlist = addnodes.desc_parameterlist()
paramlist['multi_line_parameter_list'] = multi_line_parameter_list paramlist['multi_line_parameter_list'] = multi_line_parameter_list
paramlist['multi_line_trailing_comma'] = trailing_comma
stack: list[Element] = [paramlist] stack: list[Element] = [paramlist]
try: try:
for argument in arglist.split(','): for argument in arglist.split(','):

View File

@ -310,6 +310,7 @@ class PyObject(ObjectDescription[tuple[str, str]]):
and (sig_len - (arglist_span[1] - arglist_span[0])) > max_len > 0 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) sig_prefix = self.get_signature_prefix(sig)
if sig_prefix: if sig_prefix:
if type(sig_prefix) is str: if type(sig_prefix) is str:
@ -332,7 +333,10 @@ class PyObject(ObjectDescription[tuple[str, str]]):
if tp_list: if tp_list:
try: try:
signode += _parse_type_list( 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: except Exception as exc:
logger.warning( logger.warning(
@ -341,19 +345,34 @@ class PyObject(ObjectDescription[tuple[str, str]]):
if arglist: if arglist:
try: 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: except SyntaxError:
# fallback to parse arglist original parser # fallback to parse arglist original parser
# (this may happen if the argument list is incorrectly used # (this may happen if the argument list is incorrectly used
# as a list of bases when documenting a class) # as a list of bases when documenting a class)
# it supports to represent optional arguments (ex. "func(foo [, bar])") # 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: except (NotImplementedError, ValueError) as exc:
# duplicated parameter names raise ValueError and not a SyntaxError # duplicated parameter names raise ValueError and not a SyntaxError
logger.warning( logger.warning(
'could not parse arglist (%r): %s', arglist, exc, location=signode '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: else:
if self.needs_arglist(): if self.needs_arglist():
# for callables, add an empty parameter list # for callables, add an empty parameter list

View File

@ -174,6 +174,7 @@ class HTML5Translator(SphinxTranslator, BaseTranslator): # type: ignore[misc]
self.required_params_left = sum(self.list_is_required_param) self.required_params_left = sum(self.list_is_required_param)
self.param_separator = node.child_text_separator self.param_separator = node.child_text_separator
self.multi_line_parameter_list = node.get('multi_line_parameter_list', False) 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: if self.multi_line_parameter_list:
self.body.append('\n\n') self.body.append('\n\n')
self.body.append(self.starttag(node, 'dl')) self.body.append(self.starttag(node, 'dl'))
@ -238,6 +239,7 @@ class HTML5Translator(SphinxTranslator, BaseTranslator): # type: ignore[misc]
or is_required or is_required
and (is_last_group or next_is_required) and (is_last_group or next_is_required)
): ):
if not is_last_group or opt_param_left_at_level or self.trailing_comma:
self.body.append(self.param_separator) self.body.append(self.param_separator)
self.body.append('</dd>\n') self.body.append('</dd>\n')
@ -281,19 +283,26 @@ class HTML5Translator(SphinxTranslator, BaseTranslator): # type: ignore[misc]
def depart_desc_optional(self, node: Element) -> None: def depart_desc_optional(self, node: Element) -> None:
self.optional_param_level -= 1 self.optional_param_level -= 1
level = self.optional_param_level
if self.multi_line_parameter_list: if self.multi_line_parameter_list:
# If it's the first time we go down one level, add the separator max_level = self.max_optional_param_level
# before the bracket. len_lirp = len(self.list_is_required_param)
if self.optional_param_level == self.max_optional_param_level - 1: 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(self.param_separator)
self.body.append('<span class="optional">]</span>') self.body.append('<span class="optional">]</span>')
# End the line if we have just closed the last bracket of this # End the line if we have just closed the last bracket of this
# optional parameter group. # optional parameter group.
if self.optional_param_level == 0: if level == 0:
self.body.append('</dd>\n') self.body.append('</dd>\n')
else: else:
self.body.append('<span class="optional">]</span>') self.body.append('<span class="optional">]</span>')
if self.optional_param_level == 0: if level == 0:
self.param_group_index += 1 self.param_group_index += 1
def visit_desc_annotation(self, node: Element) -> None: def visit_desc_annotation(self, node: Element) -> None:

View File

@ -954,6 +954,7 @@ class LaTeXTranslator(SphinxTranslator):
self.required_params_left = sum(self.list_is_required_param) self.required_params_left = sum(self.list_is_required_param)
self.param_separator = r'\sphinxparamcomma ' self.param_separator = r'\sphinxparamcomma '
self.multi_line_parameter_list = node.get('multi_line_parameter_list', False) 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: def visit_desc_parameterlist(self, node: Element) -> None:
if self.has_tp_list: if self.has_tp_list:
@ -1013,7 +1014,7 @@ class LaTeXTranslator(SphinxTranslator):
if ( if (
opt_param_left_at_level opt_param_left_at_level
or is_required 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) self.body.append(self.param_separator)
@ -1055,13 +1056,20 @@ class LaTeXTranslator(SphinxTranslator):
def depart_desc_optional(self, node: Element) -> None: def depart_desc_optional(self, node: Element) -> None:
self.optional_param_level -= 1 self.optional_param_level -= 1
level = self.optional_param_level
if self.multi_line_parameter_list: 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 # If it's the first time we go down one level, add the separator before the
# bracket. # bracket, except if this is the last parameter and the parameter list
if self.optional_param_level == self.max_optional_param_level - 1: # 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(self.param_separator)
self.body.append('}') self.body.append('}')
if self.optional_param_level == 0: if level == 0:
self.param_group_index += 1 self.param_group_index += 1
def visit_desc_annotation(self, node: Element) -> None: def visit_desc_annotation(self, node: Element) -> None:

View File

@ -648,6 +648,7 @@ class TextTranslator(SphinxTranslator):
self.required_params_left = sum(self.list_is_required_param) self.required_params_left = sum(self.list_is_required_param)
self.param_separator = ', ' self.param_separator = ', '
self.multi_line_parameter_list = node.get('multi_line_parameter_list', False) 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: if self.multi_line_parameter_list:
self.param_separator = self.param_separator.rstrip() self.param_separator = self.param_separator.rstrip()
self.context.append(sig_close_paren) self.context.append(sig_close_paren)
@ -699,6 +700,7 @@ class TextTranslator(SphinxTranslator):
or is_required or is_required
and (is_last_group or next_is_required) and (is_last_group or next_is_required)
): ):
if not is_last_group or opt_param_left_at_level or self.trailing_comma:
self.add_text(self.param_separator) self.add_text(self.param_separator)
self.end_state(wrap=False, end=None) self.end_state(wrap=False, end=None)
@ -740,20 +742,27 @@ class TextTranslator(SphinxTranslator):
def depart_desc_optional(self, node: Element) -> None: def depart_desc_optional(self, node: Element) -> None:
self.optional_param_level -= 1 self.optional_param_level -= 1
level = self.optional_param_level
if self.multi_line_parameter_list: 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 # If it's the first time we go down one level, add the separator before the
# bracket. # bracket, except if this is the last parameter and the parameter list
if self.optional_param_level == self.max_optional_param_level - 1: # 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(self.param_separator)
self.add_text(']') self.add_text(']')
# End the line if we have just closed the last bracket of this group of # End the line if we have just closed the last bracket of this group of
# optional parameters. # optional parameters.
if self.optional_param_level == 0: if level == 0:
self.end_state(wrap=False, end=None) self.end_state(wrap=False, end=None)
else: else:
self.add_text(']') self.add_text(']')
if self.optional_param_level == 0: if level == 0:
self.param_group_index += 1 self.param_group_index += 1
def visit_desc_annotation(self, node: Element) -> None: def visit_desc_annotation(self, node: Element) -> None:

View File

@ -12,6 +12,7 @@ import pytest
from sphinx._cli.util.errors import strip_escape_sequences from sphinx._cli.util.errors import strip_escape_sequences
from sphinx.builders.html import validate_html_extra_path, validate_html_static_path from sphinx.builders.html import validate_html_extra_path, validate_html_static_path
from sphinx.errors import ConfigError from sphinx.errors import ConfigError
from sphinx.testing.util import etree_parse
from sphinx.util.inventory import InventoryFile, _InventoryItem from sphinx.util.inventory import InventoryFile, _InventoryItem
from tests.test_builders.xpath_data import FIGURE_CAPTION from tests.test_builders.xpath_data import FIGURE_CAPTION
@ -657,3 +658,60 @@ def test_html_pep_695_one_type_per_line(app, cached_etree_parse):
r'.//dt[@id="MyList"][1]', r'.//dt[@id="MyList"][1]',
chk('class MyList[\nT,\n](list[T])'), chk('class MyList[\nT,\n](list[T])'),
) )
@pytest.mark.sphinx(
'html',
testroot='domain-py-python_maximum_signature_line_length',
confoverrides={
'python_maximum_signature_line_length': 1,
'python_trailing_comma_in_multi_line_signatures': False,
},
)
def test_html_pep_695_trailing_comma_in_multi_line_signatures(app):
app.build()
fname = app.outdir / 'index.html'
etree = etree_parse(fname)
class chk:
def __init__(self, expect: str) -> None:
self.expect = expect
def __call__(self, nodes):
assert len(nodes) == 1, nodes
objnode = ''.join(nodes[0].itertext()).replace('\n\n', '')
objnode = objnode.rstrip(chr(182)) # remove '¶' symbol
objnode = objnode.strip('\n') # remove surrounding new lines
assert objnode == self.expect
# each signature has a dangling ',' at the end of its parameters lists
check_xpath(
etree,
fname,
r'.//dt[@id="generic_foo"][1]',
chk('generic_foo[\nT\n]()'),
)
check_xpath(
etree,
fname,
r'.//dt[@id="generic_bar"][1]',
chk('generic_bar[\nT\n](\nx: list[T]\n)'),
)
check_xpath(
etree,
fname,
r'.//dt[@id="generic_ret"][1]',
chk('generic_ret[\nR\n]() → R'),
)
check_xpath(
etree,
fname,
r'.//dt[@id="MyGenericClass"][1]',
chk('class MyGenericClass[\nX\n]'),
)
check_xpath(
etree,
fname,
r'.//dt[@id="MyList"][1]',
chk('class MyList[\nT\n](list[T])'),
)

View File

@ -2214,7 +2214,6 @@ def test_one_parameter_per_line(app):
app.build(force_all=True) app.build(force_all=True)
result = (app.outdir / 'projectnamenotset.tex').read_text(encoding='utf8') result = (app.outdir / 'projectnamenotset.tex').read_text(encoding='utf8')
# TODO: should these asserts check presence or absence of a final \sphinxparamcomma?
# signature of 23 characters is too short to trigger one-param-per-line mark-up # signature of 23 characters is too short to trigger one-param-per-line mark-up
assert ( assert (
'\\pysiglinewithargsret\n' '\\pysiglinewithargsret\n'
@ -2227,6 +2226,7 @@ def test_one_parameter_per_line(app):
'{\\sphinxbfcode{\\sphinxupquote{foo}}}\n' '{\\sphinxbfcode{\\sphinxupquote{foo}}}\n'
'{\\sphinxoptional{\\sphinxparam{' in result '{\\sphinxoptional{\\sphinxparam{' in result
) )
assert r'\sphinxparam{\DUrole{n}{f}}\sphinxparamcomma' in result
# generic_arg[T] # generic_arg[T]
assert ( assert (
@ -2283,6 +2283,22 @@ def test_one_parameter_per_line(app):
) )
@pytest.mark.sphinx(
'latex',
testroot='domain-py-python_maximum_signature_line_length',
confoverrides={
'python_maximum_signature_line_length': 23,
'python_trailing_comma_in_multi_line_signatures': False,
},
)
def test_one_parameter_per_line_without_trailing_comma(app):
app.build(force_all=True)
result = (app.outdir / 'projectnamenotset.tex').read_text(encoding='utf8')
assert r'\sphinxparam{\DUrole{n}{f}}\sphinxparamcomma' not in result
assert r'\sphinxparam{\DUrole{n}{f}}}}' in result
@pytest.mark.sphinx('latex', testroot='markup-rubric') @pytest.mark.sphinx('latex', testroot='markup-rubric')
def test_latex_rubric(app): def test_latex_rubric(app):
app.build() app.build()

View File

@ -1374,7 +1374,7 @@ def test_domain_c_c_maximum_signature_line_length_in_html(app):
<dd>\ <dd>\
<span class="n"><span class="pre">str</span></span>\ <span class="n"><span class="pre">str</span></span>\
<span class="w"> </span>\ <span class="w"> </span>\
<span class="n"><span class="pre">name</span></span>,\ <span class="n"><span class="pre">name</span></span>\
</dd> </dd>
</dl> </dl>
@ -1395,6 +1395,6 @@ def test_domain_c_c_maximum_signature_line_length_in_text(app):
content = (app.outdir / 'index.txt').read_text(encoding='utf8') content = (app.outdir / 'index.txt').read_text(encoding='utf8')
param_line_fmt = STDINDENT * ' ' + '{}\n' param_line_fmt = STDINDENT * ' ' + '{}\n'
expected_parameter_list_hello = '(\n{})'.format(param_line_fmt.format('str name,')) expected_parameter_list_hello = '(\n{})'.format(param_line_fmt.format('str name'))
assert expected_parameter_list_hello in content assert expected_parameter_list_hello in content

View File

@ -2426,7 +2426,7 @@ def test_domain_cpp_cpp_maximum_signature_line_length_in_html(app):
<dd>\ <dd>\
<span class="n"><span class="pre">str</span></span>\ <span class="n"><span class="pre">str</span></span>\
<span class="w"> </span>\ <span class="w"> </span>\
<span class="n sig-param"><span class="pre">name</span></span>,\ <span class="n sig-param"><span class="pre">name</span></span>\
</dd> </dd>
</dl> </dl>
@ -2445,6 +2445,6 @@ def test_domain_cpp_cpp_maximum_signature_line_length_in_text(app):
content = (app.outdir / 'index.txt').read_text(encoding='utf8') content = (app.outdir / 'index.txt').read_text(encoding='utf8')
param_line_fmt = STDINDENT * ' ' + '{}\n' param_line_fmt = STDINDENT * ' ' + '{}\n'
expected_parameter_list_hello = '(\n{})'.format(param_line_fmt.format('str name,')) expected_parameter_list_hello = '(\n{})'.format(param_line_fmt.format('str name'))
assert expected_parameter_list_hello in content assert expected_parameter_list_hello in content

View File

@ -755,3 +755,121 @@ def test_domain_js_javascript_maximum_signature_line_length_in_text(app):
expected_f, expected_f,
) )
assert expected_parameter_list_foo in content assert expected_parameter_list_foo in content
@pytest.mark.sphinx(
'html',
testroot='domain-js-javascript_maximum_signature_line_length',
confoverrides={'javascript_trailing_comma_in_multi_line_signatures': False},
)
def test_domain_js_javascript_trailing_comma_in_multi_line_signatures_in_html(app):
app.build()
content = (app.outdir / 'index.html').read_text(encoding='utf8')
expected_parameter_list_hello = """\
<dl>
<dd>\
<em class="sig-param">\
<span class="n"><span class="pre">name</span></span>\
</em>\
</dd>
</dl>
<span class="sig-paren">)</span>\
<a class="headerlink" href="#hello" title="Link to this definition"></a>\
</dt>\
"""
assert expected_parameter_list_hello in content
param_line_fmt = '<dd>{}</dd>\n'
param_name_fmt = (
'<em class="sig-param"><span class="n"><span class="pre">{}</span></span></em>'
)
optional_fmt = '<span class="optional">{}</span>'
expected_a = param_line_fmt.format(
optional_fmt.format('[')
+ param_name_fmt.format('a')
+ ','
+ optional_fmt.format('['),
)
assert expected_a in content
expected_b = param_line_fmt.format(
param_name_fmt.format('b')
+ ','
+ optional_fmt.format(']')
+ optional_fmt.format(']'),
)
assert expected_b in content
expected_c = param_line_fmt.format(param_name_fmt.format('c') + ',')
assert expected_c in content
expected_d = param_line_fmt.format(
param_name_fmt.format('d') + optional_fmt.format('[') + ','
)
assert expected_d in content
expected_e = param_line_fmt.format(param_name_fmt.format('e') + ',')
assert expected_e in content
expected_f = param_line_fmt.format(
param_name_fmt.format('f') + optional_fmt.format(']')
)
assert expected_f in content
expected_parameter_list_foo = """\
<dl>
{}{}{}{}{}{}</dl>
<span class="sig-paren">)</span>\
<a class="headerlink" href="#foo" title="Link to this definition"></a>\
</dt>\
""".format(expected_a, expected_b, expected_c, expected_d, expected_e, expected_f)
assert expected_parameter_list_foo in content
@pytest.mark.sphinx(
'text',
testroot='domain-js-javascript_maximum_signature_line_length',
freshenv=True,
confoverrides={'javascript_trailing_comma_in_multi_line_signatures': False},
)
def test_domain_js_javascript_trailing_comma_in_multi_line_signatures_in_text(app):
app.build()
content = (app.outdir / 'index.txt').read_text(encoding='utf8')
param_line_fmt = STDINDENT * ' ' + '{}\n'
expected_parameter_list_hello = '(\n{})'.format(param_line_fmt.format('name'))
assert expected_parameter_list_hello in content
expected_a = param_line_fmt.format('[a,[')
assert expected_a in content
expected_b = param_line_fmt.format('b,]]')
assert expected_b in content
expected_c = param_line_fmt.format('c,')
assert expected_c in content
expected_d = param_line_fmt.format('d[,')
assert expected_d in content
expected_e = param_line_fmt.format('e,')
assert expected_e in content
expected_f = param_line_fmt.format('f]')
assert expected_f in content
expected_parameter_list_foo = '(\n{}{}{}{}{}{})'.format(
expected_a,
expected_b,
expected_c,
expected_d,
expected_e,
expected_f,
)
assert expected_parameter_list_foo in content

View File

@ -1073,6 +1073,133 @@ def test_domain_py_python_maximum_signature_line_length_in_text(app):
assert expected_parameter_list_foo in content assert expected_parameter_list_foo in content
@pytest.mark.sphinx(
'html',
testroot='domain-py-python_maximum_signature_line_length',
confoverrides={'python_trailing_comma_in_multi_line_signatures': False},
)
def test_domain_py_python_trailing_comma_in_multi_line_signatures_in_html(app):
app.build()
content = (app.outdir / 'index.html').read_text(encoding='utf8')
expected_parameter_list_hello = """\
<dl>
<dd>\
<em class="sig-param">\
<span class="n"><span class="pre">name</span></span>\
<span class="p"><span class="pre">:</span></span>\
<span class="w"> </span>\
<span class="n"><span class="pre">str</span></span>\
</em>\
</dd>
</dl>
<span class="sig-paren">)</span> \
<span class="sig-return">\
<span class="sig-return-icon">&#x2192;</span> \
<span class="sig-return-typehint"><span class="pre">str</span></span>\
</span>\
<a class="headerlink" href="#hello" title="Link to this definition"></a>\
</dt>\
"""
assert expected_parameter_list_hello in content
param_line_fmt = '<dd>{}</dd>\n'
param_name_fmt = (
'<em class="sig-param"><span class="n"><span class="pre">{}</span></span></em>'
)
optional_fmt = '<span class="optional">{}</span>'
expected_a = param_line_fmt.format(
optional_fmt.format('[')
+ param_name_fmt.format('a')
+ ','
+ optional_fmt.format('['),
)
assert expected_a in content
expected_b = param_line_fmt.format(
param_name_fmt.format('b')
+ ','
+ optional_fmt.format(']')
+ optional_fmt.format(']'),
)
assert expected_b in content
expected_c = param_line_fmt.format(param_name_fmt.format('c') + ',')
assert expected_c in content
expected_d = param_line_fmt.format(
param_name_fmt.format('d') + optional_fmt.format('[') + ','
)
assert expected_d in content
expected_e = param_line_fmt.format(param_name_fmt.format('e') + ',')
assert expected_e in content
expected_f = param_line_fmt.format(
param_name_fmt.format('f') + optional_fmt.format(']')
)
assert expected_f in content
expected_parameter_list_foo = """\
<dl>
{}{}{}{}{}{}</dl>
<span class="sig-paren">)</span>\
<a class="headerlink" href="#foo" title="Link to this definition"></a>\
</dt>\
""".format(expected_a, expected_b, expected_c, expected_d, expected_e, expected_f)
assert expected_parameter_list_foo in content
@pytest.mark.sphinx(
'text',
testroot='domain-py-python_maximum_signature_line_length',
freshenv=True,
confoverrides={'python_trailing_comma_in_multi_line_signatures': False},
)
def test_domain_py_python_trailing_comma_in_multi_line_signatures_in_text(app):
app.build()
content = (app.outdir / 'index.txt').read_text(encoding='utf8')
param_line_fmt = STDINDENT * ' ' + '{}\n'
expected_parameter_list_hello = '(\n{}) -> str'.format(
param_line_fmt.format('name: str')
)
assert expected_parameter_list_hello in content
expected_a = param_line_fmt.format('[a,[')
assert expected_a in content
expected_b = param_line_fmt.format('b,]]')
assert expected_b in content
expected_c = param_line_fmt.format('c,')
assert expected_c in content
expected_d = param_line_fmt.format('d[,')
assert expected_d in content
expected_e = param_line_fmt.format('e,')
assert expected_e in content
expected_f = param_line_fmt.format('f]')
assert expected_f in content
expected_parameter_list_foo = '(\n{}{}{}{}{}{})'.format(
expected_a,
expected_b,
expected_c,
expected_d,
expected_e,
expected_f,
)
assert expected_parameter_list_foo in content
@pytest.mark.sphinx('html', testroot='root') @pytest.mark.sphinx('html', testroot='root')
def test_module_content_line_number(app): def test_module_content_line_number(app):
text = '.. py:module:: foo\n\n Some link here: :ref:`abc`\n' text = '.. py:module:: foo\n\n Some link here: :ref:`abc`\n'