From be8e2be47b405723e39ff46e473829bcc8d17cb9 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Mon, 21 May 2018 00:24:15 +0900 Subject: [PATCH 01/12] Fix #4914: autodoc: Parsing error when using dataclasses without default values --- CHANGES | 1 + sphinx/pycode/parser.py | 9 +++++---- tests/test_pycode_parser.py | 6 ++++-- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/CHANGES b/CHANGES index f9b6e6af5..a2918b7b2 100644 --- a/CHANGES +++ b/CHANGES @@ -33,6 +33,7 @@ Bugs fixed * #4978: latex: shorthandoff is not set up for Brazil locale * #4928: i18n: Ignore dot-directories like .git/ in LC_MESSAGES/ * #4946: py domain: type field could not handle "None" as a type +* #4914: autodoc: Parsing error when using dataclasses without default values Testing -------- diff --git a/sphinx/pycode/parser.py b/sphinx/pycode/parser.py index deba48b1c..5c4291d3d 100644 --- a/sphinx/pycode/parser.py +++ b/sphinx/pycode/parser.py @@ -224,12 +224,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/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 == {} From cda18f119f1c42ecd44117093e6f7a003a46eb8d Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Mon, 21 May 2018 21:31:34 +0900 Subject: [PATCH 02/12] Fix #4919: node.asdom() crashes if toctree has :numbered: option --- CHANGES | 1 + sphinx/environment/collectors/toctree.py | 6 ++-- tests/test_environment_toctree.py | 36 ++++++++++++------------ 3 files changed, 22 insertions(+), 21 deletions(-) diff --git a/CHANGES b/CHANGES index 585dd42b7..3284cbe6a 100644 --- a/CHANGES +++ b/CHANGES @@ -34,6 +34,7 @@ Bugs fixed * #4928: i18n: Ignore dot-directories like .git/ in LC_MESSAGES/ * #4946: py domain: type field could not handle "None" as a type * #4979: latex: Incorrect escaping of curly braces in index entries +* #4919: node.asdom() crashes if toctree has :numbered: option Testing -------- diff --git a/sphinx/environment/collectors/toctree.py b/sphinx/environment/collectors/toctree.py index a7556eadd..c8b00c900 100644 --- a/sphinx/environment/collectors/toctree.py +++ b/sphinx/environment/collectors/toctree.py @@ -168,10 +168,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/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], From 489d86d47094155c4334851a03abddfb25fc215a Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Wed, 23 May 2018 22:50:46 +0900 Subject: [PATCH 03/12] Refactor: Add captioned_literal_block node to simplify LaTeX writer --- CHANGES | 6 +- doc/extdev/index.rst | 20 ++++++ sphinx/builders/latex/__init__.py | 6 +- sphinx/builders/latex/nodes.py | 5 ++ sphinx/builders/latex/transforms.py | 17 ++++- sphinx/writers/latex.py | 97 +++++++++++++---------------- 6 files changed, 95 insertions(+), 56 deletions(-) diff --git a/CHANGES b/CHANGES index 8ab92c401..ef1b2df9f 100644 --- a/CHANGES +++ b/CHANGES @@ -64,9 +64,13 @@ 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_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 diff --git a/doc/extdev/index.rst b/doc/extdev/index.rst index d10ada464..42a999a06 100644 --- a/doc/extdev/index.rst +++ b/doc/extdev/index.rst @@ -171,6 +171,16 @@ 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_hyperlink_ids`` + - 1.8 + - 3.0 + - N/A + * - ``sphinx.writers.latex.LaTeXWriter.restrict_footnote()`` - 1.8 - 3.0 @@ -181,6 +191,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/sphinx/builders/latex/__init__.py b/sphinx/builders/latex/__init__.py index a98b80289..83b4f1528 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, ) from sphinx.config import string_classes, ENUM from sphinx.environment import NoUri @@ -223,7 +224,8 @@ class LaTeXBuilder(Builder): transformer.set_environment(self.env) transformer.add_transforms([BibliographyTransform, ShowUrlsTransform, - LaTeXFootnoteTransform]) + LaTeXFootnoteTransform, + LiteralBlockTransform]) 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..9c8a4192f 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,18 @@ 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) diff --git a/sphinx/writers/latex.py b/sphinx/writers/latex.py index 9501d69d0..4c51097a7 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, _, __ @@ -58,6 +58,7 @@ HYPERLINK_SUPPORT_NODES = ( nodes.literal_block, nodes.table, nodes.section, + captioned_literal_block, ) DEFAULT_SETTINGS = { @@ -465,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 @@ -691,7 +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): @@ -705,15 +704,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: @@ -1790,7 +1780,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}{') @@ -1883,35 +1873,13 @@ class LaTeXTranslator(nodes.NodeVisitor): self.body.append(self.hypertarget(id, anchor=anchor)) # 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 - # 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 - 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 if 'refuri' in node: return if node.get('refid'): @@ -2221,6 +2189,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(): @@ -2229,9 +2205,11 @@ class LaTeXTranslator(nodes.NodeVisitor): self.body.append('\\begin{sphinxalltt}\n') else: labels = self.hypertarget_to(node) - # LaTeX code will insert \phantomsection prior to \label + 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 @@ -2458,22 +2436,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 @@ -2603,6 +2570,32 @@ 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_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. From c8e38fbe3e80578cd30f65007dd545124c8b868a Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Thu, 24 May 2018 00:23:50 +0900 Subject: [PATCH 04/12] Refactor: Add DocumentTargetTransform to simplify LaTeX writer --- CHANGES | 1 + doc/extdev/index.rst | 5 +++++ sphinx/builders/latex/__init__.py | 5 +++-- sphinx/builders/latex/transforms.py | 15 +++++++++++++++ sphinx/writers/latex.py | 15 +++++++-------- 5 files changed, 31 insertions(+), 10 deletions(-) diff --git a/CHANGES b/CHANGES index ef1b2df9f..17a89c2c3 100644 --- a/CHANGES +++ b/CHANGES @@ -65,6 +65,7 @@ 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 diff --git a/doc/extdev/index.rst b/doc/extdev/index.rst index 42a999a06..ec64067e4 100644 --- a/doc/extdev/index.rst +++ b/doc/extdev/index.rst @@ -176,6 +176,11 @@ The following is a list of deprecated interface. - 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 diff --git a/sphinx/builders/latex/__init__.py b/sphinx/builders/latex/__init__.py index 83b4f1528..32e813241 100644 --- a/sphinx/builders/latex/__init__.py +++ b/sphinx/builders/latex/__init__.py @@ -21,7 +21,7 @@ from sphinx.builders import Builder from sphinx.builders.latex.transforms import ( BibliographyTransform, CitationReferenceTransform, MathReferenceTransform, FootnoteDocnameUpdater, LaTeXFootnoteTransform, LiteralBlockTransform, - ShowUrlsTransform, + ShowUrlsTransform, DocumentTargetTransform, ) from sphinx.config import string_classes, ENUM from sphinx.environment import NoUri @@ -225,7 +225,8 @@ class LaTeXBuilder(Builder): transformer.add_transforms([BibliographyTransform, ShowUrlsTransform, LaTeXFootnoteTransform, - LiteralBlockTransform]) + LiteralBlockTransform, + DocumentTargetTransform]) transformer.apply_transforms() def finish(self): diff --git a/sphinx/builders/latex/transforms.py b/sphinx/builders/latex/transforms.py index 9c8a4192f..80e83c4db 100644 --- a/sphinx/builders/latex/transforms.py +++ b/sphinx/builders/latex/transforms.py @@ -581,3 +581,18 @@ class LiteralBlockTransform(SphinxTransform): 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/writers/latex.py b/sphinx/writers/latex.py index 4c51097a7..40e4a4fe2 100644 --- a/sphinx/writers/latex.py +++ b/sphinx/writers/latex.py @@ -691,7 +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_section_ids = set() # type: Set[unicode] def pushbody(self, newbody): # type: (List[unicode]) -> None @@ -921,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]) @@ -1057,11 +1054,6 @@ class LaTeXTranslator(nodes.NodeVisitor): # just use "subparagraph", it's not numbered anyway self.body.append(r'\%s%s{' % (self.sectionnames[-1], short)) self.context.append('}\n' + self.hypertarget_to(node.parent)) - - 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() elif isinstance(parent, nodes.topic): self.body.append(r'\sphinxstyletopictitle{') self.context.append('}\n') @@ -2577,6 +2569,13 @@ class LaTeXTranslator(nodes.NodeVisitor): 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 From d7a25433cefe09e5cec348a68e00dfe58054a53e Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Mon, 21 May 2018 01:11:30 +0900 Subject: [PATCH 05/12] Fix #4931: autodoc: crashed when handler for autodoc-skip-member raises an error --- CHANGES | 1 + sphinx/ext/autodoc/__init__.py | 18 ++++++++++++------ 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/CHANGES b/CHANGES index d6a531462..d730a5260 100644 --- a/CHANGES +++ b/CHANGES @@ -40,6 +40,7 @@ Bugs fixed * #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 Testing -------- diff --git a/sphinx/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py index eabe487e3..058fd7765 100644 --- a/sphinx/ext/autodoc/__init__.py +++ b/sphinx/ext/autodoc/__init__.py @@ -25,7 +25,7 @@ from sphinx.deprecation import RemovedInSphinx20Warning from sphinx.ext.autodoc.importer import mock, import_object, get_object_members from sphinx.ext.autodoc.importer import _MockImporter # to keep compatibility # NOQA from sphinx.ext.autodoc.inspector import format_annotation, formatargspec # to keep compatibility # NOQA -from sphinx.locale import _ +from sphinx.locale import _, __ from sphinx.pycode import ModuleAnalyzer, PycodeError from sphinx.util import logging from sphinx.util import rpartition, force_decode @@ -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)) From cff194d5dd3a177a448c2facc2299abdaf49b76e Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Tue, 22 May 2018 22:31:07 +0900 Subject: [PATCH 06/12] Fix #4931: autodoc: crashed when subclass of mocked class are processed by napoleon module --- CHANGES | 2 ++ sphinx/ext/autodoc/importer.py | 8 ++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/CHANGES b/CHANGES index d730a5260..3d458b1ee 100644 --- a/CHANGES +++ b/CHANGES @@ -41,6 +41,8 @@ Bugs fixed * #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 Testing -------- diff --git a/sphinx/ext/autodoc/importer.py b/sphinx/ext/autodoc/importer.py index e3bf1560c..7d2c67a4f 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 From 9d1d48fb60920423318a5b1d7ac5bf01d4a44229 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Thu, 24 May 2018 01:46:16 +0900 Subject: [PATCH 07/12] Fix #5007: sphinx-build crashes when error log contains a "%" character --- CHANGES | 1 + sphinx/util/logging.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index d6a531462..4f4afc4b1 100644 --- a/CHANGES +++ b/CHANGES @@ -40,6 +40,7 @@ Bugs fixed * #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 +* #5007: sphinx-build crashes when error log contains a "%" character Testing -------- diff --git a/sphinx/util/logging.py b/sphinx/util/logging.py index 09db0028f..5c94c9c8d 100644 --- a/sphinx/util/logging.py +++ b/sphinx/util/logging.py @@ -373,7 +373,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: From 297ea8070f8bd3c51ddc6542781c48014b67e563 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Thu, 24 May 2018 23:55:12 +0900 Subject: [PATCH 08/12] Update docs --- doc/extdev/appapi.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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) From 46797104fae18b5337af82619813be0deb6b8324 Mon Sep 17 00:00:00 2001 From: shimizukawa Date: Fri, 25 May 2018 08:21:07 +0900 Subject: [PATCH 09/12] refs #4919. As after work of 4919, remove workaround, uncomment temporary disabled code. --- sphinx/util/nodes.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) 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', From 987cd582520a2d53db538d1e255b62ef023422ab Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Tue, 22 May 2018 19:50:54 +0900 Subject: [PATCH 10/12] Remove env.need_refresh() After the refactoring of the env object, it is not needed now. Note: It was appeared only on master branch. So deprecation process is not needed for this case. --- sphinx/environment/__init__.py | 13 ------------- 1 file changed, 13 deletions(-) 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.""" From f5924831561d4f2c31f3c0a1c31be85229522b9d Mon Sep 17 00:00:00 2001 From: Jakob Lykke Andersen Date: Sat, 26 May 2018 17:25:41 +0200 Subject: [PATCH 11/12] Add cpp:texpr role (style alternative to cpp:expr) Simplified version of sphinx-doc/sphinx#4836, thanks to mickk-on-cpp. --- CHANGES | 1 + doc/usage/restructuredtext/domains.rst | 54 ++++++++------- sphinx/domains/cpp.py | 26 ++++++-- .../test-domain-cpp/xref_consistency.rst | 12 ++++ tests/test_domain_cpp.py | 65 +++++++++++++++++++ 5 files changed, 131 insertions(+), 27 deletions(-) create mode 100644 tests/roots/test-domain-cpp/xref_consistency.rst diff --git a/CHANGES b/CHANGES index 13a4ac151..bc4207f81 100644 --- a/CHANGES +++ b/CHANGES @@ -116,6 +116,7 @@ Features added * #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``. Bugs fixed ---------- diff --git a/doc/usage/restructuredtext/domains.rst b/doc/usage/restructuredtext/domains.rst index 452cf63ae..d177ec643 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 @@ -815,24 +815,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 +887,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 +956,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 +993,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/domains/cpp.py b/sphinx/domains/cpp.py index 830f15f29..903a766bc 100644 --- a/sphinx/domains/cpp.py +++ b/sphinx/domains/cpp.py @@ -5965,6 +5965,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 +5982,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): @@ -6025,7 +6040,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/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/test_domain_cpp.py b/tests/test_domain_cpp.py index de72148a5..6680510a6 100644 --- a/tests/test_domain_cpp.py +++ b/tests/test_domain_cpp.py @@ -735,3 +735,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.*?)["\'].*?>' + '.*' + '').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 From 6d52b63eeee5dcea7f9b8f0358ca85c67c19925e Mon Sep 17 00:00:00 2001 From: Jakob Lykke Andersen Date: Sat, 26 May 2018 20:03:25 +0200 Subject: [PATCH 12/12] C++, support for unions. --- CHANGES | 1 + doc/Makefile | 2 +- doc/usage/restructuredtext/domains.rst | 4 +++ sphinx/domains/cpp.py | 45 +++++++++++++++++++++++++- tests/test_domain_cpp.py | 4 +++ 5 files changed, 54 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index bc4207f81..c8d330299 100644 --- a/CHANGES +++ b/CHANGES @@ -117,6 +117,7 @@ Features added * #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 ---------- 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/usage/restructuredtext/domains.rst b/doc/usage/restructuredtext/domains.rst index d177ec643..58d77b78e 100644 --- a/doc/usage/restructuredtext/domains.rst +++ b/doc/usage/restructuredtext/domains.rst @@ -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 diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py index 903a766bc..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 @@ -6007,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'), @@ -6017,6 +6058,7 @@ class CPPDomain(Domain): directives = { 'class': CPPClassObject, + 'union': CPPUnionObject, 'function': CPPFunctionObject, 'member': CPPMemberObject, 'var': CPPMemberObject, @@ -6033,6 +6075,7 @@ class CPPDomain(Domain): roles = { 'any': CPPXRefRole(), 'class': CPPXRefRole(), + 'union': CPPXRefRole(), 'func': CPPXRefRole(fix_parens=True), 'member': CPPXRefRole(), 'var': CPPXRefRole(), diff --git a/tests/test_domain_cpp.py b/tests/test_domain_cpp.py index 6680510a6..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"})