Merge branch '2.4.x' into 3.x

This commit is contained in:
Takeshi KOMIYA 2020-02-22 19:22:31 +09:00
commit 754d04f80d
13 changed files with 106 additions and 18 deletions

View File

@ -64,6 +64,7 @@ Other contributors, listed alphabetically, are:
* \T. Powers -- HTML output improvements * \T. Powers -- HTML output improvements
* Jeppe Pihl -- literalinclude improvements * Jeppe Pihl -- literalinclude improvements
* Rob Ruana -- napoleon extension * Rob Ruana -- napoleon extension
* Vince Salvino -- JavaScript search improvements
* Stefan Seefeld -- toctree improvements * Stefan Seefeld -- toctree improvements
* Gregory Szorc -- performance improvements * Gregory Szorc -- performance improvements
* Taku Shimizu -- epub3 builder * Taku Shimizu -- epub3 builder

View File

@ -93,6 +93,14 @@ Features added
Bugs fixed Bugs fixed
---------- ----------
* #7184: autodoc: ``*args`` and ``**kwarg`` in type comments are not handled
properly
* #7189: autodoc: classmethod coroutines are not detected
* #7183: intersphinx: ``:attr:`` reference to property is broken
* #6244, #6387: html search: Search breaks/hangs when built with dirhtml builder
* #7195: todo: emit doctree-resolved event with non-document node incorrectly
Testing Testing
-------- --------

View File

@ -121,6 +121,17 @@ The following blocks exist in the ``layout.html`` template:
The contents of the document itself. It contains the block "body" where the The contents of the document itself. It contains the block "body" where the
individual content is put by subtemplates like ``page.html``. individual content is put by subtemplates like ``page.html``.
.. note::
In order for the built-in JavaScript search to show a page preview on
the results page, the document or body content should be wrapped in an
HTML element containing the ``role="main"`` attribute. For example:
.. sourcecode:: html+jinja
<div role="main">
{% block document %}{% endblock %}
</div>
`sidebar1` / `sidebar2` `sidebar1` / `sidebar2`
A possible location for a sidebar. `sidebar1` appears before the document A possible location for a sidebar. `sidebar1` appears before the document
and is empty by default, `sidebar2` after the document and contains the and is empty by default, `sidebar2` after the document and contains the
@ -427,5 +438,3 @@ are in HTML form), these variables are also available:
* ``includehidden`` (``False`` by default): if true, the TOC tree will also * ``includehidden`` (``False`` by default): if true, the TOC tree will also
contain hidden entries. contain hidden entries.

View File

@ -56,7 +56,7 @@ def signature_from_ast(node: ast.FunctionDef, bound_method: bool,
if node.args.vararg: if node.args.vararg:
param = Parameter(node.args.vararg.arg, Parameter.VAR_POSITIONAL, param = Parameter(node.args.vararg.arg, Parameter.VAR_POSITIONAL,
annotation=arg.type_comment or Parameter.empty) annotation=node.args.vararg.type_comment or Parameter.empty)
params.append(param) params.append(param)
for arg in node.args.kwonlyargs: for arg in node.args.kwonlyargs:
@ -66,7 +66,7 @@ def signature_from_ast(node: ast.FunctionDef, bound_method: bool,
if node.args.kwarg: if node.args.kwarg:
param = Parameter(node.args.kwarg.arg, Parameter.VAR_KEYWORD, param = Parameter(node.args.kwarg.arg, Parameter.VAR_KEYWORD,
annotation=arg.type_comment or Parameter.empty) annotation=node.args.kwarg.type_comment or Parameter.empty)
params.append(param) params.append(param)
# Remove first parameter when *obj* is bound_method # Remove first parameter when *obj* is bound_method

View File

@ -282,6 +282,9 @@ def missing_reference(app: Sphinx, env: BuildEnvironment, node: Element, contnod
if 'std:cmdoption' in objtypes: if 'std:cmdoption' in objtypes:
# until Sphinx-1.6, cmdoptions are stored as std:option # until Sphinx-1.6, cmdoptions are stored as std:option
objtypes.append('std:option') objtypes.append('std:option')
if 'py:attribute' in objtypes:
# Since Sphinx-2.1, properties are stored as py:method
objtypes.append('py:method')
to_try = [(inventories.main_inventory, target)] to_try = [(inventories.main_inventory, target)]
if domain: if domain:
full_qualified_name = env.get_domain(domain).get_full_qualified_name(node) full_qualified_name = env.get_domain(domain).get_full_qualified_name(node)

View File

@ -28,7 +28,7 @@ from sphinx.environment import BuildEnvironment
from sphinx.errors import NoUri from sphinx.errors import NoUri
from sphinx.locale import _, __ from sphinx.locale import _, __
from sphinx.util import logging, texescape from sphinx.util import logging, texescape
from sphinx.util.docutils import SphinxDirective from sphinx.util.docutils import SphinxDirective, new_document
from sphinx.util.nodes import make_refnode from sphinx.util.nodes import make_refnode
from sphinx.writers.html import HTMLTranslator from sphinx.writers.html import HTMLTranslator
from sphinx.writers.latex import LaTeXTranslator from sphinx.writers.latex import LaTeXTranslator
@ -159,6 +159,7 @@ class TodoListProcessor:
def process(self, doctree: nodes.document, docname: str) -> None: def process(self, doctree: nodes.document, docname: str) -> None:
todos = sum(self.domain.todos.values(), []) # type: List[todo_node] todos = sum(self.domain.todos.values(), []) # type: List[todo_node]
document = new_document('')
for node in doctree.traverse(todolist): for node in doctree.traverse(todolist):
if not self.config.todo_include_todos: if not self.config.todo_include_todos:
node.parent.remove(node) node.parent.remove(node)
@ -175,7 +176,11 @@ class TodoListProcessor:
new_todo['ids'].clear() new_todo['ids'].clear()
# (Recursively) resolve references in the todo content # (Recursively) resolve references in the todo content
self.env.resolve_references(new_todo, todo['docname'], self.builder) # type: ignore # NOQA #
# Note: To resolve references, it is needed to wrap it with document node
document += new_todo
self.env.resolve_references(document, todo['docname'], self.builder)
document.remove(new_todo)
content.append(new_todo) content.append(new_todo)
todo_ref = self.create_todo_reference(todo, docname) todo_ref = self.create_todo_reference(todo, docname)

View File

@ -63,6 +63,11 @@ var Search = {
htmlElement.innerHTML = htmlString; htmlElement.innerHTML = htmlString;
$(htmlElement).find('.headerlink').remove(); $(htmlElement).find('.headerlink').remove();
docContent = $(htmlElement).find('[role=main]')[0]; docContent = $(htmlElement).find('[role=main]')[0];
if(docContent === undefined) {
console.warn("Content block not found. Sphinx search tries to obtain it " +
"via '[role=main]'. Could you check your theme or template.");
return "";
}
return docContent.textContent || docContent.innerText; return docContent.textContent || docContent.innerText;
}, },
@ -245,6 +250,7 @@ var Search = {
if (results.length) { if (results.length) {
var item = results.pop(); var item = results.pop();
var listItem = $('<li style="display:none"></li>'); var listItem = $('<li style="display:none"></li>');
var requestUrl = "";
if (DOCUMENTATION_OPTIONS.BUILDER === 'dirhtml') { if (DOCUMENTATION_OPTIONS.BUILDER === 'dirhtml') {
// dirhtml builder // dirhtml builder
var dirname = item[0] + '/'; var dirname = item[0] + '/';
@ -253,15 +259,15 @@ var Search = {
} else if (dirname == 'index/') { } else if (dirname == 'index/') {
dirname = ''; dirname = '';
} }
listItem.append($('<a/>').attr('href', requestUrl = DOCUMENTATION_OPTIONS.URL_ROOT + dirname;
DOCUMENTATION_OPTIONS.URL_ROOT + dirname +
highlightstring + item[2]).html(item[1]));
} else { } else {
// normal html builders // normal html builders
listItem.append($('<a/>').attr('href', requestUrl = DOCUMENTATION_OPTIONS.URL_ROOT + item[0] + DOCUMENTATION_OPTIONS.FILE_SUFFIX;
item[0] + DOCUMENTATION_OPTIONS.FILE_SUFFIX +
highlightstring + item[2]).html(item[1]));
} }
listItem.append($('<a/>').attr('href',
requestUrl +
highlightstring + item[2]).html(item[1]));
if (item[3]) { if (item[3]) {
listItem.append($('<span> (' + item[3] + ')</span>')); listItem.append($('<span> (' + item[3] + ')</span>'));
Search.output.append(listItem); Search.output.append(listItem);
@ -269,7 +275,7 @@ var Search = {
displayNextItem(); displayNextItem();
}); });
} else if (DOCUMENTATION_OPTIONS.HAS_SOURCE) { } else if (DOCUMENTATION_OPTIONS.HAS_SOURCE) {
$.ajax({url: DOCUMENTATION_OPTIONS.URL_ROOT + item[0] + DOCUMENTATION_OPTIONS.FILE_SUFFIX, $.ajax({url: requestUrl,
dataType: "text", dataType: "text",
complete: function(jqxhr, textstatus) { complete: function(jqxhr, textstatus) {
var data = jqxhr.responseText; var data = jqxhr.responseText;

View File

@ -116,6 +116,19 @@ def getargspec(func: Callable) -> Any:
kwonlyargs, kwdefaults, annotations) kwonlyargs, kwdefaults, annotations)
def unwrap(obj: Any) -> Any:
"""Get an original object from wrapped object."""
while True:
if ispartial(obj):
obj = unpartial(obj)
elif isclassmethod(obj):
obj = obj.__func__
elif isstaticmethod(obj):
obj = obj.__func__
else:
return obj
def isenumclass(x: Any) -> bool: def isenumclass(x: Any) -> bool:
"""Check if the object is subclass of enum.""" """Check if the object is subclass of enum."""
return inspect.isclass(x) and issubclass(x, enum.Enum) return inspect.isclass(x) and issubclass(x, enum.Enum)
@ -146,7 +159,7 @@ def isclassmethod(obj: Any) -> bool:
"""Check if the object is classmethod.""" """Check if the object is classmethod."""
if isinstance(obj, classmethod): if isinstance(obj, classmethod):
return True return True
elif inspect.ismethod(obj) and obj.__self__ is not None: elif inspect.ismethod(obj) and obj.__self__ is not None and isclass(obj.__self__):
return True return True
return False return False
@ -213,17 +226,17 @@ def isattributedescriptor(obj: Any) -> bool:
def isfunction(obj: Any) -> bool: def isfunction(obj: Any) -> bool:
"""Check if the object is function.""" """Check if the object is function."""
return inspect.isfunction(unpartial(obj)) return inspect.isfunction(unwrap(obj))
def isbuiltin(obj: Any) -> bool: def isbuiltin(obj: Any) -> bool:
"""Check if the object is builtin.""" """Check if the object is builtin."""
return inspect.isbuiltin(unpartial(obj)) return inspect.isbuiltin(unwrap(obj))
def iscoroutinefunction(obj: Any) -> bool: def iscoroutinefunction(obj: Any) -> bool:
"""Check if the object is coroutine-function.""" """Check if the object is coroutine-function."""
obj = unpartial(obj) obj = unwrap(obj)
if hasattr(obj, '__code__') and inspect.iscoroutinefunction(obj): if hasattr(obj, '__code__') and inspect.iscoroutinefunction(obj):
# check obj.__code__ because iscoroutinefunction() crashes for custom method-like # check obj.__code__ because iscoroutinefunction() crashes for custom method-like
# objects (see https://github.com/sphinx-doc/sphinx/issues/6605) # objects (see https://github.com/sphinx-doc/sphinx/issues/6605)

View File

@ -3,6 +3,16 @@ class AsyncClass:
"""A documented coroutine function""" """A documented coroutine function"""
attr_coro_result = await _other_coro_func() # NOQA attr_coro_result = await _other_coro_func() # NOQA
@classmethod
async def do_coroutine2(cls):
"""A documented coroutine classmethod"""
pass
@staticmethod
async def do_coroutine3():
"""A documented coroutine staticmethod"""
pass
async def _other_coro_func(): async def _other_coro_func():
return "run" return "run"

View File

@ -1356,7 +1356,23 @@ def test_coroutine():
' :async:', ' :async:',
' ', ' ',
' A documented coroutine function', ' A documented coroutine function',
' ' ' ',
' ',
' .. py:method:: AsyncClass.do_coroutine2()',
' :module: target.coroutine',
' :async:',
' :classmethod:',
' ',
' A documented coroutine classmethod',
' ',
' ',
' .. py:method:: AsyncClass.do_coroutine3()',
' :module: target.coroutine',
' :async:',
' :staticmethod:',
' ',
' A documented coroutine staticmethod',
' ',
] ]

View File

@ -190,6 +190,12 @@ def test_missing_reference_pydomain(tempdir, app, status, warning):
rn = missing_reference(app, app.env, node, contnode) rn = missing_reference(app, app.env, node, contnode)
assert rn.astext() == 'func()' assert rn.astext() == 'func()'
# py:attr context helps to search objects
kwargs = {'py:module': 'module1'}
node, contnode = fake_node('py', 'attr', 'Foo.bar', 'Foo.bar', **kwargs)
rn = missing_reference(app, app.env, node, contnode)
assert rn.astext() == 'Foo.bar'
def test_missing_reference_stddomain(tempdir, app, status, warning): def test_missing_reference_stddomain(tempdir, app, status, warning):
inv_file = tempdir / 'inventory' inv_file = tempdir / 'inventory'

View File

@ -434,6 +434,16 @@ def test_dict_customtype():
assert "<CustomType(2)>: 2" in description assert "<CustomType(2)>: 2" in description
@pytest.mark.sphinx(testroot='ext-autodoc')
def test_isclassmethod(app):
from target.methods import Base, Inherited
assert inspect.isclassmethod(Base.classmeth) is True
assert inspect.isclassmethod(Base.meth) is False
assert inspect.isclassmethod(Inherited.classmeth) is True
assert inspect.isclassmethod(Inherited.meth) is False
@pytest.mark.sphinx(testroot='ext-autodoc') @pytest.mark.sphinx(testroot='ext-autodoc')
def test_isstaticmethod(app): def test_isstaticmethod(app):
from target.methods import Base, Inherited from target.methods import Base, Inherited

View File

@ -31,6 +31,7 @@ inventory_v2 = '''\
module1 py:module 0 foo.html#module-module1 Long Module desc module1 py:module 0 foo.html#module-module1 Long Module desc
module2 py:module 0 foo.html#module-$ - module2 py:module 0 foo.html#module-$ -
module1.func py:function 1 sub/foo.html#$ - module1.func py:function 1 sub/foo.html#$ -
module1.Foo.bar py:method 1 index.html#foo.Bar.baz -
CFunc c:function 2 cfunc.html#CFunc - CFunc c:function 2 cfunc.html#CFunc -
std cpp:type 1 index.html#std - std cpp:type 1 index.html#std -
std::uint8_t cpp:type 1 index.html#std_uint8_t - std::uint8_t cpp:type 1 index.html#std_uint8_t -