Merge branch '3.x'

This commit is contained in:
Takeshi KOMIYA 2020-07-05 01:32:54 +09:00
commit ec3754bd94
62 changed files with 595 additions and 246 deletions

View File

@ -9,6 +9,6 @@ jobs:
- run: /python3.6/bin/pip install -U pip setuptools
- run: /python3.6/bin/pip install -U .[test]
- run: mkdir -p test-reports/pytest
- run: make test PYTHON=/python3.6/bin/python TEST=--junitxml=test-reports/pytest/results.xml
- run: make test PYTHON=/python3.6/bin/python TEST="--junitxml=test-reports/pytest/results.xml -vv"
- store_test_results:
path: test-reports

21
.github/workflows/builddoc.yml vendored Normal file
View File

@ -0,0 +1,21 @@
name: Build document
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v1
with:
python-version: 3.6
- name: Install dependencies
run: |
sudo apt update
sudo apt install -y graphviz
pip install -U tox
- name: Run Tox
run: tox -e docs

22
.github/workflows/lint.yml vendored Normal file
View File

@ -0,0 +1,22 @@
name: Lint source code
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
tool: [docslint, flake8, mypy, twine]
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v1
with:
python-version: 3.6
- name: Install dependencies
run: pip install -U tox
- name: Run Tox
run: tox -e ${{ matrix.tool }}

View File

@ -18,4 +18,4 @@ jobs:
- name: Install dependencies
run: pip install -U tox
- name: Run Tox
run: tox -e py
run: tox -e py -- -vv

View File

@ -27,14 +27,6 @@ jobs:
- python: 'nightly'
env:
- TOXENV=py310
- python: '3.6'
env: TOXENV=docs
- python: '3.6'
env: TOXENV=docslint
- python: '3.6'
env: TOXENV=mypy
- python: '3.6'
env: TOXENV=flake8
- language: node_js
node_js: '10.7'
@ -47,7 +39,7 @@ install:
- if [ $IS_PYTHON = false ]; then npm install; fi
script:
- if [ $IS_PYTHON = true ]; then tox -- -v; fi
- if [ $IS_PYTHON = true ]; then tox -- -vv; fi
- if [ $IS_PYTHON = false ]; then npm test; fi
after_success:

25
CHANGES
View File

@ -51,10 +51,24 @@ Deprecated
Features added
--------------
* #7853: C and C++, support parameterized GNU style attributes.
* #7888: napoleon: Add aliases Warn and Raise.
* C, added :rst:dir:`c:alias` directive for inserting copies
of existing declarations.
Bugs fixed
----------
* #7839: autosummary: cannot handle umlauts in function names
* #7865: autosummary: Failed to extract summary line when abbreviations found
* #7866: autosummary: Failed to extract correct summary line when docstring
contains a hyperlink target
* #7715: LaTeX: ``numfig_secnum_depth > 1`` leads to wrong figure links
* #7846: html theme: XML-invalid files were generated
* #7869: :rst:role:`abbr` role without an explanation will show the explanation
from the previous abbr role
* C and C++, removed ``noindex`` directive option as it did
nothing.
Testing
--------
@ -77,6 +91,17 @@ Features added
Bugs fixed
----------
* #7844: autodoc: Failed to detect module when relative module name given
* #7856: autodoc: AttributeError is raised when non-class object is given to
the autoclass directive
* #7850: autodoc: KeyError is raised for invalid mark up when autodoc_typehints
is 'description'
* #7812: autodoc: crashed if the target name matches to both an attribute and
module that are same name
* #7812: autosummary: generates broken stub files if the target code contains
an attribute and module that are same name
* #7806: viewcode: Failed to resolve viewcode references on 3rd party builders
Testing
--------

View File

@ -8,10 +8,10 @@ reports/feature requests.
Our contributing guide can be found online at:
https://www.sphinx-doc.org/en/master/internals/contributing/
https://www.sphinx-doc.org/en/master/internals/contributing.html
You can also browse it from this repository from
``doc/internals/contributing/``
``doc/internals/contributing.rst``
Sphinx uses GitHub to host source code, track patches and bugs, and more.
Please make an effort to provide as much possible when filing bugs.

View File

@ -1,7 +1,7 @@
License for Sphinx
==================
Copyright (c) 2007-2019 by the Sphinx team (see AUTHORS file).
Copyright (c) 2007-2020 by the Sphinx team (see AUTHORS file).
All rights reserved.
Redistribution and use in source and binary forms, with or without

View File

@ -83,6 +83,6 @@ build:
.PHONY: docs
docs:
ifndef target
$(info You need to give a provide a target variable, e.g. `make docs target=html`.)
$(info You need to provide a target variable, e.g. `make docs target=html`.)
endif
$(MAKE) -C doc $(target)

View File

@ -157,6 +157,40 @@ connect handlers to the events. Example:
app.connect('source-read', source_read_handler)
Below is an overview of each event that happens during a build. In the list
below, we include the event name, its callback parameters, and the input and output
type for that event::
1. event.config-inited(app,config)
2. event.builder-inited(app)
3. event.env-get-outdated(app, env, added, changed, removed)
4. event.env-before-read-docs(app, env, docnames)
for docname in docnames:
5. event.env-purge-doc(app, env, docname)
if doc changed and not removed:
6. source-read(app, docname, source)
7. run source parsers: text -> docutils.document (parsers can be added with the app.add_source_parser() API)
8. apply transforms (by priority): docutils.document -> docutils.document
- event.doctree-read(app, doctree) is called in the middly of transforms,
transforms come before/after this event depending on their priority.
9. (if running in parallel mode, for each process) event.env-merged-info(app, env, docnames, other)
10. event.env-updated(app, env)
11. event.env-get-updated(app, env)
11. event.env-check-consistency(app, env)
# For builders that output a single page, they are first joined into a single doctree before post-transforms/doctree-resolved
for docname in docnames:
12. apply post-transforms (by priority): docutils.document -> docutils.document
13. event.doctree-resolved(app, doctree, docname)
- (for any reference node that fails to resolve) event.missing-reference(env, node, contnode)
14. Generate output files
15. event.build-finished(app, exception)
Here is a more detailed list of these events.
.. event:: builder-inited (app)
Emitted when the builder object has been created. It is available as

View File

@ -659,10 +659,11 @@ documentation on :ref:`intl` for details.
generated by Sphinx will be in that language. Also, Sphinx will try to
substitute individual paragraphs from your documents with the translation
sets obtained from :confval:`locale_dirs`. Sphinx will search
language-specific figures named by `figure_language_filename` and substitute
them for original figures. In the LaTeX builder, a suitable language will
be selected as an option for the *Babel* package. Default is ``None``,
which means that no translation will be done.
language-specific figures named by `figure.language.filename`
(e.g. the German version of ``myfigure.png`` will be ``myfigure.de.png``)
and substitute them for original figures. In the LaTeX builder, a suitable
language will be selected as an option for the *Babel* package. Default is
``None``, which means that no translation will be done.
.. versionadded:: 0.5

View File

@ -280,6 +280,12 @@ inserting them into the page source under a suitable :rst:dir:`py:module`,
.. versionadded:: 1.3
* As a hint to autodoc extension, you can put a ``::`` separator in between
module name and object name to let autodoc know the correct module name if
it is ambiguous. ::
.. autoclass:: module.name::Noodle
.. rst:directive:: autofunction
autodecorator

View File

@ -115,6 +115,7 @@ All of the following section headers are supported:
* ``Parameters``
* ``Return`` *(alias of Returns)*
* ``Returns``
* ``Raise`` *(alias of Raises)*
* ``Raises``
* ``References``
* ``See Also``
@ -122,6 +123,7 @@ All of the following section headers are supported:
* ``Todo``
* ``Warning``
* ``Warnings`` *(alias of Warning)*
* ``Warn`` *(alias of Warns)*
* ``Warns``
* ``Yield`` *(alias of Yields)*
* ``Yields``

View File

@ -739,6 +739,41 @@ Explicit ref: :c:var:`Data.@data.a`. Short-hand ref: :c:var:`Data.a`.
.. versionadded:: 3.0
Aliasing Declarations
~~~~~~~~~~~~~~~~~~~~~
.. c:namespace-push:: @alias
Sometimes it may be helpful list declarations elsewhere than their main
documentation, e.g., when creating a synopsis of an interface.
The following directive can be used for this purpose.
.. rst:directive:: .. c:alias:: name
Insert one or more alias declarations. Each entity can be specified
as they can in the :rst:role:`c:any` role.
For example::
.. c:var:: int data
.. c:function:: int f(double k)
.. c:alias:: data
f
becomes
.. c:var:: int data
.. c:function:: int f(double k)
.. c:alias:: data
f
.. versionadded:: 3.2
.. c:namespace-pop::
Inline Expressions and Types
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -1078,7 +1113,6 @@ Options
Some directives support options:
- ``:noindex:``, see :ref:`basic-domain-markup`.
- ``:tparam-line-spec:``, for templated declarations.
If specified, each template parameter will be rendered on a separate line.

View File

@ -253,7 +253,7 @@ class MessageCatalogBuilder(I18nBuilder):
origin = MsgOrigin(template, line)
self.catalogs['sphinx'].add(msg, origin)
except Exception as exc:
raise ThemeError('%s: %r' % (template, exc))
raise ThemeError('%s: %r' % (template, exc)) from exc
def build(self, docnames: Iterable[str], summary: str = None, method: str = 'update') -> None: # NOQA
self._extract_from_template()

View File

@ -135,7 +135,7 @@ class BuildInfo:
build_info.tags_hash = lines[3].split()[1].strip()
return build_info
except Exception as exc:
raise ValueError(__('build info file is broken: %r') % exc)
raise ValueError(__('build info file is broken: %r') % exc) from exc
def __init__(self, config: Config = None, tags: Tags = None, config_categories: List[str] = []) -> None: # NOQA
self.config_hash = ''
@ -1010,7 +1010,7 @@ class StandaloneHTMLBuilder(Builder):
return
except Exception as exc:
raise ThemeError(__("An error happened in rendering the page %s.\nReason: %r") %
(pagename, exc))
(pagename, exc)) from exc
if not outfilename:
outfilename = self.get_outfilename(pagename)

View File

@ -87,10 +87,12 @@ class UserTheme(Theme):
try:
value = self.config.get('theme', key)
setattr(self, key, value)
except configparser.NoSectionError:
raise ThemeError(__('%r doesn\'t have "theme" setting') % filename)
except configparser.NoSectionError as exc:
raise ThemeError(__('%r doesn\'t have "theme" setting') %
filename) from exc
except configparser.NoOptionError as exc:
raise ThemeError(__('%r doesn\'t have "%s" setting') % (filename, exc.args[0]))
raise ThemeError(__('%r doesn\'t have "%s" setting') %
(filename, exc.args[0])) from exc
for key in self.OPTIONAL_CONFIG_KEYS:
try:

View File

@ -191,9 +191,9 @@ class Config:
elif isinstance(defvalue, int):
try:
return int(value)
except ValueError:
except ValueError as exc:
raise ValueError(__('invalid number %r for config value %r, ignoring') %
(value, name))
(value, name)) from exc
elif hasattr(defvalue, '__call__'):
return value
elif defvalue is not None and not isinstance(defvalue, str):
@ -316,17 +316,17 @@ def eval_config_file(filename: str, tags: Tags) -> Dict[str, Any]:
exec(code, namespace)
except SyntaxError as err:
msg = __("There is a syntax error in your configuration file: %s\n")
raise ConfigError(msg % err)
except SystemExit:
raise ConfigError(msg % err) from err
except SystemExit as exc:
msg = __("The configuration file (or one of the modules it imports) "
"called sys.exit()")
raise ConfigError(msg)
raise ConfigError(msg) from exc
except ConfigError:
# pass through ConfigError from conf.py as is. It will be shown in console.
raise
except Exception:
except Exception as exc:
msg = __("There is a programmable error in your configuration file:\n\n%s")
raise ConfigError(msg % traceback.format_exc())
raise ConfigError(msg % traceback.format_exc()) from exc
return namespace

View File

@ -10,7 +10,7 @@
import re
from typing import (
Any, Callable, Dict, Generator, Iterator, List, Type, Tuple, Union
Any, Callable, Dict, Generator, Iterator, List, Type, TypeVar, Tuple, Union
)
from typing import cast
@ -26,9 +26,12 @@ from sphinx.domains import Domain, ObjType
from sphinx.environment import BuildEnvironment
from sphinx.locale import _, __
from sphinx.roles import SphinxRole, XRefRole
from sphinx.transforms import SphinxTransform
from sphinx.transforms.post_transforms import ReferencesResolver
from sphinx.util import logging
from sphinx.util.cfamily import (
NoOldIdError, ASTBaseBase, verify_description_mode, StringifyTransform,
NoOldIdError, ASTBaseBase, ASTBaseParenExprList,
verify_description_mode, StringifyTransform,
BaseParser, DefinitionError, UnsupportedMultiCharacterCharLiteral,
identifier_re, anon_identifier_re, integer_literal_re, octal_literal_re,
hex_literal_re, binary_literal_re, integers_literal_suffix_re,
@ -40,6 +43,7 @@ from sphinx.util.docutils import SphinxDirective
from sphinx.util.nodes import make_refnode
logger = logging.getLogger(__name__)
T = TypeVar('T')
# https://en.cppreference.com/w/c/keyword
_keywords = [
@ -1053,7 +1057,7 @@ class ASTDeclaratorParen(ASTDeclarator):
# Initializer
################################################################################
class ASTParenExprList(ASTBase):
class ASTParenExprList(ASTBaseParenExprList):
def __init__(self, exprs: List[ASTExpression]) -> None:
self.exprs = exprs
@ -1883,7 +1887,7 @@ class Symbol:
ourChild._fill_empty(otherChild.declaration, otherChild.docname)
elif ourChild.docname != otherChild.docname:
name = str(ourChild.declaration)
msg = __("Duplicate declaration, also defined in '%s'.\n"
msg = __("Duplicate C declaration, also defined in '%s'.\n"
"Declaration is '%s'.")
msg = msg % (ourChild.docname, name)
logger.warning(msg, location=otherChild.docname)
@ -2300,7 +2304,8 @@ class DefinitionParser(BaseParser):
errs = []
errs.append((exCast, "If type cast expression"))
errs.append((exUnary, "If unary expression"))
raise self._make_multi_error(errs, "Error in cast expression.")
raise self._make_multi_error(errs,
"Error in cast expression.") from exUnary
else:
return self._parse_unary_expression()
@ -2767,7 +2772,7 @@ class DefinitionParser(BaseParser):
msg += " (e.g., 'void (*f(int arg))(double)')"
prevErrors.append((exNoPtrParen, msg))
header = "Error in declarator"
raise self._make_multi_error(prevErrors, header)
raise self._make_multi_error(prevErrors, header) from exNoPtrParen
pos = self.pos
try:
return self._parse_declarator_name_suffix(named, paramMode, typed)
@ -2775,7 +2780,7 @@ class DefinitionParser(BaseParser):
self.pos = pos
prevErrors.append((e, "If declarator-id"))
header = "Error in declarator or parameters"
raise self._make_multi_error(prevErrors, header)
raise self._make_multi_error(prevErrors, header) from e
def _parse_initializer(self, outer: str = None, allowFallback: bool = True
) -> ASTInitializer:
@ -2843,7 +2848,7 @@ class DefinitionParser(BaseParser):
if True:
header = "Type must be either just a name or a "
header += "typedef-like declaration."
raise self._make_multi_error(prevErrors, header)
raise self._make_multi_error(prevErrors, header) from exTyped
else:
# For testing purposes.
# do it again to get the proper traceback (how do you
@ -2994,7 +2999,7 @@ class DefinitionParser(BaseParser):
errs = []
errs.append((exExpr, "If expression"))
errs.append((exType, "If type"))
raise self._make_multi_error(errs, header)
raise self._make_multi_error(errs, header) from exType
return res
@ -3017,6 +3022,13 @@ class CObject(ObjectDescription):
names=('rtype',)),
]
option_spec = {
# have a dummy option to ensure proper errors on options,
# otherwise the option is taken as a continuation of the
# argument
'dummy': None
}
def _add_enumerator_to_parent(self, ast: ASTDeclaration) -> None:
assert ast.objectType == 'enumerator'
# find the parent, if it exists && is an enum
@ -3083,7 +3095,8 @@ class CObject(ObjectDescription):
self.state.document.note_explicit_target(signode)
domain = cast(CDomain, self.env.get_domain('c'))
domain.note_object(name, self.objtype, newestId)
if name not in domain.objects:
domain.objects[name] = (domain.env.docname, newestId, self.objtype)
indexText = self.get_index_text(name)
self.indexnode['entries'].append(('single', indexText, newestId, '', None))
@ -3132,7 +3145,7 @@ class CObject(ObjectDescription):
name = _make_phony_error_name()
symbol = parentSymbol.add_name(name)
self.env.temp_data['c:last_symbol'] = symbol
raise ValueError
raise ValueError from e
try:
symbol = parentSymbol.add_declaration(ast, docname=self.env.docname)
@ -3148,7 +3161,10 @@ class CObject(ObjectDescription):
# Assume we are actually in the old symbol,
# instead of the newly created duplicate.
self.env.temp_data['c:last_symbol'] = e.symbol
logger.warning("Duplicate declaration, %s", sig, location=signode)
msg = __("Duplicate C declaration, also defined in '%s'.\n"
"Declaration is '%s'.")
msg = msg % (e.symbol.docname, sig)
logger.warning(msg, location=signode)
if ast.objectType == 'enumerator':
self._add_enumerator_to_parent(ast)
@ -3309,6 +3325,106 @@ class CNamespacePopObject(SphinxDirective):
return []
class AliasNode(nodes.Element):
def __init__(self, sig: str, env: "BuildEnvironment" = None,
parentKey: LookupKey = None) -> None:
super().__init__()
self.sig = sig
if env is not None:
if 'c:parent_symbol' not in env.temp_data:
root = env.domaindata['c']['root_symbol']
env.temp_data['c:parent_symbol'] = root
self.parentKey = env.temp_data['c:parent_symbol'].get_lookup_key()
else:
assert parentKey is not None
self.parentKey = parentKey
def copy(self: T) -> T:
return self.__class__(self.sig, env=None, parentKey=self.parentKey) # type: ignore
class AliasTransform(SphinxTransform):
default_priority = ReferencesResolver.default_priority - 1
def apply(self, **kwargs: Any) -> None:
for node in self.document.traverse(AliasNode):
sig = node.sig
parentKey = node.parentKey
try:
parser = DefinitionParser(sig, location=node,
config=self.env.config)
name = parser.parse_xref_object()
except DefinitionError as e:
logger.warning(e, location=node)
name = None
if name is None:
# could not be parsed, so stop here
signode = addnodes.desc_signature(sig, '')
signode.clear()
signode += addnodes.desc_name(sig, sig)
node.replace_self(signode)
continue
rootSymbol = self.env.domains['c'].data['root_symbol'] # type: Symbol
parentSymbol = rootSymbol.direct_lookup(parentKey) # type: Symbol
if not parentSymbol:
print("Target: ", sig)
print("ParentKey: ", parentKey)
print(rootSymbol.dump(1))
assert parentSymbol # should be there
s = parentSymbol.find_declaration(
name, 'any',
matchSelf=True, recurseInAnon=True)
if s is None:
signode = addnodes.desc_signature(sig, '')
node.append(signode)
signode.clear()
signode += addnodes.desc_name(sig, sig)
logger.warning("Could not find C declaration for alias '%s'." % name,
location=node)
node.replace_self(signode)
else:
nodes = []
options = dict() # type: ignore
signode = addnodes.desc_signature(sig, '')
nodes.append(signode)
s.declaration.describe_signature(signode, 'markName', self.env, options)
node.replace_self(nodes)
class CAliasObject(ObjectDescription):
option_spec = {} # type: Dict
def run(self) -> List[Node]:
if ':' in self.name:
self.domain, self.objtype = self.name.split(':', 1)
else:
self.domain, self.objtype = '', self.name
node = addnodes.desc()
node.document = self.state.document
node['domain'] = self.domain
# 'desctype' is a backwards compatible attribute
node['objtype'] = node['desctype'] = self.objtype
node['noindex'] = True
self.names = [] # type: List[str]
signatures = self.get_signatures()
for i, sig in enumerate(signatures):
node.append(AliasNode(sig, 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]
class CXRefRole(XRefRole):
def process_link(self, env: BuildEnvironment, refnode: Element,
has_explicit_title: bool, title: str, target: str) -> Tuple[str, str]:
@ -3392,6 +3508,8 @@ class CDomain(Domain):
'namespace': CNamespaceObject,
'namespace-push': CNamespacePushObject,
'namespace-pop': CNamespacePopObject,
# other
'alias': CAliasObject
}
roles = {
'member': CXRefRole(),
@ -3416,14 +3534,6 @@ class CDomain(Domain):
def objects(self) -> Dict[str, Tuple[str, str, str]]:
return self.data.setdefault('objects', {}) # fullname -> docname, node_id, objtype
def note_object(self, name: str, objtype: str, node_id: str, location: Any = None) -> None:
if name in self.objects:
docname = self.objects[name][0]
logger.warning(__('Duplicate C object description of %s, '
'other instance in %s, use :noindex: for one of them'),
name, docname, location=location)
self.objects[name] = (self.env.docname, node_id, objtype)
def clear_doc(self, docname: str) -> None:
if Symbol.debug_show_tree:
print("clear_doc:", docname)
@ -3469,13 +3579,9 @@ class CDomain(Domain):
ourObjects = self.data['objects']
for fullname, (fn, id_, objtype) in otherdata['objects'].items():
if fn in docnames:
if fullname in ourObjects:
msg = __("Duplicate declaration, also defined in '%s'.\n"
"Name of declaration is '%s'.")
msg = msg % (ourObjects[fullname], fullname)
logger.warning(msg, location=fn)
else:
if fullname not in ourObjects:
ourObjects[fullname] = (fn, id_, objtype)
# no need to warn on duplicates, the symbol merge already does that
def _resolve_xref_inner(self, env: BuildEnvironment, fromdocname: str, builder: Builder,
typ: str, target: str, node: pending_xref,
@ -3539,6 +3645,7 @@ def setup(app: Sphinx) -> Dict[str, Any]:
app.add_domain(CDomain)
app.add_config_value("c_id_attributes", [], 'env')
app.add_config_value("c_paren_attributes", [], 'env')
app.add_post_transform(AliasTransform)
return {
'version': 'builtin',

View File

@ -31,7 +31,8 @@ from sphinx.transforms import SphinxTransform
from sphinx.transforms.post_transforms import ReferencesResolver
from sphinx.util import logging
from sphinx.util.cfamily import (
NoOldIdError, ASTBaseBase, ASTAttribute, verify_description_mode, StringifyTransform,
NoOldIdError, ASTBaseBase, ASTAttribute, ASTBaseParenExprList,
verify_description_mode, StringifyTransform,
BaseParser, DefinitionError, UnsupportedMultiCharacterCharLiteral,
identifier_re, anon_identifier_re, integer_literal_re, octal_literal_re,
hex_literal_re, binary_literal_re, integers_literal_suffix_re,
@ -2742,7 +2743,7 @@ class ASTPackExpansionExpr(ASTExpression):
signode += nodes.Text('...')
class ASTParenExprList(ASTBase):
class ASTParenExprList(ASTBaseParenExprList):
def __init__(self, exprs: List[Union[ASTExpression, ASTBracedInitList]]) -> None:
self.exprs = exprs
@ -4454,7 +4455,7 @@ class Symbol:
ourChild._fill_empty(otherChild.declaration, otherChild.docname)
elif ourChild.docname != otherChild.docname:
name = str(ourChild.declaration)
msg = __("Duplicate declaration, also defined in '%s'.\n"
msg = __("Duplicate C++ declaration, also defined in '%s'.\n"
"Declaration is '%s'.")
msg = msg % (ourChild.docname, name)
logger.warning(msg, location=otherChild.docname)
@ -4882,7 +4883,7 @@ class DefinitionParser(BaseParser):
raise self._make_multi_error([
(eFold, "If fold expression"),
(eExpr, "If parenthesized expression")
], "Error in fold expression or parenthesized expression.")
], "Error in fold expression or parenthesized expression.") from eExpr
return ASTParenExpr(res)
# now it definitely is a fold expression
if self.skip_string(')'):
@ -5066,7 +5067,7 @@ class DefinitionParser(BaseParser):
errors = []
errors.append((eType, "If type"))
errors.append((eExpr, "If expression"))
raise self._make_multi_error(errors, header)
raise self._make_multi_error(errors, header) from eExpr
else: # a primary expression or a type
pos = self.pos
try:
@ -5093,7 +5094,7 @@ class DefinitionParser(BaseParser):
errors = []
errors.append((eOuter, "If primary expression"))
errors.append((eInner, "If type"))
raise self._make_multi_error(errors, header)
raise self._make_multi_error(errors, header) from eInner
# and now parse postfixes
postFixes = [] # type: List[ASTPostfixOp]
@ -5253,7 +5254,8 @@ class DefinitionParser(BaseParser):
errs = []
errs.append((exCast, "If type cast expression"))
errs.append((exUnary, "If unary expression"))
raise self._make_multi_error(errs, "Error in cast expression.")
raise self._make_multi_error(errs,
"Error in cast expression.") from exUnary
else:
return self._parse_unary_expression()
@ -5504,7 +5506,7 @@ class DefinitionParser(BaseParser):
self.pos = pos
prevErrors.append((e, "If non-type argument"))
header = "Error in parsing template argument list."
raise self._make_multi_error(prevErrors, header)
raise self._make_multi_error(prevErrors, header) from e
if parsedEnd:
assert not parsedComma
break
@ -5949,7 +5951,7 @@ class DefinitionParser(BaseParser):
self.pos = pos
prevErrors.append((exNoPtrParen, "If parenthesis in noptr-declarator"))
header = "Error in declarator"
raise self._make_multi_error(prevErrors, header)
raise self._make_multi_error(prevErrors, header) from exNoPtrParen
if typed: # pointer to member
pos = self.pos
try:
@ -5988,7 +5990,7 @@ class DefinitionParser(BaseParser):
self.pos = pos
prevErrors.append((e, "If declarator-id"))
header = "Error in declarator or parameters-and-qualifiers"
raise self._make_multi_error(prevErrors, header)
raise self._make_multi_error(prevErrors, header) from e
def _parse_initializer(self, outer: str = None, allowFallback: bool = True
) -> ASTInitializer:
@ -6096,7 +6098,7 @@ class DefinitionParser(BaseParser):
header = "Error when parsing function declaration."
else:
assert False
raise self._make_multi_error(prevErrors, header)
raise self._make_multi_error(prevErrors, header) from exTyped
else:
# For testing purposes.
# do it again to get the proper traceback (how do you
@ -6163,7 +6165,7 @@ class DefinitionParser(BaseParser):
errs.append((eType, "If default template argument is a type"))
msg = "Error in non-type template parameter"
msg += " or constrained template parameter."
raise self._make_multi_error(errs, msg)
raise self._make_multi_error(errs, msg) from eType
def _parse_type_using(self) -> ASTTypeUsing:
name = self._parse_nested_name()
@ -6510,7 +6512,7 @@ class DefinitionParser(BaseParser):
self.pos = pos
prevErrors.append((e, "If type alias or template alias"))
header = "Error in type declaration."
raise self._make_multi_error(prevErrors, header)
raise self._make_multi_error(prevErrors, header) from e
elif objectType == 'concept':
declaration = self._parse_concept()
elif objectType == 'member':
@ -6576,7 +6578,7 @@ class DefinitionParser(BaseParser):
errs.append((e1, "If shorthand ref"))
errs.append((e2, "If full function ref"))
msg = "Error in cross-reference."
raise self._make_multi_error(errs, msg)
raise self._make_multi_error(errs, msg) from e2
def parse_expression(self) -> Union[ASTExpression, ASTType]:
pos = self.pos
@ -6597,7 +6599,7 @@ class DefinitionParser(BaseParser):
errs = []
errs.append((exExpr, "If expression"))
errs.append((exType, "If type"))
raise self._make_multi_error(errs, header)
raise self._make_multi_error(errs, header) from exType
def _make_phony_error_name() -> ASTNestedName:
@ -6622,8 +6624,9 @@ class CPPObject(ObjectDescription):
names=('returns', 'return')),
]
option_spec = dict(ObjectDescription.option_spec)
option_spec['tparam-line-spec'] = directives.flag
option_spec = {
'tparam-line-spec': directives.flag,
}
def _add_enumerator_to_parent(self, ast: ASTDeclaration) -> None:
assert ast.objectType == 'enumerator'
@ -6790,7 +6793,7 @@ class CPPObject(ObjectDescription):
name = _make_phony_error_name()
symbol = parentSymbol.add_name(name)
self.env.temp_data['cpp:last_symbol'] = symbol
raise ValueError
raise ValueError from e
try:
symbol = parentSymbol.add_declaration(ast, docname=self.env.docname)
@ -6806,7 +6809,10 @@ class CPPObject(ObjectDescription):
# Assume we are actually in the old symbol,
# instead of the newly created duplicate.
self.env.temp_data['cpp:last_symbol'] = e.symbol
logger.warning("Duplicate declaration, %s", sig, location=signode)
msg = __("Duplicate C++ declaration, also defined in '%s'.\n"
"Declaration is '%s'.")
msg = msg % (e.symbol.docname, sig)
logger.warning(msg, location=signode)
if ast.objectType == 'enumerator':
self._add_enumerator_to_parent(ast)
@ -7081,7 +7087,6 @@ class CPPAliasObject(ObjectDescription):
node['domain'] = self.domain
# 'desctype' is a backwards compatible attribute
node['objtype'] = node['desctype'] = self.objtype
node['noindex'] = True
self.names = [] # type: List[str]
signatures = self.get_signatures()
@ -7273,13 +7278,9 @@ class CPPDomain(Domain):
ourNames = self.data['names']
for name, docname in otherdata['names'].items():
if docname in docnames:
if name in ourNames:
msg = __("Duplicate declaration, also defined in '%s'.\n"
"Name of declaration is '%s'.")
msg = msg % (ourNames[name], name)
logger.warning(msg, location=docname)
else:
if name not in ourNames:
ourNames[name] = docname
# no need to warn on duplicates, the symbol merge already does that
if Symbol.debug_show_tree:
print("\tresult:")
print(self.data['root_symbol'].dump(1))

View File

@ -1041,10 +1041,10 @@ class StandardDomain(Domain):
try:
figure_id = target_node['ids'][0]
return env.toc_fignumbers[docname][figtype][figure_id]
except (KeyError, IndexError):
except (KeyError, IndexError) as exc:
# target_node is found, but fignumber is not assigned.
# Maybe it is defined in orphaned document.
raise ValueError
raise ValueError from exc
def get_full_qualified_name(self, node: Element) -> str:
if node.get('reftype') == 'option':

View File

@ -376,7 +376,8 @@ class BuildEnvironment:
if catalog.domain == domain:
self.dependencies[docname].add(catalog.mo_path)
except OSError as exc:
raise DocumentError(__('Failed to scan documents in %s: %r') % (self.srcdir, exc))
raise DocumentError(__('Failed to scan documents in %s: %r') %
(self.srcdir, exc)) from exc
def get_outdated_files(self, config_changed: bool) -> Tuple[Set[str], Set[str], Set[str]]:
"""Return (added, changed, removed) sets."""
@ -501,8 +502,8 @@ class BuildEnvironment:
"""
try:
return self.domains[domainname]
except KeyError:
raise ExtensionError(__('Domain %r is not registered') % domainname)
except KeyError as exc:
raise ExtensionError(__('Domain %r is not registered') % domainname) from exc
# --------- RESOLVING REFERENCES AND TOCTREES ------------------------------

View File

@ -33,7 +33,6 @@ from sphinx.locale import _, __
from sphinx.pycode import ModuleAnalyzer, PycodeError
from sphinx.util import inspect
from sphinx.util import logging
from sphinx.util import split_full_qualified_name
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
@ -821,7 +820,12 @@ class Documenter:
self.add_line('', sourcename)
# format the object's signature, if any
sig = self.format_signature()
try:
sig = self.format_signature()
except Exception as exc:
logger.warning(__('error while formatting signature for %s: %s'),
self.fullname, exc, type='autodoc')
return
# generate the directive header and options, if applicable
self.add_directive_header(sig)
@ -970,14 +974,8 @@ class ModuleLevelDocumenter(Documenter):
) -> Tuple[str, List[str]]:
if modname is None:
if path:
stripped = path.rstrip('.')
modname, qualname = split_full_qualified_name(stripped)
if qualname:
parents = qualname.split(".")
else:
parents = []
if modname is None:
modname = path.rstrip('.')
else:
# if documenting a toplevel object without explicit module,
# it can be contained in another auto directive ...
modname = self.env.temp_data.get('autodoc:module')
@ -1010,13 +1008,8 @@ class ClassLevelDocumenter(Documenter):
# ... if still None, there's no way to know
if mod_cls is None:
return None, []
try:
modname, qualname = split_full_qualified_name(mod_cls)
parents = qualname.split(".") if qualname else []
except ImportError:
parents = mod_cls.split(".")
modname, sep, cls = mod_cls.rpartition('.')
parents = [cls]
# if the module name is still missing, get it like above
if not modname:
modname = self.env.temp_data.get('autodoc:module')

View File

@ -32,7 +32,7 @@ def import_module(modname: str, warningiserror: bool = False) -> Any:
except BaseException as exc:
# Importing modules may cause any side effects, including
# SystemExit, so we need to catch all errors.
raise ImportError(exc, traceback.format_exc())
raise ImportError(exc, traceback.format_exc()) from exc
def import_object(modname: str, objpath: List[str], objtype: str = '',
@ -97,7 +97,7 @@ def import_object(modname: str, objpath: List[str], objtype: str = '',
errmsg += '; the following exception was raised:\n%s' % traceback.format_exc()
logger.debug(errmsg)
raise ImportError(errmsg)
raise ImportError(errmsg) from exc
def get_module_members(module: Any) -> List[Tuple[str, Any]]:

View File

@ -46,11 +46,16 @@ def merge_typehints(app: Sphinx, domain: str, objtype: str, contentnode: Element
if objtype == 'class' and app.config.autoclass_content not in ('init', 'both'):
return
signature = cast(addnodes.desc_signature, contentnode.parent[0])
if signature['module']:
fullname = '.'.join([signature['module'], signature['fullname']])
else:
fullname = signature['fullname']
try:
signature = cast(addnodes.desc_signature, contentnode.parent[0])
if signature['module']:
fullname = '.'.join([signature['module'], signature['fullname']])
else:
fullname = signature['fullname']
except KeyError:
# signature node does not have valid context info for the target object
return
annotations = app.env.temp_data.get('annotations', {})
if annotations.get(fullname, {}):
field_lists = [n for n in contentnode if isinstance(n, nodes.field_list)]

View File

@ -95,6 +95,8 @@ logger = logging.getLogger(__name__)
periods_re = re.compile(r'\.(?:\s+)')
literal_re = re.compile(r'::\s*$')
WELL_KNOWN_ABBREVIATIONS = (' i.e.',)
# -- autosummary_toc node ------------------------------------------------------
@ -470,6 +472,13 @@ def mangle_signature(sig: str, max_chars: int = 30) -> str:
def extract_summary(doc: List[str], document: Any) -> str:
"""Extract summary from docstring."""
def parse(doc: List[str], settings: Any) -> nodes.document:
state_machine = RSTStateMachine(state_classes, 'Body')
node = new_document('', settings)
node.reporter = NullReporter()
state_machine.run(doc, node)
return node
# Skip a blank lines at the top
while doc and not doc[0].strip():
@ -487,11 +496,7 @@ def extract_summary(doc: List[str], document: Any) -> str:
return ''
# parse the docstring
state_machine = RSTStateMachine(state_classes, 'Body')
node = new_document('', document.settings)
node.reporter = NullReporter()
state_machine.run(doc, node)
node = parse(doc, document.settings)
if not isinstance(node[0], nodes.paragraph):
# document starts with non-paragraph: pick up the first line
summary = doc[0].strip()
@ -502,11 +507,13 @@ def extract_summary(doc: List[str], document: Any) -> str:
summary = sentences[0].strip()
else:
summary = ''
while sentences:
summary += sentences.pop(0) + '.'
for i in range(len(sentences)):
summary = ". ".join(sentences[:i + 1]).rstrip(".") + "."
node[:] = []
state_machine.run([summary], node)
if not node.traverse(nodes.system_message):
node = parse(doc, document.settings)
if summary.endswith(WELL_KNOWN_ABBREVIATIONS):
pass
elif not node.traverse(nodes.system_message):
# considered as that splitting by period does not break inline markups
break
@ -620,7 +627,7 @@ def _import_by_name(name: str) -> Tuple[Any, Any, str]:
else:
return sys.modules[modname], None, modname
except (ValueError, ImportError, AttributeError, KeyError) as e:
raise ImportError(*e.args)
raise ImportError(*e.args) from e
# -- :autolink: (smart default role) -------------------------------------------
@ -701,7 +708,8 @@ def process_generate_options(app: Sphinx) -> None:
with mock(app.config.autosummary_mock_imports):
generate_autosummary_docs(genfiles, suffix=suffix, base_path=app.srcdir,
app=app, imported_members=imported_members,
overwrite=app.config.autosummary_generate_overwrite)
overwrite=app.config.autosummary_generate_overwrite,
encoding=app.config.source_encoding)
def setup(app: Sphinx) -> Dict[str, Any]:

View File

@ -226,7 +226,8 @@ class ModuleScanner:
def generate_autosummary_content(name: str, obj: Any, parent: Any,
template: AutosummaryRenderer, template_name: str,
imported_members: bool, app: Any,
recursive: bool, context: Dict) -> str:
recursive: bool, context: Dict,
modname: str = None, qualname: str = None) -> str:
doc = get_documenter(app, obj, parent)
def skip_member(obj: Any, name: str, objtype: str) -> bool:
@ -315,7 +316,9 @@ def generate_autosummary_content(name: str, obj: Any, parent: Any,
ns['attributes'], ns['all_attributes'] = \
get_members(obj, {'attribute', 'property'})
modname, qualname = split_full_qualified_name(name)
if modname is None or qualname is None:
modname, qualname = split_full_qualified_name(name)
if doc.objtype in ('method', 'attribute', 'property'):
ns['class'] = qualname.rsplit(".", 1)[0]
@ -342,7 +345,7 @@ def generate_autosummary_docs(sources: List[str], output_dir: str = None,
suffix: str = '.rst', base_path: str = None,
builder: Builder = None, template_dir: str = None,
imported_members: bool = False, app: Any = None,
overwrite: bool = True) -> None:
overwrite: bool = True, encoding: str = 'utf-8') -> None:
if builder:
warnings.warn('builder argument for generate_autosummary_docs() is deprecated.',
RemovedInSphinx50Warning, stacklevel=2)
@ -382,7 +385,8 @@ def generate_autosummary_docs(sources: List[str], output_dir: str = None,
ensuredir(path)
try:
name, obj, parent, mod_name = import_by_name(entry.name)
name, obj, parent, modname = import_by_name(entry.name)
qualname = name.replace(modname + ".", "")
except ImportError as e:
logger.warning(__('[autosummary] failed to import %r: %s') % (entry.name, e))
continue
@ -392,21 +396,22 @@ def generate_autosummary_docs(sources: List[str], output_dir: str = None,
context.update(app.config.autosummary_context)
content = generate_autosummary_content(name, obj, parent, template, entry.template,
imported_members, app, entry.recursive, context)
imported_members, app, entry.recursive, context,
modname, qualname)
filename = os.path.join(path, name + suffix)
if os.path.isfile(filename):
with open(filename) as f:
with open(filename, encoding=encoding) as f:
old_content = f.read()
if content == old_content:
continue
elif overwrite: # content has changed
with open(filename, 'w') as f:
with open(filename, 'w', encoding=encoding) as f:
f.write(content)
new_files.append(filename)
else:
with open(filename, 'w') as f:
with open(filename, 'w', encoding=encoding) as f:
f.write(content)
new_files.append(filename)

View File

@ -256,7 +256,7 @@ def render_dot(self: SphinxTranslator, code: str, options: Dict,
return None, None
except CalledProcessError as exc:
raise GraphvizError(__('dot exited with error:\n[stderr]\n%r\n'
'[stdout]\n%r') % (exc.stderr, exc.stdout))
'[stdout]\n%r') % (exc.stderr, exc.stdout)) from exc
def render_dot_html(self: HTMLTranslator, node: graphviz, code: str, options: Dict,
@ -270,7 +270,7 @@ def render_dot_html(self: HTMLTranslator, node: graphviz, code: str, options: Di
fname, outfn = render_dot(self, code, options, format, prefix)
except GraphvizError as exc:
logger.warning(__('dot code %r: %s'), code, exc)
raise nodes.SkipNode
raise nodes.SkipNode from exc
classes = [imgcls, 'graphviz'] + node.get('classes', [])
imgcls = ' '.join(filter(None, classes))
@ -321,7 +321,7 @@ def render_dot_latex(self: LaTeXTranslator, node: graphviz, code: str,
fname, outfn = render_dot(self, code, options, 'pdf', prefix)
except GraphvizError as exc:
logger.warning(__('dot code %r: %s'), code, exc)
raise nodes.SkipNode
raise nodes.SkipNode from exc
is_inline = self.is_inline(node)
@ -358,7 +358,7 @@ def render_dot_texinfo(self: TexinfoTranslator, node: graphviz, code: str,
fname, outfn = render_dot(self, code, options, 'png', prefix)
except GraphvizError as exc:
logger.warning(__('dot code %r: %s'), code, exc)
raise nodes.SkipNode
raise nodes.SkipNode from exc
if fname is not None:
self.body.append('@image{%s,,,[graphviz],png}\n' % fname[:-4])
raise nodes.SkipNode

View File

@ -70,7 +70,7 @@ class ImagemagickConverter(ImageConverter):
except CalledProcessError as exc:
raise ExtensionError(__('convert exited with error:\n'
'[stderr]\n%r\n[stdout]\n%r') %
(exc.stderr, exc.stdout))
(exc.stderr, exc.stdout)) from exc
def setup(app: Sphinx) -> Dict[str, Any]:

View File

@ -137,13 +137,13 @@ def compile_math(latex: str, builder: Builder) -> str:
try:
subprocess.run(command, stdout=PIPE, stderr=PIPE, cwd=tempdir, check=True)
return path.join(tempdir, 'math.dvi')
except OSError:
except OSError as exc:
logger.warning(__('LaTeX command %r cannot be run (needed for math '
'display), check the imgmath_latex setting'),
builder.config.imgmath_latex)
raise InvokeError
raise InvokeError from exc
except CalledProcessError as exc:
raise MathExtError('latex exited with error', exc.stderr, exc.stdout)
raise MathExtError('latex exited with error', exc.stderr, exc.stdout) from exc
def convert_dvi_to_image(command: List[str], name: str) -> Tuple[str, str]:
@ -151,13 +151,13 @@ def convert_dvi_to_image(command: List[str], name: str) -> Tuple[str, str]:
try:
ret = subprocess.run(command, stdout=PIPE, stderr=PIPE, check=True, encoding='ascii')
return ret.stdout, ret.stderr
except OSError:
except OSError as exc:
logger.warning(__('%s command %r cannot be run (needed for math '
'display), check the imgmath_%s setting'),
name, command[0], name)
raise InvokeError
raise InvokeError from exc
except CalledProcessError as exc:
raise MathExtError('%s exited with error' % name, exc.stderr, exc.stdout)
raise MathExtError('%s exited with error' % name, exc.stderr, exc.stdout) from exc
def convert_dvi_to_png(dvipath: str, builder: Builder) -> Tuple[str, int]:
@ -298,7 +298,7 @@ def html_visit_math(self: HTMLTranslator, node: nodes.math) -> None:
backrefs=[], source=node.astext())
sm.walkabout(self)
logger.warning(__('display latex %r: %s'), node.astext(), msg)
raise nodes.SkipNode
raise nodes.SkipNode from exc
if fname is None:
# something failed -- use text-only as a bad substitute
self.body.append('<span class="math">%s</span>' %
@ -324,7 +324,7 @@ def html_visit_displaymath(self: HTMLTranslator, node: nodes.math_block) -> None
backrefs=[], source=node.astext())
sm.walkabout(self)
logger.warning(__('inline latex %r: %s'), node.astext(), msg)
raise nodes.SkipNode
raise nodes.SkipNode from exc
self.body.append(self.starttag(node, 'div', CLASS='math'))
self.body.append('<p>')
if node['number']:

View File

@ -179,7 +179,7 @@ def fetch_inventory(app: Sphinx, uri: str, inv: Any) -> Any:
join = path.join if localuri else posixpath.join
invdata = InventoryFile.load(f, uri, join)
except ValueError as exc:
raise ValueError('unknown or unsupported inventory version: %r' % exc)
raise ValueError('unknown or unsupported inventory version: %r' % exc) from exc
except Exception as err:
err.args = ('intersphinx inventory %r not readable due to %s: %s',
inv, err.__class__.__name__, str(err))

View File

@ -158,6 +158,7 @@ class GoogleDocstring:
'parameters': self._parse_parameters_section,
'return': self._parse_returns_section,
'returns': self._parse_returns_section,
'raise': self._parse_raises_section,
'raises': self._parse_raises_section,
'references': self._parse_references_section,
'see also': self._parse_see_also_section,
@ -165,6 +166,7 @@ class GoogleDocstring:
'todo': partial(self._parse_admonition, 'todo'),
'warning': partial(self._parse_admonition, 'warning'),
'warnings': partial(self._parse_admonition, 'warning'),
'warn': self._parse_warns_section,
'warns': self._parse_warns_section,
'yield': self._parse_yields_section,
'yields': self._parse_yields_section,

View File

@ -131,10 +131,8 @@ def env_merge_info(app: Sphinx, env: BuildEnvironment, docnames: Iterable[str],
def missing_reference(app: Sphinx, env: BuildEnvironment, node: Element, contnode: Node
) -> Node:
if app.builder.format != 'html':
return None
elif node['reftype'] == 'viewcode':
# resolve our "viewcode" reference nodes -- they need special treatment
# resolve our "viewcode" reference nodes -- they need special treatment
if node['reftype'] == 'viewcode':
return make_refnode(app.builder, node['refdoc'], node['reftarget'],
node['refid'], contnode)

View File

@ -35,7 +35,7 @@ class ModuleAnalyzer:
try:
mod = import_module(modname)
except Exception as err:
raise PycodeError('error importing %r' % modname, err)
raise PycodeError('error importing %r' % modname, err) from err
loader = getattr(mod, '__loader__', None)
filename = getattr(mod, '__file__', None)
if loader and getattr(loader, 'get_source', None):
@ -52,7 +52,7 @@ class ModuleAnalyzer:
try:
filename = loader.get_filename(modname)
except ImportError as err:
raise PycodeError('error getting filename for %r' % modname, err)
raise PycodeError('error getting filename for %r' % modname, err) from err
if filename is None:
# all methods for getting filename failed, so raise...
raise PycodeError('no source found for module %r' % modname)
@ -90,7 +90,7 @@ class ModuleAnalyzer:
if '.egg' + path.sep in filename:
obj = cls.cache['file', filename] = cls.for_egg(filename, modname)
else:
raise PycodeError('error opening %r' % filename, err)
raise PycodeError('error opening %r' % filename, err) from err
return obj
@classmethod
@ -102,7 +102,7 @@ class ModuleAnalyzer:
code = egg.read(relpath).decode()
return cls.for_string(code, modname, filename)
except Exception as exc:
raise PycodeError('error opening %r' % filename, exc)
raise PycodeError('error opening %r' % filename, exc) from exc
@classmethod
def for_module(cls, modname: str) -> "ModuleAnalyzer":
@ -158,7 +158,7 @@ class ModuleAnalyzer:
self.tags = parser.definitions
self.tagorder = parser.deforders
except Exception as exc:
raise PycodeError('parsing %r failed: %r' % (self.srcname, exc))
raise PycodeError('parsing %r failed: %r' % (self.srcname, exc)) from exc
def find_attr_docs(self) -> Dict[Tuple[str, str], List[str]]:
"""Find class and module-level attributes and their documentation."""

View File

@ -138,9 +138,9 @@ class SphinxComponentRegistry:
entry_points = iter_entry_points('sphinx.builders', name)
try:
entry_point = next(entry_points)
except StopIteration:
except StopIteration as exc:
raise SphinxError(__('Builder name %s not registered or available'
' through entry point') % name)
' through entry point') % name) from exc
self.load_extension(app, entry_point.module_name)
@ -272,8 +272,8 @@ class SphinxComponentRegistry:
def get_source_parser(self, filetype: str) -> "Type[Parser]":
try:
return self.source_parsers[filetype]
except KeyError:
raise SphinxError(__('Source parser for %s not registered') % filetype)
except KeyError as exc:
raise SphinxError(__('Source parser for %s not registered') % filetype) from exc
def get_source_parsers(self) -> Dict[str, "Type[Parser]"]:
return self.source_parsers
@ -310,9 +310,11 @@ class SphinxComponentRegistry:
try:
visit, depart = handlers # unpack once for assertion
translation_handlers[node.__name__] = (visit, depart)
except ValueError:
raise ExtensionError(__('kwargs for add_node() must be a (visit, depart) '
'function tuple: %r=%r') % (builder_name, handlers))
except ValueError as exc:
raise ExtensionError(
__('kwargs for add_node() must be a (visit, depart) '
'function tuple: %r=%r') % (builder_name, handlers)
) from exc
def get_translator_class(self, builder: Builder) -> "Type[nodes.NodeVisitor]":
return self.translators.get(builder.name,
@ -406,7 +408,8 @@ class SphinxComponentRegistry:
mod = import_module(extname)
except ImportError as err:
logger.verbose(__('Original exception:\n') + traceback.format_exc())
raise ExtensionError(__('Could not import extension %s') % extname, err)
raise ExtensionError(__('Could not import extension %s') % extname,
err) from err
setup = getattr(mod, 'setup', None)
if setup is None:
@ -422,7 +425,7 @@ class SphinxComponentRegistry:
__('The %s extension used by this project needs at least '
'Sphinx v%s; it therefore cannot be built with this '
'version.') % (extname, err)
)
) from err
if metadata is None:
metadata = {}

View File

@ -331,14 +331,15 @@ class Abbreviation(SphinxRole):
abbr_re = re.compile(r'\((.*)\)$', re.S)
def run(self) -> Tuple[List[Node], List[system_message]]:
options = self.options.copy()
matched = self.abbr_re.search(self.text)
if matched:
text = self.text[:matched.start()].strip()
self.options['explanation'] = matched.group(1)
options['explanation'] = matched.group(1)
else:
text = self.text
return [nodes.abbreviation(self.rawtext, text, **self.options)], []
return [nodes.abbreviation(self.rawtext, text, **options)], []
specific_docroles = {

View File

@ -528,9 +528,9 @@ class SearchJapanese(SearchLanguage):
dotted_path = options.get('type', 'sphinx.search.ja.DefaultSplitter')
try:
self.splitter = import_object(dotted_path)(options)
except ExtensionError:
except ExtensionError as exc:
raise ExtensionError("Splitter module %r can't be imported" %
dotted_path)
dotted_path) from exc
def split(self, input: str) -> List[str]:
return self.splitter.split(input)

View File

@ -198,7 +198,7 @@ class BuildDoc(Command):
except Exception as exc:
handle_exception(app, self, exc, sys.stderr)
if not self.pdb:
raise SystemExit(1)
raise SystemExit(1) from exc
if not self.link_index:
continue

View File

@ -120,7 +120,7 @@
{%- else %}
<meta http-equiv="Content-Type" content="text/html; charset={{ encoding }}" />
{%- endif %}
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
{{- metatags }}
{%- block htmltitle %}
<title>{{ title|striptags|e }}{{ titlesuffix }}</title>

View File

@ -20,7 +20,7 @@
{%- endblock %}
{%- block extrahead %}
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
<!--[if lt IE 9]>
<script src="_static/css3-mediaqueries.js"></script>
<![endif]-->

View File

@ -74,17 +74,17 @@ class Theme:
try:
inherit = self.config.get('theme', 'inherit')
except configparser.NoSectionError:
raise ThemeError(__('theme %r doesn\'t have "theme" setting') % name)
except configparser.NoOptionError:
raise ThemeError(__('theme %r doesn\'t have "inherit" setting') % name)
except configparser.NoSectionError as exc:
raise ThemeError(__('theme %r doesn\'t have "theme" setting') % name) from exc
except configparser.NoOptionError as exc:
raise ThemeError(__('theme %r doesn\'t have "inherit" setting') % name) from exc
if inherit != 'none':
try:
self.base = factory.create(inherit)
except ThemeError:
except ThemeError as exc:
raise ThemeError(__('no theme named %r found, inherited by %r') %
(inherit, name))
(inherit, name)) from exc
def get_theme_dirs(self) -> List[str]:
"""Return a list of theme directories, beginning with this theme's,
@ -101,13 +101,13 @@ class Theme:
"""
try:
return self.config.get(section, name)
except (configparser.NoOptionError, configparser.NoSectionError):
except (configparser.NoOptionError, configparser.NoSectionError) as exc:
if self.base:
return self.base.get_config(section, name, default)
if default is NODEFAULT:
raise ThemeError(__('setting %s.%s occurs in none of the '
'searched theme configs') % (section, name))
'searched theme configs') % (section, name)) from exc
else:
return default

View File

@ -82,8 +82,8 @@ class ReferencesResolver(SphinxPostTransform):
# let the domain try to resolve the reference
try:
domain = self.env.domains[node['refdomain']]
except KeyError:
raise NoUri(target, typ)
except KeyError as exc:
raise NoUri(target, typ) from exc
newnode = domain.resolve_xref(self.env, refdoc, self.app.builder,
typ, target, node, contnode)
# really hardwired reference types

View File

@ -334,8 +334,8 @@ def parselinenos(spec: str, total: int) -> List[int]:
items.extend(range(start - 1, end))
else:
raise ValueError
except Exception:
raise ValueError('invalid line number spec: %r' % spec)
except Exception as exc:
raise ValueError('invalid line number spec: %r' % spec) from exc
return items
@ -406,9 +406,9 @@ def import_object(objname: str, source: str = None) -> Any:
except (AttributeError, ImportError) as exc:
if source:
raise ExtensionError('Could not import %s (needed for %s)' %
(objname, source), exc)
(objname, source), exc) from exc
else:
raise ExtensionError('Could not import %s' % objname, exc)
raise ExtensionError('Could not import %s' % objname, exc) from exc
def split_full_qualified_name(name: str) -> Tuple[str, str]:
@ -425,26 +425,16 @@ def split_full_qualified_name(name: str) -> Tuple[str, str]:
Therefore you need to mock 3rd party modules if needed before
calling this function.
"""
from sphinx.util import inspect
parts = name.split('.')
for i, part in enumerate(parts, 1):
try:
modname = ".".join(parts[:i])
module = import_module(modname)
# check the module has a member named as attrname
#
# Note: This is needed to detect the attribute having the same name
# as the module.
# ref: https://github.com/sphinx-doc/sphinx/issues/7812
attrname = parts[i]
if hasattr(module, attrname):
value = inspect.safe_getattr(module, attrname)
if not inspect.ismodule(value):
return ".".join(parts[:i]), ".".join(parts[i:])
import_module(modname)
except ImportError:
return ".".join(parts[:i - 1]), ".".join(parts[i - 1:])
if parts[:i - 1]:
return ".".join(parts[:i - 1]), ".".join(parts[i - 1:])
else:
return None, ".".join(parts)
except IndexError:
pass

View File

@ -11,7 +11,7 @@
import re
from copy import deepcopy
from typing import (
Any, Callable, List, Match, Pattern, Tuple, Union
Any, Callable, List, Match, Optional, Pattern, Tuple, Union
)
from docutils import nodes
@ -141,16 +141,14 @@ class ASTCPPAttribute(ASTAttribute):
class ASTGnuAttribute(ASTBaseBase):
def __init__(self, name: str, args: Any) -> None:
def __init__(self, name: str, args: Optional["ASTBaseParenExprList"]) -> None:
self.name = name
self.args = args
def _stringify(self, transform: StringifyTransform) -> str:
res = [self.name]
if self.args:
res.append('(')
res.append(transform(self.args))
res.append(')')
return ''.join(res)
@ -204,6 +202,11 @@ class ASTParenAttribute(ASTAttribute):
################################################################################
class ASTBaseParenExprList(ASTBaseBase):
pass
################################################################################
class UnsupportedMultiCharacterCharLiteral(Exception):
pass
@ -398,11 +401,8 @@ class BaseParser:
while 1:
if self.match(identifier_re):
name = self.matched_text
self.skip_ws()
if self.skip_string_and_ws('('):
self.fail('Parameterized GNU style attribute not yet supported.')
attrs.append(ASTGnuAttribute(name, None))
# TODO: parse arguments for the attribute
exprs = self._parse_paren_expression_list()
attrs.append(ASTGnuAttribute(name, exprs))
if self.skip_string_and_ws(','):
continue
elif self.skip_string_and_ws(')'):
@ -430,3 +430,6 @@ class BaseParser:
return ASTParenAttribute(id, arg)
return None
def _parse_paren_expression_list(self) -> ASTBaseParenExprList:
raise NotImplementedError

View File

@ -238,7 +238,7 @@ def get_image_filename_for_language(filename: str, env: "BuildEnvironment") -> s
try:
return filename_format.format(**d)
except KeyError as exc:
raise SphinxError('Invalid figure_language_filename: %r' % exc)
raise SphinxError('Invalid figure_language_filename: %r' % exc) from exc
def search_image_for_language(filename: str, env: "BuildEnvironment") -> str:

View File

@ -322,7 +322,7 @@ def safe_getattr(obj: Any, name: str, *defargs: Any) -> Any:
"""A getattr() that turns all exceptions into AttributeErrors."""
try:
return getattr(obj, name, *defargs)
except Exception:
except Exception as exc:
# sometimes accessing a property raises an exception (e.g.
# NotImplementedError), so let's try to read the attribute directly
try:
@ -337,7 +337,7 @@ def safe_getattr(obj: Any, name: str, *defargs: Any) -> Any:
if defargs:
return defargs[0]
raise AttributeError(name)
raise AttributeError(name) from exc
def object_description(object: Any) -> str:
@ -369,8 +369,8 @@ def object_description(object: Any) -> str:
for x in sorted_values)
try:
s = repr(object)
except Exception:
raise ValueError
except Exception as exc:
raise ValueError from exc
# Strip non-deterministic memory addresses such as
# ``<__main__.A at 0x7f68cb685710>``
s = memory_address_re.sub('', s)

View File

@ -148,10 +148,10 @@ def abspath(pathdir: str) -> str:
if isinstance(pathdir, bytes):
try:
pathdir = pathdir.decode(fs_encoding)
except UnicodeDecodeError:
except UnicodeDecodeError as exc:
raise UnicodeDecodeError('multibyte filename not supported on '
'this filesystem encoding '
'(%r)' % fs_encoding)
'(%r)' % fs_encoding) from exc
return pathdir

View File

@ -26,11 +26,11 @@ def convert_with_2to3(filepath: str) -> str:
try:
from lib2to3.refactor import RefactoringTool, get_fixers_from_package
from lib2to3.pgen2.parse import ParseError
except ImportError:
except ImportError as exc:
# python 3.9.0a6+ emits PendingDeprecationWarning for lib2to3.
# Additionally, removal of the module is still discussed at PEP-594.
# To support future python, this catches ImportError for lib2to3.
raise SyntaxError
raise SyntaxError from exc
fixers = get_fixers_from_package('lib2to3.fixes')
refactoring_tool = RefactoringTool(fixers)
@ -41,7 +41,8 @@ def convert_with_2to3(filepath: str) -> str:
# do not propagate lib2to3 exceptions
lineno, offset = err.context[1]
# try to match ParseError details with SyntaxError details
raise SyntaxError(err.msg, (filepath, lineno, offset, err.value))
raise SyntaxError(err.msg, (filepath, lineno, offset, err.value)) from err
return str(tree)

View File

@ -852,8 +852,8 @@ class TexinfoTranslator(SphinxTranslator):
num = node.astext().strip()
try:
footnode, used = self.footnotestack[-1][num]
except (KeyError, IndexError):
raise nodes.SkipNode
except (KeyError, IndexError) as exc:
raise nodes.SkipNode from exc
# footnotes are repeated for each reference
footnode.walkabout(self) # type: ignore
raise nodes.SkipChildren

View File

@ -222,7 +222,7 @@ class Table:
for left, right in zip(out, out[1:])
]
glue.append(tail)
return head + "".join(chain(*zip(out, glue)))
return head + "".join(chain.from_iterable(zip(out, glue)))
for lineno, line in enumerate(self.lines):
if self.separator and lineno == self.separator:

View File

@ -0,0 +1,5 @@
from .foo import bar
class foo:
"""docstring of target.name_conflict::foo."""
pass

View File

@ -0,0 +1,2 @@
class bar:
"""docstring of target.name_conflict.foo::bar."""

View File

@ -63,8 +63,8 @@ def compile_latex_document(app, filename='python.tex'):
'-output-directory=%s' % app.config.latex_engine,
filename]
subprocess.run(args, stdout=PIPE, stderr=PIPE, check=True)
except OSError: # most likely the latex executable was not found
raise pytest.skip.Exception
except OSError as exc: # most likely the latex executable was not found
raise pytest.skip.Exception from exc
except CalledProcessError as exc:
print(exc.stdout)
print(exc.stderr)
@ -1538,7 +1538,7 @@ def test_texescape_for_unicode_supported_engine(app, status, warning):
assert 'superscript: ⁰, ¹' in result
assert 'subscript: ₀, ₁' in result
@pytest.mark.sphinx('latex', testroot='basic',
confoverrides={'latex_elements': {'extrapackages': r'\usepackage{foo}'}})
def test_latex_elements_extrapackages(app, status, warning):

View File

@ -58,8 +58,8 @@ def test_texinfo(app, status, warning):
try:
args = ['makeinfo', '--no-split', 'sphinxtests.texi']
subprocess.run(args, stdout=PIPE, stderr=PIPE, cwd=app.outdir, check=True)
except OSError:
raise pytest.skip.Exception # most likely makeinfo was not found
except OSError as exc:
raise pytest.skip.Exception from exc # most likely makeinfo was not found
except CalledProcessError as exc:
print(exc.stdout)
print(exc.stderr)

View File

@ -469,6 +469,8 @@ def test_attributes():
check('member', '__attribute__(()) int f', {1: 'f'})
check('member', '__attribute__((a)) int f', {1: 'f'})
check('member', '__attribute__((a, b)) int f', {1: 'f'})
check('member', '__attribute__((optimize(3))) int f', {1: 'f'})
check('member', '__attribute__((format(printf, 1, 2))) int f', {1: 'f'})
# style: user-defined id
check('member', 'id_attr int f', {1: 'f'})
# style: user-defined paren

View File

@ -896,6 +896,8 @@ def test_attributes():
check('member', '__attribute__(()) int f', {1: 'f__i', 2: '1f'})
check('member', '__attribute__((a)) int f', {1: 'f__i', 2: '1f'})
check('member', '__attribute__((a, b)) int f', {1: 'f__i', 2: '1f'})
check('member', '__attribute__((optimize(3))) int f', {1: 'f__i', 2: '1f'})
check('member', '__attribute__((format(printf, 1, 2))) int f', {1: 'f__i', 2: '1f'})
# style: user-defined id
check('member', 'id_attr int f', {1: 'f__i', 2: '1f'})
# style: user-defined paren

View File

@ -121,15 +121,16 @@ def test_parse_name(app):
verify('class', 'Base', ('test_ext_autodoc', ['Base'], None, None))
# for members
directive.env.ref_context['py:module'] = 'foo'
verify('method', 'util.SphinxTestApp.cleanup',
('foo', ['util', 'SphinxTestApp', 'cleanup'], None, None))
directive.env.ref_context['py:module'] = 'util'
directive.env.ref_context['py:module'] = 'sphinx.testing.util'
verify('method', 'SphinxTestApp.cleanup',
('sphinx.testing.util', ['SphinxTestApp', 'cleanup'], None, None))
directive.env.ref_context['py:module'] = 'sphinx.testing.util'
directive.env.ref_context['py:class'] = 'Foo'
directive.env.temp_data['autodoc:class'] = 'SphinxTestApp'
verify('method', 'cleanup', ('util', ['SphinxTestApp', 'cleanup'], None, None))
verify('method', 'cleanup',
('sphinx.testing.util', ['SphinxTestApp', 'cleanup'], None, None))
verify('method', 'SphinxTestApp.cleanup',
('util', ['SphinxTestApp', 'cleanup'], None, None))
('sphinx.testing.util', ['SphinxTestApp', 'cleanup'], None, None))
def test_format_signature(app):
@ -800,14 +801,14 @@ def test_autodoc_inner_class(app):
actual = do_autodoc(app, 'class', 'target.Outer.Inner', options)
assert list(actual) == [
'',
'.. py:class:: Outer.Inner()',
' :module: target',
'.. py:class:: Inner()',
' :module: target.Outer',
'',
' Foo',
'',
'',
' .. py:method:: Outer.Inner.meth()',
' :module: target',
' .. py:method:: Inner.meth()',
' :module: target.Outer',
'',
' Foo',
'',
@ -1881,6 +1882,43 @@ def test_overload(app):
]
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_pymodule_for_ModuleLevelDocumenter(app):
app.env.ref_context['py:module'] = 'target.classes'
actual = do_autodoc(app, 'class', 'Foo')
assert list(actual) == [
'',
'.. py:class:: Foo()',
' :module: target.classes',
'',
]
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_pymodule_for_ClassLevelDocumenter(app):
app.env.ref_context['py:module'] = 'target.methods'
actual = do_autodoc(app, 'method', 'Base.meth')
assert list(actual) == [
'',
'.. py:method:: Base.meth()',
' :module: target.methods',
'',
]
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_pyclass_for_ClassLevelDocumenter(app):
app.env.ref_context['py:module'] = 'target.methods'
app.env.ref_context['py:class'] = 'Base'
actual = do_autodoc(app, 'method', 'meth')
assert list(actual) == [
'',
'.. py:method:: Base.meth()',
' :module: target.methods',
'',
]
@pytest.mark.sphinx('dummy', testroot='ext-autodoc')
def test_autodoc(app, status, warning):
app.builder.build_all()
@ -1899,3 +1937,26 @@ my_name
alias of bug2437.autodoc_dummy_foo.Foo"""
assert warning.getvalue() == ''
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_name_conflict(app):
actual = do_autodoc(app, 'class', 'target.name_conflict.foo')
assert list(actual) == [
'',
'.. py:class:: foo()',
' :module: target.name_conflict',
'',
' docstring of target.name_conflict::foo.',
'',
]
actual = do_autodoc(app, 'class', 'target.name_conflict.foo.bar')
assert list(actual) == [
'',
'.. py:class:: bar()',
' :module: target.name_conflict.foo',
'',
' docstring of target.name_conflict.foo::bar.',
'',
]

View File

@ -85,8 +85,8 @@ def test_methoddescriptor(app):
actual = do_autodoc(app, 'function', 'builtins.int.__add__')
assert list(actual) == [
'',
'.. py:function:: int.__add__(self, value, /)',
' :module: builtins',
'.. py:function:: __add__(self, value, /)',
' :module: builtins.int',
'',
' Return self+value.',
'',

View File

@ -13,6 +13,8 @@ import sys
import pytest
from sphinx.testing import restructuredtext
from test_ext_autodoc import do_autodoc
IS_PYPY = platform.python_implementation() == 'PyPy'
@ -633,6 +635,12 @@ def test_autodoc_typehints_description(app):
in context)
@pytest.mark.sphinx('text', testroot='ext-autodoc',
confoverrides={'autodoc_typehints': "description"})
def test_autodoc_typehints_description_for_invalid_node(app):
text = ".. py:function:: hello; world"
restructuredtext.parse(app, text) # raises no error
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_autodoc_default_options(app):

View File

@ -97,7 +97,7 @@ def test_extract_summary(capsys):
# abbreviations
doc = ['Blabla, i.e. bla.']
assert extract_summary(doc, document) == 'Blabla, i.e.'
assert extract_summary(doc, document) == ' '.join(doc)
# literal
doc = ['blah blah::']
@ -108,6 +108,12 @@ def test_extract_summary(capsys):
'=========']
assert extract_summary(doc, document) == 'blah blah'
# hyperlink target
doc = ['Do `this <https://www.sphinx-doc.org/>`_ and that. '
'blah blah blah.']
assert (extract_summary(doc, document) ==
'Do `this <https://www.sphinx-doc.org/>`_ and that.')
_, err = capsys.readouterr()
assert err == ''

12
tox.ini
View File

@ -1,6 +1,6 @@
[tox]
minversion = 2.4.0
envlist = docs,flake8,mypy,coverage,py{36,37,38,39},du{14,15,16}
envlist = docs,flake8,mypy,twine,coverage,py{36,37,38,39},du{14,15,16}
[testenv]
usedevelop = True
@ -86,6 +86,16 @@ extras =
commands =
python utils/doclinter.py CHANGES CONTRIBUTING.rst README.rst doc/
[testenv:twine]
basepython = python3
description =
Lint package.
deps =
twine
commands =
python setup.py release bdist_wheel sdist
twine check dist/*
[testenv:bindep]
description =
Install binary dependencies.

View File

@ -11,7 +11,6 @@ for stable releases
* ``git commit -am 'Bump to X.Y.Z final'``
* ``make clean``
* ``python setup.py release bdist_wheel sdist``
* ``twine check dist/Sphinx-*``
* ``twine upload dist/Sphinx-* --sign --identity [your GPG key]``
* open https://pypi.org/project/Sphinx/ and check there are no obvious errors
* ``sh utils/bump_docker.sh X.Y.Z``
@ -38,7 +37,6 @@ for first beta releases
* ``git commit -am 'Bump to X.Y.0 beta1'``
* ``make clean``
* ``python setup.py release bdist_wheel sdist``
* ``twine check dist/Sphinx-*``
* ``twine upload dist/Sphinx-* --sign --identity [your GPG key]``
* open https://pypi.org/project/Sphinx/ and check there are no obvious errors
* ``git tag vX.Y.0b1``
@ -67,7 +65,6 @@ for other beta releases
* ``git commit -am 'Bump to X.Y.0 betaN'``
* ``make clean``
* ``python setup.py release bdist_wheel sdist``
* ``twine check dist/Sphinx-*``
* ``twine upload dist/Sphinx-* --sign --identity [your GPG key]``
* open https://pypi.org/project/Sphinx/ and check there are no obvious errors
* ``git tag vX.Y.0bN``
@ -95,7 +92,6 @@ for major releases
* ``git commit -am 'Bump to X.Y.0 final'``
* ``make clean``
* ``python setup.py release bdist_wheel sdist``
* ``twine check dist/Sphinx-*``
* ``twine upload dist/Sphinx-* --sign --identity [your GPG key]``
* open https://pypi.org/project/Sphinx/ and check there are no obvious errors
* ``sh utils/bump_docker.sh X.Y.Z``