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
16 changed files with 451 additions and 28 deletions

View File

@@ -12,6 +12,7 @@ import pytest
from sphinx._cli.util.errors import strip_escape_sequences
from sphinx.builders.html import validate_html_extra_path, validate_html_static_path
from sphinx.errors import ConfigError
from sphinx.testing.util import etree_parse
from sphinx.util.inventory import InventoryFile, _InventoryItem
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]',
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)
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
assert (
'\\pysiglinewithargsret\n'
@@ -2227,6 +2226,7 @@ def test_one_parameter_per_line(app):
'{\\sphinxbfcode{\\sphinxupquote{foo}}}\n'
'{\\sphinxoptional{\\sphinxparam{' in result
)
assert r'\sphinxparam{\DUrole{n}{f}}\sphinxparamcomma' in result
# generic_arg[T]
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')
def test_latex_rubric(app):
app.build()

View File

@@ -1374,7 +1374,7 @@ def test_domain_c_c_maximum_signature_line_length_in_html(app):
<dd>\
<span class="n"><span class="pre">str</span></span>\
<span class="w"> </span>\
<span class="n"><span class="pre">name</span></span>,\
<span class="n"><span class="pre">name</span></span>\
</dd>
</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')
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

View File

@@ -2426,7 +2426,7 @@ def test_domain_cpp_cpp_maximum_signature_line_length_in_html(app):
<dd>\
<span class="n"><span class="pre">str</span></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>
</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')
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

View File

@@ -755,3 +755,121 @@ def test_domain_js_javascript_maximum_signature_line_length_in_text(app):
expected_f,
)
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
@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')
def test_module_content_line_number(app):
text = '.. py:module:: foo\n\n Some link here: :ref:`abc`\n'