diff --git a/CHANGES b/CHANGES
index 058dd9f4a..2df1e910a 100644
--- a/CHANGES
+++ b/CHANGES
@@ -33,6 +33,7 @@ Incompatible changes
:confval:`viewcode_follow_imported_members` (refs: #4035)
* #1857: latex: :confval:`latex_show_pagerefs` does not add pagerefs for
citations
+* #4648: latex: Now "rubric" elements are rendered as unnumbered section title
* #4983: html: The URL for the productionlist has been changed
Deprecated
@@ -64,9 +65,14 @@ Deprecated
* ``sphinx.writers.latex.Table.caption_footnotetexts`` is deprecated
* ``sphinx.writers.latex.Table.header_footnotetexts`` is deprecated
* ``sphinx.writers.latex.LaTeXWriter.footnotestack`` is deprecated
+* ``sphinx.writers.latex.LaTeXWriter.in_container_literal_block`` is deprecated
+* ``sphinx.writers.latex.LaTeXWriter.next_section_ids`` is deprecated
+* ``sphinx.writers.latex.LaTeXWriter.next_hyperlink_ids`` is deprecated
* ``sphinx.writers.latex.LaTeXWriter.restrict_footnote()`` is deprecated
* ``sphinx.writers.latex.LaTeXWriter.unrestrict_footnote()`` is deprecated
-* ``LaTeXWriter.bibitems`` is deprecated
+* ``sphinx.writers.latex.LaTeXWriter.push_hyperlink_ids()`` is deprecated
+* ``sphinx.writers.latex.LaTeXWriter.pop_hyperlink_ids()`` is deprecated
+* ``sphinx.writers.latex.LaTeXWriter.bibitems`` is deprecated
* ``BuildEnvironment.load()`` is deprecated
* ``BuildEnvironment.loads()`` is deprecated
* ``BuildEnvironment.frompickle()`` is deprecated
@@ -113,6 +119,11 @@ Features added
configuration file
* #4866: Wrap graphviz diagrams in ``
`` tag
* Add :event:`viewcode-find-source` event to viewcode extension.
+* #4785: napoleon: Add strings to translation file for localisation
+* #4927: Display a warning when invalid values are passed to linenothreshold
+ option of highlight directive
+* C++, add a ``cpp:texpr`` role as a sibling to ``cpp:expr``.
+* C++, add support for unions.
Bugs fixed
----------
@@ -169,6 +180,14 @@ Bugs fixed
* #4979: latex: Incorrect escaping of curly braces in index entries
* #4956: autodoc: Failed to extract document from a subclass of the class on
mocked module
+* #4973: latex: glossary directive adds whitespace to each item
+* #4980: latex: Explicit labels on code blocks are duplicated
+* #4919: node.asdom() crashes if toctree has :numbered: option
+* #4914: autodoc: Parsing error when using dataclasses without default values
+* #4931: autodoc: crashed when handler for autodoc-skip-member raises an error
+* #4931: autodoc: crashed when subclass of mocked class are processed by
+ napoleon module
+* #5007: sphinx-build crashes when error log contains a "%" character
Testing
--------
diff --git a/doc/Makefile b/doc/Makefile
index c54236be0..293ccca2e 100644
--- a/doc/Makefile
+++ b/doc/Makefile
@@ -3,7 +3,7 @@
# You can set these variables from the command line.
SPHINXOPTS =
-SPHINXBUILD = python ../sphinx/cmd/build.py
+SPHINXBUILD = python3 ../sphinx/cmd/build.py
SPHINXPROJ = sphinx
SOURCEDIR = .
BUILDDIR = _build
diff --git a/doc/extdev/appapi.rst b/doc/extdev/appapi.rst
index 584cf375b..55be8d4b2 100644
--- a/doc/extdev/appapi.rst
+++ b/doc/extdev/appapi.rst
@@ -73,7 +73,7 @@ package.
.. automethod:: Sphinx.add_javascript(filename)
-.. automethod:: Sphinx.add_stylesheet(filename, alternate=None, title=None)
+.. automethod:: Sphinx.add_css_file(filename, **kwargs)
.. automethod:: Sphinx.add_latex_package(packagename, options=None)
diff --git a/doc/extdev/index.rst b/doc/extdev/index.rst
index d10ada464..ec64067e4 100644
--- a/doc/extdev/index.rst
+++ b/doc/extdev/index.rst
@@ -171,6 +171,21 @@ The following is a list of deprecated interface.
- 3.0
- N/A
+ * - ``sphinx.writers.latex.LaTeXWriter.in_container_literal_block``
+ - 1.8
+ - 3.0
+ - N/A
+
+ * - ``sphinx.writers.latex.LaTeXWriter.next_section_ids``
+ - 1.8
+ - 3.0
+ - N/A
+
+ * - ``sphinx.writers.latex.LaTeXWriter.next_hyperlink_ids``
+ - 1.8
+ - 3.0
+ - N/A
+
* - ``sphinx.writers.latex.LaTeXWriter.restrict_footnote()``
- 1.8
- 3.0
@@ -181,6 +196,16 @@ The following is a list of deprecated interface.
- 3.0
- N/A
+ * - ``sphinx.writers.latex.LaTeXWriter.push_hyperlink_ids()``
+ - 1.8
+ - 3.0
+ - N/A
+
+ * - ``sphinx.writers.latex.LaTeXWriter.pop_hyperlink_ids()``
+ - 1.8
+ - 3.0
+ - N/A
+
* - ``sphinx.writers.latex.LaTeXWriter.bibitems``
- 1.8
- 3.0
diff --git a/doc/usage/restructuredtext/domains.rst b/doc/usage/restructuredtext/domains.rst
index 452cf63ae..58d77b78e 100644
--- a/doc/usage/restructuredtext/domains.rst
+++ b/doc/usage/restructuredtext/domains.rst
@@ -569,10 +569,10 @@ visibility statement (``public``, ``private`` or ``protected``).
Full and partial template specialisations can be declared::
.. cpp:class:: template<> \
- std::array
+ std::array
.. cpp:class:: template \
- std::array
+ std::array
.. rst:directive:: .. cpp:function:: (member) function prototype
@@ -702,6 +702,10 @@ visibility statement (``public``, ``private`` or ``protected``).
.. cpp:enumerator:: MyEnum::myOtherEnumerator = 42
+.. rst:directive:: .. cpp:union:: name
+
+ Describe a union.
+
.. rst:directive:: .. cpp:concept:: template-parameter-list name
.. warning:: The support for concepts is experimental. It is based on the
@@ -815,24 +819,31 @@ Inline Expressions and Tpes
~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. rst:role:: cpp:expr
+ cpp:texpr
- A role for inserting a C++ expression or type as inline text. For example::
+ Insert a C++ expression or type either as inline code (``cpp:expr``)
+ or inline text (``cpp:texpr``). For example::
.. cpp:var:: int a = 42
.. cpp:function:: int f(int i)
- An expression: :cpp:expr:`a * f(a)`.
- A type: :cpp:expr:`const MySortedContainer&`.
+ An expression: :cpp:expr:`a * f(a)` (or as text: :cpp:texpr:`a * f(a)`).
+
+ A type: :cpp:expr:`const MySortedContainer&`
+ (or as text :cpp:texpr:`const MySortedContainer&`).
will be rendered as follows:
- .. cpp:var:: int a = 42
+ .. cpp:var:: int a = 42
- .. cpp:function:: int f(int i)
+ .. cpp:function:: int f(int i)
+
+ An expression: :cpp:expr:`a * f(a)` (or as text: :cpp:texpr:`a * f(a)`).
+
+ A type: :cpp:expr:`const MySortedContainer&`
+ (or as text :cpp:texpr:`const MySortedContainer&`).
- An expression: :cpp:expr:`a * f(a)`. A type: :cpp:expr:`const
- MySortedContainer&`.
Namespacing
~~~~~~~~~~~
@@ -880,7 +891,7 @@ The ``cpp:namespace-pop`` directive undoes the most recent
.. cpp:function:: std::size_t size() const
- or:::
+ or::
.. cpp:class:: template \
std::vector
@@ -949,20 +960,23 @@ These roles link to the given declaration types:
.. admonition:: Note on References with Templates Parameters/Arguments
- Sphinx's syntax to give references a custom title can interfere with linking
- to class templates, if nothing follows the closing angle bracket, i.e. if
- the link looks like this: ``:cpp:class:`MyClass```. This is
- interpreted as a link to ``int`` with a title of ``MyClass``. In this case,
- please escape the opening angle bracket with a backslash, like this:
- ``:cpp:class:`MyClass\```.
+ These roles follow the Sphinx :ref:`xref-syntax` rules. This means care must be
+ taken when referencing a (partial) template specialization, e.g. if the link looks like
+ this: ``:cpp:class:`MyClass```.
+ This is interpreted as a link to ``int`` with a title of ``MyClass``.
+ In this case, escape the opening angle bracket with a backslash,
+ like this: ``:cpp:class:`MyClass\```.
+
+ When a custom title is not needed it may be useful to use the roles for inline expressions,
+ :rst:role:`cpp:expr` and :rst:role:`cpp:texpr`, where angle brackets do not need escaping.
.. admonition:: Note on References to Overloaded Functions
It is currently impossible to link to a specific version of an overloaded
- method. Currently the C++ domain is the first domain that has basic support
- for overloaded methods and until there is more data for comparison we don't
- want to select a bad syntax to reference a specific overload. Currently
- Sphinx will link to the first overloaded version of the method / function.
+ function. Currently the C++ domain is the first domain that has basic
+ support for overloaded functions and until there is more data for comparison
+ we don't want to select a bad syntax to reference a specific overload.
+ Currently Sphinx will link to the first overloaded version of the function.
Declarations without template parameters and template arguments
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -983,13 +997,13 @@ Assume the following declarations.
.. cpp:class:: template \
Inner
-In general the reference must include the template paraemter declarations,
+In general the reference must include the template parameter declarations,
e.g., ``template\ Wrapper::Outer``
(:cpp:class:`template\ Wrapper::Outer`). Currently the lookup
only succeed if the template parameter identifiers are equal strings. That is,
``template\ Wrapper::Outer`` will not work.
-The inner class template can not be directly referenced, unless the current
+The inner class template cannot be directly referenced, unless the current
namespace is changed or the following shorthand is used. If a template
parameter list is omitted, then the lookup will assume either a template or a
non-template, but not a partial template specialisation. This means the
diff --git a/sphinx/builders/latex/__init__.py b/sphinx/builders/latex/__init__.py
index a98b80289..32e813241 100644
--- a/sphinx/builders/latex/__init__.py
+++ b/sphinx/builders/latex/__init__.py
@@ -20,7 +20,8 @@ from sphinx import package_dir, addnodes, highlighting
from sphinx.builders import Builder
from sphinx.builders.latex.transforms import (
BibliographyTransform, CitationReferenceTransform, MathReferenceTransform,
- FootnoteDocnameUpdater, LaTeXFootnoteTransform, ShowUrlsTransform
+ FootnoteDocnameUpdater, LaTeXFootnoteTransform, LiteralBlockTransform,
+ ShowUrlsTransform, DocumentTargetTransform,
)
from sphinx.config import string_classes, ENUM
from sphinx.environment import NoUri
@@ -223,7 +224,9 @@ class LaTeXBuilder(Builder):
transformer.set_environment(self.env)
transformer.add_transforms([BibliographyTransform,
ShowUrlsTransform,
- LaTeXFootnoteTransform])
+ LaTeXFootnoteTransform,
+ LiteralBlockTransform,
+ DocumentTargetTransform])
transformer.apply_transforms()
def finish(self):
diff --git a/sphinx/builders/latex/nodes.py b/sphinx/builders/latex/nodes.py
index c79adbae4..32ac7c6a0 100644
--- a/sphinx/builders/latex/nodes.py
+++ b/sphinx/builders/latex/nodes.py
@@ -12,6 +12,11 @@
from docutils import nodes
+class captioned_literal_block(nodes.container):
+ """A node for a container of literal_block having a caption."""
+ pass
+
+
class footnotemark(nodes.Inline, nodes.Referential, nodes.TextElement):
"""A node represents ``\footnotemark``."""
pass
diff --git a/sphinx/builders/latex/transforms.py b/sphinx/builders/latex/transforms.py
index 635f2489c..80e83c4db 100644
--- a/sphinx/builders/latex/transforms.py
+++ b/sphinx/builders/latex/transforms.py
@@ -13,7 +13,7 @@ from docutils import nodes
from sphinx import addnodes
from sphinx.builders.latex.nodes import (
- footnotemark, footnotetext, math_reference, thebibliography
+ captioned_literal_block, footnotemark, footnotetext, math_reference, thebibliography
)
from sphinx.transforms import SphinxTransform
@@ -566,3 +566,33 @@ class MathReferenceTransform(SphinxTransform):
if docname:
refnode = math_reference('', docname=docname, target=node['reftarget'])
node.replace_self(refnode)
+
+
+class LiteralBlockTransform(SphinxTransform):
+ """Replace container nodes for literal_block by captioned_literal_block."""
+ default_priority = 400
+
+ def apply(self):
+ # type: () -> None
+ if self.app.builder.name != 'latex':
+ return
+
+ for node in self.document.traverse(nodes.container):
+ if node['literal_block'] is True:
+ newnode = captioned_literal_block('', *node.children, **node.attributes)
+ node.replace_self(newnode)
+
+
+class DocumentTargetTransform(SphinxTransform):
+ """Add :doc label to the first section of each document."""
+ default_priority = 400
+
+ def apply(self):
+ # type: () -> None
+ if self.app.builder.name != 'latex':
+ return
+
+ for node in self.document.traverse(addnodes.start_of_file):
+ section = node.next_node(nodes.section)
+ if section:
+ section['ids'].append(':doc') # special label for :doc:
diff --git a/sphinx/directives/code.py b/sphinx/directives/code.py
index 713e5ad0c..20ad5a20c 100644
--- a/sphinx/directives/code.py
+++ b/sphinx/directives/code.py
@@ -45,18 +45,12 @@ class Highlight(SphinxDirective):
optional_arguments = 0
final_argument_whitespace = False
option_spec = {
- 'linenothreshold': directives.unchanged,
+ 'linenothreshold': directives.positive_int,
}
def run(self):
# type: () -> List[nodes.Node]
- if 'linenothreshold' in self.options:
- try:
- linenothreshold = int(self.options['linenothreshold'])
- except Exception:
- linenothreshold = 10
- else:
- linenothreshold = sys.maxsize
+ linenothreshold = self.options.get('linenothreshold', sys.maxsize)
return [addnodes.highlightlang(lang=self.arguments[0].strip(),
linenothreshold=linenothreshold)]
diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py
index 830f15f29..d247b4c7f 100644
--- a/sphinx/domains/cpp.py
+++ b/sphinx/domains/cpp.py
@@ -3196,6 +3196,27 @@ class ASTClass(ASTBase):
signode.pop()
+class ASTUnion(ASTBase):
+ def __init__(self, name):
+ # type: (Any) -> None
+ self.name = name
+
+ def get_id(self, version, objectType, symbol):
+ # type: (int, unicode, Symbol) -> unicode
+ if version == 1:
+ raise NoOldIdError()
+ return symbol.get_full_nested_name().get_id(version)
+
+ def __unicode__(self):
+ # type: () -> unicode
+ return text_type(self.name)
+
+ def describe_signature(self, signode, mode, env, symbol):
+ # type: (addnodes.desc_signature, unicode, BuildEnvironment, Symbol) -> None
+ _verify_description_mode(mode)
+ self.name.describe_signature(signode, mode, env, symbol=symbol)
+
+
class ASTEnum(ASTBase):
def __init__(self, name, scoped, underlyingType):
# type: (Any, unicode, Any) -> None
@@ -3361,6 +3382,8 @@ class ASTDeclaration(ASTBase):
pass
elif self.objectType == 'class':
mainDeclNode += addnodes.desc_annotation('class ', 'class ')
+ elif self.objectType == 'union':
+ mainDeclNode += addnodes.desc_annotation('union ', 'union ')
elif self.objectType == 'enum':
prefix = 'enum '
if self.scoped: # type: ignore
@@ -5259,6 +5282,11 @@ class DefinitionParser(object):
break
return ASTClass(name, final, bases)
+ def _parse_union(self):
+ # type: () -> ASTUnion
+ name = self._parse_nested_name()
+ return ASTUnion(name)
+
def _parse_enum(self):
# type: () -> ASTEnum
scoped = None # type: unicode # is set by CPPEnumObject
@@ -5466,7 +5494,7 @@ class DefinitionParser(object):
def parse_declaration(self, objectType):
# type: (unicode) -> ASTDeclaration
if objectType not in ('type', 'concept', 'member',
- 'function', 'class', 'enum', 'enumerator'):
+ 'function', 'class', 'union', 'enum', 'enumerator'):
raise Exception('Internal error, unknown objectType "%s".' % objectType)
visibility = None
templatePrefix = None
@@ -5505,6 +5533,8 @@ class DefinitionParser(object):
declaration = self._parse_type(named=True, outer='function')
elif objectType == 'class':
declaration = self._parse_class()
+ elif objectType == 'union':
+ declaration = self._parse_union()
elif objectType == 'enum':
declaration = self._parse_enum()
elif objectType == 'enumerator':
@@ -5807,6 +5837,16 @@ class CPPClassObject(CPPObject):
return parser.parse_declaration("class")
+class CPPUnionObject(CPPObject):
+ def get_index_text(self, name):
+ # type: (unicode) -> unicode
+ return _('%s (C++ union)') % name
+
+ def parse_definition(self, parser):
+ # type: (Any) -> Any
+ return parser.parse_declaration("union")
+
+
class CPPEnumObject(CPPObject):
def get_index_text(self, name):
# type: (unicode) -> unicode
@@ -5965,6 +6005,16 @@ class CPPXRefRole(XRefRole):
class CPPExprRole(object):
+ def __init__(self, asCode):
+ if asCode:
+ # render the expression as inline code
+ self.class_type = 'cpp-expr'
+ self.node_type = nodes.literal
+ else:
+ # render the expression as inline text
+ self.class_type = 'cpp-texpr'
+ self.node_type = nodes.inline
+
def __call__(self, typ, rawtext, text, lineno, inliner, options={}, content=[]):
class Warner(object):
def warn(self, msg):
@@ -5972,18 +6022,23 @@ class CPPExprRole(object):
text = utils.unescape(text).replace('\n', ' ')
env = inliner.document.settings.env
parser = DefinitionParser(text, Warner(), env.config)
+ # attempt to mimic XRefRole classes, except that...
+ classes = ['xref', 'cpp', self.class_type]
try:
ast = parser.parse_expression()
except DefinitionError as ex:
Warner().warn('Unparseable C++ expression: %r\n%s'
% (text, text_type(ex.description)))
- return [nodes.literal(text)], []
+ # see below
+ return [self.node_type(text, text, classes=classes)], []
parentSymbol = env.temp_data.get('cpp:parent_symbol', None)
if parentSymbol is None:
parentSymbol = env.domaindata['cpp']['root_symbol']
- p = nodes.literal()
- ast.describe_signature(p, 'markType', env, parentSymbol)
- return [p], []
+ # ...most if not all of these classes should really apply to the individual references,
+ # not the container node
+ signode = self.node_type(classes=classes)
+ ast.describe_signature(signode, 'markType', env, parentSymbol)
+ return [signode], []
class CPPDomain(Domain):
@@ -5992,6 +6047,7 @@ class CPPDomain(Domain):
label = 'C++'
object_types = {
'class': ObjType(_('class'), 'class', 'type', 'identifier'),
+ 'union': ObjType(_('union'), 'union', 'type', 'identifier'),
'function': ObjType(_('function'), 'function', 'func', 'type', 'identifier'),
'member': ObjType(_('member'), 'member', 'var'),
'type': ObjType(_('type'), 'type', 'identifier'),
@@ -6002,6 +6058,7 @@ class CPPDomain(Domain):
directives = {
'class': CPPClassObject,
+ 'union': CPPUnionObject,
'function': CPPFunctionObject,
'member': CPPMemberObject,
'var': CPPMemberObject,
@@ -6018,6 +6075,7 @@ class CPPDomain(Domain):
roles = {
'any': CPPXRefRole(),
'class': CPPXRefRole(),
+ 'union': CPPXRefRole(),
'func': CPPXRefRole(fix_parens=True),
'member': CPPXRefRole(),
'var': CPPXRefRole(),
@@ -6025,7 +6083,8 @@ class CPPDomain(Domain):
'concept': CPPXRefRole(),
'enum': CPPXRefRole(),
'enumerator': CPPXRefRole(),
- 'expr': CPPExprRole()
+ 'expr': CPPExprRole(asCode=True),
+ 'texpr': CPPExprRole(asCode=False)
}
initial_data = {
'root_symbol': Symbol(None, None, None, None, None, None),
diff --git a/sphinx/environment/__init__.py b/sphinx/environment/__init__.py
index 69649829f..e9f3338aa 100644
--- a/sphinx/environment/__init__.py
+++ b/sphinx/environment/__init__.py
@@ -313,19 +313,6 @@ class BuildEnvironment(object):
"""Like :meth:`warn`, but with source information taken from *node*."""
self._warnfunc(msg, '%s:%s' % get_source_line(node), **kwargs)
- def need_refresh(self, app):
- # type: (Sphinx) -> Tuple[bool, unicode]
- """Check refresh environment is needed.
-
- If needed, this method returns the reason for refresh.
- """
- if self.version != app.registry.get_envversion(app):
- return True, __('build environment version not current')
- elif self.srcdir != app.srcdir:
- return True, __('source directory has changed')
- else:
- return False, None
-
def clear_doc(self, docname):
# type: (unicode) -> None
"""Remove all traces of a source file in the inventory."""
diff --git a/sphinx/environment/collectors/toctree.py b/sphinx/environment/collectors/toctree.py
index dcbee07f7..3d8c89f43 100644
--- a/sphinx/environment/collectors/toctree.py
+++ b/sphinx/environment/collectors/toctree.py
@@ -172,10 +172,10 @@ class TocTreeCollector(EnvironmentCollector):
number = tuple(numstack)
else:
number = None
- secnums[subnode[0]['anchorname']] = \
- subnode[0]['secnumber'] = number
+ secnums[subnode[0]['anchorname']] = number
+ subnode[0]['secnumber'] = list(number)
if titlenode:
- titlenode['secnumber'] = number
+ titlenode['secnumber'] = list(number)
titlenode = None
elif isinstance(subnode, addnodes.toctree):
_walk_toctree(subnode, depth)
diff --git a/sphinx/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py
index 353188aaf..62143fda4 100644
--- a/sphinx/ext/autodoc/__init__.py
+++ b/sphinx/ext/autodoc/__init__.py
@@ -643,11 +643,17 @@ class Documenter(object):
# should be skipped
if self.env.app:
# let extensions preprocess docstrings
- skip_user = self.env.app.emit_firstresult(
- 'autodoc-skip-member', self.objtype, membername, member,
- not keep, self.options)
- if skip_user is not None:
- keep = not skip_user
+ try:
+ skip_user = self.env.app.emit_firstresult(
+ 'autodoc-skip-member', self.objtype, membername, member,
+ not keep, self.options)
+ if skip_user is not None:
+ keep = not skip_user
+ except Exception as exc:
+ logger.warning(__('autodoc: failed to determine %r to be documented.'
+ 'the following exception was raised:\n%s'),
+ member, exc)
+ keep = False
if keep:
ret.append((membername, member, isattr))
diff --git a/sphinx/ext/autodoc/importer.py b/sphinx/ext/autodoc/importer.py
index 789cfc1c1..bf6bfc03d 100644
--- a/sphinx/ext/autodoc/importer.py
+++ b/sphinx/ext/autodoc/importer.py
@@ -23,7 +23,7 @@ from sphinx.util.inspect import isenumclass, safe_getattr
if False:
# For type annotation
- from typing import Any, Callable, Dict, Generator, List, Optional, Tuple # NOQA
+ from typing import Any, Callable, Dict, Generator, Iterator, List, Optional, Tuple # NOQA
logger = logging.getLogger(__name__)
@@ -41,7 +41,7 @@ class _MockObject(object):
def __init__(self, *args, **kwargs):
# type: (Any, Any) -> None
- pass
+ self.__qualname__ = ''
def __len__(self):
# type: () -> int
@@ -52,8 +52,8 @@ class _MockObject(object):
return False
def __iter__(self):
- # type: () -> None
- pass
+ # type: () -> Iterator
+ return iter([])
def __mro_entries__(self, bases):
# type: (Tuple) -> Tuple
diff --git a/sphinx/ext/napoleon/docstring.py b/sphinx/ext/napoleon/docstring.py
index 530cec203..433ea94c0 100644
--- a/sphinx/ext/napoleon/docstring.py
+++ b/sphinx/ext/napoleon/docstring.py
@@ -20,6 +20,7 @@ from six import string_types, u
from six.moves import range
from sphinx.ext.napoleon.iterators import modify_iter
+from sphinx.locale import _
from sphinx.util.pycompat import UnicodeMixin
if False:
@@ -614,8 +615,13 @@ class GoogleDocstring(UnicodeMixin):
def _parse_examples_section(self, section):
# type: (unicode) -> List[unicode]
+ labels = {
+ 'example': _('Example'),
+ 'examples': _('Examples'),
+ } # type: Dict[unicode, unicode]
use_admonition = self._config.napoleon_use_admonition_for_examples
- return self._parse_generic_section(section, use_admonition)
+ label = labels.get(section.lower(), section)
+ return self._parse_generic_section(label, use_admonition)
def _parse_custom_generic_section(self, section):
# for now, no admonition for simple custom sections
@@ -652,12 +658,12 @@ class GoogleDocstring(UnicodeMixin):
field_role="keyword",
type_role="kwtype")
else:
- return self._format_fields('Keyword Arguments', fields)
+ return self._format_fields(_('Keyword Arguments'), fields)
def _parse_methods_section(self, section):
# type: (unicode) -> List[unicode]
lines = [] # type: List[unicode]
- for _name, _, _desc in self._consume_fields(parse_type=False):
+ for _name, _type, _desc in self._consume_fields(parse_type=False):
lines.append('.. method:: %s' % _name)
if _desc:
lines.extend([u''] + self._indent(_desc, 3))
@@ -667,11 +673,11 @@ class GoogleDocstring(UnicodeMixin):
def _parse_notes_section(self, section):
# type: (unicode) -> List[unicode]
use_admonition = self._config.napoleon_use_admonition_for_notes
- return self._parse_generic_section('Notes', use_admonition)
+ return self._parse_generic_section(_('Notes'), use_admonition)
def _parse_other_parameters_section(self, section):
# type: (unicode) -> List[unicode]
- return self._format_fields('Other Parameters', self._consume_fields())
+ return self._format_fields(_('Other Parameters'), self._consume_fields())
def _parse_parameters_section(self, section):
# type: (unicode) -> List[unicode]
@@ -679,7 +685,7 @@ class GoogleDocstring(UnicodeMixin):
if self._config.napoleon_use_param:
return self._format_docutils_params(fields)
else:
- return self._format_fields('Parameters', fields)
+ return self._format_fields(_('Parameters'), fields)
def _parse_raises_section(self, section):
# type: (unicode) -> List[unicode]
@@ -688,7 +694,7 @@ class GoogleDocstring(UnicodeMixin):
padding = ' ' * len(field_type)
multi = len(fields) > 1
lines = [] # type: List[unicode]
- for _, _type, _desc in fields:
+ for _name, _type, _desc in fields:
_desc = self._strip_empty(_desc)
has_desc = any(_desc)
separator = has_desc and ' -- ' or ''
@@ -723,7 +729,7 @@ class GoogleDocstring(UnicodeMixin):
def _parse_references_section(self, section):
# type: (unicode) -> List[unicode]
use_admonition = self._config.napoleon_use_admonition_for_references
- return self._parse_generic_section('References', use_admonition)
+ return self._parse_generic_section(_('References'), use_admonition)
def _parse_returns_section(self, section):
# type: (unicode) -> List[unicode]
@@ -760,12 +766,12 @@ class GoogleDocstring(UnicodeMixin):
def _parse_warns_section(self, section):
# type: (unicode) -> List[unicode]
- return self._format_fields('Warns', self._consume_fields())
+ return self._format_fields(_('Warns'), self._consume_fields())
def _parse_yields_section(self, section):
# type: (unicode) -> List[unicode]
fields = self._consume_returns_section()
- return self._format_fields('Yields', fields)
+ return self._format_fields(_('Yields'), fields)
def _partition_field_on_colon(self, line):
# type: (unicode) -> Tuple[unicode, unicode, unicode]
diff --git a/sphinx/pycode/parser.py b/sphinx/pycode/parser.py
index f943b7985..31d2c465a 100644
--- a/sphinx/pycode/parser.py
+++ b/sphinx/pycode/parser.py
@@ -225,12 +225,13 @@ class AfterCommentParser(TokenProcessor):
def parse(self):
# type: () -> None
"""Parse the code and obtain comment after assignment."""
- # skip lvalue (until '=' operator)
- while self.fetch_token() != [OP, '=']:
+ # skip lvalue (or whole of AnnAssign)
+ while not self.fetch_token().match([OP, '='], NEWLINE, COMMENT):
assert self.current
- # skip rvalue
- self.fetch_rvalue()
+ # skip rvalue (if exists)
+ if self.current == [OP, '=']:
+ self.fetch_rvalue()
if self.current == COMMENT:
self.comment = self.current.value
diff --git a/sphinx/util/logging.py b/sphinx/util/logging.py
index 5034c007d..d2f6cafbe 100644
--- a/sphinx/util/logging.py
+++ b/sphinx/util/logging.py
@@ -392,7 +392,7 @@ class WarningIsErrorFilter(logging.Filter):
location = getattr(record, 'location', '')
try:
message = record.msg % record.args
- except TypeError:
+ except (TypeError, ValueError):
message = record.msg # use record.msg itself
if location:
diff --git a/sphinx/util/nodes.py b/sphinx/util/nodes.py
index d3441565b..448dd2eaa 100644
--- a/sphinx/util/nodes.py
+++ b/sphinx/util/nodes.py
@@ -56,8 +56,7 @@ def repr_domxml(node, length=80):
returns full of DOM XML representation.
:return: DOM XML representation
"""
- # text = node.asdom().toxml() # #4919 crush if node has secnumber with tuple value
- text = text_type(node) # workaround for #4919
+ text = node.asdom().toxml()
if length and len(text) > length:
text = text[:length] + '...'
return text
@@ -82,9 +81,8 @@ def apply_source_workaround(node):
get_full_module_name(node), repr_domxml(node))
node.source, node.line = node.parent.source, node.parent.line
if isinstance(node, nodes.title) and node.source is None:
- # Uncomment these lines after merging into master(1.8)
- # logger.debug('[i18n] PATCH: %r to have source: %s',
- # get_full_module_name(node), repr_domxml(node))
+ logger.debug('[i18n] PATCH: %r to have source: %s',
+ get_full_module_name(node), repr_domxml(node))
node.source, node.line = node.parent.source, node.parent.line
if isinstance(node, nodes.term):
logger.debug('[i18n] PATCH: %r to have rawsource: %s',
diff --git a/sphinx/writers/latex.py b/sphinx/writers/latex.py
index 006b476c4..40e4a4fe2 100644
--- a/sphinx/writers/latex.py
+++ b/sphinx/writers/latex.py
@@ -25,7 +25,7 @@ from six import itervalues, text_type
from sphinx import addnodes
from sphinx import highlighting
-from sphinx.builders.latex.nodes import footnotetext
+from sphinx.builders.latex.nodes import captioned_literal_block, footnotetext
from sphinx.deprecation import RemovedInSphinx30Warning
from sphinx.errors import SphinxError
from sphinx.locale import admonitionlabels, _, __
@@ -53,6 +53,13 @@ BEGIN_DOC = r'''
MAX_CITATION_LABEL_LENGTH = 8
LATEXSECTIONNAMES = ["part", "chapter", "section", "subsection",
"subsubsection", "paragraph", "subparagraph"]
+HYPERLINK_SUPPORT_NODES = (
+ nodes.figure,
+ nodes.literal_block,
+ nodes.table,
+ nodes.section,
+ captioned_literal_block,
+)
DEFAULT_SETTINGS = {
'latex_engine': 'pdflatex',
@@ -459,7 +466,6 @@ class LaTeXTranslator(nodes.NodeVisitor):
self.in_production_list = 0
self.in_footnote = 0
self.in_caption = 0
- self.in_container_literal_block = 0
self.in_term = 0
self.needs_linetrimming = 0
self.in_minipage = 0
@@ -685,8 +691,6 @@ class LaTeXTranslator(nodes.NodeVisitor):
self.pending_footnotes = [] # type: List[nodes.footnote_reference]
self.curfilestack = [] # type: List[unicode]
self.handled_abbrs = set() # type: Set[unicode]
- self.next_hyperlink_ids = {} # type: Dict[unicode, Set[unicode]]
- self.next_section_ids = set() # type: Set[unicode]
def pushbody(self, newbody):
# type: (List[unicode]) -> None
@@ -699,15 +703,6 @@ class LaTeXTranslator(nodes.NodeVisitor):
self.body = self.bodystack.pop()
return body
- def push_hyperlink_ids(self, figtype, ids):
- # type: (unicode, Set[unicode]) -> None
- hyperlink_ids = self.next_hyperlink_ids.setdefault(figtype, set())
- hyperlink_ids.update(ids)
-
- def pop_hyperlink_ids(self, figtype):
- # type: (unicode) -> Set[unicode]
- return self.next_hyperlink_ids.pop(figtype, set())
-
def check_latex_elements(self):
# type: () -> None
for key in self.builder.config.latex_elements:
@@ -766,6 +761,14 @@ class LaTeXTranslator(nodes.NodeVisitor):
return (anchor and '\\phantomsection' or '') + \
'\\label{%s}' % self.idescape(id)
+ def hypertarget_to(self, node, anchor=False):
+ # type: (nodes.Node, bool) -> unicode
+ labels = ''.join(self.hypertarget(node_id, anchor=False) for node_id in node['ids'])
+ if anchor:
+ return r'\phantomsection' + labels
+ else:
+ return labels
+
def hyperlink(self, id):
# type: (unicode) -> unicode
return '{\\hyperref[%s]{' % self.idescape(id)
@@ -917,8 +920,6 @@ class LaTeXTranslator(nodes.NodeVisitor):
def visit_start_of_file(self, node):
# type: (nodes.Node) -> None
- # also add a document target
- self.next_section_ids.add(':doc')
self.curfilestack.append(node['docname'])
# use default highlight settings for new file
self.hlsettingstack.append(self.hlsettingstack[0])
@@ -958,8 +959,6 @@ class LaTeXTranslator(nodes.NodeVisitor):
if not self.this_is_the_title:
self.sectionlevel += 1
self.body.append('\n\n')
- if node.get('ids'):
- self.next_section_ids.update(node['ids'])
def depart_section(self, node):
# type: (nodes.Node) -> None
@@ -1054,12 +1053,7 @@ class LaTeXTranslator(nodes.NodeVisitor):
except IndexError:
# just use "subparagraph", it's not numbered anyway
self.body.append(r'\%s%s{' % (self.sectionnames[-1], short))
- self.context.append('}\n')
-
- if self.next_section_ids:
- for id in self.next_section_ids:
- self.context[-1] += self.hypertarget(id, anchor=False)
- self.next_section_ids.clear()
+ self.context.append('}\n' + self.hypertarget_to(node.parent))
elif isinstance(parent, nodes.topic):
self.body.append(r'\sphinxstyletopictitle{')
self.context.append('}\n')
@@ -1252,7 +1246,7 @@ class LaTeXTranslator(nodes.NodeVisitor):
if len(node.children) == 1 and node.children[0].astext() in \
('Footnotes', _('Footnotes')):
raise nodes.SkipNode
- self.body.append('\\paragraph{')
+ self.body.append('\\subsubsection*{')
self.context.append('}\n')
self.in_title = 1
@@ -1303,12 +1297,7 @@ class LaTeXTranslator(nodes.NodeVisitor):
def depart_table(self, node):
# type: (nodes.Node) -> None
- labels = '' # type: unicode
- for labelid in self.pop_hyperlink_ids('table'):
- labels += self.hypertarget(labelid, anchor=False)
- if node['ids']:
- labels += self.hypertarget(node['ids'][0], anchor=False)
-
+ labels = self.hypertarget_to(node)
table_type = self.table.get_table_type()
table = self.render(table_type + '.tex_t',
dict(table=self.table, labels=labels))
@@ -1542,9 +1531,12 @@ class LaTeXTranslator(nodes.NodeVisitor):
def visit_term(self, node):
# type: (nodes.Node) -> None
self.in_term += 1
- ctx = '}] \\leavevmode' # type: unicode
+ ctx = '' # type: unicode
if node.get('ids'):
- ctx += self.hypertarget(node['ids'][0])
+ ctx = '\\phantomsection'
+ for node_id in node['ids']:
+ ctx += self.hypertarget(node_id, anchor=False)
+ ctx += '}] \\leavevmode'
self.body.append('\\item[{')
self.context.append(ctx)
@@ -1741,15 +1733,7 @@ class LaTeXTranslator(nodes.NodeVisitor):
def visit_figure(self, node):
# type: (nodes.Node) -> None
- ids = '' # type: unicode
- for id in self.pop_hyperlink_ids('figure'):
- ids += self.hypertarget(id, anchor=False)
- if node['ids']:
- ids += self.hypertarget(node['ids'][0], anchor=False)
- if (len(node.children) and
- isinstance(node.children[0], nodes.image) and
- node.children[0]['ids']):
- ids += self.hypertarget(node.children[0]['ids'][0], anchor=False)
+ labels = self.hypertarget_to(node)
if self.table:
# TODO: support align option
if 'width' in node:
@@ -1761,7 +1745,7 @@ class LaTeXTranslator(nodes.NodeVisitor):
self.body.append('\\begin{sphinxfigure-in-table}\n\\centering\n')
if any(isinstance(child, nodes.caption) for child in node):
self.body.append('\\capstart')
- self.context.append(ids + '\\end{sphinxfigure-in-table}\\relax\n')
+ self.context.append(labels + '\\end{sphinxfigure-in-table}\\relax\n')
elif node.get('align', '') in ('left', 'right'):
length = None
if 'width' in node:
@@ -1770,7 +1754,7 @@ class LaTeXTranslator(nodes.NodeVisitor):
length = self.latex_image_length(node[0]['width'])
self.body.append('\\begin{wrapfigure}{%s}{%s}\n\\centering' %
(node['align'] == 'right' and 'r' or 'l', length or '0pt'))
- self.context.append(ids + '\\end{wrapfigure}\n')
+ self.context.append(labels + '\\end{wrapfigure}\n')
elif self.in_minipage:
self.body.append('\n\\begin{center}')
self.context.append('\\end{center}\n')
@@ -1779,7 +1763,7 @@ class LaTeXTranslator(nodes.NodeVisitor):
self.elements['figure_align'])
if any(isinstance(child, nodes.caption) for child in node):
self.body.append('\\capstart\n')
- self.context.append(ids + '\\end{figure}\n')
+ self.context.append(labels + '\\end{figure}\n')
def depart_figure(self, node):
# type: (nodes.Node) -> None
@@ -1788,7 +1772,7 @@ class LaTeXTranslator(nodes.NodeVisitor):
def visit_caption(self, node):
# type: (nodes.Node) -> None
self.in_caption += 1
- if self.in_container_literal_block:
+ if isinstance(node.parent, captioned_literal_block):
self.body.append('\\sphinxSetupCaptionForVerbatim{')
elif self.in_minipage and isinstance(node.parent, nodes.figure):
self.body.append('\\captionof{figure}{')
@@ -1880,37 +1864,14 @@ class LaTeXTranslator(nodes.NodeVisitor):
anchor = not self.in_title
self.body.append(self.hypertarget(id, anchor=anchor))
- # postpone the labels until after the sectioning command
- parindex = node.parent.index(node)
- try:
- try:
- next = node.parent[parindex + 1]
- except IndexError:
- # last node in parent, look at next after parent
- # (for section of equal level) if it exists
- if node.parent.parent is not None:
- next = node.parent.parent[
- node.parent.parent.index(node.parent)]
- else:
- raise
- if isinstance(next, nodes.section):
- if node.get('refid'):
- self.next_section_ids.add(node['refid'])
- self.next_section_ids.update(node['ids'])
- return
- else:
- domain = self.builder.env.get_domain('std')
- figtype = domain.get_enumerable_node_type(next)
- if figtype and domain.get_numfig_title(next):
- ids = set()
- # labels for figures go in the figure body, not before
- if node.get('refid'):
- ids.add(node['refid'])
- ids.update(node['ids'])
- self.push_hyperlink_ids(figtype, ids)
- return
- except IndexError:
- pass
+ # skip if visitor for next node supports hyperlink
+ domain = self.builder.env.get_domain('std')
+ next_node = node.next_node(ascend=True)
+ if isinstance(next_node, HYPERLINK_SUPPORT_NODES):
+ return
+ elif domain.get_enumerable_node_type(next_node) and domain.get_numfig_title(next_node):
+ return
+
if 'refuri' in node:
return
if node.get('refid'):
@@ -2220,6 +2181,14 @@ class LaTeXTranslator(nodes.NodeVisitor):
# the \ignorespaces in particular for after table header use
self.body.append('%\n\\end{footnotetext}\\ignorespaces ')
+ def visit_captioned_literal_block(self, node):
+ # type: (nodes.Node) -> None
+ pass
+
+ def depart_captioned_literal_block(self, node):
+ # type: (nodes.Node) -> None
+ pass
+
def visit_literal_block(self, node):
# type: (nodes.Node) -> None
if node.rawsource != node.astext():
@@ -2227,15 +2196,12 @@ class LaTeXTranslator(nodes.NodeVisitor):
self.in_parsed_literal += 1
self.body.append('\\begin{sphinxalltt}\n')
else:
- ids = '' # type: unicode
- for id in self.pop_hyperlink_ids('code-block'):
- ids += self.hypertarget(id, anchor=False)
- if node['ids']:
- # suppress with anchor=False \phantomsection insertion
- ids += self.hypertarget(node['ids'][0], anchor=False)
- # LaTeX code will insert \phantomsection prior to \label
- if ids and not self.in_footnote:
- self.body.append('\n\\def\\sphinxLiteralBlockLabel{' + ids + '}')
+ labels = self.hypertarget_to(node)
+ if isinstance(node.parent, captioned_literal_block):
+ labels += self.hypertarget_to(node.parent)
+ if labels and not self.in_footnote:
+ self.body.append('\n\\def\\sphinxLiteralBlockLabel{' + labels + '}')
+
code = node.astext()
lang = self.hlsettingstack[-1][0]
linenos = code.count('\n') >= self.hlsettingstack[-1][1] - 1
@@ -2462,22 +2428,11 @@ class LaTeXTranslator(nodes.NodeVisitor):
def visit_container(self, node):
# type: (nodes.Node) -> None
- if node.get('literal_block'):
- self.in_container_literal_block += 1
- ids = '' # type: unicode
- for id in self.pop_hyperlink_ids('code-block'):
- ids += self.hypertarget(id, anchor=False)
- if node['ids']:
- # suppress with anchor=False \phantomsection insertion
- ids += self.hypertarget(node['ids'][0], anchor=False)
- # define label for use in caption.
- if ids:
- self.body.append('\n\\def\\sphinxLiteralBlockLabel{' + ids + '}\n')
+ pass
def depart_container(self, node):
# type: (nodes.Node) -> None
- if node.get('literal_block'):
- self.in_container_literal_block -= 1
+ pass
def visit_decoration(self, node):
# type: (nodes.Node) -> None
@@ -2607,6 +2562,39 @@ class LaTeXTranslator(nodes.NodeVisitor):
RemovedInSphinx30Warning)
return []
+ @property
+ def in_container_literal_block(self):
+ # type: () -> int
+ warnings.warn('LaTeXTranslator.in_container_literal_block is deprecated.',
+ RemovedInSphinx30Warning)
+ return 0
+
+ @property
+ def next_section_ids(self):
+ # type: () -> Set[unicode]
+ warnings.warn('LaTeXTranslator.next_section_ids is deprecated.',
+ RemovedInSphinx30Warning)
+ return set()
+
+ @property
+ def next_hyperlink_ids(self):
+ # type: () -> Dict
+ warnings.warn('LaTeXTranslator.next_hyperlink_ids is deprecated.',
+ RemovedInSphinx30Warning)
+ return {}
+
+ def push_hyperlink_ids(self, figtype, ids):
+ # type: (unicode, Set[unicode]) -> None
+ warnings.warn('LaTeXTranslator.push_hyperlink_ids() is deprecated.',
+ RemovedInSphinx30Warning)
+ pass
+
+ def pop_hyperlink_ids(self, figtype):
+ # type: (unicode) -> Set[unicode]
+ warnings.warn('LaTeXTranslator.pop_hyperlink_ids() is deprecated.',
+ RemovedInSphinx30Warning)
+ return set()
+
# Import old modules here for compatibility
# They should be imported after `LaTeXTranslator` to avoid recursive import.
diff --git a/tests/roots/test-domain-cpp/xref_consistency.rst b/tests/roots/test-domain-cpp/xref_consistency.rst
new file mode 100644
index 000000000..cb33000f7
--- /dev/null
+++ b/tests/roots/test-domain-cpp/xref_consistency.rst
@@ -0,0 +1,12 @@
+xref consistency
+----------------
+
+.. cpp:namespace:: xref_consistency
+
+.. cpp:class:: item
+
+code-role: :code:`item`
+any-role: :any:`item`
+cpp-any-role: :cpp:any:`item`
+cpp-expr-role: :cpp:expr:`item`
+cpp-texpr-role: :cpp:texpr:`item`
diff --git a/tests/roots/test-glossary/conf.py b/tests/roots/test-glossary/conf.py
new file mode 100644
index 000000000..31e7a6ed4
--- /dev/null
+++ b/tests/roots/test-glossary/conf.py
@@ -0,0 +1,7 @@
+# -*- coding: utf-8 -*-
+
+master_doc = 'index'
+
+latex_documents = [
+ (master_doc, 'test.tex', 'The basic Sphinx documentation for testing', 'Sphinx', 'report')
+]
diff --git a/tests/roots/test-glossary/index.rst b/tests/roots/test-glossary/index.rst
new file mode 100644
index 000000000..88f6ef1b3
--- /dev/null
+++ b/tests/roots/test-glossary/index.rst
@@ -0,0 +1,22 @@
+test-glossary
+=============
+
+.. glossary::
+ :sorted:
+
+ boson
+ Particle with integer spin.
+
+ *fermion*
+ Particle with half-integer spin.
+
+ tauon
+ myon
+ electron
+ Examples for fermions.
+
+ über
+ Gewisse
+
+ änhlich
+ Dinge
diff --git a/tests/roots/test-latex-labels/conf.py b/tests/roots/test-latex-labels/conf.py
new file mode 100644
index 000000000..31e7a6ed4
--- /dev/null
+++ b/tests/roots/test-latex-labels/conf.py
@@ -0,0 +1,7 @@
+# -*- coding: utf-8 -*-
+
+master_doc = 'index'
+
+latex_documents = [
+ (master_doc, 'test.tex', 'The basic Sphinx documentation for testing', 'Sphinx', 'report')
+]
diff --git a/tests/roots/test-latex-labels/index.rst b/tests/roots/test-latex-labels/index.rst
new file mode 100644
index 000000000..5859fb6d2
--- /dev/null
+++ b/tests/roots/test-latex-labels/index.rst
@@ -0,0 +1,68 @@
+latex-labels
+============
+
+figures
+-------
+
+.. _figure1:
+.. _figure2:
+
+.. figure:: logo.jpg
+
+ labeled figure
+
+.. figure:: logo.jpg
+ :name: figure3
+
+ labeled figure
+
+code-blocks
+-----------
+
+.. _codeblock1:
+.. _codeblock2:
+
+.. code-block:: none
+
+ blah blah blah
+
+.. code-block:: none
+ :name: codeblock3
+
+ blah blah blah
+
+tables
+------
+
+.. _table1:
+.. _table2:
+
+.. table:: table caption
+
+ ==== ====
+ head head
+ cell cell
+ ==== ====
+
+.. table:: table caption
+ :name: table3
+
+ ==== ====
+ head head
+ cell cell
+ ==== ====
+
+.. _section1:
+.. _section2:
+
+subsection
+----------
+
+.. _section3:
+
+subsubsection
+~~~~~~~~~~~~~
+
+.. toctree::
+
+ otherdoc
diff --git a/tests/roots/test-latex-labels/otherdoc.rst b/tests/roots/test-latex-labels/otherdoc.rst
new file mode 100644
index 000000000..55c5ca051
--- /dev/null
+++ b/tests/roots/test-latex-labels/otherdoc.rst
@@ -0,0 +1,2 @@
+otherdoc
+========
diff --git a/tests/test_build_latex.py b/tests/test_build_latex.py
index 73e91fdea..546ba139b 100644
--- a/tests/test_build_latex.py
+++ b/tests/test_build_latex.py
@@ -617,7 +617,7 @@ def test_reference_in_caption_and_codeblock_in_footnote(app, status, warning):
assert '\\chapter{The section with a reference to {[}AuthorYear{]}}' in result
assert ('\\sphinxcaption{The table title with a reference'
' to {[}AuthorYear{]}}' in result)
- assert '\\paragraph{The rubric title with a reference to {[}AuthorYear{]}}' in result
+ assert '\\subsubsection*{The rubric title with a reference to {[}AuthorYear{]}}' in result
assert ('\\chapter{The section with a reference to \\sphinxfootnotemark[5]}\n'
'\\label{\\detokenize{index:the-section-with-a-reference-to}}'
'%\n\\begin{footnotetext}[5]\\sphinxAtStartFootnote\n'
@@ -1247,3 +1247,69 @@ def test_latex_thebibliography(app, status, warning):
'Author, Title, Year\n'
'\\end{sphinxthebibliography}\n' in result)
assert '\\sphinxcite{index:authoryear}' in result
+
+
+@pytest.mark.sphinx('latex', testroot='glossary')
+def test_latex_glossary(app, status, warning):
+ app.builder.build_all()
+
+ result = (app.outdir / 'test.tex').text(encoding='utf8')
+ assert (u'\\item[{änhlich\\index{änhlich|textbf}\\phantomsection'
+ r'\label{\detokenize{index:term-anhlich}}}] \leavevmode' in result)
+ assert (r'\item[{boson\index{boson|textbf}\phantomsection'
+ r'\label{\detokenize{index:term-boson}}}] \leavevmode' in result)
+ assert (r'\item[{\sphinxstyleemphasis{fermion}\index{fermion|textbf}'
+ r'\phantomsection'
+ r'\label{\detokenize{index:term-fermion}}}] \leavevmode' in result)
+ assert (r'\item[{tauon\index{tauon|textbf}\phantomsection'
+ r'\label{\detokenize{index:term-tauon}}}] \leavevmode'
+ r'\item[{myon\index{myon|textbf}\phantomsection'
+ r'\label{\detokenize{index:term-myon}}}] \leavevmode'
+ r'\item[{electron\index{electron|textbf}\phantomsection'
+ r'\label{\detokenize{index:term-electron}}}] \leavevmode' in result)
+ assert (u'\\item[{über\\index{über|textbf}\\phantomsection'
+ r'\label{\detokenize{index:term-uber}}}] \leavevmode' in result)
+
+
+@pytest.mark.sphinx('latex', testroot='latex-labels')
+def test_latex_labels(app, status, warning):
+ app.builder.build_all()
+
+ result = (app.outdir / 'test.tex').text(encoding='utf8')
+
+ # figures
+ assert (r'\caption{labeled figure}'
+ r'\label{\detokenize{index:id1}}'
+ r'\label{\detokenize{index:figure2}}'
+ r'\label{\detokenize{index:figure1}}'
+ r'\end{figure}' in result)
+ assert (r'\caption{labeled figure}'
+ r'\label{\detokenize{index:figure3}}'
+ r'\end{figure}' in result)
+
+ # code-blocks
+ assert (r'\def\sphinxLiteralBlockLabel{'
+ r'\label{\detokenize{index:codeblock2}}'
+ r'\label{\detokenize{index:codeblock1}}}' in result)
+ assert (r'\def\sphinxLiteralBlockLabel{'
+ r'\label{\detokenize{index:codeblock3}}}' in result)
+
+ # tables
+ assert (r'\sphinxcaption{table caption}'
+ r'\label{\detokenize{index:id2}}'
+ r'\label{\detokenize{index:table2}}'
+ r'\label{\detokenize{index:table1}}' in result)
+ assert (r'\sphinxcaption{table caption}'
+ r'\label{\detokenize{index:table3}}' in result)
+
+ # sections
+ assert ('\\chapter{subsection}\n'
+ r'\label{\detokenize{index:subsection}}'
+ r'\label{\detokenize{index:section2}}'
+ r'\label{\detokenize{index:section1}}' in result)
+ assert ('\\section{subsubsection}\n'
+ r'\label{\detokenize{index:subsubsection}}'
+ r'\label{\detokenize{index:section3}}' in result)
+ assert ('\\subsection{otherdoc}\n'
+ r'\label{\detokenize{otherdoc:otherdoc}}'
+ r'\label{\detokenize{otherdoc::doc}}' in result)
diff --git a/tests/test_domain_cpp.py b/tests/test_domain_cpp.py
index de72148a5..759b0b74b 100644
--- a/tests/test_domain_cpp.py
+++ b/tests/test_domain_cpp.py
@@ -491,6 +491,10 @@ def test_class_definitions():
{2: 'I0E7has_varI1TNSt6void_tIDTadN1T3varEEEEE'})
+def test_union_definitions():
+ check('union', 'A', {2: "1A"})
+
+
def test_enum_definitions():
check('enum', 'A', {2: "1A"})
check('enum', 'A : std::underlying_type::type', {2: "1A"})
@@ -735,3 +739,68 @@ def test_build_domain_cpp_with_add_function_parentheses_is_False(app, status, wa
t = (app.outdir / f).text()
for s in parenPatterns:
check(s, t, f)
+
+
+@pytest.mark.sphinx(testroot='domain-cpp')
+def test_xref_consistency(app, status, warning):
+ app.builder.build_all()
+
+ test = 'xref_consistency.html'
+ output = (app.outdir / test).text()
+
+ def classes(role, tag):
+ pattern = (r'{role}-role:.*?'
+ '<(?P{tag}) .*?class=["\'](?P.*?)["\'].*?>'
+ '.*'
+ '(?P=tag)>').format(role=role, tag=tag)
+ result = re.search(pattern, output)
+ expect = '''\
+Pattern for role `{role}` with tag `{tag}`
+\t{pattern}
+not found in `{test}`
+'''.format(role=role, tag=tag, pattern=pattern, test=test)
+ assert result, expect
+ return set(result.group('classes').split())
+
+ class RoleClasses(object):
+ """Collect the classes from the layout that was generated for a given role."""
+
+ def __init__(self, role, root, contents):
+ self.name = role
+ self.classes = classes(role, root)
+ self.content_classes = dict()
+ for tag in contents:
+ self.content_classes[tag] = classes(role, tag)
+
+ # not actually used as a reference point
+ #code_role = RoleClasses('code', 'code', [])
+ any_role = RoleClasses('any', 'a', ['code'])
+ cpp_any_role = RoleClasses('cpp-any', 'a', ['code'])
+ # NYI: consistent looks
+ #texpr_role = RoleClasses('cpp-texpr', 'span', ['a', 'code'])
+ expr_role = RoleClasses('cpp-expr', 'code', ['a'])
+ texpr_role = RoleClasses('cpp-texpr', 'span', ['a', 'span'])
+
+ # XRefRole-style classes
+
+ ## any and cpp:any do not put these classes at the root
+
+ # n.b. the generic any machinery finds the specific 'cpp-class' object type
+ expect = 'any uses XRefRole classes'
+ assert {'xref', 'any', 'cpp', 'cpp-class'} <= any_role.content_classes['code'], expect
+
+ expect = 'cpp:any uses XRefRole classes'
+ assert {'xref', 'cpp-any', 'cpp'} <= cpp_any_role.content_classes['code'], expect
+
+ for role in (expr_role, texpr_role):
+ name = role.name
+ expect = '`{name}` puts the domain and role classes at its root'.format(name=name)
+ # NYI: xref should go in the references
+ assert {'xref', 'cpp', name} <= role.classes, expect
+
+ # reference classes
+
+ expect = 'the xref roles use the same reference classes'
+ assert any_role.classes == cpp_any_role.classes, expect
+ assert any_role.classes == expr_role.content_classes['a'], expect
+ assert any_role.classes == texpr_role.content_classes['a'], expect
diff --git a/tests/test_environment_toctree.py b/tests/test_environment_toctree.py
index 1bebbaa3e..3fbb21856 100644
--- a/tests/test_environment_toctree.py
+++ b/tests/test_environment_toctree.py
@@ -227,11 +227,11 @@ def test_get_toctree_for(app):
[list_item, compact_paragraph, reference, "foo.1"],
[list_item, compact_paragraph, reference, "foo.2"]))
- assert_node(toctree[1][0][0][0], reference, refuri="foo", secnumber=(1,))
- assert_node(toctree[1][0][1][0][0][0], reference, refuri="quux", secnumber=(1, 1))
- assert_node(toctree[1][0][1][1][0][0], reference, refuri="foo#foo-1", secnumber=(1, 2))
- assert_node(toctree[1][0][1][2][0][0], reference, refuri="foo#foo-2", secnumber=(1, 3))
- assert_node(toctree[1][1][0][0], reference, refuri="bar", secnumber=(2,))
+ assert_node(toctree[1][0][0][0], reference, refuri="foo", secnumber=[1])
+ assert_node(toctree[1][0][1][0][0][0], reference, refuri="quux", secnumber=[1, 1])
+ assert_node(toctree[1][0][1][1][0][0], reference, refuri="foo#foo-1", secnumber=[1, 2])
+ assert_node(toctree[1][0][1][2][0][0], reference, refuri="foo#foo-2", secnumber=[1, 3])
+ assert_node(toctree[1][1][0][0], reference, refuri="bar", secnumber=[2])
assert_node(toctree[1][2][0][0], reference, refuri="http://sphinx-doc.org/")
assert_node(toctree[2],
@@ -258,8 +258,8 @@ def test_get_toctree_for_collapse(app):
([list_item, compact_paragraph, reference, "foo"],
[list_item, compact_paragraph, reference, "bar"],
[list_item, compact_paragraph, reference, "http://sphinx-doc.org/"]))
- assert_node(toctree[1][0][0][0], reference, refuri="foo", secnumber=(1,))
- assert_node(toctree[1][1][0][0], reference, refuri="bar", secnumber=(2,))
+ assert_node(toctree[1][0][0][0], reference, refuri="foo", secnumber=[1])
+ assert_node(toctree[1][1][0][0], reference, refuri="bar", secnumber=[2])
assert_node(toctree[1][2][0][0], reference, refuri="http://sphinx-doc.org/")
assert_node(toctree[2],
@@ -296,13 +296,13 @@ def test_get_toctree_for_maxdepth(app):
assert_node(toctree[1][0][1][1][1],
[bullet_list, list_item, compact_paragraph, reference, "foo.1-1"])
- assert_node(toctree[1][0][0][0], reference, refuri="foo", secnumber=(1,))
- assert_node(toctree[1][0][1][0][0][0], reference, refuri="quux", secnumber=(1, 1))
- assert_node(toctree[1][0][1][1][0][0], reference, refuri="foo#foo-1", secnumber=(1, 2))
+ assert_node(toctree[1][0][0][0], reference, refuri="foo", secnumber=[1])
+ assert_node(toctree[1][0][1][0][0][0], reference, refuri="quux", secnumber=[1, 1])
+ assert_node(toctree[1][0][1][1][0][0], reference, refuri="foo#foo-1", secnumber=[1, 2])
assert_node(toctree[1][0][1][1][1][0][0][0],
- reference, refuri="foo#foo-1-1", secnumber=(1, 2, 1))
- assert_node(toctree[1][0][1][2][0][0], reference, refuri="foo#foo-2", secnumber=(1, 3))
- assert_node(toctree[1][1][0][0], reference, refuri="bar", secnumber=(2,))
+ reference, refuri="foo#foo-1-1", secnumber=[1, 2, 1])
+ assert_node(toctree[1][0][1][2][0][0], reference, refuri="foo#foo-2", secnumber=[1, 3])
+ assert_node(toctree[1][1][0][0], reference, refuri="bar", secnumber=[2])
assert_node(toctree[1][2][0][0], reference, refuri="http://sphinx-doc.org/")
assert_node(toctree[2],
@@ -335,11 +335,11 @@ def test_get_toctree_for_includehidden(app):
[list_item, compact_paragraph, reference, "foo.1"],
[list_item, compact_paragraph, reference, "foo.2"]))
- assert_node(toctree[1][0][0][0], reference, refuri="foo", secnumber=(1,))
- assert_node(toctree[1][0][1][0][0][0], reference, refuri="quux", secnumber=(1, 1))
- assert_node(toctree[1][0][1][1][0][0], reference, refuri="foo#foo-1", secnumber=(1, 2))
- assert_node(toctree[1][0][1][2][0][0], reference, refuri="foo#foo-2", secnumber=(1, 3))
- assert_node(toctree[1][1][0][0], reference, refuri="bar", secnumber=(2,))
+ assert_node(toctree[1][0][0][0], reference, refuri="foo", secnumber=[1])
+ assert_node(toctree[1][0][1][0][0][0], reference, refuri="quux", secnumber=[1, 1])
+ assert_node(toctree[1][0][1][1][0][0], reference, refuri="foo#foo-1", secnumber=[1, 2])
+ assert_node(toctree[1][0][1][2][0][0], reference, refuri="foo#foo-2", secnumber=[1, 3])
+ assert_node(toctree[1][1][0][0], reference, refuri="bar", secnumber=[2])
assert_node(toctree[1][2][0][0], reference, refuri="http://sphinx-doc.org/")
assert_node(toctree[2],
diff --git a/tests/test_pycode_parser.py b/tests/test_pycode_parser.py
index 09f1f41f5..29363e17e 100644
--- a/tests/test_pycode_parser.py
+++ b/tests/test_pycode_parser.py
@@ -100,11 +100,13 @@ def test_comment_picker_location():
def test_annotated_assignment_py36():
source = ('a: str = "Sphinx" #: comment\n'
'b: int = 1\n'
- '"""string on next line"""')
+ '"""string on next line"""\n'
+ 'c: int #: comment')
parser = Parser(source)
parser.parse()
assert parser.comments == {('', 'a'): 'comment',
- ('', 'b'): 'string on next line'}
+ ('', 'b'): 'string on next line',
+ ('', 'c'): 'comment'}
assert parser.definitions == {}