Merge branch 'sphinx-doc:master' into master

This commit is contained in:
Caleb Chiam 2022-03-05 13:00:51 -05:00 committed by GitHub
commit ca0d432a36
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 115 additions and 49 deletions

View File

@ -107,6 +107,12 @@ Bugs fixed
unhashable object unhashable object
* #9529: LaTeX: named auto numbered footnote (ex. ``[#named]``) that is referred * #9529: LaTeX: named auto numbered footnote (ex. ``[#named]``) that is referred
multiple times was rendered to a question mark multiple times was rendered to a question mark
* #9924: LaTeX: multi-line :rst:dir:`cpp:function` directive has big vertical
spacing in Latexpdf
* #10158: LaTeX: excessive whitespace since v4.4.0 for undocumented
variables/structure members
* #10175: LaTeX: named footnote reference is linked to an incorrect footnote if
the name is also used in the different document
* #10181: napoleon: attributes are displayed like class attributes for google * #10181: napoleon: attributes are displayed like class attributes for google
style docstrings when :confval:`napoleon_use_ivar` is enabled style docstrings when :confval:`napoleon_use_ivar` is enabled
* #10122: sphinx-build: make.bat does not check the installation of sphinx-build * #10122: sphinx-build: make.bat does not check the installation of sphinx-build

View File

@ -57,7 +57,7 @@ Notice several things:
- Sphinx parsed the argument of the ``.. py:function`` directive and - Sphinx parsed the argument of the ``.. py:function`` directive and
highlighted the module, the function name, and the parameters appropriately. highlighted the module, the function name, and the parameters appropriately.
- The directive content includes a one-line description of the function, - The directive content includes a one-line description of the function,
as well as a :ref:`info field list <info-field-lists>` containing the function as well as an :ref:`info field list <info-field-lists>` containing the function
parameter, its expected type, the return value, and the return type. parameter, its expected type, the return value, and the return type.
.. note:: .. note::

View File

@ -1,7 +1,7 @@
%% MODULE RELEASE DATA AND OBJECT DESCRIPTIONS %% MODULE RELEASE DATA AND OBJECT DESCRIPTIONS
% %
% change this info string if making any custom modification % change this info string if making any custom modification
\ProvidesFile{sphinxlatexobjects.sty}[2021/12/05 documentation environments] \ProvidesFile{sphinxlatexobjects.sty}[2022/01/13 documentation environments]
% Provides support for this output mark-up from Sphinx latex writer: % Provides support for this output mark-up from Sphinx latex writer:
% %
@ -77,56 +77,86 @@
% Signatures, possibly multi-line % Signatures, possibly multi-line
% %
% For legacy reasons Sphinx uses LaTeX \list and \item's for signatures
% This is delicate:
% - the actual item label is not typeset immediately by \item but later as part
% of the \everypar which will be triggered by either next paragraph or a manual
% \leavevmode, or if nothing in-between by the next \item,
% - \begingroup <set-up>\item[foo] <setup>\endgroup leads to errors,
% - vertical space depends on \parskip and \itemsep values in somewhat
% subtle manners.
%
% Since the 2022/01/13 version things are simpler as \parskip is simply set
% to zero during execution of \pysigline/\pysiglinewithargsret
%
% Parameter for separation via \itemsep of multiple signatures with common desc
\newlength\sphinxsignaturesep
\setlength\sphinxsignaturesep{\smallskipamount}
% latex.py outputs mark-up like this:
% \pysigstartsignatures <signatures> \pysigstopsignatures <actual desc>
\newcommand{\pysigstartsignatures}{%
% store current \parskip and \itemsep
\edef\pysig@restore@itemsep@and@parskip{%
\itemsep\the\itemsep\relax
\parskip\the\parskip\relax
}%
% set them to control the spacing between signatures sharing common desc
\parskip\z@skip
\itemsep\sphinxsignaturesep
}
\newcommand{\pysigstopsignatures}{%
% 1) encourage a pagebreak in an attempt to try to avoid last
% signature ending up separated from description (due to voodoo next)
\penalty-100
% 2) some voodoo to separate last signature from description in a manner
% robust with respect to the latter being itself a LaTeX list object
\leavevmode\par\kern-\baselineskip\item[\strut]
%
\leavevmode
% it is important \leavevmode was issued before the \parskip reset, and
% it is also needed for the case of an object desc itself a LaTeX \list
% now restore \itemsep and \parskip
\pysig@restore@itemsep@and@parskip
}
%
% Use a \parbox to accomodate long argument list in signatures
% LaTeX did not imagine that an \item label could need multi-line rendering
\newlength{\py@argswidth} \newlength{\py@argswidth}
\newcommand{\py@sigparams}[2]{% \newcommand{\py@sigparams}[2]{%
% The \py@argswidth has been computed in \pysiglinewithargsret to make this % The \py@argswidth has been computed in \pysiglinewithargsret to make the
% occupy full available width on line. % argument list use full available width
\parbox[t]{\py@argswidth}{\raggedright #1\sphinxcode{)}#2\strut}% \parbox[t]{\py@argswidth}{\raggedright #1\sphinxcode{)}#2\strut}%
% final strut is to help get correct vertical separation in case of multi-line % final strut is to help get correct vertical separation
% box with the item contents.
} }
\newcommand{\pysigline}[1]{% \newcommand{\pysigline}[1]{%
% the \py@argswidth is available we use it despite its name (no "args" here) % as \py@argswidth is available, we use it but no "args" here
% the \relax\relax is because \py@argswidth is a "skip" variable and the first % the \relax\relax is because \py@argswidth is a "skip" variable
% \relax only ends its "dimen" part % this will make the label occupy the full available linewidth
\py@argswidth=\dimexpr\linewidth+\labelwidth\relax\relax \py@argswidth=\dimexpr\linewidth+\labelwidth\relax\relax
\item[{\parbox[t]{\py@argswidth}{\raggedright #1\strut}}] \item[{\parbox[t]{\py@argswidth}{\raggedright #1\strut}}]
\futurelet\sphinx@token\pysigline@preparevspace@i \pysigadjustitemsep
} }
\newcommand{\pysiglinewithargsret}[3]{% \newcommand{\pysiglinewithargsret}[3]{%
\settowidth{\py@argswidth}{#1\sphinxcode{(}}% \settowidth{\py@argswidth}{#1\sphinxcode{(}}%
\py@argswidth=\dimexpr\linewidth+\labelwidth-\py@argswidth\relax\relax \py@argswidth=\dimexpr\linewidth+\labelwidth-\py@argswidth\relax\relax
\item[{#1\sphinxcode{(}\py@sigparams{#2}{#3}\strut}] \item[{#1\sphinxcode{(}\py@sigparams{#2}{#3}\strut}]
\futurelet\sphinx@token\pysigline@preparevspace@i \pysigadjustitemsep
} }
\def\pysigline@preparevspace@i{% \newcommand{\pysigadjustitemsep}{%
\ifx\sphinx@token\@sptoken % adjust \itemsep to control the separation with the next signature
\expandafter\pysigline@preparevspace@again % sharing common description
\else\expandafter\pysigline@preparevspace@ii \ifsphinxsigismultiline
\fi % inside a multiline signature, no extra vertical spacing
} % ("multiline" here does not refer to possibly long
\@firstofone{\def\pysigline@preparevspace@again} {\futurelet\sphinx@token\pysigline@preparevspace@i} % list of arguments, but to a cpp domain feature)
\long\def\pysigline@preparevspace@ii#1{% \itemsep\z@skip
\ifx\sphinx@token\bgroup\expandafter\@firstoftwo
\else \else
\ifx\sphinx@token\phantomsection \itemsep\sphinxsignaturesep
\else
% this strange incantation is because at its root LaTeX in fact did not
% imagine a multi-line label, it is always wrapped in a horizontal box at core
% LaTeX level and we have to find tricks to get correct interline distances.
% It interacts badly with a follow-up \phantomsection hence the test above
\leavevmode\par\nobreak\vskip-\parskip\prevdepth\dp\strutbox
\fi
\expandafter\@secondoftwo
\fi \fi
{{#1}}{#1}%
}
\newcommand{\pysigstartmultiline}{%
\def\pysigstartmultiline{\vskip\smallskipamount\parskip\z@skip\itemsep\z@skip}%
\edef\pysigstopmultiline
{\noexpand\leavevmode\parskip\the\parskip\relax\itemsep\the\itemsep\relax}%
\parskip\z@skip\itemsep\z@skip
} }
\newif\ifsphinxsigismultiline
\newcommand{\pysigstartmultiline}{\sphinxsigismultilinetrue}%
\newcommand{\pysigstopmultiline}{\sphinxsigismultilinefalse\itemsep\sphinxsignaturesep}%
% Production lists % Production lists
% %

View File

@ -167,8 +167,9 @@ const Search = {
setIndex: (index) => { setIndex: (index) => {
Search._index = index; Search._index = index;
if (Search._queued_query !== null) { if (Search._queued_query !== null) {
const query = Search._queued_query;
Search._queued_query = null; Search._queued_query = null;
Search.query(Search._queued_query); Search.query(query);
} }
}, },

View File

@ -291,6 +291,7 @@ class LaTeXTranslator(SphinxTranslator):
self.in_parsed_literal = 0 self.in_parsed_literal = 0
self.compact_list = 0 self.compact_list = 0
self.first_param = 0 self.first_param = 0
self.in_desc_signature = False
sphinxpkgoptions = [] sphinxpkgoptions = []
@ -528,6 +529,7 @@ class LaTeXTranslator(SphinxTranslator):
def visit_start_of_file(self, node: Element) -> None: def visit_start_of_file(self, node: Element) -> None:
self.curfilestack.append(node['docname']) self.curfilestack.append(node['docname'])
self.body.append(CR + r'\sphinxstepscope' + CR)
def depart_start_of_file(self, node: Element) -> None: def depart_start_of_file(self, node: Element) -> None:
self.curfilestack.pop() self.curfilestack.pop()
@ -672,6 +674,9 @@ class LaTeXTranslator(SphinxTranslator):
self.table.has_problematic = True self.table.has_problematic = True
def depart_desc(self, node: Element) -> None: def depart_desc(self, node: Element) -> None:
if self.in_desc_signature:
self.body.append(CR + r'\pysigstopsignatures')
self.in_desc_signature = False
if self.config.latex_show_urls == 'footnote': if self.config.latex_show_urls == 'footnote':
self.body.append(CR + r'\end{fulllineitems}\end{savenotes}' + BLANKLINE) self.body.append(CR + r'\end{fulllineitems}\end{savenotes}' + BLANKLINE)
else: else:
@ -680,10 +685,10 @@ class LaTeXTranslator(SphinxTranslator):
def _visit_signature_line(self, node: Element) -> None: def _visit_signature_line(self, node: Element) -> None:
for child in node: for child in node:
if isinstance(child, addnodes.desc_parameterlist): if isinstance(child, addnodes.desc_parameterlist):
self.body.append(r'\pysiglinewithargsret{') self.body.append(CR + r'\pysiglinewithargsret{')
break break
else: else:
self.body.append(r'\pysigline{') self.body.append(CR + r'\pysigline{')
def _depart_signature_line(self, node: Element) -> None: def _depart_signature_line(self, node: Element) -> None:
self.body.append('}') self.body.append('}')
@ -694,18 +699,19 @@ class LaTeXTranslator(SphinxTranslator):
else: else:
hyper = '' hyper = ''
self.body.append(hyper) self.body.append(hyper)
if not self.in_desc_signature:
self.in_desc_signature = True
self.body.append(CR + r'\pysigstartsignatures')
if not node.get('is_multiline'): if not node.get('is_multiline'):
self._visit_signature_line(node) self._visit_signature_line(node)
else: else:
self.body.append('%' + CR) self.body.append(CR + r'\pysigstartmultiline')
self.body.append(r'\pysigstartmultiline' + CR)
def depart_desc_signature(self, node: Element) -> None: def depart_desc_signature(self, node: Element) -> None:
if not node.get('is_multiline'): if not node.get('is_multiline'):
self._depart_signature_line(node) self._depart_signature_line(node)
else: else:
self.body.append('%' + CR) self.body.append(CR + r'\pysigstopmultiline')
self.body.append(r'\pysigstopmultiline')
def visit_desc_signature_line(self, node: Element) -> None: def visit_desc_signature_line(self, node: Element) -> None:
self._visit_signature_line(node) self._visit_signature_line(node)
@ -714,7 +720,9 @@ class LaTeXTranslator(SphinxTranslator):
self._depart_signature_line(node) self._depart_signature_line(node)
def visit_desc_content(self, node: Element) -> None: def visit_desc_content(self, node: Element) -> None:
pass assert self.in_desc_signature
self.body.append(CR + r'\pysigstopsignatures')
self.in_desc_signature = False
def depart_desc_content(self, node: Element) -> None: def depart_desc_content(self, node: Element) -> None:
pass pass

View File

@ -1192,6 +1192,7 @@ def test_latex_table_tabulars(app, status, warning):
tables = {} tables = {}
for chap in re.split(r'\\(?:section|chapter){', result)[1:]: for chap in re.split(r'\\(?:section|chapter){', result)[1:]:
sectname, content = chap.split('}', 1) sectname, content = chap.split('}', 1)
content = re.sub(r'\\sphinxstepscope', '', content) # filter a separator
tables[sectname] = content.strip() tables[sectname] = content.strip()
def get_expected(name): def get_expected(name):
@ -1261,6 +1262,7 @@ def test_latex_table_longtable(app, status, warning):
tables = {} tables = {}
for chap in re.split(r'\\(?:section|chapter){', result)[1:]: for chap in re.split(r'\\(?:section|chapter){', result)[1:]:
sectname, content = chap.split('}', 1) sectname, content = chap.split('}', 1)
content = re.sub(r'\\sphinxstepscope', '', content) # filter a separator
tables[sectname] = content.strip() tables[sectname] = content.strip()
def get_expected(name): def get_expected(name):

View File

@ -599,6 +599,11 @@ def test_mocked_module_imports(app, warning):
@pytest.mark.sphinx('html', testroot='ext-autodoc', @pytest.mark.sphinx('html', testroot='ext-autodoc',
confoverrides={'autodoc_typehints': "signature"}) confoverrides={'autodoc_typehints': "signature"})
def test_autodoc_typehints_signature(app): def test_autodoc_typehints_signature(app):
if sys.version_info < (3, 11):
type_o = "~typing.Optional[~typing.Any]"
else:
type_o = "~typing.Any"
options = {"members": None, options = {"members": None,
"undoc-members": None} "undoc-members": None}
actual = do_autodoc(app, 'module', 'target.typehints', options) actual = do_autodoc(app, 'module', 'target.typehints', options)
@ -612,7 +617,7 @@ def test_autodoc_typehints_signature(app):
' :type: int', ' :type: int',
'', '',
'', '',
'.. py:class:: Math(s: str, o: ~typing.Optional[~typing.Any] = None)', '.. py:class:: Math(s: str, o: %s = None)' % type_o,
' :module: target.typehints', ' :module: target.typehints',
'', '',
'', '',
@ -1146,6 +1151,11 @@ def test_autodoc_typehints_description_and_type_aliases(app):
@pytest.mark.sphinx('html', testroot='ext-autodoc', @pytest.mark.sphinx('html', testroot='ext-autodoc',
confoverrides={'autodoc_typehints_format': "fully-qualified"}) confoverrides={'autodoc_typehints_format': "fully-qualified"})
def test_autodoc_typehints_format_fully_qualified(app): def test_autodoc_typehints_format_fully_qualified(app):
if sys.version_info < (3, 11):
type_o = "typing.Optional[typing.Any]"
else:
type_o = "typing.Any"
options = {"members": None, options = {"members": None,
"undoc-members": None} "undoc-members": None}
actual = do_autodoc(app, 'module', 'target.typehints', options) actual = do_autodoc(app, 'module', 'target.typehints', options)
@ -1159,7 +1169,7 @@ def test_autodoc_typehints_format_fully_qualified(app):
' :type: int', ' :type: int',
'', '',
'', '',
'.. py:class:: Math(s: str, o: typing.Optional[typing.Any] = None)', '.. py:class:: Math(s: str, o: %s = None)' % type_o,
' :module: target.typehints', ' :module: target.typehints',
'', '',
'', '',

View File

@ -190,7 +190,10 @@ def test_signature_annotations():
# Space around '=' for defaults # Space around '=' for defaults
sig = inspect.signature(f7) sig = inspect.signature(f7)
assert stringify_signature(sig) == '(x: typing.Optional[int] = None, y: dict = {}) -> None' if sys.version_info < (3, 11):
assert stringify_signature(sig) == '(x: typing.Optional[int] = None, y: dict = {}) -> None'
else:
assert stringify_signature(sig) == '(x: int = None, y: dict = {}) -> None'
# Callable types # Callable types
sig = inspect.signature(f8) sig = inspect.signature(f8)
@ -261,11 +264,17 @@ def test_signature_annotations():
# show_return_annotation is False # show_return_annotation is False
sig = inspect.signature(f7) sig = inspect.signature(f7)
assert stringify_signature(sig, show_return_annotation=False) == '(x: typing.Optional[int] = None, y: dict = {})' if sys.version_info < (3, 11):
assert stringify_signature(sig, show_return_annotation=False) == '(x: typing.Optional[int] = None, y: dict = {})'
else:
assert stringify_signature(sig, show_return_annotation=False) == '(x: int = None, y: dict = {})'
# unqualified_typehints is True # unqualified_typehints is True
sig = inspect.signature(f7) sig = inspect.signature(f7)
assert stringify_signature(sig, unqualified_typehints=True) == '(x: ~typing.Optional[int] = None, y: dict = {}) -> None' if sys.version_info < (3, 11):
assert stringify_signature(sig, unqualified_typehints=True) == '(x: ~typing.Optional[int] = None, y: dict = {}) -> None'
else:
assert stringify_signature(sig, unqualified_typehints=True) == '(x: int = None, y: dict = {}) -> None'
@pytest.mark.skipif(sys.version_info < (3, 8), reason='python 3.8+ is required.') @pytest.mark.skipif(sys.version_info < (3, 8), reason='python 3.8+ is required.')