diff --git a/CHANGES.rst b/CHANGES.rst index 616492687..139ce579a 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -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 ------- diff --git a/doc/usage/configuration.rst b/doc/usage/configuration.rst index 6e526ef11..cbfc4075f 100644 --- a/doc/usage/configuration.rst +++ b/doc/usage/configuration.rst @@ -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` diff --git a/sphinx/addnodes.py b/sphinx/addnodes.py index 1a58495df..f84d1e106 100644 --- a/sphinx/addnodes.py +++ b/sphinx/addnodes.py @@ -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 = ', ' diff --git a/sphinx/domains/javascript.py b/sphinx/domains/javascript.py index 58e35e1ad..cd9e4ea83 100644 --- a/sphinx/domains/javascript.py +++ b/sphinx/domains/javascript.py @@ -109,6 +109,10 @@ class JSObject(ObjectDescription[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 @@ class JSObject(ObjectDescription[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, diff --git a/sphinx/domains/python/__init__.py b/sphinx/domains/python/__init__.py index 5ee59fc4c..2fa152602 100644 --- a/sphinx/domains/python/__init__.py +++ b/sphinx/domains/python/__init__.py @@ -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) diff --git a/sphinx/domains/python/_annotations.py b/sphinx/domains/python/_annotations.py index 96373759d..432e240a5 100644 --- a/sphinx/domains/python/_annotations.py +++ b/sphinx/domains/python/_annotations.py @@ -400,11 +400,15 @@ class _TypeParameterListParser(TokenProcessor): 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(','): diff --git a/sphinx/domains/python/_object.py b/sphinx/domains/python/_object.py index 90cfd935d..b72f35a6f 100644 --- a/sphinx/domains/python/_object.py +++ b/sphinx/domains/python/_object.py @@ -310,6 +310,7 @@ class PyObject(ObjectDescription[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 @@ class PyObject(ObjectDescription[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 @@ class PyObject(ObjectDescription[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 diff --git a/sphinx/writers/html5.py b/sphinx/writers/html5.py index d65823026..402175a84 100644 --- a/sphinx/writers/html5.py +++ b/sphinx/writers/html5.py @@ -174,6 +174,7 @@ class HTML5Translator(SphinxTranslator, BaseTranslator): # type: ignore[misc] 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 @@ class HTML5Translator(SphinxTranslator, BaseTranslator): # type: ignore[misc] 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('\n') elif self.required_params_left: @@ -281,19 +283,26 @@ class HTML5Translator(SphinxTranslator, BaseTranslator): # type: ignore[misc] 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(']') # 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('\n') else: 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: diff --git a/sphinx/writers/latex.py b/sphinx/writers/latex.py index 1004b716e..a4f80c8bb 100644 --- a/sphinx/writers/latex.py +++ b/sphinx/writers/latex.py @@ -954,6 +954,7 @@ class LaTeXTranslator(SphinxTranslator): 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 @@ class LaTeXTranslator(SphinxTranslator): 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 @@ class LaTeXTranslator(SphinxTranslator): 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: diff --git a/sphinx/writers/text.py b/sphinx/writers/text.py index 748603d71..6190a64f5 100644 --- a/sphinx/writers/text.py +++ b/sphinx/writers/text.py @@ -648,6 +648,7 @@ class TextTranslator(SphinxTranslator): 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 @@ class TextTranslator(SphinxTranslator): 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 @@ class TextTranslator(SphinxTranslator): 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: diff --git a/tests/test_builders/test_build_html.py b/tests/test_builders/test_build_html.py index b82db80f1..035ee90ba 100644 --- a/tests/test_builders/test_build_html.py +++ b/tests/test_builders/test_build_html.py @@ -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])'), + ) diff --git a/tests/test_builders/test_build_latex.py b/tests/test_builders/test_build_latex.py index 49c4bb5fd..0a67e55bc 100644 --- a/tests/test_builders/test_build_latex.py +++ b/tests/test_builders/test_build_latex.py @@ -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() diff --git a/tests/test_domains/test_domain_c.py b/tests/test_domains/test_domain_c.py index 5416f6ec8..6a5de744f 100644 --- a/tests/test_domains/test_domain_c.py +++ b/tests/test_domains/test_domain_c.py @@ -1374,7 +1374,7 @@ def test_domain_c_c_maximum_signature_line_length_in_html(app):
\ str\ \ -name,\ +name\
@@ -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 diff --git a/tests/test_domains/test_domain_cpp.py b/tests/test_domains/test_domain_cpp.py index 19e5e0752..f0de741e9 100644 --- a/tests/test_domains/test_domain_cpp.py +++ b/tests/test_domains/test_domain_cpp.py @@ -2426,7 +2426,7 @@ def test_domain_cpp_cpp_maximum_signature_line_length_in_html(app):
\ str\ \ -name,\ +name\
@@ -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 diff --git a/tests/test_domains/test_domain_js.py b/tests/test_domains/test_domain_js.py index ab3ed26c6..fad3907fb 100644 --- a/tests/test_domains/test_domain_js.py +++ b/tests/test_domains/test_domain_js.py @@ -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 = """\ + +
+
\ +\ +name\ +\ +
+
+ +)\ +\ +\ +""" + assert expected_parameter_list_hello in content + + param_line_fmt = '
{}
\n' + param_name_fmt = ( + '{}' + ) + optional_fmt = '{}' + + 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 = """\ + +
+{}{}{}{}{}{}
+ +)\ +\ +\ +""".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 diff --git a/tests/test_domains/test_domain_py.py b/tests/test_domains/test_domain_py.py index 9f19f1da3..c67a1f09d 100644 --- a/tests/test_domains/test_domain_py.py +++ b/tests/test_domains/test_domain_py.py @@ -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 = """\ + +
+
\ +\ +name\ +:\ + \ +str\ +\ +
+
+ +) \ +\ + \ +str\ +\ +\ +\ +""" + assert expected_parameter_list_hello in content + + param_line_fmt = '
{}
\n' + param_name_fmt = ( + '{}' + ) + optional_fmt = '{}' + + 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 = """\ + +
+{}{}{}{}{}{}
+ +)\ +\ +\ +""".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'