Merge branch '3.x' into 8190_autodoc-process-docstring-without_ending_blankline

This commit is contained in:
Takeshi KOMIYA
2020-09-21 00:49:23 +09:00
committed by GitHub
15 changed files with 163 additions and 43 deletions

12
CHANGES
View File

@@ -13,14 +13,22 @@ Deprecated
Features added Features added
-------------- --------------
* #8100: html: Show a better error message for failures on copying
html_static_files
* #8141: C: added a ``maxdepth`` option to :rst:dir:`c:alias` to insert
nested declarations.
Bugs fixed Bugs fixed
---------- ----------
* #8085: i18n: Add support for having single text domain * #8085: i18n: Add support for having single text domain
* #8143: autodoc: AttributeError is raised when False value is passed to * #8143: autodoc: AttributeError is raised when False value is passed to
autodoc_default_options autodoc_default_options
* #8103: autodoc: functools.cached_property is not considered as a property
* #8190: autodoc: parsing error is raised if some extension replaces docstring * #8190: autodoc: parsing error is raised if some extension replaces docstring
by string not ending with blank lines by string not ending with blank lines
* #8192: napoleon: description is disappeared when it contains inline literals
* #8169: LaTeX: pxjahyper loaded even when latex_engine is not platex
* #8093: The highlight warning has wrong location in some builders (LaTeX, * #8093: The highlight warning has wrong location in some builders (LaTeX,
singlehtml and so on) singlehtml and so on)
@@ -45,6 +53,10 @@ Features added
Bugs fixed Bugs fixed
---------- ----------
* #8188: C, add missing items to internal object types dictionary,
e.g., preventing intersphinx from resolving them.
Testing Testing
-------- --------

View File

@@ -744,6 +744,18 @@ The following directive can be used for this purpose.
.. versionadded:: 3.2 .. versionadded:: 3.2
.. rubric:: Options
.. rst:directive:option:: maxdepth: int
Insert nested declarations as well, up to the total depth given.
Use 0 for infinite depth and 1 for just the mentioned declaration.
Defaults to 1.
.. versionadded:: 3.3
.. c:namespace-pop:: .. c:namespace-pop::

22
package-lock.json generated
View File

@@ -385,12 +385,6 @@
"integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=",
"dev": true "dev": true
}, },
"eventemitter3": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.2.tgz",
"integrity": "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==",
"dev": true
},
"extend": { "extend": {
"version": "3.0.2", "version": "3.0.2",
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
@@ -535,14 +529,22 @@
} }
}, },
"http-proxy": { "http-proxy": {
"version": "1.17.0", "version": "1.18.1",
"resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.17.0.tgz", "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz",
"integrity": "sha512-Taqn+3nNvYRfJ3bGvKfBSRwy1v6eePlm3oc/aWVxZp57DQr5Eq3xhKJi7Z4hZpS8PC3H4qI+Yly5EmFacGuA/g==", "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"eventemitter3": "^3.0.0", "eventemitter3": "^4.0.0",
"follow-redirects": "^1.0.0", "follow-redirects": "^1.0.0",
"requires-port": "^1.0.0" "requires-port": "^1.0.0"
},
"dependencies": {
"eventemitter3": {
"version": "4.0.7",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
"integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==",
"dev": true
}
} }
}, },
"iconv-lite": { "iconv-lite": {

View File

@@ -751,18 +751,27 @@ class StandaloneHTMLBuilder(Builder):
copyfile(jsfile, path.join(self.outdir, '_static', '_stemmer.js')) copyfile(jsfile, path.join(self.outdir, '_static', '_stemmer.js'))
def copy_theme_static_files(self, context: Dict) -> None: def copy_theme_static_files(self, context: Dict) -> None:
def onerror(filename: str, error: Exception) -> None:
logger.warning(__('Failed to copy a file in html_static_file: %s: %r'),
filename, error)
if self.theme: if self.theme:
for entry in self.theme.get_theme_dirs()[::-1]: for entry in self.theme.get_theme_dirs()[::-1]:
copy_asset(path.join(entry, 'static'), copy_asset(path.join(entry, 'static'),
path.join(self.outdir, '_static'), path.join(self.outdir, '_static'),
excluded=DOTFILES, context=context, renderer=self.templates) excluded=DOTFILES, context=context,
renderer=self.templates, onerror=onerror)
def copy_html_static_files(self, context: Dict) -> None: def copy_html_static_files(self, context: Dict) -> None:
def onerror(filename: str, error: Exception) -> None:
logger.warning(__('Failed to copy a file in html_static_file: %s: %r'),
filename, error)
excluded = Matcher(self.config.exclude_patterns + ["**/.*"]) excluded = Matcher(self.config.exclude_patterns + ["**/.*"])
for entry in self.config.html_static_path: for entry in self.config.html_static_path:
copy_asset(path.join(self.confdir, entry), copy_asset(path.join(self.confdir, entry),
path.join(self.outdir, '_static'), path.join(self.outdir, '_static'),
excluded, context=context, renderer=self.templates) excluded, context=context, renderer=self.templates, onerror=onerror)
def copy_html_logo(self) -> None: def copy_html_logo(self) -> None:
if self.config.html_logo: if self.config.html_logo:

View File

@@ -505,7 +505,7 @@ def validate_latex_theme_options(app: Sphinx, config: Config) -> None:
def install_pakcages_for_ja(app: Sphinx) -> None: def install_pakcages_for_ja(app: Sphinx) -> None:
"""Install packages for Japanese.""" """Install packages for Japanese."""
if app.config.language == 'ja': if app.config.language == 'ja' and app.config.latex_engine in ('platex', 'uplatex'):
app.add_latex_package('pxjahyper', after_hyperref=True) app.add_latex_package('pxjahyper', after_hyperref=True)

View File

@@ -72,7 +72,7 @@ def dedent_lines(lines: List[str], dedent: int, location: Tuple[str, int] = None
return lines return lines
if any(s[:dedent].strip() for s in lines): if any(s[:dedent].strip() for s in lines):
logger.warning(__('Over dedent has detected'), location=location) logger.warning(__('non-whitespace stripped by dedent'), location=location)
new_lines = [] new_lines = []
for line in lines: for line in lines:

View File

@@ -136,8 +136,8 @@ class ASTIdentifier(ASTBaseBase):
reftype='identifier', reftype='identifier',
reftarget=targetText, modname=None, reftarget=targetText, modname=None,
classname=None) classname=None)
# key = symbol.get_lookup_key() key = symbol.get_lookup_key()
# pnode['c:parent_key'] = key pnode['c:parent_key'] = key
if self.is_anon(): if self.is_anon():
pnode += nodes.strong(text="[anonymous]") pnode += nodes.strong(text="[anonymous]")
else: else:
@@ -1562,6 +1562,11 @@ class Symbol:
for s in sChild.get_all_symbols(): for s in sChild.get_all_symbols():
yield s yield s
@property
def children(self) -> Iterator["Symbol"]:
for c in self._children:
yield c
@property @property
def children_recurse_anon(self) -> Iterator["Symbol"]: def children_recurse_anon(self) -> Iterator["Symbol"]:
for c in self._children: for c in self._children:
@@ -1792,7 +1797,7 @@ class Symbol:
if not declaration: if not declaration:
if Symbol.debug_lookup: if Symbol.debug_lookup:
Symbol.debug_print("no delcaration") Symbol.debug_print("no declaration")
Symbol.debug_indent -= 2 Symbol.debug_indent -= 2
# good, just a scope creation # good, just a scope creation
# TODO: what if we have more than one symbol? # TODO: what if we have more than one symbol?
@@ -3408,10 +3413,13 @@ class CNamespacePopObject(SphinxDirective):
class AliasNode(nodes.Element): class AliasNode(nodes.Element):
def __init__(self, sig: str, env: "BuildEnvironment" = None, def __init__(self, sig: str, maxdepth: int, document: Any, env: "BuildEnvironment" = None,
parentKey: LookupKey = None) -> None: parentKey: LookupKey = None) -> None:
super().__init__() super().__init__()
self.sig = sig self.sig = sig
self.maxdepth = maxdepth
assert maxdepth >= 0
self.document = document
if env is not None: if env is not None:
if 'c:parent_symbol' not in env.temp_data: if 'c:parent_symbol' not in env.temp_data:
root = env.domaindata['c']['root_symbol'] root = env.domaindata['c']['root_symbol']
@@ -3428,6 +3436,37 @@ class AliasNode(nodes.Element):
class AliasTransform(SphinxTransform): class AliasTransform(SphinxTransform):
default_priority = ReferencesResolver.default_priority - 1 default_priority = ReferencesResolver.default_priority - 1
def _render_symbol(self, s: Symbol, maxdepth: int, document: Any) -> List[Node]:
nodes = [] # type: List[Node]
options = dict() # type: ignore
signode = addnodes.desc_signature('', '')
nodes.append(signode)
s.declaration.describe_signature(signode, 'markName', self.env, options)
if maxdepth == 0:
recurse = True
elif maxdepth == 1:
recurse = False
else:
maxdepth -= 1
recurse = True
if recurse:
content = addnodes.desc_content()
desc = addnodes.desc()
content.append(desc)
desc.document = document
desc['domain'] = 'c'
# 'desctype' is a backwards compatible attribute
desc['objtype'] = desc['desctype'] = 'alias'
desc['noindex'] = True
for sChild in s.children:
childNodes = self._render_symbol(sChild, maxdepth, document)
desc.extend(childNodes)
if len(desc.children) != 0:
nodes.append(content)
return nodes
def apply(self, **kwargs: Any) -> None: def apply(self, **kwargs: Any) -> None:
for node in self.document.traverse(AliasNode): for node in self.document.traverse(AliasNode):
sig = node.sig sig = node.sig
@@ -3468,17 +3507,16 @@ class AliasTransform(SphinxTransform):
logger.warning("Could not find C declaration for alias '%s'." % name, logger.warning("Could not find C declaration for alias '%s'." % name,
location=node) location=node)
node.replace_self(signode) node.replace_self(signode)
else: continue
nodes = []
options = dict() # type: ignore nodes = self._render_symbol(s, maxdepth=node.maxdepth, document=node.document)
signode = addnodes.desc_signature(sig, '') node.replace_self(nodes)
nodes.append(signode)
s.declaration.describe_signature(signode, 'markName', self.env, options)
node.replace_self(nodes)
class CAliasObject(ObjectDescription): class CAliasObject(ObjectDescription):
option_spec = {} # type: Dict option_spec = {
'maxdepth': directives.nonnegative_int
} # type: Dict
def run(self) -> List[Node]: def run(self) -> List[Node]:
if ':' in self.name: if ':' in self.name:
@@ -3494,16 +3532,10 @@ class CAliasObject(ObjectDescription):
node['noindex'] = True node['noindex'] = True
self.names = [] # type: List[str] self.names = [] # type: List[str]
maxdepth = self.options.get('maxdepth', 1)
signatures = self.get_signatures() signatures = self.get_signatures()
for i, sig in enumerate(signatures): for i, sig in enumerate(signatures):
node.append(AliasNode(sig, env=self.env)) node.append(AliasNode(sig, maxdepth, self.state.document, env=self.env))
contentnode = addnodes.desc_content()
node.append(contentnode)
self.before_content()
self.state.nested_parse(self.content, self.content_offset, contentnode)
self.env.temp_data['object'] = None
self.after_content()
return [node] return [node]
@@ -3607,6 +3639,10 @@ class CDomain(Domain):
'macro': ObjType(_('macro'), 'macro'), 'macro': ObjType(_('macro'), 'macro'),
'type': ObjType(_('type'), 'type'), 'type': ObjType(_('type'), 'type'),
'var': ObjType(_('variable'), 'data'), 'var': ObjType(_('variable'), 'data'),
'enum': ObjType(_('enum'), 'enum'),
'enumerator': ObjType(_('enumerator'), 'enumerator'),
'struct': ObjType(_('struct'), 'struct'),
'union': ObjType(_('union'), 'union'),
} }
directives = { directives = {

View File

@@ -4292,7 +4292,7 @@ class Symbol:
if not declaration: if not declaration:
if Symbol.debug_lookup: if Symbol.debug_lookup:
Symbol.debug_print("no delcaration") Symbol.debug_print("no declaration")
Symbol.debug_indent -= 2 Symbol.debug_indent -= 2
# good, just a scope creation # good, just a scope creation
# TODO: what if we have more than one symbol? # TODO: what if we have more than one symbol?

View File

@@ -66,6 +66,10 @@ module_sig_re = re.compile(r'''^(?:([\w.]*)\.)? # module names
''', re.VERBOSE) ''', re.VERBOSE)
py_builtins = [obj for obj in vars(builtins).values()
if inspect.isclass(obj)]
def try_import(objname: str) -> Any: def try_import(objname: str) -> Any:
"""Import a object or module using *name* and *currentmodule*. """Import a object or module using *name* and *currentmodule*.
*name* should be a relative name from *currentmodule* or *name* should be a relative name from *currentmodule* or
@@ -178,7 +182,6 @@ class InheritanceGraph:
traverse to. Multiple names can be specified separated by comma. traverse to. Multiple names can be specified separated by comma.
""" """
all_classes = {} all_classes = {}
py_builtins = vars(builtins).values()
def recurse(cls: Any) -> None: def recurse(cls: Any) -> None:
if not show_builtins and cls in py_builtins: if not show_builtins and cls in py_builtins:

View File

@@ -36,7 +36,7 @@ _numpy_section_regex = re.compile(r'^[=\-`:\'"~^_*+#<>]{2,}\s*$')
_single_colon_regex = re.compile(r'(?<!:):(?!:)') _single_colon_regex = re.compile(r'(?<!:):(?!:)')
_xref_or_code_regex = re.compile( _xref_or_code_regex = re.compile(
r'((?::(?:[a-zA-Z0-9]+[\-_+:.])*[a-zA-Z0-9]+:`.+?`)|' r'((?::(?:[a-zA-Z0-9]+[\-_+:.])*[a-zA-Z0-9]+:`.+?`)|'
r'(?:``.+``))') r'(?:``.+?``))')
_xref_regex = re.compile( _xref_regex = re.compile(
r'(?:(?::(?:[a-zA-Z0-9]+[\-_+:.])*[a-zA-Z0-9]+:)?`.+?`)' r'(?:(?::(?:[a-zA-Z0-9]+[\-_+:.])*[a-zA-Z0-9]+:)?`.+?`)'
) )

View File

@@ -10,7 +10,7 @@
import os import os
import posixpath import posixpath
from typing import Dict from typing import Callable, Dict
from docutils.utils import relative_path from docutils.utils import relative_path
@@ -56,7 +56,8 @@ def copy_asset_file(source: str, destination: str,
def copy_asset(source: str, destination: str, excluded: PathMatcher = lambda path: False, def copy_asset(source: str, destination: str, excluded: PathMatcher = lambda path: False,
context: Dict = None, renderer: "BaseRenderer" = None) -> None: context: Dict = None, renderer: "BaseRenderer" = None,
onerror: Callable[[str, Exception], None] = None) -> None:
"""Copy asset files to destination recursively. """Copy asset files to destination recursively.
On copying, it expands the template variables if context argument is given and On copying, it expands the template variables if context argument is given and
@@ -67,6 +68,7 @@ def copy_asset(source: str, destination: str, excluded: PathMatcher = lambda pat
:param excluded: The matcher to determine the given path should be copied or not :param excluded: The matcher to determine the given path should be copied or not
:param context: The template variables. If not given, template files are simply copied :param context: The template variables. If not given, template files are simply copied
:param renderer: The template engine. If not given, SphinxRenderer is used by default :param renderer: The template engine. If not given, SphinxRenderer is used by default
:param onerror: The error handler.
""" """
if not os.path.exists(source): if not os.path.exists(source):
return return
@@ -90,6 +92,12 @@ def copy_asset(source: str, destination: str, excluded: PathMatcher = lambda pat
for filename in files: for filename in files:
if not excluded(posixpath.join(reldir, filename)): if not excluded(posixpath.join(reldir, filename)):
copy_asset_file(posixpath.join(root, filename), try:
posixpath.join(destination, reldir), copy_asset_file(posixpath.join(root, filename),
context, renderer) posixpath.join(destination, reldir),
context, renderer)
except Exception as exc:
if onerror:
onerror(posixpath.join(root, filename), exc)
else:
raise

View File

@@ -304,6 +304,11 @@ def iscoroutinefunction(obj: Any) -> bool:
def isproperty(obj: Any) -> bool: def isproperty(obj: Any) -> bool:
"""Check if the object is property.""" """Check if the object is property."""
if sys.version_info > (3, 8):
from functools import cached_property # cached_property is available since py3.8
if isinstance(obj, cached_property):
return True
return isinstance(obj, property) return isinstance(obj, property)

View File

@@ -0,0 +1,7 @@
from functools import cached_property
class Foo:
@cached_property
def prop(self) -> int:
return 1

View File

@@ -10,8 +10,10 @@
import os import os
import re import re
from distutils.version import LooseVersion
from itertools import cycle, chain from itertools import cycle, chain
import pygments
import pytest import pytest
from html5lib import HTMLParser from html5lib import HTMLParser
@@ -1591,4 +1593,8 @@ def test_html_codeblock_linenos_style_inline(app):
app.build() app.build()
content = (app.outdir / 'index.html').read_text() content = (app.outdir / 'index.html').read_text()
assert '<span class="lineno">1 </span>' in content pygments_version = tuple(LooseVersion(pygments.__version__).version)
if pygments_version > (2, 7):
assert '<span class="linenos">1</span>' in content
else:
assert '<span class="lineno">1 </span>' in content

View File

@@ -881,6 +881,26 @@ def test_autodoc_descriptor(app):
] ]
@pytest.mark.skipif(sys.version_info < (3, 8),
reason='cached_property is available since python3.8.')
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_autodoc_cached_property(app):
options = {"members": None,
"undoc-members": True}
actual = do_autodoc(app, 'class', 'target.cached_property.Foo', options)
assert list(actual) == [
'',
'.. py:class:: Foo()',
' :module: target.cached_property',
'',
'',
' .. py:method:: Foo.prop',
' :module: target.cached_property',
' :property:',
'',
]
@pytest.mark.sphinx('html', testroot='ext-autodoc') @pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_autodoc_member_order(app): def test_autodoc_member_order(app):
# case member-order='bysource' # case member-order='bysource'