summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTakeshi KOMIYA <i.tkomiya@gmail.com>2020-02-09 00:57:53 +0900
committerTakeshi KOMIYA <i.tkomiya@gmail.com>2020-02-09 00:57:53 +0900
commit2e87ee85a22279ee4ecc658f830520a88c90236d (patch)
tree124b080e0203aead764362ba1e5bd11da62822e5
parentd4aeae475943aef9b935f7487baf90ccc1c58b42 (diff)
parent1e5342faa9147c7a3c60e41dc7671e88f6795855 (diff)
downloadsphinx-git-2e87ee85a22279ee4ecc658f830520a88c90236d.tar.gz
Merge branch '2.0'
-rw-r--r--CHANGES13
-rw-r--r--doc/man/sphinx-apidoc.rst5
-rw-r--r--doc/usage/restructuredtext/domains.rst24
-rw-r--r--doc/usage/restructuredtext/field-lists.rst6
-rw-r--r--doc/usage/theming.rst4
-rw-r--r--sphinx/directives/other.py1
-rw-r--r--sphinx/domains/python.py43
-rw-r--r--sphinx/domains/std.py10
-rw-r--r--sphinx/ext/apidoc.py15
-rw-r--r--sphinx/ext/autodoc/__init__.py36
-rw-r--r--sphinx/ext/autosummary/generate.py3
-rw-r--r--sphinx/pycode/__init__.py8
-rw-r--r--sphinx/pycode/ast.py2
-rw-r--r--sphinx/pycode/parser.py29
-rw-r--r--sphinx/themes/basic/static/doctools.js5
-rw-r--r--sphinx/writers/latex.py2
-rw-r--r--tests/roots/test-domain-py/module.rst6
-rw-r--r--tests/roots/test-ext-autodoc/target/typed_vars.py9
-rw-r--r--tests/test_autodoc.py78
-rw-r--r--tests/test_directive_other.py17
-rw-r--r--tests/test_domain_py.py62
-rw-r--r--tests/test_environment_toctree.py6
-rw-r--r--tests/test_pycode_parser.py11
23 files changed, 329 insertions, 66 deletions
diff --git a/CHANGES b/CHANGES
index 226421776..ef16f6aac 100644
--- a/CHANGES
+++ b/CHANGES
@@ -110,6 +110,7 @@ Features added
images (imagesize-1.2.0 or above is required)
* #6994: imgconverter: Support illustrator file (.ai) to .png conversion
* autodoc: Support Positional-Only Argument separator (PEP-570 compliant)
+* autodoc: Support type annotations for variables
* #2755: autodoc: Add new event: :event:`autodoc-before-process-signature`
* #2755: autodoc: Support type_comment style (ex. ``# type: (str) -> str``)
annotation (python3.8+ or `typed_ast <https://github.com/python/typed_ast>`_
@@ -122,6 +123,10 @@ Features added
* SphinxTranslator now calls visitor/departure method for super node class if
visitor/departure method for original node class not found
* #6418: Add new event: :event:`object-description-transform`
+* py domain: :rst:dir:`py:data` and :rst:dir:`py:attribute` take new options
+ named ``:type:`` and ``:value:`` to describe its type and initial value
+* #6785: py domain: ``:py:attr:`` is able to refer properties again
+* #6772: apidoc: Add ``-q`` option for quiet mode
Bugs fixed
----------
@@ -129,6 +134,8 @@ Bugs fixed
* #6925: html: Remove redundant type="text/javascript" from <script> elements
* #6906, #6907: autodoc: failed to read the source codes encoeded in cp1251
* #6961: latex: warning for babel shown twice
+* #7059: latex: LaTeX compilation falls into infinite loop (wrapfig issue)
+* #6581: latex: ``:reversed:`` option for toctree does not effect to LaTeX build
* #6559: Wrong node-ids are generated in glossary directive
* #6986: apidoc: misdetects module name for .so file inside module
* #6899: apidoc: private members are not shown even if ``--private`` given
@@ -138,7 +145,13 @@ Bugs fixed
* #7023: autodoc: partial functions imported from other modules are listed as
module members without :impoprted-members: option
* #6889: autodoc: Trailing comma in ``:members::`` option causes cryptic warning
+* #6568: autosummary: ``autosummary_imported_members`` is ignored on generating
+ a stub file for submodule
* #7055: linkcheck: redirect is treated as an error
+* #7088: HTML template: If ``navigation_with_keys`` option is activated,
+ modifier keys are ignored, which means the feature can interfere with browser
+ features
+* #7090: std domain: Can't assign numfig-numbers for custom container nodes
Testing
--------
diff --git a/doc/man/sphinx-apidoc.rst b/doc/man/sphinx-apidoc.rst
index 457e2e7f6..725d2f169 100644
--- a/doc/man/sphinx-apidoc.rst
+++ b/doc/man/sphinx-apidoc.rst
@@ -39,6 +39,11 @@ Options
Directory to place the output files. If it does not exist, it is created.
+.. option:: -q
+
+ Do not output anything on standard output, only write warnings and errors to
+ standard error.
+
.. option:: -f, --force
Force overwriting of any existing generated files.
diff --git a/doc/usage/restructuredtext/domains.rst b/doc/usage/restructuredtext/domains.rst
index e107acac1..9c69fe88f 100644
--- a/doc/usage/restructuredtext/domains.rst
+++ b/doc/usage/restructuredtext/domains.rst
@@ -195,6 +195,18 @@ The following directives are provided for module and class contents:
as "defined constants." Class and object attributes are not documented
using this environment.
+ .. rubric:: options
+
+ .. rst:directive:option:: type: type of the variable
+ :type: text
+
+ .. versionadded:: 2.4
+
+ .. rst:directive:option:: value: initial value of the variable
+ :type: text
+
+ .. versionadded:: 2.4
+
.. rst:directive:: .. py:exception:: name
Describes an exception class. The signature can, but need not include
@@ -229,6 +241,18 @@ The following directives are provided for module and class contents:
information about the type of the data to be expected and whether it may be
changed directly.
+ .. rubric:: options
+
+ .. rst:directive:option:: type: type of the attribute
+ :type: text
+
+ .. versionadded:: 2.4
+
+ .. rst:directive:option:: value: initial value of the attribute
+ :type: text
+
+ .. versionadded:: 2.4
+
.. rst:directive:: .. py:method:: name(parameters)
Describes an object method. The parameters should not include the ``self``
diff --git a/doc/usage/restructuredtext/field-lists.rst b/doc/usage/restructuredtext/field-lists.rst
index fcecfe708..b84d238ba 100644
--- a/doc/usage/restructuredtext/field-lists.rst
+++ b/doc/usage/restructuredtext/field-lists.rst
@@ -30,6 +30,12 @@ At the moment, these metadata fields are recognized:
:tocdepth: 2
+ .. note::
+
+ This metadata effects to the depth of local toctree. But it does not
+ effect to the depth of *global* toctree. So this would not be change
+ the sidebar of some themes which uses global one.
+
.. versionadded:: 0.4
``nocomments``
diff --git a/doc/usage/theming.rst b/doc/usage/theming.rst
index 3b42bc4cb..5ed9f1f1b 100644
--- a/doc/usage/theming.rst
+++ b/doc/usage/theming.rst
@@ -151,6 +151,10 @@ These themes are:
dimension string such as '70em' or '50%'. Use 'none' if you don't
want a width limit. Defaults may depend on the theme (often 800px).
+ - **navigation_with_keys** (true or false): Allow navigating to the
+ previous/next page using the keyboard's left and right arrows. Defaults to
+ ``False``.
+
**alabaster**
`Alabaster theme`_ is a modified "Kr" Sphinx theme from @kennethreitz
(especially as used in his Requests project), which was itself originally
diff --git a/sphinx/directives/other.py b/sphinx/directives/other.py
index bf7bd2784..87d769b41 100644
--- a/sphinx/directives/other.py
+++ b/sphinx/directives/other.py
@@ -145,6 +145,7 @@ class TocTree(SphinxDirective):
# entries contains all entries (self references, external links etc.)
if 'reversed' in self.options:
toctree['entries'] = list(reversed(toctree['entries']))
+ toctree['includefiles'] = list(reversed(toctree['includefiles']))
return ret
diff --git a/sphinx/domains/python.py b/sphinx/domains/python.py
index 3c3d3d707..1b551c70b 100644
--- a/sphinx/domains/python.py
+++ b/sphinx/domains/python.py
@@ -437,6 +437,25 @@ class PyFunction(PyObject):
class PyVariable(PyObject):
"""Description of a variable."""
+ option_spec = PyObject.option_spec.copy()
+ option_spec.update({
+ 'type': directives.unchanged,
+ 'value': directives.unchanged,
+ })
+
+ def handle_signature(self, sig: str, signode: desc_signature) -> Tuple[str, str]:
+ fullname, prefix = super().handle_signature(sig, signode)
+
+ typ = self.options.get('type')
+ if typ:
+ signode += addnodes.desc_annotation(typ, ': ' + typ)
+
+ value = self.options.get('value')
+ if value:
+ signode += addnodes.desc_annotation(value, ' = ' + value)
+
+ return fullname, prefix
+
def get_index_text(self, modname: str, name_cls: Tuple[str, str]) -> str:
name, cls = name_cls
if modname:
@@ -629,6 +648,25 @@ class PyStaticMethod(PyMethod):
class PyAttribute(PyObject):
"""Description of an attribute."""
+ option_spec = PyObject.option_spec.copy()
+ option_spec.update({
+ 'type': directives.unchanged,
+ 'value': directives.unchanged,
+ })
+
+ def handle_signature(self, sig: str, signode: desc_signature) -> Tuple[str, str]:
+ fullname, prefix = super().handle_signature(sig, signode)
+
+ typ = self.options.get('type')
+ if typ:
+ signode += addnodes.desc_annotation(typ, ': ' + typ)
+
+ value = self.options.get('value')
+ if value:
+ signode += addnodes.desc_annotation(value, ' = ' + value)
+
+ return fullname, prefix
+
def get_index_text(self, modname: str, name_cls: Tuple[str, str]) -> str:
name, cls = name_cls
try:
@@ -1017,6 +1055,11 @@ class PythonDomain(Domain):
searchmode = 1 if node.hasattr('refspecific') else 0
matches = self.find_obj(env, modname, clsname, target,
type, searchmode)
+
+ if not matches and type == 'attr':
+ # fallback to meth (for property)
+ matches = self.find_obj(env, modname, clsname, target, 'meth', searchmode)
+
if not matches:
return None
elif len(matches) > 1:
diff --git a/sphinx/domains/std.py b/sphinx/domains/std.py
index f545ec7d1..52633d194 100644
--- a/sphinx/domains/std.py
+++ b/sphinx/domains/std.py
@@ -946,11 +946,11 @@ class StandardDomain(Domain):
if isinstance(node, nodes.section):
return 'section'
- elif isinstance(node, nodes.container):
- if node.get('literal_block') and has_child(node, nodes.literal_block):
- return 'code-block'
- else:
- return None
+ elif (isinstance(node, nodes.container) and
+ 'literal_block' in node and
+ has_child(node, nodes.literal_block)):
+ # given node is a code-block having caption
+ return 'code-block'
else:
figtype, _ = self.enumerable_nodes.get(node.__class__, (None, None))
return figtype
diff --git a/sphinx/ext/apidoc.py b/sphinx/ext/apidoc.py
index 0c70b4ec8..99cf67016 100644
--- a/sphinx/ext/apidoc.py
+++ b/sphinx/ext/apidoc.py
@@ -73,14 +73,19 @@ def module_join(*modnames: str) -> str:
def write_file(name: str, text: str, opts: Any) -> None:
"""Write the output file for module/package <name>."""
+ quiet = getattr(opts, 'quiet', None)
+
fname = path.join(opts.destdir, '%s.%s' % (name, opts.suffix))
if opts.dryrun:
- print(__('Would create file %s.') % fname)
+ if not quiet:
+ print(__('Would create file %s.') % fname)
return
if not opts.force and path.isfile(fname):
- print(__('File %s already exists, skipping.') % fname)
+ if not quiet:
+ print(__('File %s already exists, skipping.') % fname)
else:
- print(__('Creating file %s.') % fname)
+ if not quiet:
+ print(__('Creating file %s.') % fname)
with FileAvoidWrite(fname) as f:
f.write(text)
@@ -324,6 +329,8 @@ Note: By default this script will not overwrite already created files."""))
parser.add_argument('-o', '--output-dir', action='store', dest='destdir',
required=True,
help=__('directory to place all output'))
+ parser.add_argument('-q', action='store_true', dest='quiet',
+ help=__('no output on stdout, just warnings on stderr'))
parser.add_argument('-d', '--maxdepth', action='store', dest='maxdepth',
type=int, default=4,
help=__('maximum depth of submodules to show in the TOC '
@@ -451,6 +458,8 @@ def main(argv: List[str] = sys.argv[1:]) -> int:
}
if args.extensions:
d['extensions'].extend(args.extensions)
+ if args.quiet:
+ d['quiet'] = True
for ext in d['extensions'][:]:
if ',' in ext:
diff --git a/sphinx/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py
index fd5d2bd05..b9404804b 100644
--- a/sphinx/ext/autodoc/__init__.py
+++ b/sphinx/ext/autodoc/__init__.py
@@ -10,6 +10,7 @@
:license: BSD, see LICENSE for details.
"""
+import importlib
import re
import warnings
from types import ModuleType
@@ -31,6 +32,7 @@ from sphinx.util import logging
from sphinx.util import rpartition
from sphinx.util.docstrings import extract_metadata, prepare_docstring
from sphinx.util.inspect import getdoc, object_description, safe_getattr, stringify_signature
+from sphinx.util.typing import stringify as stringify_typehint
if False:
# For type annotation
@@ -1262,12 +1264,22 @@ class DataDocumenter(ModuleLevelDocumenter):
super().add_directive_header(sig)
sourcename = self.get_sourcename()
if not self.options.annotation:
+ # obtain annotation for this data
+ annotations = getattr(self.parent, '__annotations__', {})
+ if self.objpath[-1] in annotations:
+ objrepr = stringify_typehint(annotations.get(self.objpath[-1]))
+ self.add_line(' :type: ' + objrepr, sourcename)
+ else:
+ key = ('.'.join(self.objpath[:-1]), self.objpath[-1])
+ if self.analyzer and key in self.analyzer.annotations:
+ self.add_line(' :type: ' + self.analyzer.annotations[key],
+ sourcename)
+
try:
objrepr = object_description(self.object)
+ self.add_line(' :value: ' + objrepr, sourcename)
except ValueError:
pass
- else:
- self.add_line(' :annotation: = ' + objrepr, sourcename)
elif self.options.annotation is SUPPRESS:
pass
else:
@@ -1306,6 +1318,12 @@ class DataDeclarationDocumenter(DataDocumenter):
"""Never import anything."""
# disguise as a data
self.objtype = 'data'
+ try:
+ # import module to obtain type annotation
+ self.parent = importlib.import_module(self.modname)
+ except ImportError:
+ pass
+
return True
def add_content(self, more_content: Any, no_docstring: bool = False) -> None:
@@ -1434,12 +1452,22 @@ class AttributeDocumenter(DocstringStripSignatureMixin, ClassLevelDocumenter):
sourcename = self.get_sourcename()
if not self.options.annotation:
if not self._datadescriptor:
+ # obtain annotation for this attribute
+ annotations = getattr(self.parent, '__annotations__', {})
+ if self.objpath[-1] in annotations:
+ objrepr = stringify_typehint(annotations.get(self.objpath[-1]))
+ self.add_line(' :type: ' + objrepr, sourcename)
+ else:
+ key = ('.'.join(self.objpath[:-1]), self.objpath[-1])
+ if self.analyzer and key in self.analyzer.annotations:
+ self.add_line(' :type: ' + self.analyzer.annotations[key],
+ sourcename)
+
try:
objrepr = object_description(self.object)
+ self.add_line(' :value: ' + objrepr, sourcename)
except ValueError:
pass
- else:
- self.add_line(' :annotation: = ' + objrepr, sourcename)
elif self.options.annotation is SUPPRESS:
pass
else:
diff --git a/sphinx/ext/autosummary/generate.py b/sphinx/ext/autosummary/generate.py
index 7c29d41e8..006dab815 100644
--- a/sphinx/ext/autosummary/generate.py
+++ b/sphinx/ext/autosummary/generate.py
@@ -299,7 +299,8 @@ def generate_autosummary_docs(sources: List[str], output_dir: str = None,
generate_autosummary_docs(new_files, output_dir=output_dir,
suffix=suffix, warn=warn, info=info,
base_path=base_path, builder=builder,
- template_dir=template_dir, app=app,
+ template_dir=template_dir,
+ imported_members=imported_members, app=app,
overwrite=overwrite)
diff --git a/sphinx/pycode/__init__.py b/sphinx/pycode/__init__.py
index 12bd8d9ef..55d5d2c1d 100644
--- a/sphinx/pycode/__init__.py
+++ b/sphinx/pycode/__init__.py
@@ -142,9 +142,10 @@ class ModuleAnalyzer:
self.code = source.read()
# will be filled by parse()
- self.attr_docs = None # type: Dict[Tuple[str, str], List[str]]
- self.tagorder = None # type: Dict[str, int]
- self.tags = None # type: Dict[str, Tuple[str, int, int]]
+ self.annotations = None # type: Dict[Tuple[str, str], str]
+ self.attr_docs = None # type: Dict[Tuple[str, str], List[str]]
+ self.tagorder = None # type: Dict[str, int]
+ self.tags = None # type: Dict[str, Tuple[str, int, int]]
def parse(self) -> None:
"""Parse the source code."""
@@ -159,6 +160,7 @@ class ModuleAnalyzer:
else:
self.attr_docs[scope] = ['']
+ self.annotations = parser.annotations
self.tags = parser.definitions
self.tagorder = parser.deforders
except Exception as exc:
diff --git a/sphinx/pycode/ast.py b/sphinx/pycode/ast.py
index 155ae86d5..22207b715 100644
--- a/sphinx/pycode/ast.py
+++ b/sphinx/pycode/ast.py
@@ -38,6 +38,8 @@ def unparse(node: ast.AST) -> str:
"""Unparse an AST to string."""
if node is None:
return None
+ elif isinstance(node, str):
+ return node
elif isinstance(node, ast.Attribute):
return "%s.%s" % (unparse(node.value), node.attr)
elif isinstance(node, ast.Bytes):
diff --git a/sphinx/pycode/parser.py b/sphinx/pycode/parser.py
index c14d5773b..cb3cf0cc1 100644
--- a/sphinx/pycode/parser.py
+++ b/sphinx/pycode/parser.py
@@ -7,7 +7,6 @@
:copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
-import ast
import inspect
import itertools
import re
@@ -17,6 +16,9 @@ from token import NAME, NEWLINE, INDENT, DEDENT, NUMBER, OP, STRING
from tokenize import COMMENT, NL
from typing import Any, Dict, List, Tuple
+from sphinx.pycode.ast import ast # for py37 or older
+from sphinx.pycode.ast import parse, unparse
+
comment_re = re.compile('^\\s*#: ?(.*)\r?\n?$')
indent_re = re.compile('^\\s*$')
@@ -226,6 +228,7 @@ class VariableCommentPicker(ast.NodeVisitor):
self.current_classes = [] # type: List[str]
self.current_function = None # type: ast.FunctionDef
self.comments = {} # type: Dict[Tuple[str, str], str]
+ self.annotations = {} # type: Dict[Tuple[str, str], str]
self.previous = None # type: ast.AST
self.deforders = {} # type: Dict[str, int]
super().__init__()
@@ -254,6 +257,18 @@ class VariableCommentPicker(ast.NodeVisitor):
self.comments[(context, name)] = comment
+ def add_variable_annotation(self, name: str, annotation: ast.AST) -> None:
+ if self.current_function:
+ if self.current_classes and self.context[-1] == "__init__":
+ # store variable comments inside __init__ method of classes
+ context = ".".join(self.context[:-1])
+ else:
+ return
+ else:
+ context = ".".join(self.context)
+
+ self.annotations[(context, name)] = unparse(annotation)
+
def get_self(self) -> ast.arg:
"""Returns the name of first argument if in function."""
if self.current_function and self.current_function.args.args:
@@ -295,6 +310,14 @@ class VariableCommentPicker(ast.NodeVisitor):
except TypeError:
return # this assignment is not new definition!
+ # record annotation
+ if hasattr(node, 'annotation') and node.annotation: # type: ignore
+ for varname in varnames:
+ self.add_variable_annotation(varname, node.annotation) # type: ignore
+ elif hasattr(node, 'type_comment') and node.type_comment:
+ for varname in varnames:
+ self.add_variable_annotation(varname, node.type_comment) # type: ignore
+
# check comments after assignment
parser = AfterCommentParser([current_line[node.col_offset:]] +
self.buffers[node.lineno:])
@@ -468,6 +491,7 @@ class Parser:
def __init__(self, code: str, encoding: str = 'utf-8') -> None:
self.code = filter_whitespace(code)
self.encoding = encoding
+ self.annotations = {} # type: Dict[Tuple[str, str], str]
self.comments = {} # type: Dict[Tuple[str, str], str]
self.deforders = {} # type: Dict[str, int]
self.definitions = {} # type: Dict[str, Tuple[str, int, int]]
@@ -479,9 +503,10 @@ class Parser:
def parse_comments(self) -> None:
"""Parse the code and pick up comments."""
- tree = ast.parse(self.code)
+ tree = parse(self.code)
picker = VariableCommentPicker(self.code.splitlines(True), self.encoding)
picker.visit(tree)
+ self.annotations = picker.annotations
self.comments = picker.comments
self.deforders = picker.deforders
diff --git a/sphinx/themes/basic/static/doctools.js b/sphinx/themes/basic/static/doctools.js
index ddca10ca3..daccd209d 100644
--- a/sphinx/themes/basic/static/doctools.js
+++ b/sphinx/themes/basic/static/doctools.js
@@ -283,10 +283,11 @@ var Documentation = {
},
initOnKeyListeners: function() {
- $(document).keyup(function(event) {
+ $(document).keydown(function(event) {
var activeElementType = document.activeElement.tagName;
// don't navigate when in search box or textarea
- if (activeElementType !== 'TEXTAREA' && activeElementType !== 'INPUT' && activeElementType !== 'SELECT') {
+ if (activeElementType !== 'TEXTAREA' && activeElementType !== 'INPUT' && activeElementType !== 'SELECT'
+ && !event.altKey && !event.ctrlKey && !event.metaKey && !event.shiftKey) {
switch (event.keyCode) {
case 37: // left
var prevHref = $('link[rel="prev"]').prop('href');
diff --git a/sphinx/writers/latex.py b/sphinx/writers/latex.py
index 16501472f..4da3a0cd8 100644
--- a/sphinx/writers/latex.py
+++ b/sphinx/writers/latex.py
@@ -1329,6 +1329,8 @@ class LaTeXTranslator(SphinxTranslator):
length = self.latex_image_length(node['width'])
elif isinstance(node[0], nodes.image) and 'width' in node[0]:
length = self.latex_image_length(node[0]['width'])
+ self.body.append('\n\n') # Insert a blank line to prevent infinite loop
+ # https://github.com/sphinx-doc/sphinx/issues/7059
self.body.append('\\begin{wrapfigure}{%s}{%s}\n\\centering' %
('r' if node['align'] == 'right' else 'l', length or '0pt'))
self.context.append('\\end{wrapfigure}\n')
diff --git a/tests/roots/test-domain-py/module.rst b/tests/roots/test-domain-py/module.rst
index 64601bc95..c01032b26 100644
--- a/tests/roots/test-domain-py/module.rst
+++ b/tests/roots/test-domain-py/module.rst
@@ -18,6 +18,12 @@ module
* Link to :py:meth:`module_a.submodule.ModTopLevel.mod_child_1`
+.. py:method:: ModTopLevel.prop
+ :property:
+
+ * Link to :py:attr:`prop attribute <.prop>`
+ * Link to :py:meth:`prop method <.prop>`
+
.. py:currentmodule:: None
.. py:class:: ModNoModule
diff --git a/tests/roots/test-ext-autodoc/target/typed_vars.py b/tests/roots/test-ext-autodoc/target/typed_vars.py
index 4a9a6f7b5..b0782787e 100644
--- a/tests/roots/test-ext-autodoc/target/typed_vars.py
+++ b/tests/roots/test-ext-autodoc/target/typed_vars.py
@@ -2,12 +2,17 @@
attr1: str = ''
#: attr2
attr2: str
+#: attr3
+attr3 = '' # type: str
class Class:
attr1: int = 0
attr2: int
+ attr3 = 0 # type: int
def __init__(self):
- self.attr3: int = 0 #: attr3
- self.attr4: int #: attr4
+ self.attr4: int = 0 #: attr4
+ self.attr5: int #: attr5
+ self.attr6 = 0 # type: int
+ """attr6"""
diff --git a/tests/test_autodoc.py b/tests/test_autodoc.py
index 6fb4db6ad..52e9bf5c5 100644
--- a/tests/test_autodoc.py
+++ b/tests/test_autodoc.py
@@ -930,7 +930,7 @@ def test_autodoc_module_scope(app):
'',
'.. py:attribute:: Class.mdocattr',
' :module: target',
- ' :annotation: = <_io.StringIO object>',
+ ' :value: <_io.StringIO object>',
'',
' should be documented as well - süß',
' '
@@ -946,7 +946,7 @@ def test_autodoc_class_scope(app):
'',
'.. py:attribute:: Class.mdocattr',
' :module: target',
- ' :annotation: = <_io.StringIO object>',
+ ' :value: <_io.StringIO object>',
'',
' should be documented as well - süß',
' '
@@ -966,12 +966,12 @@ def test_class_attributes(app):
' ',
' .. py:attribute:: AttCls.a1',
' :module: target',
- ' :annotation: = hello world',
+ ' :value: hello world',
' ',
' ',
' .. py:attribute:: AttCls.a2',
' :module: target',
- ' :annotation: = None',
+ ' :value: None',
' '
]
@@ -990,7 +990,7 @@ def test_instance_attributes(app):
' ',
' .. py:attribute:: InstAttCls.ca1',
' :module: target',
- " :annotation: = 'a'",
+ " :value: 'a'",
' ',
' Doc comment for class attribute InstAttCls.ca1.',
' It can have multiple lines.',
@@ -998,28 +998,28 @@ def test_instance_attributes(app):
' ',
' .. py:attribute:: InstAttCls.ca2',
' :module: target',
- " :annotation: = 'b'",
+ " :value: 'b'",
' ',
' Doc comment for InstAttCls.ca2. One line only.',
' ',
' ',
' .. py:attribute:: InstAttCls.ca3',
' :module: target',
- " :annotation: = 'c'",
+ " :value: 'c'",
' ',
' Docstring for class attribute InstAttCls.ca3.',
' ',
' ',
' .. py:attribute:: InstAttCls.ia1',
' :module: target',
- ' :annotation: = None',
+ ' :value: None',
' ',
' Doc comment for instance attribute InstAttCls.ia1',
' ',
' ',
' .. py:attribute:: InstAttCls.ia2',
' :module: target',
- ' :annotation: = None',
+ ' :value: None',
' ',
' Docstring for instance attribute InstAttCls.ia2.',
' '
@@ -1038,7 +1038,7 @@ def test_instance_attributes(app):
' ',
' .. py:attribute:: InstAttCls.ca1',
' :module: target',
- " :annotation: = 'a'",
+ " :value: 'a'",
' ',
' Doc comment for class attribute InstAttCls.ca1.',
' It can have multiple lines.',
@@ -1046,7 +1046,7 @@ def test_instance_attributes(app):
' ',
' .. py:attribute:: InstAttCls.ia1',
' :module: target',
- ' :annotation: = None',
+ ' :value: None',
' ',
' Doc comment for instance attribute InstAttCls.ia1',
' '
@@ -1114,28 +1114,28 @@ def test_enum_class(app):
' ',
' .. py:attribute:: EnumCls.val1',
' :module: target.enum',
- ' :annotation: = 12',
+ ' :value: 12',
' ',
' doc for val1',
' ',
' ',
' .. py:attribute:: EnumCls.val2',
' :module: target.enum',
- ' :annotation: = 23',
+ ' :value: 23',
' ',
' doc for val2',
' ',
' ',
' .. py:attribute:: EnumCls.val3',
' :module: target.enum',
- ' :annotation: = 34',
+ ' :value: 34',
' ',
' doc for val3',
' ',
' ',
' .. py:attribute:: EnumCls.val4',
' :module: target.enum',
- ' :annotation: = 34',
+ ' :value: 34',
' '
]
@@ -1145,7 +1145,7 @@ def test_enum_class(app):
'',
'.. py:attribute:: EnumCls.val1',
' :module: target.enum',
- ' :annotation: = 12',
+ ' :value: 12',
'',
' doc for val1',
' '
@@ -1429,40 +1429,68 @@ def test_autodoc_typed_instance_variables(app):
' ',
' .. py:attribute:: Class.attr1',
' :module: target.typed_vars',
- ' :annotation: = 0',
+ ' :type: int',
+ ' :value: 0',
' ',
' ',
' .. py:attribute:: Class.attr2',
' :module: target.typed_vars',
- ' :annotation: = None',
+ ' :type: int',
+ ' :value: None',
' ',
' ',
' .. py:attribute:: Class.attr3',
' :module: target.typed_vars',
- ' :annotation: = None',
+ ' :type: int',
+ ' :value: 0',
' ',
- ' attr3',
- ' ',
' ',
' .. py:attribute:: Class.attr4',
' :module: target.typed_vars',
- ' :annotation: = None',
+ ' :type: int',
+ ' :value: None',
' ',
' attr4',
' ',
+ ' ',
+ ' .. py:attribute:: Class.attr5',
+ ' :module: target.typed_vars',
+ ' :type: int',
+ ' :value: None',
+ ' ',
+ ' attr5',
+ ' ',
+ ' ',
+ ' .. py:attribute:: Class.attr6',
+ ' :module: target.typed_vars',
+ ' :type: int',
+ ' :value: None',
+ ' ',
+ ' attr6',
+ ' ',
'',
'.. py:data:: attr1',
' :module: target.typed_vars',
- " :annotation: = ''",
+ ' :type: str',
+ " :value: ''",
'',
' attr1',
' ',
'',
'.. py:data:: attr2',
' :module: target.typed_vars',
- " :annotation: = None",
+ ' :type: str',
+ ' :value: None',
'',
' attr2',
+ ' ',
+ '',
+ '.. py:data:: attr3',
+ ' :module: target.typed_vars',
+ ' :type: str',
+ " :value: ''",
+ '',
+ ' attr3',
' '
]
@@ -1479,7 +1507,7 @@ def test_autodoc_for_egged_code(app):
'',
'.. py:data:: CONSTANT',
' :module: sample',
- ' :annotation: = 1',
+ ' :value: 1',
'',
' constant on sample.py',
' ',
diff --git a/tests/test_directive_other.py b/tests/test_directive_other.py
index bfdc1aac8..52e4a937c 100644
--- a/tests/test_directive_other.py
+++ b/tests/test_directive_other.py
@@ -127,6 +127,23 @@ def test_toctree_glob_and_url(app):
@pytest.mark.sphinx(testroot='toctree-glob')
+def test_reversed_toctree(app):
+ text = (".. toctree::\n"
+ " :reversed:\n"
+ "\n"
+ " foo\n"
+ " bar/index\n"
+ " baz\n")
+
+ app.env.find_files(app.config, app.builder)
+ doctree = restructuredtext.parse(app, text, 'index')
+ assert_node(doctree, [nodes.document, nodes.compound, addnodes.toctree])
+ assert_node(doctree[0][0],
+ entries=[(None, 'baz'), (None, 'bar/index'), (None, 'foo')],
+ includefiles=['baz', 'bar/index', 'foo'])
+
+
+@pytest.mark.sphinx(testroot='toctree-glob')
def test_toctree_twice(app):
text = (".. toctree::\n"
"\n"
diff --git a/tests/test_domain_py.py b/tests/test_domain_py.py
index 3ff29cbb7..f78c1e9d8 100644
--- a/tests/test_domain_py.py
+++ b/tests/test_domain_py.py
@@ -105,19 +105,22 @@ def test_domain_py_xrefs(app, status, warning):
'mod_child_2', 'meth')
assert_refnode(refnodes[4], 'module_a.submodule', 'ModTopLevel',
'module_a.submodule.ModTopLevel.mod_child_1', 'meth')
- assert_refnode(refnodes[5], 'module_b.submodule', None,
+ assert_refnode(refnodes[5], 'module_a.submodule', 'ModTopLevel',
+ 'prop', 'attr')
+ assert_refnode(refnodes[6], 'module_a.submodule', 'ModTopLevel',
+ 'prop', 'meth')
+ assert_refnode(refnodes[7], 'module_b.submodule', None,
'ModTopLevel', 'class')
- assert_refnode(refnodes[6], 'module_b.submodule', 'ModTopLevel',
+ assert_refnode(refnodes[8], 'module_b.submodule', 'ModTopLevel',
'ModNoModule', 'class')
- assert_refnode(refnodes[7], False, False, 'int', 'class')
- assert_refnode(refnodes[8], False, False, 'tuple', 'class')
- assert_refnode(refnodes[9], False, False, 'str', 'class')
- assert_refnode(refnodes[10], False, False, 'float', 'class')
- assert_refnode(refnodes[11], False, False, 'list', 'class')
- assert_refnode(refnodes[11], False, False, 'list', 'class')
- assert_refnode(refnodes[12], False, False, 'ModTopLevel', 'class')
- assert_refnode(refnodes[13], False, False, 'index', 'doc', domain='std')
- assert len(refnodes) == 14
+ assert_refnode(refnodes[9], False, False, 'int', 'class')
+ assert_refnode(refnodes[10], False, False, 'tuple', 'class')
+ assert_refnode(refnodes[11], False, False, 'str', 'class')
+ assert_refnode(refnodes[12], False, False, 'float', 'class')
+ assert_refnode(refnodes[13], False, False, 'list', 'class')
+ assert_refnode(refnodes[14], False, False, 'ModTopLevel', 'class')
+ assert_refnode(refnodes[15], False, False, 'index', 'doc', domain='std')
+ assert len(refnodes) == 16
doctree = app.env.get_doctree('module_option')
refnodes = list(doctree.traverse(addnodes.pending_xref))
@@ -161,6 +164,21 @@ def test_domain_py_objects(app, status, warning):
assert objects['NestedParentB.child_1'] == ('roles', 'method')
+@pytest.mark.sphinx('html', testroot='domain-py')
+def test_resolve_xref_for_properties(app, status, warning):
+ app.builder.build_all()
+
+ content = (app.outdir / 'module.html').text()
+ assert ('Link to <a class="reference internal" href="#module_a.submodule.ModTopLevel.prop"'
+ ' title="module_a.submodule.ModTopLevel.prop">'
+ '<code class="xref py py-attr docutils literal notranslate"><span class="pre">'
+ 'prop</span> <span class="pre">attribute</span></code></a>' in content)
+ assert ('Link to <a class="reference internal" href="#module_a.submodule.ModTopLevel.prop"'
+ ' title="module_a.submodule.ModTopLevel.prop">'
+ '<code class="xref py py-meth docutils literal notranslate"><span class="pre">'
+ 'prop</span> <span class="pre">method</span></code></a>' in content)
+
+
@pytest.mark.sphinx('dummy', testroot='domain-py')
def test_domain_py_find_obj(app, status, warning):
@@ -268,6 +286,20 @@ def test_exceptions_module_is_ignored(app):
def test_pydata_signature(app):
text = (".. py:data:: version\n"
+ " :type: int\n"
+ " :value: 1\n")
+ doctree = restructuredtext.parse(app, text)
+ assert_node(doctree, (addnodes.index,
+ [desc, ([desc_signature, ([desc_name, "version"],
+ [desc_annotation, ": int"],
+ [desc_annotation, " = 1"])],
+ desc_content)]))
+ assert_node(doctree[1], addnodes.desc, desctype="data",
+ domain="py", objtype="data", noindex=False)
+
+
+def test_pydata_signature_old(app):
+ text = (".. py:data:: version\n"
" :annotation: = 1\n")
doctree = restructuredtext.parse(app, text)
assert_node(doctree, (addnodes.index,
@@ -463,7 +495,9 @@ def test_pystaticmethod(app):
def test_pyattribute(app):
text = (".. py:class:: Class\n"
"\n"
- " .. py:attribute:: attr\n")
+ " .. py:attribute:: attr\n"
+ " :type: str\n"
+ " :value: ''\n")
domain = app.env.get_domain('py')
doctree = restructuredtext.parse(app, text)
assert_node(doctree, (addnodes.index,
@@ -473,7 +507,9 @@ def test_pyattribute(app):
desc)])]))
assert_node(doctree[1][1][0], addnodes.index,
entries=[('single', 'attr (Class attribute)', 'Class.attr', '', None)])
- assert_node(doctree[1][1][1], ([desc_signature, desc_name, "attr"],
+ assert_node(doctree[1][1][1], ([desc_signature, ([desc_name, "attr"],
+ [desc_annotation, ": str"],
+ [desc_annotation, " = ''"])],
[desc_content, ()]))
assert 'Class.attr' in domain.objects
assert domain.objects['Class.attr'] == ('index', 'attribute')
diff --git a/tests/test_environment_toctree.py b/tests/test_environment_toctree.py
index 6a23b384a..a8c7da62e 100644
--- a/tests/test_environment_toctree.py
+++ b/tests/test_environment_toctree.py
@@ -119,16 +119,14 @@ def test_glob(app):
[bullet_list, addnodes.toctree])]) # [0][1][1][1][0]
assert_node(toctree[0][1][1][1][0], addnodes.toctree, caption=None,
glob=True, hidden=False, titlesonly=False,
- maxdepth=-1, numbered=0, includefiles=includefiles,
+ maxdepth=-1, numbered=0, includefiles=list(reversed(includefiles)),
entries=[(None, 'qux/index'), (None, 'baz'), (None, 'bar/bar_3'),
(None, 'bar/bar_2'), (None, 'bar/bar_1'), (None, 'bar/index'),
(None, 'foo')])
- includefiles = ['foo', 'bar/index', 'bar/bar_1', 'bar/bar_2',
- 'bar/bar_3', 'baz', 'qux/index']
# other collections
assert app.env.toc_num_entries['index'] == 3
- assert app.env.toctree_includes['index'] == includefiles + includefiles
+ assert app.env.toctree_includes['index'] == includefiles + list(reversed(includefiles))
for file in includefiles:
assert 'index' in app.env.files_to_rebuild[file]
assert 'index' in app.env.glob_toctrees
diff --git a/tests/test_pycode_parser.py b/tests/test_pycode_parser.py
index b8bece84e..0bf505a33 100644
--- a/tests/test_pycode_parser.py
+++ b/tests/test_pycode_parser.py
@@ -99,12 +99,19 @@ def test_annotated_assignment_py36():
source = ('a: str = "Sphinx" #: comment\n'
'b: int = 1\n'
'"""string on next line"""\n'
- 'c: int #: comment')
+ 'c: int #: comment\n'
+ 'd = 1 # type: int\n'
+ '"""string on next line"""\n')
parser = Parser(source)
parser.parse()
assert parser.comments == {('', 'a'): 'comment',
('', 'b'): 'string on next line',
- ('', 'c'): 'comment'}
+ ('', 'c'): 'comment',
+ ('', 'd'): 'string on next line'}
+ assert parser.annotations == {('', 'a'): 'str',
+ ('', 'b'): 'int',
+ ('', 'c'): 'int',
+ ('', 'd'): 'int'}
assert parser.definitions == {}