diff --git a/AUTHORS b/AUTHORS
index 8505b644d..dbe1082a6 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -64,6 +64,7 @@ Other contributors, listed alphabetically, are:
* \T. Powers -- HTML output improvements
* Jeppe Pihl -- literalinclude improvements
* Rob Ruana -- napoleon extension
+* Vince Salvino -- JavaScript search improvements
* Stefan Seefeld -- toctree improvements
* Gregory Szorc -- performance improvements
* Taku Shimizu -- epub3 builder
diff --git a/CHANGES b/CHANGES
index 39800c633..4a78be671 100644
--- a/CHANGES
+++ b/CHANGES
@@ -93,6 +93,14 @@ Features added
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
--------
diff --git a/doc/templating.rst b/doc/templating.rst
index 14fb31ed8..fd0f6b637 100644
--- a/doc/templating.rst
+++ b/doc/templating.rst
@@ -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
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
+
+
+ {% block document %}{% endblock %}
+
+
`sidebar1` / `sidebar2`
A possible location for a sidebar. `sidebar1` appears before the document
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
contain hidden entries.
-
-
diff --git a/sphinx/ext/autodoc/type_comment.py b/sphinx/ext/autodoc/type_comment.py
index a7eb2c4a2..e6a77f24d 100644
--- a/sphinx/ext/autodoc/type_comment.py
+++ b/sphinx/ext/autodoc/type_comment.py
@@ -56,7 +56,7 @@ def signature_from_ast(node: ast.FunctionDef, bound_method: bool,
if node.args.vararg:
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)
for arg in node.args.kwonlyargs:
@@ -66,7 +66,7 @@ def signature_from_ast(node: ast.FunctionDef, bound_method: bool,
if node.args.kwarg:
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)
# Remove first parameter when *obj* is bound_method
diff --git a/sphinx/ext/intersphinx.py b/sphinx/ext/intersphinx.py
index da0988b79..362583fa2 100644
--- a/sphinx/ext/intersphinx.py
+++ b/sphinx/ext/intersphinx.py
@@ -282,6 +282,9 @@ def missing_reference(app: Sphinx, env: BuildEnvironment, node: Element, contnod
if 'std:cmdoption' in objtypes:
# until Sphinx-1.6, cmdoptions are stored as 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)]
if domain:
full_qualified_name = env.get_domain(domain).get_full_qualified_name(node)
diff --git a/sphinx/ext/todo.py b/sphinx/ext/todo.py
index 69aaaf8b2..c5cacc437 100644
--- a/sphinx/ext/todo.py
+++ b/sphinx/ext/todo.py
@@ -28,7 +28,7 @@ from sphinx.environment import BuildEnvironment
from sphinx.errors import NoUri
from sphinx.locale import _, __
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.writers.html import HTMLTranslator
from sphinx.writers.latex import LaTeXTranslator
@@ -159,6 +159,7 @@ class TodoListProcessor:
def process(self, doctree: nodes.document, docname: str) -> None:
todos = sum(self.domain.todos.values(), []) # type: List[todo_node]
+ document = new_document('')
for node in doctree.traverse(todolist):
if not self.config.todo_include_todos:
node.parent.remove(node)
@@ -175,7 +176,11 @@ class TodoListProcessor:
new_todo['ids'].clear()
# (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)
todo_ref = self.create_todo_reference(todo, docname)
diff --git a/sphinx/themes/basic/static/searchtools.js b/sphinx/themes/basic/static/searchtools.js
index edef8acf5..d11b33a78 100644
--- a/sphinx/themes/basic/static/searchtools.js
+++ b/sphinx/themes/basic/static/searchtools.js
@@ -63,6 +63,11 @@ var Search = {
htmlElement.innerHTML = htmlString;
$(htmlElement).find('.headerlink').remove();
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;
},
@@ -245,6 +250,7 @@ var Search = {
if (results.length) {
var item = results.pop();
var listItem = $('');
+ var requestUrl = "";
if (DOCUMENTATION_OPTIONS.BUILDER === 'dirhtml') {
// dirhtml builder
var dirname = item[0] + '/';
@@ -253,15 +259,15 @@ var Search = {
} else if (dirname == 'index/') {
dirname = '';
}
- listItem.append($('').attr('href',
- DOCUMENTATION_OPTIONS.URL_ROOT + dirname +
- highlightstring + item[2]).html(item[1]));
+ requestUrl = DOCUMENTATION_OPTIONS.URL_ROOT + dirname;
+
} else {
// normal html builders
- listItem.append($('').attr('href',
- item[0] + DOCUMENTATION_OPTIONS.FILE_SUFFIX +
- highlightstring + item[2]).html(item[1]));
+ requestUrl = DOCUMENTATION_OPTIONS.URL_ROOT + item[0] + DOCUMENTATION_OPTIONS.FILE_SUFFIX;
}
+ listItem.append($('').attr('href',
+ requestUrl +
+ highlightstring + item[2]).html(item[1]));
if (item[3]) {
listItem.append($(' (' + item[3] + ')'));
Search.output.append(listItem);
@@ -269,7 +275,7 @@ var Search = {
displayNextItem();
});
} else if (DOCUMENTATION_OPTIONS.HAS_SOURCE) {
- $.ajax({url: DOCUMENTATION_OPTIONS.URL_ROOT + item[0] + DOCUMENTATION_OPTIONS.FILE_SUFFIX,
+ $.ajax({url: requestUrl,
dataType: "text",
complete: function(jqxhr, textstatus) {
var data = jqxhr.responseText;
diff --git a/sphinx/util/inspect.py b/sphinx/util/inspect.py
index 5bf3ffbbf..281ef4493 100644
--- a/sphinx/util/inspect.py
+++ b/sphinx/util/inspect.py
@@ -116,6 +116,19 @@ def getargspec(func: Callable) -> Any:
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:
"""Check if the object is subclass of 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."""
if isinstance(obj, classmethod):
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 False
@@ -213,17 +226,17 @@ def isattributedescriptor(obj: Any) -> bool:
def isfunction(obj: Any) -> bool:
"""Check if the object is function."""
- return inspect.isfunction(unpartial(obj))
+ return inspect.isfunction(unwrap(obj))
def isbuiltin(obj: Any) -> bool:
"""Check if the object is builtin."""
- return inspect.isbuiltin(unpartial(obj))
+ return inspect.isbuiltin(unwrap(obj))
def iscoroutinefunction(obj: Any) -> bool:
"""Check if the object is coroutine-function."""
- obj = unpartial(obj)
+ obj = unwrap(obj)
if hasattr(obj, '__code__') and inspect.iscoroutinefunction(obj):
# check obj.__code__ because iscoroutinefunction() crashes for custom method-like
# objects (see https://github.com/sphinx-doc/sphinx/issues/6605)
diff --git a/tests/roots/test-ext-autodoc/target/coroutine.py b/tests/roots/test-ext-autodoc/target/coroutine.py
index b3223a820..69602325d 100644
--- a/tests/roots/test-ext-autodoc/target/coroutine.py
+++ b/tests/roots/test-ext-autodoc/target/coroutine.py
@@ -3,6 +3,16 @@ class AsyncClass:
"""A documented coroutine function"""
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():
return "run"
diff --git a/tests/test_autodoc.py b/tests/test_autodoc.py
index e6b4cc5b6..d81df8245 100644
--- a/tests/test_autodoc.py
+++ b/tests/test_autodoc.py
@@ -1356,7 +1356,23 @@ def test_coroutine():
' :async:',
' ',
' 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',
+ ' ',
]
diff --git a/tests/test_ext_intersphinx.py b/tests/test_ext_intersphinx.py
index 9f0f18663..53faa7a37 100644
--- a/tests/test_ext_intersphinx.py
+++ b/tests/test_ext_intersphinx.py
@@ -190,6 +190,12 @@ def test_missing_reference_pydomain(tempdir, app, status, warning):
rn = missing_reference(app, app.env, node, contnode)
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):
inv_file = tempdir / 'inventory'
diff --git a/tests/test_util_inspect.py b/tests/test_util_inspect.py
index 5c9520370..627d20f54 100644
--- a/tests/test_util_inspect.py
+++ b/tests/test_util_inspect.py
@@ -434,6 +434,16 @@ def test_dict_customtype():
assert ": 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')
def test_isstaticmethod(app):
from target.methods import Base, Inherited
diff --git a/tests/test_util_inventory.py b/tests/test_util_inventory.py
index 7491b217d..2183313e1 100644
--- a/tests/test_util_inventory.py
+++ b/tests/test_util_inventory.py
@@ -31,6 +31,7 @@ inventory_v2 = '''\
module1 py:module 0 foo.html#module-module1 Long Module desc
module2 py:module 0 foo.html#module-$ -
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 -
std cpp:type 1 index.html#std -
std::uint8_t cpp:type 1 index.html#std_uint8_t -