summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTLouf <31036680+TLouf@users.noreply.github.com>2023-05-11 15:28:57 +0200
committerGitHub <noreply@github.com>2023-05-11 14:28:57 +0100
commit86b07d4a97a225e79150d14e25a768ebc4c087cc (patch)
tree2c0c8691fff120604b9071cb24019ed1d49986a6
parentc73628dfcac844f89198ccd805e8e35609b37636 (diff)
downloadsphinx-git-86b07d4a97a225e79150d14e25a768ebc4c087cc.tar.gz
Allow multi-line object description signatures (#11011)
Co-authored-by: Adam Turner <9087854+aa-turner@users.noreply.github.com> Co-authored-by: Jean-François B <2589111+jfbu@users.noreply.github.com> Co-authored-by: TLouf <loufthomas@gmail.com>
-rw-r--r--CHANGES7
-rw-r--r--doc/latex.rst6
-rw-r--r--doc/usage/configuration.rst54
-rw-r--r--doc/usage/restructuredtext/domains.rst91
-rw-r--r--sphinx/addnodes.py7
-rw-r--r--sphinx/config.py2
-rw-r--r--sphinx/domains/c.py22
-rw-r--r--sphinx/domains/cpp.py22
-rw-r--r--sphinx/domains/javascript.py17
-rw-r--r--sphinx/domains/python.py27
-rw-r--r--sphinx/texinputs/sphinxlatexobjects.sty21
-rw-r--r--sphinx/texinputs/sphinxlatexstyletext.sty8
-rw-r--r--sphinx/themes/basic/static/basic.css_t10
-rw-r--r--sphinx/themes/epub/static/epub.css_t10
-rw-r--r--sphinx/writers/html5.py84
-rw-r--r--sphinx/writers/latex.py68
-rw-r--r--sphinx/writers/text.py87
-rw-r--r--tests/roots/test-domain-c-c_maximum_signature_line_length/conf.py1
-rw-r--r--tests/roots/test-domain-c-c_maximum_signature_line_length/index.rst4
-rw-r--r--tests/roots/test-domain-cpp-cpp_maximum_signature_line_length/conf.py1
-rw-r--r--tests/roots/test-domain-cpp-cpp_maximum_signature_line_length/index.rst4
-rw-r--r--tests/roots/test-domain-js-javascript_maximum_signature_line_length/conf.py1
-rw-r--r--tests/roots/test-domain-js-javascript_maximum_signature_line_length/index.rst6
-rw-r--r--tests/roots/test-domain-py-python_maximum_signature_line_length/conf.py1
-rw-r--r--tests/roots/test-domain-py-python_maximum_signature_line_length/index.rst6
-rw-r--r--tests/test_build_latex.py31
-rw-r--r--tests/test_domain_c.py262
-rw-r--r--tests/test_domain_cpp.py254
-rw-r--r--tests/test_domain_js.py261
-rw-r--r--tests/test_domain_py.py303
30 files changed, 1635 insertions, 43 deletions
diff --git a/CHANGES b/CHANGES
index 9ac823cdb..07bc8157e 100644
--- a/CHANGES
+++ b/CHANGES
@@ -26,6 +26,13 @@ Features added
generated HTML, using the CRC32 algorithm.
* :meth:`~sphinx.application.Sphinx.require_sphinx` now allows the version
requirement to be specified as ``(major, minor)``.
+* #11011: Allow configuring a line-length limit for object signatures, via
+ :confval:`maximum_signature_line_length` and the domain-specific variants.
+ If the length of the signature (in characters) is greater than the configured
+ limit, each parameter in the signature will be split to its own logical line.
+ This behaviour may also be controlled by options on object description
+ directives, for example :rst:dir:`py:function:single-line-parameter-list`.
+ Patch by Thomas Louf, Adam Turner, and Jean-François Burnol.
Bugs fixed
----------
diff --git a/doc/latex.rst b/doc/latex.rst
index 504403427..a451ae6a4 100644
--- a/doc/latex.rst
+++ b/doc/latex.rst
@@ -1479,6 +1479,12 @@ Macros
.. versionadded:: 6.2.0
``\sphinxparam``, ``\sphinxsamedocref``
+ .. versionadded:: 7.1.0
+ ``\sphinxparamcomma`` which defaults to a comma followed by a space and
+ ``\sphinxparamcommaoneperline`` which is used for one-parameter-per-line
+ signatures (see :confval:`maximum_signature_line_length`). It defaults
+ to ``\texttt{,}`` to make these end-of-line separators more distinctive.
+
- More text styling:
.. csv-table::
diff --git a/doc/usage/configuration.rst b/doc/usage/configuration.rst
index 23e9ba7ab..1fc4c674b 100644
--- a/doc/usage/configuration.rst
+++ b/doc/usage/configuration.rst
@@ -675,6 +675,25 @@ General configuration
If the value is a fully-qualified name of a custom Pygments style class,
this is then used as custom style.
+.. confval:: maximum_signature_line_length
+
+ If a signature's length in characters exceeds the number set, each
+ parameter within the signature will be displayed on an individual logical
+ line.
+
+ When ``None`` (the default), there is no maximum length and the entire
+ signature will be displayed on a single logical line.
+
+ A 'logical line' is similar to a hard line break---builders or themes may
+ choose to 'soft wrap' a single logical line, and this setting does not affect
+ that behaviour.
+
+ Domains may provide options to suppress any hard wrapping on an individual
+ object directive, such as seen in the C, C++, and Python domains (e.g.
+ :rst:dir:`py:function:single-line-parameter-list`).
+
+ .. versionadded:: 7.1
+
.. confval:: add_function_parentheses
A boolean that decides whether parentheses are appended to function and
@@ -2912,6 +2931,14 @@ Options for the C domain
.. versionadded:: 4.0.3
+.. confval:: c_maximum_signature_line_length
+
+ If a signature's length in characters exceeds the number set, each
+ parameter will be displayed on an individual logical line. This is a
+ domain-specific setting, overriding :confval:`maximum_signature_line_length`.
+
+ .. versionadded:: 7.1
+
.. _cpp-config:
Options for the C++ domain
@@ -2942,6 +2969,14 @@ Options for the C++ domain
.. versionadded:: 1.5
+.. confval:: cpp_maximum_signature_line_length
+
+ If a signature's length in characters exceeds the number set, each
+ parameter will be displayed on an individual logical line. This is a
+ domain-specific setting, overriding :confval:`maximum_signature_line_length`.
+
+ .. versionadded:: 7.1
+
Options for the Python domain
-----------------------------
@@ -2984,6 +3019,25 @@ Options for the Python domain
.. note:: This configuration is still in experimental
+.. confval:: python_maximum_signature_line_length
+
+ If a signature's length in characters exceeds the number set, each
+ argument will be displayed on an individual logical line. This is a
+ domain-specific setting, overriding :confval:`maximum_signature_line_length`.
+
+ .. versionadded:: 7.1
+
+Options for the Javascript domain
+---------------------------------
+
+.. confval:: javascript_maximum_signature_line_length
+
+ If a signature's length in characters exceeds the number set, each
+ parameter will be displayed on an individual logical line. This is a
+ domain-specific setting, overriding :confval:`maximum_signature_line_length`.
+
+ .. versionadded:: 7.1
+
Example of configuration file
-----------------------------
diff --git a/doc/usage/restructuredtext/domains.rst b/doc/usage/restructuredtext/domains.rst
index ac99a28bd..cbece86e8 100644
--- a/doc/usage/restructuredtext/domains.rst
+++ b/doc/usage/restructuredtext/domains.rst
@@ -231,6 +231,16 @@ The following directives are provided for module and class contents:
Describe the location where the object is defined. The default value is
the module specified by :rst:dir:`py:currentmodule`.
+ .. rst:directive:option:: single-line-parameter-list
+ :type: no value
+
+ Ensures that the function's arguments will be emitted on a single logical
+ line, overriding :confval:`python_maximum_signature_line_length` and
+ :confval:`maximum_signature_line_length`.
+
+ .. versionadded:: 7.1
+
+
.. rst:directive:: .. py:data:: name
Describes global data in a module, including both variables and values used
@@ -329,6 +339,15 @@ The following directives are provided for module and class contents:
Describe the location where the object is defined. The default value is
the module specified by :rst:dir:`py:currentmodule`.
+ .. rst:directive:option:: single-line-parameter-list
+ :type: no value
+
+ Ensures that the class constructor's arguments will be emitted on a single
+ logical line, overriding :confval:`python_maximum_signature_line_length`
+ and :confval:`maximum_signature_line_length`.
+
+ .. versionadded:: 7.1
+
.. rst:directive:: .. py:attribute:: name
Describes an object data attribute. The description should include
@@ -441,6 +460,15 @@ The following directives are provided for module and class contents:
Describe the location where the object is defined. The default value is
the module specified by :rst:dir:`py:currentmodule`.
+ .. rst:directive:option:: single-line-parameter-list
+ :type: no value
+
+ Ensures that the method's arguments will be emitted on a single logical
+ line, overriding :confval:`python_maximum_signature_line_length` and
+ :confval:`maximum_signature_line_length`.
+
+ .. versionadded:: 7.1
+
.. rst:directive:option:: staticmethod
:type: no value
@@ -494,6 +522,15 @@ The following directives are provided for module and class contents:
There is no ``py:deco`` role to link to a decorator that is marked up with
this directive; rather, use the :rst:role:`py:func` role.
+ .. rst:directive:option:: single-line-parameter-list
+ :type: no value
+
+ Ensures that the decorator's arguments will be emitted on a single logical
+ line, overriding :confval:`python_maximum_signature_line_length` and
+ :confval:`maximum_signature_line_length`.
+
+ .. versionadded:: 7.1
+
.. rst:directive:: .. py:decoratormethod:: name
.. py:decoratormethod:: name(signature)
@@ -763,6 +800,15 @@ The C domain (name **c**) is suited for documentation of C API.
:retval NULL: under some conditions.
:retval NULL: under some other conditions as well.
+ .. rst:directive:option:: single-line-parameter-list
+ :type: no value
+
+ Ensures that the function's parameters will be emitted on a single logical
+ line, overriding :confval:`c_maximum_signature_line_length` and
+ :confval:`maximum_signature_line_length`.
+
+ .. versionadded:: 7.1
+
.. rst:directive:: .. c:macro:: name
.. c:macro:: name(arg list)
@@ -776,6 +822,15 @@ The C domain (name **c**) is suited for documentation of C API.
.. versionadded:: 3.0
The function style variant.
+ .. rst:directive:option:: single-line-parameter-list
+ :type: no value
+
+ Ensures that the macro's parameters will be emitted on a single logical
+ line, overriding :confval:`c_maximum_signature_line_length` and
+ :confval:`maximum_signature_line_length`.
+
+ .. versionadded:: 7.1
+
.. rst:directive:: .. c:struct:: name
Describes a C struct.
@@ -1126,6 +1181,15 @@ visibility statement (``public``, ``private`` or ``protected``).
.. cpp:function:: template<> \
void print(int i)
+ .. rst:directive:option:: single-line-parameter-list
+ :type: no value
+
+ Ensures that the function's parameters will be emitted on a single logical
+ line, overriding :confval:`cpp_maximum_signature_line_length` and
+ :confval:`maximum_signature_line_length`.
+
+ .. versionadded:: 7.1
+
.. rst:directive:: .. cpp:member:: (member) variable declaration
.. cpp:var:: (member) variable declaration
@@ -1908,6 +1972,15 @@ The JavaScript domain (name **js**) provides the following directives:
:throws SomeError: For whatever reason in that case.
:returns: Something.
+ .. rst:directive:option:: single-line-parameter-list
+ :type: no value
+
+ Ensures that the function's parameters will be emitted on a single logical
+ line, overriding :confval:`javascript_maximum_signature_line_length` and
+ :confval:`maximum_signature_line_length`.
+
+ .. versionadded:: 7.1
+
.. rst:directive:: .. js:method:: name(signature)
This directive is an alias for :rst:dir:`js:function`, however it describes
@@ -1915,6 +1988,15 @@ The JavaScript domain (name **js**) provides the following directives:
.. versionadded:: 1.6
+ .. rst:directive:option:: single-line-parameter-list
+ :type: no value
+
+ Ensures that the function's parameters will be emitted on a single logical
+ line, overriding :confval:`javascript_maximum_signature_line_length` and
+ :confval:`maximum_signature_line_length`.
+
+ .. versionadded:: 7.1
+
.. rst:directive:: .. js:class:: name
Describes a constructor that creates an object. This is basically like a
@@ -1933,6 +2015,15 @@ The JavaScript domain (name **js**) provides the following directives:
:param string name: The name of the animal
:param number age: an optional age for the animal
+ .. rst:directive:option:: single-line-parameter-list
+ :type: no value
+
+ Ensures that the function's parameters will be emitted on a single logical
+ line, overriding :confval:`javascript_maximum_signature_line_length` and
+ :confval:`maximum_signature_line_length`.
+
+ .. versionadded:: 7.1
+
.. rst:directive:: .. js:data:: name
Describes a global variable or constant.
diff --git a/sphinx/addnodes.py b/sphinx/addnodes.py
index 44655d9be..e92d32a0e 100644
--- a/sphinx/addnodes.py
+++ b/sphinx/addnodes.py
@@ -246,7 +246,12 @@ class desc_returns(desc_type):
class desc_parameterlist(nodes.Part, nodes.Inline, nodes.FixedTextElement):
- """Node for a general parameter list."""
+ """Node for a general parameter list.
+
+ 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.
+ """
child_text_separator = ', '
def astext(self):
diff --git a/sphinx/config.py b/sphinx/config.py
index ad7c3b568..a4e661934 100644
--- a/sphinx/config.py
+++ b/sphinx/config.py
@@ -137,7 +137,7 @@ class Config:
'numfig': (False, 'env', []),
'numfig_secnum_depth': (1, 'env', []),
'numfig_format': ({}, 'env', []), # will be initialized in init_numfig_format()
-
+ 'maximum_signature_line_length': (None, 'env', {int, None}),
'math_number_all': (False, 'env', []),
'math_eqref_format': (None, 'env', [str]),
'math_numfig': (True, 'env', []),
diff --git a/sphinx/domains/c.py b/sphinx/domains/c.py
index c583a770d..0bb505fba 100644
--- a/sphinx/domains/c.py
+++ b/sphinx/domains/c.py
@@ -727,9 +727,19 @@ class ASTParameters(ASTBase):
def describe_signature(self, signode: TextElement, mode: str,
env: BuildEnvironment, symbol: Symbol) -> None:
verify_description_mode(mode)
+ multi_line_parameter_list = False
+ test_node: Element = signode
+ while test_node.parent:
+ if not isinstance(test_node, addnodes.desc_signature):
+ test_node = test_node.parent
+ continue
+ multi_line_parameter_list = test_node.get('multi_line_parameter_list', False)
+ break
+
# only use the desc_parameterlist for the outer list, not for inner lists
if mode == 'lastIsName':
paramlist = addnodes.desc_parameterlist()
+ paramlist['multi_line_parameter_list'] = multi_line_parameter_list
for arg in self.args:
param = addnodes.desc_parameter('', '', noemph=True)
arg.describe_signature(param, 'param', env, symbol=symbol)
@@ -3153,6 +3163,7 @@ class CObject(ObjectDescription[ASTDeclaration]):
option_spec: OptionSpec = {
'noindexentry': directives.flag,
'nocontentsentry': directives.flag,
+ 'single-line-parameter-list': directives.flag,
}
def _add_enumerator_to_parent(self, ast: ASTDeclaration) -> None:
@@ -3258,6 +3269,14 @@ class CObject(ObjectDescription[ASTDeclaration]):
def handle_signature(self, sig: str, signode: TextElement) -> ASTDeclaration:
parentSymbol: Symbol = self.env.temp_data['c:parent_symbol']
+ max_len = (self.env.config.c_maximum_signature_line_length
+ or self.env.config.maximum_signature_line_length
+ or 0)
+ signode['multi_line_parameter_list'] = (
+ 'single-line-parameter-list' not in self.options
+ and (len(sig) > max_len > 0)
+ )
+
parser = DefinitionParser(sig, location=signode, config=self.env.config)
try:
ast = self.parse_definition(parser)
@@ -3866,11 +3885,12 @@ def setup(app: Sphinx) -> dict[str, Any]:
app.add_config_value("c_id_attributes", [], 'env')
app.add_config_value("c_paren_attributes", [], 'env')
app.add_config_value("c_extra_keywords", _macroKeywords, 'env')
+ app.add_config_value("c_maximum_signature_line_length", None, 'env', types={int, None})
app.add_post_transform(AliasTransform)
return {
'version': 'builtin',
- 'env_version': 2,
+ 'env_version': 3,
'parallel_read_safe': True,
'parallel_write_safe': True,
}
diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py
index a7d16aa06..41f2bd076 100644
--- a/sphinx/domains/cpp.py
+++ b/sphinx/domains/cpp.py
@@ -2142,9 +2142,19 @@ class ASTParametersQualifiers(ASTBase):
def describe_signature(self, signode: TextElement, mode: str,
env: BuildEnvironment, symbol: Symbol) -> None:
verify_description_mode(mode)
+ multi_line_parameter_list = False
+ test_node: Element = signode
+ while test_node.parent:
+ if not isinstance(test_node, addnodes.desc_signature):
+ test_node = test_node.parent
+ continue
+ multi_line_parameter_list = test_node.get('multi_line_parameter_list', False)
+ break
+
# only use the desc_parameterlist for the outer list, not for inner lists
if mode == 'lastIsName':
paramlist = addnodes.desc_parameterlist()
+ paramlist['multi_line_parameter_list'] = multi_line_parameter_list
for arg in self.args:
param = addnodes.desc_parameter('', '', noemph=True)
arg.describe_signature(param, 'param', env, symbol=symbol)
@@ -7192,6 +7202,7 @@ class CPPObject(ObjectDescription[ASTDeclaration]):
'noindexentry': directives.flag,
'nocontentsentry': directives.flag,
'tparam-line-spec': directives.flag,
+ 'single-line-parameter-list': directives.flag,
}
def _add_enumerator_to_parent(self, ast: ASTDeclaration) -> None:
@@ -7348,6 +7359,14 @@ class CPPObject(ObjectDescription[ASTDeclaration]):
def handle_signature(self, sig: str, signode: desc_signature) -> ASTDeclaration:
parentSymbol: Symbol = self.env.temp_data['cpp:parent_symbol']
+ max_len = (self.env.config.cpp_maximum_signature_line_length
+ or self.env.config.maximum_signature_line_length
+ or 0)
+ signode['multi_line_parameter_list'] = (
+ 'single-line-parameter-list' not in self.options
+ and (len(sig) > max_len > 0)
+ )
+
parser = DefinitionParser(sig, location=signode, config=self.env.config)
try:
ast = self.parse_definition(parser)
@@ -8140,6 +8159,7 @@ def setup(app: Sphinx) -> dict[str, Any]:
app.add_config_value("cpp_index_common_prefix", [], 'env')
app.add_config_value("cpp_id_attributes", [], 'env')
app.add_config_value("cpp_paren_attributes", [], 'env')
+ app.add_config_value("cpp_maximum_signature_line_length", None, 'env', types={int, None})
app.add_post_transform(AliasTransform)
# debug stuff
@@ -8154,7 +8174,7 @@ def setup(app: Sphinx) -> dict[str, Any]:
return {
'version': 'builtin',
- 'env_version': 8,
+ 'env_version': 9,
'parallel_read_safe': True,
'parallel_write_safe': True,
}
diff --git a/sphinx/domains/javascript.py b/sphinx/domains/javascript.py
index 093e291ca..c6baab8a9 100644
--- a/sphinx/domains/javascript.py
+++ b/sphinx/domains/javascript.py
@@ -43,6 +43,7 @@ class JSObject(ObjectDescription[Tuple[str, str]]):
'noindex': directives.flag,
'noindexentry': directives.flag,
'nocontentsentry': directives.flag,
+ 'single-line-parameter-list': directives.flag,
}
def get_display_prefix(self) -> list[Node]:
@@ -88,6 +89,14 @@ class JSObject(ObjectDescription[Tuple[str, str]]):
signode['object'] = prefix
signode['fullname'] = fullname
+ max_len = (self.env.config.javascript_maximum_signature_line_length
+ or self.env.config.maximum_signature_line_length
+ or 0)
+ multi_line_parameter_list = (
+ 'single-line-parameter-list' not in self.options
+ and (len(sig) > max_len > 0)
+ )
+
display_prefix = self.get_display_prefix()
if display_prefix:
signode += addnodes.desc_annotation('', '', *display_prefix)
@@ -108,7 +117,7 @@ class JSObject(ObjectDescription[Tuple[str, str]]):
if not arglist:
signode += addnodes.desc_parameterlist()
else:
- _pseudo_parse_arglist(signode, arglist)
+ _pseudo_parse_arglist(signode, arglist, multi_line_parameter_list)
return fullname, prefix
def _object_hierarchy_parts(self, sig_node: desc_signature) -> tuple[str, ...]:
@@ -473,10 +482,12 @@ class JavaScriptDomain(Domain):
def setup(app: Sphinx) -> dict[str, Any]:
app.add_domain(JavaScriptDomain)
-
+ app.add_config_value(
+ 'javascript_maximum_signature_line_length', None, 'env', types={int, None},
+ )
return {
'version': 'builtin',
- 'env_version': 2,
+ 'env_version': 3,
'parallel_read_safe': True,
'parallel_write_safe': True,
}
diff --git a/sphinx/domains/python.py b/sphinx/domains/python.py
index eef78aa80..3fda52703 100644
--- a/sphinx/domains/python.py
+++ b/sphinx/domains/python.py
@@ -258,10 +258,11 @@ def _parse_annotation(annotation: str, env: BuildEnvironment | None) -> list[Nod
def _parse_arglist(
- arglist: str, env: BuildEnvironment | None = None,
+ arglist: str, env: BuildEnvironment | None = None, multi_line_parameter_list: bool = False,
) -> 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
sig = signature_from_str('(%s)' % arglist)
last_kind = None
for param in sig.parameters.values():
@@ -309,7 +310,9 @@ def _parse_arglist(
return params
-def _pseudo_parse_arglist(signode: desc_signature, arglist: str) -> None:
+def _pseudo_parse_arglist(
+ signode: desc_signature, arglist: str, multi_line_parameter_list: bool = False,
+) -> None:
""""Parse" a list of arguments separated by commas.
Arguments can have "optional" annotations given by enclosing them in
@@ -317,6 +320,7 @@ def _pseudo_parse_arglist(signode: desc_signature, arglist: str) -> None:
string literal (e.g. default argument value).
"""
paramlist = addnodes.desc_parameterlist()
+ paramlist['multi_line_parameter_list'] = multi_line_parameter_list
stack: list[Element] = [paramlist]
try:
for argument in arglist.split(','):
@@ -459,6 +463,7 @@ class PyObject(ObjectDescription[Tuple[str, str]]):
'noindex': directives.flag,
'noindexentry': directives.flag,
'nocontentsentry': directives.flag,
+ 'single-line-parameter-list': directives.flag,
'module': directives.unchanged,
'canonical': directives.unchanged,
'annotation': directives.unchanged,
@@ -541,6 +546,14 @@ class PyObject(ObjectDescription[Tuple[str, str]]):
signode['class'] = classname
signode['fullname'] = fullname
+ max_len = (self.env.config.python_maximum_signature_line_length
+ or self.env.config.maximum_signature_line_length
+ or 0)
+ multi_line_parameter_list = (
+ 'single-line-parameter-list' not in self.options
+ and (len(sig) > max_len > 0)
+ )
+
sig_prefix = self.get_signature_prefix(sig)
if sig_prefix:
if type(sig_prefix) is str:
@@ -559,15 +572,15 @@ class PyObject(ObjectDescription[Tuple[str, str]]):
signode += addnodes.desc_name(name, name)
if arglist:
try:
- signode += _parse_arglist(arglist, self.env)
+ signode += _parse_arglist(arglist, self.env, multi_line_parameter_list)
except SyntaxError:
# fallback to parse arglist original parser.
# it supports to represent optional arguments (ex. "func(foo [, bar])")
- _pseudo_parse_arglist(signode, arglist)
+ _pseudo_parse_arglist(signode, arglist, multi_line_parameter_list)
except NotImplementedError as exc:
logger.warning("could not parse arglist (%r): %s", arglist, exc,
location=signode)
- _pseudo_parse_arglist(signode, arglist)
+ _pseudo_parse_arglist(signode, arglist, multi_line_parameter_list)
else:
if self.needs_arglist():
# for callables, add an empty parameter list
@@ -1505,13 +1518,15 @@ def setup(app: Sphinx) -> dict[str, Any]:
app.add_domain(PythonDomain)
app.add_config_value('python_use_unqualified_type_names', False, 'env')
+ app.add_config_value('python_maximum_signature_line_length', None, 'env',
+ types={int, None})
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)
return {
'version': 'builtin',
- 'env_version': 3,
+ 'env_version': 4,
'parallel_read_safe': True,
'parallel_write_safe': True,
}
diff --git a/sphinx/texinputs/sphinxlatexobjects.sty b/sphinx/texinputs/sphinxlatexobjects.sty
index b4ff1f9d0..a2038a9f1 100644
--- a/sphinx/texinputs/sphinxlatexobjects.sty
+++ b/sphinx/texinputs/sphinxlatexobjects.sty
@@ -146,6 +146,27 @@
\item[{#1\sphinxcode{(}\py@sigparams{#2}{#3}\strut}]
\pysigadjustitemsep
}
+
+\def\sphinxoptionalextraspace{0.5mm}
+\newcommand{\pysigwithonelineperarg}[3]{%
+ % render each argument on its own line
+ \item[#1\sphinxcode{(}\strut]
+ \leavevmode\par\nopagebreak
+ % this relies on \pysigstartsignatures having set \parskip to zero
+ \begingroup
+ \let\sphinxparamcomma\sphinxparamcommaoneperline
+ \def\sphinxoptionalhook{\ifvmode\else\kern\sphinxoptionalextraspace\relax\fi}%
+ % The very first \sphinxparam should not emit a \par hence a complication
+ % with a group and global definition here as it may occur in a \sphinxoptional
+ \global\let\spx@sphinxparam\sphinxparam
+ \gdef\sphinxparam{\gdef\sphinxparam{\par\spx@sphinxparam}\spx@sphinxparam}%
+ #2\par
+ \endgroup
+ \global\let\sphinxparam\spx@sphinxparam
+ % fulllineitems sets \labelwidth to be like \leftmargin
+ \nopagebreak\noindent\kern-\labelwidth\sphinxcode{)}{#3}
+ \pysigadjustitemsep
+}
\newcommand{\pysigadjustitemsep}{%
% adjust \itemsep to control the separation with the next signature
% sharing common description
diff --git a/sphinx/texinputs/sphinxlatexstyletext.sty b/sphinx/texinputs/sphinxlatexstyletext.sty
index 913bc8210..292facc91 100644
--- a/sphinx/texinputs/sphinxlatexstyletext.sty
+++ b/sphinx/texinputs/sphinxlatexstyletext.sty
@@ -58,7 +58,8 @@
\protected\def\sphinxparam#1{\emph{#1}}
% \optional is used for ``[, arg]``, i.e. desc_optional nodes.
\long\protected\def\sphinxoptional#1{%
- {\textnormal{\Large[}}{#1}\hspace{0.5mm}{\textnormal{\Large]}}}
+ {\sphinxoptionalhook\textnormal{\Large[}}{#1}\hspace{0.5mm}{\textnormal{\Large]}}}
+\let\sphinxoptionalhook\empty
% additional customizable styling
\def\sphinxstyleindexentry #1{\texttt{#1}}
@@ -112,6 +113,11 @@
% Special characters
%
+\def\sphinxparamcomma{, }% by default separate parameters with comma + space
+% If the signature is rendered with one line per param, this wil be used
+% instead (this \texttt makes the comma slightly more distinctive).
+\def\sphinxparamcommaoneperline{\texttt{,}}
+%
% The \kern\z@ is to prevent en-dash and em-dash TeX ligatures.
% A linebreak can occur after the dash in regular text (this is
% normal behaviour of "-" in TeX, it is not related to \kern\z@).
diff --git a/sphinx/themes/basic/static/basic.css_t b/sphinx/themes/basic/static/basic.css_t
index 9d5e4419d..9ae180267 100644
--- a/sphinx/themes/basic/static/basic.css_t
+++ b/sphinx/themes/basic/static/basic.css_t
@@ -670,6 +670,16 @@ dd {
margin-left: 30px;
}
+.sig dd {
+ margin-top: 0px;
+ margin-bottom: 0px;
+}
+
+.sig dl {
+ margin-top: 0px;
+ margin-bottom: 0px;
+}
+
dl > dd:last-child,
dl > dd:last-child > :last-child {
margin-bottom: 0;
diff --git a/sphinx/themes/epub/static/epub.css_t b/sphinx/themes/epub/static/epub.css_t
index 767d558be..15938cdc5 100644
--- a/sphinx/themes/epub/static/epub.css_t
+++ b/sphinx/themes/epub/static/epub.css_t
@@ -458,6 +458,11 @@ dl {
margin-bottom: 15px;
}
+.sig dl {
+ margin-top: 0px;
+ margin-bottom: 0px;
+}
+
dd p {
margin-top: 0px;
}
@@ -472,6 +477,11 @@ dd {
margin-left: 30px;
}
+.sig dd {
+ margin-top: 0px;
+ margin-bottom: 0px;
+}
+
dt:target, .highlighted {
background-color: #ddd;
}
diff --git a/sphinx/writers/html5.py b/sphinx/writers/html5.py
index ab26bab1e..e7d932286 100644
--- a/sphinx/writers/html5.py
+++ b/sphinx/writers/html5.py
@@ -150,14 +150,26 @@ class HTML5Translator(SphinxTranslator, BaseTranslator):
def visit_desc_parameterlist(self, node: Element) -> None:
self.body.append('<span class="sig-paren">(</span>')
- self.first_param = 1
+ self.is_first_param = True
self.optional_param_level = 0
+ self.params_left_at_level = 0
+ self.param_group_index = 0
+ # Counts as what we call a parameter group either a required parameter, or a
+ # set of contiguous optional ones.
+ self.list_is_required_param = [isinstance(c, addnodes.desc_parameter)
+ for c in node.children]
# How many required parameters are left.
- self.required_params_left = sum([isinstance(c, addnodes.desc_parameter)
- for c in node.children])
+ 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)
+ if self.multi_line_parameter_list:
+ self.body.append('\n\n')
+ self.body.append(self.starttag(node, 'dl'))
+ self.param_separator = self.param_separator.rstrip()
def depart_desc_parameterlist(self, node: Element) -> None:
+ if node.get('multi_line_parameter_list'):
+ self.body.append('</dl>\n\n')
self.body.append('<span class="sig-paren">)</span>')
# If required parameters are still to come, then put the comma after
@@ -167,28 +179,82 @@ class HTML5Translator(SphinxTranslator, BaseTranslator):
# foo([a, ]b, c[, d])
#
def visit_desc_parameter(self, node: Element) -> None:
- if self.first_param:
- self.first_param = 0
- elif not self.required_params_left:
+ on_separate_line = self.multi_line_parameter_list
+ if on_separate_line and not (self.is_first_param and self.optional_param_level > 0):
+ self.body.append(self.starttag(node, 'dd', ''))
+ if self.is_first_param:
+ self.is_first_param = False
+ elif not on_separate_line and not self.required_params_left:
self.body.append(self.param_separator)
if self.optional_param_level == 0:
self.required_params_left -= 1
+ else:
+ self.params_left_at_level -= 1
if not node.hasattr('noemph'):
self.body.append('<em class="sig-param">')
def depart_desc_parameter(self, node: Element) -> None:
if not node.hasattr('noemph'):
self.body.append('</em>')
- if self.required_params_left:
+ is_required = self.list_is_required_param[self.param_group_index]
+ if self.multi_line_parameter_list:
+ is_last_group = self.param_group_index + 1 == len(self.list_is_required_param)
+ next_is_required = (
+ not is_last_group
+ and self.list_is_required_param[self.param_group_index + 1]
+ )
+ opt_param_left_at_level = self.params_left_at_level > 0
+ if opt_param_left_at_level or is_required and (is_last_group or next_is_required):
+ self.body.append(self.param_separator)
+ self.body.append('</dd>\n')
+
+ elif self.required_params_left:
self.body.append(self.param_separator)
+ if is_required:
+ self.param_group_index += 1
+
def visit_desc_optional(self, node: Element) -> None:
+ self.params_left_at_level = sum([isinstance(c, addnodes.desc_parameter)
+ for c in node.children])
self.optional_param_level += 1
- self.body.append('<span class="optional">[</span>')
+ self.max_optional_param_level = self.optional_param_level
+ if self.multi_line_parameter_list:
+ # If the first parameter is optional, start a new line and open the bracket.
+ if self.is_first_param:
+ self.body.append(self.starttag(node, 'dd', ''))
+ self.body.append('<span class="optional">[</span>')
+ # Else, if there remains at least one required parameter, append the
+ # parameter separator, open a new bracket, and end the line.
+ elif self.required_params_left:
+ self.body.append(self.param_separator)
+ self.body.append('<span class="optional">[</span>')
+ self.body.append('</dd>\n')
+ # Else, open a new bracket, append the parameter separator,
+ # and end the line.
+ else:
+ self.body.append('<span class="optional">[</span>')
+ self.body.append(self.param_separator)
+ self.body.append('</dd>\n')
+ else:
+ self.body.append('<span class="optional">[</span>')
def depart_desc_optional(self, node: Element) -> None:
self.optional_param_level -= 1
- self.body.append('<span class="optional">]</span>')
+ 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:
+ 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:
+ self.body.append('</dd>\n')
+ else:
+ self.body.append('<span class="optional">]</span>')
+ if self.optional_param_level == 0:
+ self.param_group_index += 1
def visit_desc_annotation(self, node: Element) -> None:
self.body.append(self.starttag(node, 'em', '', CLASS='property'))
diff --git a/sphinx/writers/latex.py b/sphinx/writers/latex.py
index e7d31b70e..37c73ae5a 100644
--- a/sphinx/writers/latex.py
+++ b/sphinx/writers/latex.py
@@ -702,7 +702,10 @@ class LaTeXTranslator(SphinxTranslator):
def _visit_signature_line(self, node: Element) -> None:
for child in node:
if isinstance(child, addnodes.desc_parameterlist):
- self.body.append(CR + r'\pysiglinewithargsret{')
+ if child.get('multi_line_parameter_list'):
+ self.body.append(CR + r'\pysigwithonelineperarg{')
+ else:
+ self.body.append(CR + r'\pysiglinewithargsret{')
break
else:
self.body.append(CR + r'\pysigline{')
@@ -784,29 +787,82 @@ class LaTeXTranslator(SphinxTranslator):
def visit_desc_parameterlist(self, node: Element) -> None:
# close name, open parameterlist
self.body.append('}{')
- self.first_param = 1
+ self.is_first_param = True
+ self.optional_param_level = 0
+ self.params_left_at_level = 0
+ self.param_group_index = 0
+ # Counts as what we call a parameter group either a required parameter, or a
+ # set of contiguous optional ones.
+ self.list_is_required_param = [isinstance(c, addnodes.desc_parameter)
+ for c in node.children]
+ # How many required parameters are left.
+ 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)
def depart_desc_parameterlist(self, node: Element) -> None:
# close parameterlist, open return annotation
self.body.append('}{')
def visit_desc_parameter(self, node: Element) -> None:
- if not self.first_param:
- self.body.append(', ')
+ if self.is_first_param:
+ self.is_first_param = False
+ elif not self.multi_line_parameter_list and not self.required_params_left:
+ self.body.append(self.param_separator)
+ if self.optional_param_level == 0:
+ self.required_params_left -= 1
else:
- self.first_param = 0
+ self.params_left_at_level -= 1
if not node.hasattr('noemph'):
self.body.append(r'\sphinxparam{')
def depart_desc_parameter(self, node: Element) -> None:
if not node.hasattr('noemph'):
self.body.append('}')
+ is_required = self.list_is_required_param[self.param_group_index]
+ if self.multi_line_parameter_list:
+ is_last_group = self.param_group_index + 1 == len(self.list_is_required_param)
+ next_is_required = (
+ not is_last_group
+ and self.list_is_required_param[self.param_group_index + 1]
+ )
+ opt_param_left_at_level = self.params_left_at_level > 0
+ if opt_param_left_at_level or is_required and (is_last_group or next_is_required):
+ self.body.append(self.param_separator)
+
+ elif self.required_params_left:
+ self.body.append(self.param_separator)
+
+ if is_required:
+ self.param_group_index += 1
def visit_desc_optional(self, node: Element) -> None:
- self.body.append(r'\sphinxoptional{')
+ self.params_left_at_level = sum([isinstance(c, addnodes.desc_parameter)
+ for c in node.children])
+ self.optional_param_level += 1
+ self.max_optional_param_level = self.optional_param_level
+ if self.multi_line_parameter_list:
+ if self.is_first_param:
+ self.body.append(r'\sphinxoptional{')
+ elif self.required_params_left:
+ self.body.append(self.param_separator)
+ self.body.append(r'\sphinxoptional{')
+ else:
+ self.body.append(r'\sphinxoptional{')
+ self.body.append(self.param_separator)
+ else:
+ self.body.append(r'\sphinxoptional{')
def depart_desc_optional(self, node: Element) -> None:
+ self.optional_param_level -= 1
+ 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:
+ self.body.append(self.param_separator)
self.body.append('}')
+ if self.optional_param_level == 0:
+ self.param_group_index += 1
def visit_desc_annotation(self, node: Element) -> None:
self.body.append(r'\sphinxbfcode{\sphinxupquote{')
diff --git a/sphinx/writers/text.py b/sphinx/writers/text.py
index 3bce03ac6..8e3d9df24 100644
--- a/sphinx/writers/text.py
+++ b/sphinx/writers/text.py
@@ -594,24 +594,99 @@ class TextTranslator(SphinxTranslator):
def visit_desc_parameterlist(self, node: Element) -> None:
self.add_text('(')
- self.first_param = 1
+ self.is_first_param = True
+ self.optional_param_level = 0
+ self.params_left_at_level = 0
+ self.param_group_index = 0
+ # Counts as what we call a parameter group are either a required parameter, or a
+ # set of contiguous optional ones.
+ self.list_is_required_param = [isinstance(c, addnodes.desc_parameter)
+ for c in node.children]
+ 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)
+ if self.multi_line_parameter_list:
+ self.param_separator = self.param_separator.rstrip()
def depart_desc_parameterlist(self, node: Element) -> None:
self.add_text(')')
def visit_desc_parameter(self, node: Element) -> None:
- if not self.first_param:
- self.add_text(', ')
+ on_separate_line = self.multi_line_parameter_list
+ if on_separate_line and not (self.is_first_param and self.optional_param_level > 0):
+ self.new_state()
+ if self.is_first_param:
+ self.is_first_param = False
+ elif not on_separate_line and not self.required_params_left:
+ self.add_text(self.param_separator)
+ if self.optional_param_level == 0:
+ self.required_params_left -= 1
else:
- self.first_param = 0
+ self.params_left_at_level -= 1
+
self.add_text(node.astext())
+
+ is_required = self.list_is_required_param[self.param_group_index]
+ if on_separate_line:
+ is_last_group = self.param_group_index + 1 == len(self.list_is_required_param)
+ next_is_required = (
+ not is_last_group
+ and self.list_is_required_param[self.param_group_index + 1]
+ )
+ opt_param_left_at_level = self.params_left_at_level > 0
+ if opt_param_left_at_level or is_required and (is_last_group or next_is_required):
+ self.add_text(self.param_separator)
+ self.end_state(wrap=False, end=None)
+
+ elif self.required_params_left:
+ self.add_text(self.param_separator)
+
+ if is_required:
+ self.param_group_index += 1
raise nodes.SkipNode
def visit_desc_optional(self, node: Element) -> None:
- self.add_text('[')
+ self.params_left_at_level = sum([isinstance(c, addnodes.desc_parameter)
+ for c in node.children])
+ self.optional_param_level += 1
+ self.max_optional_param_level = self.optional_param_level
+ if self.multi_line_parameter_list:
+ # If the first parameter is optional, start a new line and open the bracket.
+ if self.is_first_param:
+ self.new_state()
+ self.add_text('[')
+ # Else, if there remains at least one required parameter, append the
+ # parameter separator, open a new bracket, and end the line.
+ elif self.required_params_left:
+ self.add_text(self.param_separator)
+ self.add_text('[')
+ self.end_state(wrap=False, end=None)
+ # Else, open a new bracket, append the parameter separator, and end the
+ # line.
+ else:
+ self.add_text('[')
+ self.add_text(self.param_separator)
+ self.end_state(wrap=False, end=None)
+ else:
+ self.add_text('[')
def depart_desc_optional(self, node: Element) -> None:
- self.add_text(']')
+ self.optional_param_level -= 1
+ 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:
+ 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:
+ self.end_state(wrap=False, end=None)
+
+ else:
+ self.add_text(']')
+ if self.optional_param_level == 0:
+ self.param_group_index += 1
def visit_desc_annotation(self, node: Element) -> None:
pass
diff --git a/tests/roots/test-domain-c-c_maximum_signature_line_length/conf.py b/tests/roots/test-domain-c-c_maximum_signature_line_length/conf.py
new file mode 100644
index 000000000..ba480ed28
--- /dev/null
+++ b/tests/roots/test-domain-c-c_maximum_signature_line_length/conf.py
@@ -0,0 +1 @@
+c_maximum_signature_line_length = len("str hello(str name)") - 1
diff --git a/tests/roots/test-domain-c-c_maximum_signature_line_length/index.rst b/tests/roots/test-domain-c-c_maximum_signature_line_length/index.rst
new file mode 100644
index 000000000..be20940ec
--- /dev/null
+++ b/tests/roots/test-domain-c-c_maximum_signature_line_length/index.rst
@@ -0,0 +1,4 @@
+domain-c-c_maximum_signature_line_length
+========================================
+
+.. c:function:: str hello(str name)
diff --git a/tests/roots/test-domain-cpp-cpp_maximum_signature_line_length/conf.py b/tests/roots/test-domain-cpp-cpp_maximum_signature_line_length/conf.py
new file mode 100644
index 000000000..1eb3a64bf
--- /dev/null
+++ b/tests/roots/test-domain-cpp-cpp_maximum_signature_line_length/conf.py
@@ -0,0 +1 @@
+cpp_maximum_signature_line_length = len("str hello(str name)") - 1
diff --git a/tests/roots/test-domain-cpp-cpp_maximum_signature_line_length/index.rst b/tests/roots/test-domain-cpp-cpp_maximum_signature_line_length/index.rst
new file mode 100644
index 000000000..425908cb9
--- /dev/null
+++ b/tests/roots/test-domain-cpp-cpp_maximum_signature_line_length/index.rst
@@ -0,0 +1,4 @@
+domain-cpp-cpp_maximum_signature_line_length
+============================================
+
+.. cpp:function:: str hello(str name)
diff --git a/tests/roots/test-domain-js-javascript_maximum_signature_line_length/conf.py b/tests/roots/test-domain-js-javascript_maximum_signature_line_length/conf.py
new file mode 100644
index 000000000..d7c9331bd
--- /dev/null
+++ b/tests/roots/test-domain-js-javascript_maximum_signature_line_length/conf.py
@@ -0,0 +1 @@
+javascript_maximum_signature_line_length = 1
diff --git a/tests/roots/test-domain-js-javascript_maximum_signature_line_length/index.rst b/tests/roots/test-domain-js-javascript_maximum_signature_line_length/index.rst
new file mode 100644
index 000000000..b79fc1a8f
--- /dev/null
+++ b/tests/roots/test-domain-js-javascript_maximum_signature_line_length/index.rst
@@ -0,0 +1,6 @@
+domain-js-maximum_signature_line_length
+=======================================
+
+.. js:function:: hello(name)
+
+.. js:function:: foo([a, [b, ]]c, d[, e, f])
diff --git a/tests/roots/test-domain-py-python_maximum_signature_line_length/conf.py b/tests/roots/test-domain-py-python_maximum_signature_line_length/conf.py
new file mode 100644
index 000000000..45f620db4
--- /dev/null
+++ b/tests/roots/test-domain-py-python_maximum_signature_line_length/conf.py
@@ -0,0 +1 @@
+python_maximum_signature_line_length = 1
diff --git a/tests/roots/test-domain-py-python_maximum_signature_line_length/index.rst b/tests/roots/test-domain-py-python_maximum_signature_line_length/index.rst
new file mode 100644
index 000000000..75e468305
--- /dev/null
+++ b/tests/roots/test-domain-py-python_maximum_signature_line_length/index.rst
@@ -0,0 +1,6 @@
+domain-py-maximum_signature_line_length
+=======================================
+
+.. py:function:: hello(name: str) -> str
+
+.. py:function:: foo([a, [b, ]]c, d[, e, f])
diff --git a/tests/test_build_latex.py b/tests/test_build_latex.py
index 7bf65b794..f6c336935 100644
--- a/tests/test_build_latex.py
+++ b/tests/test_build_latex.py
@@ -3,7 +3,7 @@
import os
import re
import subprocess
-from itertools import product
+from itertools import chain, product
from pathlib import Path
from shutil import copyfile
from subprocess import CalledProcessError
@@ -95,11 +95,18 @@ def skip_if_stylefiles_notfound(testfunc):
@skip_if_requested
@skip_if_stylefiles_notfound
@pytest.mark.parametrize(
- "engine,docclass",
- product(LATEX_ENGINES, DOCCLASSES),
+ "engine,docclass,python_maximum_signature_line_length",
+ # Only running test with `python_maximum_signature_line_length` not None with last
+ # LaTeX engine to reduce testing time, as if this configuration does not fail with
+ # one engine, it's almost impossible it would fail with another.
+ chain(
+ product(LATEX_ENGINES[:-1], DOCCLASSES, [None]),
+ product([LATEX_ENGINES[-1]], DOCCLASSES, [1]),
+ ),
)
-@pytest.mark.sphinx('latex')
-def test_build_latex_doc(app, status, warning, engine, docclass):
+@pytest.mark.sphinx('latex', freshenv=True)
+def test_build_latex_doc(app, status, warning, engine, docclass, python_maximum_signature_line_length):
+ app.config.python_maximum_signature_line_length = python_maximum_signature_line_length
app.config.intersphinx_mapping = {
'sphinx': ('https://www.sphinx-doc.org/en/master/', None),
}
@@ -113,7 +120,6 @@ def test_build_latex_doc(app, status, warning, engine, docclass):
normalize_intersphinx_mapping(app, app.config)
load_mappings(app)
app.builder.init()
-
LaTeXTranslator.ignore_missing_images = True
app.builder.build_all()
@@ -1734,3 +1740,16 @@ def test_duplicated_labels_before_module(app, status, warning):
# ensure that we did not forget any label to check
# and if so, report them nicely in case of failure
assert sorted(tested_labels) == sorted(output_labels)
+
+
+@pytest.mark.sphinx('latex', testroot='domain-py-python_maximum_signature_line_length',
+ confoverrides={'python_maximum_signature_line_length': 23})
+def test_one_parameter_per_line(app, status, warning):
+ app.builder.build_all()
+ result = (app.outdir / 'python.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{\\sphinxbfcode{\\sphinxupquote{hello}}}' in result)
+
+ assert ('\\pysigwithonelineperarg{\\sphinxbfcode{\\sphinxupquote{foo}}}' in result)
diff --git a/tests/test_domain_c.py b/tests/test_domain_c.py
index 1f894c84a..a63164845 100644
--- a/tests/test_domain_c.py
+++ b/tests/test_domain_c.py
@@ -7,7 +7,18 @@ from xml.etree import ElementTree
import pytest
from sphinx import addnodes
-from sphinx.addnodes import desc
+from sphinx.addnodes import (
+ desc,
+ desc_content,
+ desc_name,
+ desc_parameter,
+ desc_parameterlist,
+ desc_sig_name,
+ desc_sig_space,
+ desc_signature,
+ desc_signature_line,
+ pending_xref,
+)
from sphinx.domains.c import (
DefinitionError,
DefinitionParser,
@@ -19,6 +30,7 @@ from sphinx.domains.c import (
from sphinx.ext.intersphinx import load_mappings, normalize_intersphinx_mapping
from sphinx.testing import restructuredtext
from sphinx.testing.util import assert_node
+from sphinx.writers.text import STDINDENT
class Config:
@@ -814,3 +826,251 @@ def test_domain_c_parse_noindexentry(app):
assert_node(doctree, (addnodes.index, desc, addnodes.index, desc))
assert_node(doctree[0], addnodes.index, entries=[('single', 'f (C function)', 'c.f', '', None)])
assert_node(doctree[2], addnodes.index, entries=[])
+
+
+@pytest.mark.sphinx('html', confoverrides={
+ 'c_maximum_signature_line_length': len("str hello(str name)"),
+})
+def test_cfunction_signature_with_c_maximum_signature_line_length_equal(app):
+ text = ".. c:function:: str hello(str name)"
+ doctree = restructuredtext.parse(app, text)
+ assert_node(doctree, (
+ addnodes.index,
+ [desc, (
+ [desc_signature, (
+ [desc_signature_line, (
+ pending_xref,
+ desc_sig_space,
+ [desc_name, [desc_sig_name, "hello"]],
+ desc_parameterlist,
+ )],
+ )],
+ desc_content,
+ )],
+ ))
+ assert_node(doctree[1], addnodes.desc, desctype="function",
+ domain="c", objtype="function", noindex=False)
+ assert_node(doctree[1][0][0][3], [desc_parameterlist, desc_parameter, (
+ [pending_xref, [desc_sig_name, "str"]],
+ desc_sig_space,
+ [desc_sig_name, "name"],
+ )])
+ assert_node(doctree[1][0][0][3], desc_parameterlist, multi_line_parameter_list=False)
+
+
+@pytest.mark.sphinx('html', confoverrides={
+ 'c_maximum_signature_line_length': len("str hello(str name)"),
+})
+def test_cfunction_signature_with_c_maximum_signature_line_length_force_single(app):
+ text = (".. c:function:: str hello(str names)\n"
+ " :single-line-parameter-list:")
+ doctree = restructuredtext.parse(app, text)
+ assert_node(doctree, (
+ addnodes.index,
+ [desc, (
+ [desc_signature, (
+ [desc_signature_line, (
+ pending_xref,
+ desc_sig_space,
+ [desc_name, [desc_sig_name, "hello"]],
+ desc_parameterlist,
+ )],
+ )],
+ desc_content,
+ )],
+ ))
+ assert_node(doctree[1], addnodes.desc, desctype="function",
+ domain="c", objtype="function", noindex=False)
+ assert_node(doctree[1][0][0][3], [desc_parameterlist, desc_parameter, (
+ [pending_xref, [desc_sig_name, "str"]],
+ desc_sig_space,
+ [desc_sig_name, "names"],
+ )])
+ assert_node(doctree[1][0][0][3], desc_parameterlist, multi_line_parameter_list=False)
+
+
+@pytest.mark.sphinx('html', confoverrides={
+ 'c_maximum_signature_line_length': len("str hello(str name)"),
+})
+def test_cfunction_signature_with_c_maximum_signature_line_length_break(app):
+ text = ".. c:function:: str hello(str names)"
+ doctree = restructuredtext.parse(app, text)
+ assert_node(doctree, (
+ addnodes.index,
+ [desc, (
+ [desc_signature, (
+ [desc_signature_line, (
+ pending_xref,
+ desc_sig_space,
+ [desc_name, [desc_sig_name, "hello"]],
+ desc_parameterlist,
+ )],
+ )],
+ desc_content,
+ )],
+ ))
+ assert_node(doctree[1], addnodes.desc, desctype="function",
+ domain="c", objtype="function", noindex=False)
+ assert_node(doctree[1][0][0][3], [desc_parameterlist, desc_parameter, (
+ [pending_xref, [desc_sig_name, "str"]],
+ desc_sig_space,
+ [desc_sig_name, "names"],
+ )])
+ assert_node(doctree[1][0][0][3], desc_parameterlist, multi_line_parameter_list=True)
+
+
+@pytest.mark.sphinx('html', confoverrides={
+ 'maximum_signature_line_length': len("str hello(str name)"),
+})
+def test_cfunction_signature_with_maximum_signature_line_length_equal(app):
+ text = ".. c:function:: str hello(str name)"
+ doctree = restructuredtext.parse(app, text)
+ assert_node(doctree, (
+ addnodes.index,
+ [desc, (
+ [desc_signature, (
+ [desc_signature_line, (
+ pending_xref,
+ desc_sig_space,
+ [desc_name, [desc_sig_name, "hello"]],
+ desc_parameterlist,
+ )],
+ )],
+ desc_content,
+ )],
+ ))
+ assert_node(doctree[1], addnodes.desc, desctype="function",
+ domain="c", objtype="function", noindex=False)
+ assert_node(doctree[1][0][0][3], [desc_parameterlist, desc_parameter, (
+ [pending_xref, [desc_sig_name, "str"]],
+ desc_sig_space,
+ [desc_sig_name, "name"],
+ )])
+ assert_node(doctree[1][0][0][3], desc_parameterlist, multi_line_parameter_list=False)
+
+
+@pytest.mark.sphinx('html', confoverrides={
+ 'maximum_signature_line_length': len("str hello(str name)"),
+})
+def test_cfunction_signature_with_maximum_signature_line_length_force_single(app):
+ text = (".. c:function:: str hello(str names)\n"
+ " :single-line-parameter-list:")
+ doctree = restructuredtext.parse(app, text)
+ assert_node(doctree, (
+ addnodes.index,
+ [desc, (
+ [desc_signature, (
+ [desc_signature_line, (
+ pending_xref,
+ desc_sig_space,
+ [desc_name, [desc_sig_name, "hello"]],
+ desc_parameterlist,
+ )],
+ )],
+ desc_content,
+ )],
+ ))
+ assert_node(doctree[1], addnodes.desc, desctype="function",
+ domain="c", objtype="function", noindex=False)
+ assert_node(doctree[1][0][0][3], [desc_parameterlist, desc_parameter, (
+ [pending_xref, [desc_sig_name, "str"]],
+ desc_sig_space,
+ [desc_sig_name, "names"],
+ )])
+ assert_node(doctree[1][0][0][3], desc_parameterlist, multi_line_parameter_list=False)
+
+
+@pytest.mark.sphinx('html', confoverrides={
+ 'maximum_signature_line_length': len("str hello(str name)"),
+})
+def test_cfunction_signature_with_maximum_signature_line_length_break(app):
+ text = ".. c:function:: str hello(str names)"
+ doctree = restructuredtext.parse(app, text)
+ assert_node(doctree, (
+ addnodes.index,
+ [desc, (
+ [desc_signature, (
+ [desc_signature_line, (
+ pending_xref,
+ desc_sig_space,
+ [desc_name, [desc_sig_name, "hello"]],
+ desc_parameterlist,
+ )],
+ )],
+ desc_content,
+ )],
+ ))
+ assert_node(doctree[1], addnodes.desc, desctype="function",
+ domain="c", objtype="function", noindex=False)
+ assert_node(doctree[1][0][0][3], [desc_parameterlist, desc_parameter, (
+ [pending_xref, [desc_sig_name, "str"]],
+ desc_sig_space,
+ [desc_sig_name, "names"],
+ )])
+ assert_node(doctree[1][0][0][3], desc_parameterlist, multi_line_parameter_list=True)
+
+
+@pytest.mark.sphinx('html', confoverrides={
+ 'c_maximum_signature_line_length': len('str hello(str name)'),
+ 'maximum_signature_line_length': 1,
+})
+def test_c_maximum_signature_line_length_overrides_global(app):
+ text = '.. c:function:: str hello(str name)'
+ doctree = restructuredtext.parse(app, text)
+ assert_node(doctree, (
+ addnodes.index,
+ [desc, (
+ [desc_signature, (
+ [desc_signature_line, (
+ pending_xref,
+ desc_sig_space,
+ [desc_name, [desc_sig_name, 'hello']],
+ desc_parameterlist,
+ )]
+ )],
+ desc_content,
+ )],
+ ))
+ assert_node(doctree[1], addnodes.desc, desctype='function',
+ domain='c', objtype='function', noindex=False)
+ assert_node(doctree[1][0][0][3], [desc_parameterlist, desc_parameter, (
+ [pending_xref, [desc_sig_name, "str"]],
+ desc_sig_space,
+ [desc_sig_name, 'name'],
+ )])
+ assert_node(doctree[1][0][0][3], desc_parameterlist, multi_line_parameter_list=False)
+
+
+@pytest.mark.sphinx('html', testroot='domain-c-c_maximum_signature_line_length')
+def test_domain_c_c_maximum_signature_line_length_in_html(app, status, warning):
+ app.build()
+ content = (app.outdir / 'index.html').read_text(encoding='utf-8')
+ expected = """\
+
+<dl>
+<dd>\
+<span class="n"><span class="pre">str</span></span>\
+<span class="w"> </span>\
+<span class="n"><span class="pre">name</span></span>,\
+</dd>
+</dl>
+
+<span class="sig-paren">)</span>\
+<a class="headerlink" href="#c.hello" title="Permalink to this definition">¶</a>\
+<br />\
+</dt>
+"""
+ assert expected in content
+
+
+@pytest.mark.sphinx(
+ 'text', testroot='domain-c-c_maximum_signature_line_length',
+)
+def test_domain_c_c_maximum_signature_line_length_in_text(app, status, warning):
+ 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("str name,"))
+
+ assert expected_parameter_list_hello in content
diff --git a/tests/test_domain_cpp.py b/tests/test_domain_cpp.py
index 55542e655..49b0c22e9 100644
--- a/tests/test_domain_cpp.py
+++ b/tests/test_domain_cpp.py
@@ -8,7 +8,18 @@ import pytest
import sphinx.domains.cpp as cppDomain
from sphinx import addnodes
-from sphinx.addnodes import desc
+from sphinx.addnodes import (
+ desc,
+ desc_content,
+ desc_name,
+ desc_parameter,
+ desc_parameterlist,
+ desc_sig_name,
+ desc_sig_space,
+ desc_signature,
+ desc_signature_line,
+ pending_xref,
+)
from sphinx.domains.cpp import (
DefinitionError,
DefinitionParser,
@@ -20,6 +31,7 @@ from sphinx.domains.cpp import (
from sphinx.ext.intersphinx import load_mappings, normalize_intersphinx_mapping
from sphinx.testing import restructuredtext
from sphinx.testing.util import assert_node
+from sphinx.writers.text import STDINDENT
def parse(name, string):
@@ -1486,3 +1498,243 @@ def test_domain_cpp_normalize_unspecialized_template_args(make_app, app_params):
)
warning = app2._warning.getvalue()
assert 'Internal C++ domain error during symbol merging' not in warning
+
+
+@pytest.mark.sphinx('html', confoverrides={
+ 'cpp_maximum_signature_line_length': len('str hello(str name)'),
+})
+def test_cpp_function_signature_with_cpp_maximum_signature_line_length_equal(app):
+ text = '.. cpp:function:: str hello(str name)'
+ doctree = restructuredtext.parse(app, text)
+ assert_node(doctree, (
+ addnodes.index,
+ [desc, (
+ [desc_signature, (
+ [desc_signature_line, (
+ pending_xref,
+ desc_sig_space,
+ [desc_name, [desc_sig_name, 'hello']],
+ desc_parameterlist,
+ )],
+ )],
+ desc_content,
+ )],
+ ))
+ assert_node(doctree[1], addnodes.desc, desctype='function',
+ domain='cpp', objtype='function', noindex=False)
+ assert_node(doctree[1][0][0][3], [desc_parameterlist, desc_parameter, (
+ [pending_xref, [desc_sig_name, 'str']],
+ desc_sig_space,
+ [desc_sig_name, 'name'],
+ )])
+ assert_node(doctree[1][0][0][3], desc_parameterlist, multi_line_parameter_list=False)
+
+
+@pytest.mark.sphinx('html', confoverrides={
+ 'cpp_maximum_signature_line_length': len('str hello(str name)'),
+})
+def test_cpp_function_signature_with_cpp_maximum_signature_line_length_force_single(app):
+ text = ('.. cpp:function:: str hello(str names)\n'
+ ' :single-line-parameter-list:')
+ doctree = restructuredtext.parse(app, text)
+ assert_node(doctree, (
+ addnodes.index,
+ [desc, (
+ [desc_signature, (
+ [desc_signature_line, (
+ pending_xref,
+ desc_sig_space,
+ [desc_name, [desc_sig_name, 'hello']],
+ desc_parameterlist,
+ )],
+ )],
+ desc_content,
+ )],
+ ))
+ assert_node(doctree[1], addnodes.desc, desctype='function',
+ domain='cpp', objtype='function', noindex=False)
+ assert_node(doctree[1][0][0][3], [desc_parameterlist, desc_parameter, (
+ [pending_xref, [desc_sig_name, 'str']],
+ desc_sig_space,
+ [desc_sig_name, 'names']),
+ ])
+ assert_node(doctree[1][0][0][3], desc_parameterlist, multi_line_parameter_list=False)
+
+
+@pytest.mark.sphinx('html', confoverrides={
+ 'cpp_maximum_signature_line_length': len("str hello(str name)"),
+})
+def test_cpp_function_signature_with_cpp_maximum_signature_line_length_break(app):
+ text = '.. cpp:function:: str hello(str names)'
+ doctree = restructuredtext.parse(app, text)
+ assert_node(doctree, (
+ addnodes.index,
+ [desc, (
+ [desc_signature, (
+ [desc_signature_line, (
+ pending_xref,
+ desc_sig_space,
+ [desc_name, [desc_sig_name, 'hello']],
+ desc_parameterlist,
+ )],
+ )],
+ desc_content,
+ )],
+ ))
+ assert_node(doctree[1], addnodes.desc, desctype='function',
+ domain='cpp', objtype='function', noindex=False)
+ assert_node(doctree[1][0][0][3], [desc_parameterlist, desc_parameter, (
+ [pending_xref, [desc_sig_name, 'str']],
+ desc_sig_space,
+ [desc_sig_name, 'names']),
+ ])
+ assert_node(doctree[1][0][0][3], desc_parameterlist, multi_line_parameter_list=True)
+
+
+@pytest.mark.sphinx('html', confoverrides={
+ 'maximum_signature_line_length': len('str hello(str name)'),
+})
+def test_cpp_function_signature_with_maximum_signature_line_length_equal(app):
+ text = '.. cpp:function:: str hello(str name)'
+ doctree = restructuredtext.parse(app, text)
+ assert_node(doctree, (
+ addnodes.index,
+ [desc, (
+ [desc_signature, (
+ [desc_signature_line, (
+ pending_xref,
+ desc_sig_space,
+ [desc_name, [desc_sig_name, 'hello']],
+ desc_parameterlist,
+ )],
+ )],
+ desc_content,
+ )],
+ ))
+ assert_node(doctree[1], addnodes.desc, desctype='function',
+ domain='cpp', objtype='function', noindex=False)
+ assert_node(doctree[1][0][0][3], [desc_parameterlist, desc_parameter, (
+ [pending_xref, [desc_sig_name, 'str']],
+ desc_sig_space,
+ [desc_sig_name, 'name'],
+ )])
+ assert_node(doctree[1][0][0][3], desc_parameterlist, multi_line_parameter_list=False)
+
+
+@pytest.mark.sphinx('html', confoverrides={
+ 'maximum_signature_line_length': len('str hello(str name)'),
+})
+def test_cpp_function_signature_with_maximum_signature_line_length_force_single(app):
+ text = ('.. cpp:function:: str hello(str names)\n'
+ ' :single-line-parameter-list:')
+ doctree = restructuredtext.parse(app, text)
+ assert_node(doctree, (
+ addnodes.index,
+ [desc, (
+ [desc_signature, (
+ [desc_signature_line, (
+ pending_xref,
+ desc_sig_space,
+ [desc_name, [desc_sig_name, 'hello']],
+ desc_parameterlist,
+ )],
+ )],
+ desc_content,
+ )],
+ ))
+ assert_node(doctree[1], addnodes.desc, desctype='function',
+ domain='cpp', objtype='function', noindex=False)
+ assert_node(doctree[1][0][0][3], [desc_parameterlist, desc_parameter, (
+ [pending_xref, [desc_sig_name, 'str']],
+ desc_sig_space,
+ [desc_sig_name, 'names']),
+ ])
+ assert_node(doctree[1][0][0][3], desc_parameterlist, multi_line_parameter_list=False)
+
+
+@pytest.mark.sphinx('html', confoverrides={
+ 'maximum_signature_line_length': len("str hello(str name)"),
+})
+def test_cpp_function_signature_with_maximum_signature_line_length_break(app):
+ text = '.. cpp:function:: str hello(str names)'
+ doctree = restructuredtext.parse(app, text)
+ assert_node(doctree, (
+ addnodes.index,
+ [desc, (
+ [desc_signature, (
+ [desc_signature_line, (
+ pending_xref,
+ desc_sig_space,
+ [desc_name, [desc_sig_name, 'hello']],
+ desc_parameterlist,
+ )],
+ )],
+ desc_content,
+ )],
+ ))
+ assert_node(doctree[1], addnodes.desc, desctype='function',
+ domain='cpp', objtype='function', noindex=False)
+ assert_node(doctree[1][0][0][3], [desc_parameterlist, desc_parameter, (
+ [pending_xref, [desc_sig_name, 'str']],
+ desc_sig_space,
+ [desc_sig_name, 'names']),
+ ])
+ assert_node(doctree[1][0][0][3], desc_parameterlist, multi_line_parameter_list=True)
+
+
+@pytest.mark.sphinx('html', confoverrides={
+ 'cpp_maximum_signature_line_length': len('str hello(str name)'),
+ 'maximum_signature_line_length': 1,
+})
+def test_cpp_maximum_signature_line_length_overrides_global(app):
+ text = '.. cpp:function:: str hello(str name)'
+ doctree = restructuredtext.parse(app, text)
+ assert_node(doctree, (
+ addnodes.index,
+ [desc, ([desc_signature, ([desc_signature_line, (pending_xref,
+ desc_sig_space,
+ [desc_name, [desc_sig_name, "hello"]],
+ desc_parameterlist)])],
+ desc_content)],
+ ))
+ assert_node(doctree[1], addnodes.desc, desctype='function',
+ domain='cpp', objtype='function', noindex=False)
+ assert_node(doctree[1][0][0][3], [desc_parameterlist, desc_parameter, (
+ [pending_xref, [desc_sig_name, 'str']],
+ desc_sig_space,
+ [desc_sig_name, 'name'],
+ )])
+ assert_node(doctree[1][0][0][3], desc_parameterlist, multi_line_parameter_list=False)
+
+
+@pytest.mark.sphinx('html', testroot='domain-cpp-cpp_maximum_signature_line_length')
+def test_domain_cpp_cpp_maximum_signature_line_length_in_html(app, status, warning):
+ app.build()
+ content = (app.outdir / 'index.html').read_text(encoding='utf-8')
+ expected = """\
+
+<dl>
+<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>,\
+</dd>
+</dl>
+
+<span class="sig-paren">)</span>\
+<a class="headerlink" href=\
+"""
+ assert expected in content
+
+
+@pytest.mark.sphinx(
+ 'text', testroot='domain-cpp-cpp_maximum_signature_line_length',
+)
+def test_domain_cpp_cpp_maximum_signature_line_length_in_text(app, status, warning):
+ 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("str name,"))
+
+ assert expected_parameter_list_hello in content
diff --git a/tests/test_domain_js.py b/tests/test_domain_js.py
index 634b02b37..e927ad072 100644
--- a/tests/test_domain_js.py
+++ b/tests/test_domain_js.py
@@ -22,6 +22,7 @@ from sphinx.addnodes import (
from sphinx.domains.javascript import JavaScriptDomain
from sphinx.testing import restructuredtext
from sphinx.testing.util import assert_node
+from sphinx.writers.text import STDINDENT
@pytest.mark.sphinx('dummy', testroot='domain-js')
@@ -242,3 +243,263 @@ def test_module_content_line_number(app):
source, line = docutils.utils.get_source_line(xrefs[0])
assert 'index.rst' in source
assert line == 3
+
+
+@pytest.mark.sphinx('html', confoverrides={
+ 'javascript_maximum_signature_line_length': len("hello(name)"),
+})
+def test_jsfunction_signature_with_javascript_maximum_signature_line_length_equal(app):
+ text = ".. js:function:: hello(name)"
+ doctree = restructuredtext.parse(app, text)
+ assert_node(doctree, (
+ addnodes.index,
+ [desc, (
+ [desc_signature, (
+ [desc_name, ([desc_sig_name, "hello"])],
+ desc_parameterlist,
+ )],
+ desc_content,
+ )],
+ ))
+ assert_node(doctree[1], desc, desctype="function",
+ domain="js", objtype="function", noindex=False)
+ assert_node(doctree[1][0][1],
+ [desc_parameterlist, desc_parameter, ([desc_sig_name, "name"])])
+ assert_node(doctree[1][0][1], desc_parameterlist, multi_line_parameter_list=False)
+
+
+@pytest.mark.sphinx('html', confoverrides={
+ 'javascript_maximum_signature_line_length': len("hello(name)"),
+})
+def test_jsfunction_signature_with_javascript_maximum_signature_line_length_force_single(app):
+ text = (".. js:function:: hello(names)\n"
+ " :single-line-parameter-list:")
+ doctree = restructuredtext.parse(app, text)
+ assert_node(doctree, (
+ addnodes.index,
+ [desc, (
+ [desc_signature, (
+ [desc_name, ([desc_sig_name, "hello"])],
+ desc_parameterlist,
+ )],
+ desc_content,
+ )],
+ ))
+ assert_node(doctree[1], desc, desctype="function",
+ domain="js", objtype="function", noindex=False)
+ assert_node(doctree[1][0][1],
+ [desc_parameterlist, desc_parameter, ([desc_sig_name, "names"])])
+ assert_node(doctree[1][0][1], desc_parameterlist, multi_line_parameter_list=False)
+
+
+@pytest.mark.sphinx('html', confoverrides={
+ 'javascript_maximum_signature_line_length': len("hello(name)"),
+})
+def test_jsfunction_signature_with_javascript_maximum_signature_line_length_break(app):
+ text = ".. js:function:: hello(names)"
+ doctree = restructuredtext.parse(app, text)
+ assert_node(doctree, (
+ addnodes.index,
+ [desc, (
+ [desc_signature, (
+ [desc_name, ([desc_sig_name, "hello"])],
+ desc_parameterlist,
+ )],
+ desc_content,
+ )],
+ ))
+ assert_node(doctree[1], desc, desctype="function",
+ domain="js", objtype="function", noindex=False)
+ assert_node(doctree[1][0][1],
+ [desc_parameterlist, desc_parameter, ([desc_sig_name, "names"])])
+ assert_node(doctree[1][0][1], desc_parameterlist, multi_line_parameter_list=True)
+
+
+@pytest.mark.sphinx('html', confoverrides={
+ 'maximum_signature_line_length': len("hello(name)"),
+})
+def test_jsfunction_signature_with_maximum_signature_line_length_equal(app):
+ text = ".. js:function:: hello(name)"
+ doctree = restructuredtext.parse(app, text)
+ assert_node(doctree, (
+ addnodes.index,
+ [desc, (
+ [desc_signature, (
+ [desc_name, ([desc_sig_name, "hello"])],
+ desc_parameterlist,
+ )],
+ desc_content,
+ )],
+ ))
+ assert_node(doctree[1], desc, desctype="function",
+ domain="js", objtype="function", noindex=False)
+ assert_node(doctree[1][0][1],
+ [desc_parameterlist, desc_parameter, ([desc_sig_name, "name"])])
+ assert_node(doctree[1][0][1], desc_parameterlist, multi_line_parameter_list=False)
+
+
+@pytest.mark.sphinx('html', confoverrides={
+ 'maximum_signature_line_length': len("hello(name)"),
+})
+def test_jsfunction_signature_with_maximum_signature_line_length_force_single(app):
+ text = (".. js:function:: hello(names)\n"
+ " :single-line-parameter-list:")
+ doctree = restructuredtext.parse(app, text)
+ assert_node(doctree, (
+ addnodes.index,
+ [desc, (
+ [desc_signature, (
+ [desc_name, ([desc_sig_name, "hello"])],
+ desc_parameterlist,
+ )],
+ desc_content,
+ )],
+ ))
+ assert_node(doctree[1], desc, desctype="function",
+ domain="js", objtype="function", noindex=False)
+ assert_node(doctree[1][0][1],
+ [desc_parameterlist, desc_parameter, ([desc_sig_name, "names"])])
+ assert_node(doctree[1][0][1], desc_parameterlist, multi_line_parameter_list=False)
+
+
+@pytest.mark.sphinx('html', confoverrides={
+ 'maximum_signature_line_length': len("hello(name)"),
+})
+def test_jsfunction_signature_with_maximum_signature_line_length_break(app):
+ text = ".. js:function:: hello(names)"
+ doctree = restructuredtext.parse(app, text)
+ assert_node(doctree, (
+ addnodes.index,
+ [desc, (
+ [desc_signature, (
+ [desc_name, ([desc_sig_name, "hello"])],
+ desc_parameterlist,
+ )],
+ desc_content,
+ )],
+ ))
+ assert_node(doctree[1], desc, desctype="function",
+ domain="js", objtype="function", noindex=False)
+ assert_node(doctree[1][0][1],
+ [desc_parameterlist, desc_parameter, ([desc_sig_name, "names"])])
+ assert_node(doctree[1][0][1], desc_parameterlist, multi_line_parameter_list=True)
+
+
+@pytest.mark.sphinx(
+ 'html',
+ confoverrides={
+ 'javascript_maximum_signature_line_length': len("hello(name)"),
+ 'maximum_signature_line_length': 1,
+ },
+)
+def test_javascript_maximum_signature_line_length_overrides_global(app):
+ text = ".. js:function:: hello(name)"
+ doctree = restructuredtext.parse(app, text)
+ expected_doctree = (addnodes.index,
+ [desc, ([desc_signature, ([desc_name, ([desc_sig_name, "hello"])],
+ desc_parameterlist)],
+ desc_content)])
+ assert_node(doctree, expected_doctree)
+ assert_node(doctree[1], desc, desctype="function",
+ domain="js", objtype="function", noindex=False)
+ expected_sig = [desc_parameterlist, desc_parameter, [desc_sig_name, "name"]]
+ assert_node(doctree[1][0][1], expected_sig)
+ assert_node(doctree[1][0][1], desc_parameterlist, multi_line_parameter_list=False)
+
+
+@pytest.mark.sphinx(
+ 'html', testroot='domain-js-javascript_maximum_signature_line_length',
+)
+def test_domain_js_javascript_maximum_signature_line_length_in_html(app, status, warning):
+ 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="Permalink 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="Permalink 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',
+)
+def test_domain_js_javascript_maximum_signature_line_length_in_text(app, status, warning):
+ 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_domain_py.py b/tests/test_domain_py.py
index 6cac6cba1..2b84f01c0 100644
--- a/tests/test_domain_py.py
+++ b/tests/test_domain_py.py
@@ -38,6 +38,7 @@ from sphinx.domains.python import (
)
from sphinx.testing import restructuredtext
from sphinx.testing.util import assert_node
+from sphinx.writers.text import STDINDENT
def parse(sig):
@@ -1460,6 +1461,308 @@ def test_signature_line_number(app, include_options):
assert line == 1
+@pytest.mark.sphinx('html', confoverrides={
+ 'python_maximum_signature_line_length': len("hello(name: str) -> str"),
+})
+def test_pyfunction_signature_with_python_maximum_signature_line_length_equal(app):
+ text = ".. py:function:: hello(name: str) -> str"
+ doctree = restructuredtext.parse(app, text)
+ assert_node(doctree, (
+ addnodes.index,
+ [desc, (
+ [desc_signature, (
+ [desc_name, "hello"],
+ desc_parameterlist,
+ [desc_returns, pending_xref, "str"],
+ )],
+ desc_content,
+ )],
+ ))
+ assert_node(doctree[1], addnodes.desc, desctype="function",
+ domain="py", objtype="function", noindex=False)
+ assert_node(doctree[1][0][1], [desc_parameterlist, desc_parameter, (
+ [desc_sig_name, "name"],
+ [desc_sig_punctuation, ":"],
+ desc_sig_space,
+ [nodes.inline, pending_xref, "str"],
+ )])
+ assert_node(doctree[1][0][1], desc_parameterlist, multi_line_parameter_list=False)
+
+
+@pytest.mark.sphinx('html', confoverrides={
+ 'python_maximum_signature_line_length': len("hello(name: str) -> str"),
+})
+def test_pyfunction_signature_with_python_maximum_signature_line_length_force_single(app):
+ text = (".. py:function:: hello(names: str) -> str\n"
+ " :single-line-parameter-list:")
+ doctree = restructuredtext.parse(app, text)
+ assert_node(doctree, (
+ addnodes.index,
+ [desc, (
+ [desc_signature, (
+ [desc_name, "hello"],
+ desc_parameterlist,
+ [desc_returns, pending_xref, "str"],
+ )],
+ desc_content,
+ )],
+ ))
+ assert_node(doctree[1], addnodes.desc, desctype="function",
+ domain="py", objtype="function", noindex=False)
+ assert_node(doctree[1][0][1], [desc_parameterlist, desc_parameter, (
+ [desc_sig_name, "names"],
+ [desc_sig_punctuation, ":"],
+ desc_sig_space,
+ [nodes.inline, pending_xref, "str"],
+ )])
+ assert_node(doctree[1][0][1], desc_parameterlist, multi_line_parameter_list=False)
+
+
+@pytest.mark.sphinx('html', confoverrides={
+ 'python_maximum_signature_line_length': len("hello(name: str) -> str"),
+})
+def test_pyfunction_signature_with_python_maximum_signature_line_length_break(app):
+ text = ".. py:function:: hello(names: str) -> str"
+ doctree = restructuredtext.parse(app, text)
+ assert_node(doctree, (
+ addnodes.index,
+ [desc, (
+ [desc_signature, (
+ [desc_name, "hello"],
+ desc_parameterlist,
+ [desc_returns, pending_xref, "str"],
+ )],
+ desc_content,
+ )],
+ ))
+ assert_node(doctree[1], addnodes.desc, desctype="function",
+ domain="py", objtype="function", noindex=False)
+ assert_node(doctree[1][0][1], [desc_parameterlist, desc_parameter, (
+ [desc_sig_name, "names"],
+ [desc_sig_punctuation, ":"],
+ desc_sig_space,
+ [nodes.inline, pending_xref, "str"],
+ )])
+ assert_node(doctree[1][0][1], desc_parameterlist, multi_line_parameter_list=True)
+
+
+@pytest.mark.sphinx('html', confoverrides={
+ 'maximum_signature_line_length': len("hello(name: str) -> str"),
+})
+def test_pyfunction_signature_with_maximum_signature_line_length_equal(app):
+ text = ".. py:function:: hello(name: str) -> str"
+ doctree = restructuredtext.parse(app, text)
+ assert_node(doctree, (
+ addnodes.index,
+ [desc, (
+ [desc_signature, (
+ [desc_name, "hello"],
+ desc_parameterlist,
+ [desc_returns, pending_xref, "str"],
+ )],
+ desc_content,
+ )],
+ ))
+ assert_node(doctree[1], addnodes.desc, desctype="function",
+ domain="py", objtype="function", noindex=False)
+ assert_node(doctree[1][0][1], [desc_parameterlist, desc_parameter, (
+ [desc_sig_name, "name"],
+ [desc_sig_punctuation, ":"],
+ desc_sig_space,
+ [nodes.inline, pending_xref, "str"],
+ )])
+ assert_node(doctree[1][0][1], desc_parameterlist, multi_line_parameter_list=False)
+
+
+@pytest.mark.sphinx('html', confoverrides={
+ 'maximum_signature_line_length': len("hello(name: str) -> str"),
+})
+def test_pyfunction_signature_with_maximum_signature_line_length_force_single(app):
+ text = (".. py:function:: hello(names: str) -> str\n"
+ " :single-line-parameter-list:")
+ doctree = restructuredtext.parse(app, text)
+ assert_node(doctree, (
+ addnodes.index,
+ [desc, (
+ [desc_signature, (
+ [desc_name, "hello"],
+ desc_parameterlist,
+ [desc_returns, pending_xref, "str"],
+ )],
+ desc_content,
+ )],
+ ))
+ assert_node(doctree[1], addnodes.desc, desctype="function",
+ domain="py", objtype="function", noindex=False)
+ assert_node(doctree[1][0][1], [desc_parameterlist, desc_parameter, (
+ [desc_sig_name, "names"],
+ [desc_sig_punctuation, ":"],
+ desc_sig_space,
+ [nodes.inline, pending_xref, "str"],
+ )])
+ assert_node(doctree[1][0][1], desc_parameterlist, multi_line_parameter_list=False)
+
+
+@pytest.mark.sphinx('html', confoverrides={
+ 'maximum_signature_line_length': len("hello(name: str) -> str"),
+})
+def test_pyfunction_signature_with_maximum_signature_line_length_break(app):
+ text = ".. py:function:: hello(names: str) -> str"
+ doctree = restructuredtext.parse(app, text)
+ assert_node(doctree, (
+ addnodes.index,
+ [desc, (
+ [desc_signature, (
+ [desc_name, "hello"],
+ desc_parameterlist,
+ [desc_returns, pending_xref, "str"],
+ )],
+ desc_content,
+ )],
+ ))
+ assert_node(doctree[1], addnodes.desc, desctype="function",
+ domain="py", objtype="function", noindex=False)
+ assert_node(doctree[1][0][1], [desc_parameterlist, desc_parameter, (
+ [desc_sig_name, "names"],
+ [desc_sig_punctuation, ":"],
+ desc_sig_space,
+ [nodes.inline, pending_xref, "str"],
+ )])
+ assert_node(doctree[1][0][1], desc_parameterlist, multi_line_parameter_list=True)
+
+
+@pytest.mark.sphinx(
+ 'html',
+ confoverrides={
+ 'python_maximum_signature_line_length': len("hello(name: str) -> str"),
+ 'maximum_signature_line_length': 1,
+ },
+)
+def test_python_maximum_signature_line_length_overrides_global(app):
+ text = ".. py:function:: hello(name: str) -> str"
+ doctree = restructuredtext.parse(app, text)
+ expected_doctree = (addnodes.index,
+ [desc, ([desc_signature, ([desc_name, "hello"],
+ desc_parameterlist,
+ [desc_returns, pending_xref, "str"])],
+ desc_content)])
+ assert_node(doctree, expected_doctree)
+ assert_node(doctree[1], addnodes.desc, desctype="function",
+ domain="py", objtype="function", noindex=False)
+ signame_node = [desc_sig_name, "name"]
+ expected_sig = [desc_parameterlist, desc_parameter, (signame_node,
+ [desc_sig_punctuation, ":"],
+ desc_sig_space,
+ [nodes.inline, pending_xref, "str"])]
+ assert_node(doctree[1][0][1], expected_sig)
+ assert_node(doctree[1][0][1], desc_parameterlist, multi_line_parameter_list=False)
+
+
+@pytest.mark.sphinx(
+ 'html', testroot='domain-py-python_maximum_signature_line_length',
+)
+def test_domain_py_python_maximum_signature_line_length_in_html(app, status, warning):
+ 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="Permalink 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="Permalink 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',
+)
+def test_domain_py_python_maximum_signature_line_length_in_text(app, status, warning):
+ 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
+
+
def test_module_content_line_number(app):
text = (".. py:module:: foo\n" +
"\n" +