summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.circleci/config.yml2
-rw-r--r--.github/workflows/builddoc.yml21
-rw-r--r--.github/workflows/lint.yml22
-rw-r--r--.github/workflows/main.yml2
-rw-r--r--.travis.yml10
-rw-r--r--CHANGES25
-rw-r--r--CONTRIBUTING.rst4
-rw-r--r--LICENSE2
-rw-r--r--Makefile2
-rw-r--r--doc/extdev/appapi.rst34
-rw-r--r--doc/usage/configuration.rst9
-rw-r--r--doc/usage/extensions/autodoc.rst6
-rw-r--r--doc/usage/extensions/napoleon.rst2
-rw-r--r--doc/usage/restructuredtext/domains.rst36
-rw-r--r--sphinx/builders/gettext.py2
-rw-r--r--sphinx/builders/html/__init__.py4
-rw-r--r--sphinx/builders/latex/theming.py8
-rw-r--r--sphinx/config.py14
-rw-r--r--sphinx/domains/c.py159
-rw-r--r--sphinx/domains/cpp.py53
-rw-r--r--sphinx/domains/std.py4
-rw-r--r--sphinx/environment/__init__.py7
-rw-r--r--sphinx/ext/autodoc/__init__.py27
-rw-r--r--sphinx/ext/autodoc/importer.py4
-rw-r--r--sphinx/ext/autodoc/typehints.py15
-rw-r--r--sphinx/ext/autosummary/__init__.py30
-rw-r--r--sphinx/ext/autosummary/generate.py21
-rw-r--r--sphinx/ext/graphviz.py8
-rw-r--r--sphinx/ext/imgconverter.py2
-rw-r--r--sphinx/ext/imgmath.py16
-rw-r--r--sphinx/ext/intersphinx.py2
-rw-r--r--sphinx/ext/napoleon/docstring.py2
-rw-r--r--sphinx/ext/viewcode.py6
-rw-r--r--sphinx/pycode/__init__.py10
-rw-r--r--sphinx/registry.py21
-rw-r--r--sphinx/roles.py5
-rw-r--r--sphinx/search/ja.py4
-rw-r--r--sphinx/setup_command.py2
-rw-r--r--sphinx/themes/basic/layout.html2
-rw-r--r--sphinx/themes/bizstyle/layout.html2
-rw-r--r--sphinx/theming.py16
-rw-r--r--sphinx/transforms/post_transforms/__init__.py4
-rw-r--r--sphinx/util/__init__.py28
-rw-r--r--sphinx/util/cfamily.py21
-rw-r--r--sphinx/util/i18n.py2
-rw-r--r--sphinx/util/inspect.py8
-rw-r--r--sphinx/util/osutil.py4
-rw-r--r--sphinx/util/pycompat.py7
-rw-r--r--sphinx/writers/texinfo.py4
-rw-r--r--sphinx/writers/text.py2
-rw-r--r--tests/roots/test-ext-autodoc/target/name_conflict/__init__.py5
-rw-r--r--tests/roots/test-ext-autodoc/target/name_conflict/foo.py2
-rw-r--r--tests/test_build_latex.py6
-rw-r--r--tests/test_build_texinfo.py4
-rw-r--r--tests/test_domain_c.py2
-rw-r--r--tests/test_domain_cpp.py2
-rw-r--r--tests/test_ext_autodoc.py81
-rw-r--r--tests/test_ext_autodoc_autofunction.py4
-rw-r--r--tests/test_ext_autodoc_configs.py8
-rw-r--r--tests/test_ext_autosummary.py8
-rw-r--r--tox.ini12
-rw-r--r--utils/release-checklist4
62 files changed, 595 insertions, 246 deletions
diff --git a/.circleci/config.yml b/.circleci/config.yml
index 7f4de4ae0..6b5c7379b 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -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
diff --git a/.github/workflows/builddoc.yml b/.github/workflows/builddoc.yml
new file mode 100644
index 000000000..809fb68e6
--- /dev/null
+++ b/.github/workflows/builddoc.yml
@@ -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
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
new file mode 100644
index 000000000..fea1f17a2
--- /dev/null
+++ b/.github/workflows/lint.yml
@@ -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 }}
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index fa2c1154a..7acfef6d2 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -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
diff --git a/.travis.yml b/.travis.yml
index 61f400f63..f9c4cb714 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -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:
diff --git a/CHANGES b/CHANGES
index 41f7bcf83..57215deb4 100644
--- a/CHANGES
+++ b/CHANGES
@@ -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
--------
diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst
index 73ea1e52e..a20192679 100644
--- a/CONTRIBUTING.rst
+++ b/CONTRIBUTING.rst
@@ -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.
diff --git a/LICENSE b/LICENSE
index e294e8208..f709c9ad7 100644
--- a/LICENSE
+++ b/LICENSE
@@ -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
diff --git a/Makefile b/Makefile
index 84b2000c2..d37d8546e 100644
--- a/Makefile
+++ b/Makefile
@@ -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)
diff --git a/doc/extdev/appapi.rst b/doc/extdev/appapi.rst
index d5b1a8a98..036b57ec1 100644
--- a/doc/extdev/appapi.rst
+++ b/doc/extdev/appapi.rst
@@ -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
diff --git a/doc/usage/configuration.rst b/doc/usage/configuration.rst
index 51ae2bd1a..76b50ce2e 100644
--- a/doc/usage/configuration.rst
+++ b/doc/usage/configuration.rst
@@ -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
diff --git a/doc/usage/extensions/autodoc.rst b/doc/usage/extensions/autodoc.rst
index ec1d6c9b5..150b62c4d 100644
--- a/doc/usage/extensions/autodoc.rst
+++ b/doc/usage/extensions/autodoc.rst
@@ -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
diff --git a/doc/usage/extensions/napoleon.rst b/doc/usage/extensions/napoleon.rst
index b12dd03ba..76c423dc0 100644
--- a/doc/usage/extensions/napoleon.rst
+++ b/doc/usage/extensions/napoleon.rst
@@ -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``
diff --git a/doc/usage/restructuredtext/domains.rst b/doc/usage/restructuredtext/domains.rst
index 59e273c93..2d4247fee 100644
--- a/doc/usage/restructuredtext/domains.rst
+++ b/doc/usage/restructuredtext/domains.rst
@@ -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.
diff --git a/sphinx/builders/gettext.py b/sphinx/builders/gettext.py
index cfc615847..eef5f072f 100644
--- a/sphinx/builders/gettext.py
+++ b/sphinx/builders/gettext.py
@@ -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()
diff --git a/sphinx/builders/html/__init__.py b/sphinx/builders/html/__init__.py
index 440ee1f1e..626f173f5 100644
--- a/sphinx/builders/html/__init__.py
+++ b/sphinx/builders/html/__init__.py
@@ -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)
diff --git a/sphinx/builders/latex/theming.py b/sphinx/builders/latex/theming.py
index da6a7fa04..130bded4a 100644
--- a/sphinx/builders/latex/theming.py
+++ b/sphinx/builders/latex/theming.py
@@ -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:
diff --git a/sphinx/config.py b/sphinx/config.py
index 8206653ab..a6d070e3f 100644
--- a/sphinx/config.py
+++ b/sphinx/config.py
@@ -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
diff --git a/sphinx/domains/c.py b/sphinx/domains/c.py
index 36a8f1f65..8a0b8a105 100644
--- a/sphinx/domains/c.py
+++ b/sphinx/domains/c.py
@@ -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',
diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py
index 1783db491..310691d49 100644
--- a/sphinx/domains/cpp.py
+++ b/sphinx/domains/cpp.py
@@ -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))
diff --git a/sphinx/domains/std.py b/sphinx/domains/std.py
index 637dbd305..865feb67b 100644
--- a/sphinx/domains/std.py
+++ b/sphinx/domains/std.py
@@ -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':
diff --git a/sphinx/environment/__init__.py b/sphinx/environment/__init__.py
index 9451d1451..6de352498 100644
--- a/sphinx/environment/__init__.py
+++ b/sphinx/environment/__init__.py
@@ -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 ------------------------------
diff --git a/sphinx/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py
index a8c571f3e..28cfe06b5 100644
--- a/sphinx/ext/autodoc/__init__.py
+++ b/sphinx/ext/autodoc/__init__.py
@@ -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')
diff --git a/sphinx/ext/autodoc/importer.py b/sphinx/ext/autodoc/importer.py
index cb06edbac..637c69814 100644
--- a/sphinx/ext/autodoc/importer.py
+++ b/sphinx/ext/autodoc/importer.py
@@ -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]]:
diff --git a/sphinx/ext/autodoc/typehints.py b/sphinx/ext/autodoc/typehints.py
index b763bdfc7..4f81a6eae 100644
--- a/sphinx/ext/autodoc/typehints.py
+++ b/sphinx/ext/autodoc/typehints.py
@@ -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)]
diff --git a/sphinx/ext/autosummary/__init__.py b/sphinx/ext/autosummary/__init__.py
index bc8319a20..38a5e7e85 100644
--- a/sphinx/ext/autosummary/__init__.py
+++ b/sphinx/ext/autosummary/__init__.py
@@ -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]:
diff --git a/sphinx/ext/autosummary/generate.py b/sphinx/ext/autosummary/generate.py
index cccbd1487..91ef067a9 100644
--- a/sphinx/ext/autosummary/generate.py
+++ b/sphinx/ext/autosummary/generate.py
@@ -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)
diff --git a/sphinx/ext/graphviz.py b/sphinx/ext/graphviz.py
index c21868a6f..4a8dd0a4d 100644
--- a/sphinx/ext/graphviz.py
+++ b/sphinx/ext/graphviz.py
@@ -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
diff --git a/sphinx/ext/imgconverter.py b/sphinx/ext/imgconverter.py
index bf4b9b9d1..dd13a9879 100644
--- a/sphinx/ext/imgconverter.py
+++ b/sphinx/ext/imgconverter.py
@@ -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]:
diff --git a/sphinx/ext/imgmath.py b/sphinx/ext/imgmath.py
index 65f281223..a8da39709 100644
--- a/sphinx/ext/imgmath.py
+++ b/sphinx/ext/imgmath.py
@@ -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']:
diff --git a/sphinx/ext/intersphinx.py b/sphinx/ext/intersphinx.py
index 02f605bac..a6c4ef694 100644
--- a/sphinx/ext/intersphinx.py
+++ b/sphinx/ext/intersphinx.py
@@ -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))
diff --git a/sphinx/ext/napoleon/docstring.py b/sphinx/ext/napoleon/docstring.py
index 0683a06ed..39f2335f4 100644
--- a/sphinx/ext/napoleon/docstring.py
+++ b/sphinx/ext/napoleon/docstring.py
@@ -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,
diff --git a/sphinx/ext/viewcode.py b/sphinx/ext/viewcode.py
index a2eeb7891..dc24a1993 100644
--- a/sphinx/ext/viewcode.py
+++ b/sphinx/ext/viewcode.py
@@ -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)
diff --git a/sphinx/pycode/__init__.py b/sphinx/pycode/__init__.py
index 87ac5d9ec..295963ca3 100644
--- a/sphinx/pycode/__init__.py
+++ b/sphinx/pycode/__init__.py
@@ -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."""
diff --git a/sphinx/registry.py b/sphinx/registry.py
index c36671103..3dde26b67 100644
--- a/sphinx/registry.py
+++ b/sphinx/registry.py
@@ -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 = {}
diff --git a/sphinx/roles.py b/sphinx/roles.py
index 7bb7ac5e2..426a62e90 100644
--- a/sphinx/roles.py
+++ b/sphinx/roles.py
@@ -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 = {
diff --git a/sphinx/search/ja.py b/sphinx/search/ja.py
index d1f444be1..c1e72b8f8 100644
--- a/sphinx/search/ja.py
+++ b/sphinx/search/ja.py
@@ -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)
diff --git a/sphinx/setup_command.py b/sphinx/setup_command.py
index 36e15cd19..2b0e4b74b 100644
--- a/sphinx/setup_command.py
+++ b/sphinx/setup_command.py
@@ -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
diff --git a/sphinx/themes/basic/layout.html b/sphinx/themes/basic/layout.html
index 50feffd71..9163a18a2 100644
--- a/sphinx/themes/basic/layout.html
+++ b/sphinx/themes/basic/layout.html
@@ -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>
diff --git a/sphinx/themes/bizstyle/layout.html b/sphinx/themes/bizstyle/layout.html
index c946f8435..603eb2326 100644
--- a/sphinx/themes/bizstyle/layout.html
+++ b/sphinx/themes/bizstyle/layout.html
@@ -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]-->
diff --git a/sphinx/theming.py b/sphinx/theming.py
index 2636329a0..c05d87407 100644
--- a/sphinx/theming.py
+++ b/sphinx/theming.py
@@ -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
diff --git a/sphinx/transforms/post_transforms/__init__.py b/sphinx/transforms/post_transforms/__init__.py
index 4499e3376..7dc14af52 100644
--- a/sphinx/transforms/post_transforms/__init__.py
+++ b/sphinx/transforms/post_transforms/__init__.py
@@ -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
diff --git a/sphinx/util/__init__.py b/sphinx/util/__init__.py
index fd6410a36..e02e8adce 100644
--- a/sphinx/util/__init__.py
+++ b/sphinx/util/__init__.py
@@ -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
diff --git a/sphinx/util/cfamily.py b/sphinx/util/cfamily.py
index 8e07ae92a..f8ce4c827 100644
--- a/sphinx/util/cfamily.py
+++ b/sphinx/util/cfamily.py
@@ -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
diff --git a/sphinx/util/i18n.py b/sphinx/util/i18n.py
index 398accf1a..7ead23683 100644
--- a/sphinx/util/i18n.py
+++ b/sphinx/util/i18n.py
@@ -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:
diff --git a/sphinx/util/inspect.py b/sphinx/util/inspect.py
index 036bbc364..8e85ea392 100644
--- a/sphinx/util/inspect.py
+++ b/sphinx/util/inspect.py
@@ -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)
diff --git a/sphinx/util/osutil.py b/sphinx/util/osutil.py
index e3c4ebedb..bd774cdad 100644
--- a/sphinx/util/osutil.py
+++ b/sphinx/util/osutil.py
@@ -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
diff --git a/sphinx/util/pycompat.py b/sphinx/util/pycompat.py
index 7d22a792a..5aeb0f9cc 100644
--- a/sphinx/util/pycompat.py
+++ b/sphinx/util/pycompat.py
@@ -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)
diff --git a/sphinx/writers/texinfo.py b/sphinx/writers/texinfo.py
index bd30a73ee..317c6231b 100644
--- a/sphinx/writers/texinfo.py
+++ b/sphinx/writers/texinfo.py
@@ -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
diff --git a/sphinx/writers/text.py b/sphinx/writers/text.py
index 7ac5f52c6..858c68806 100644
--- a/sphinx/writers/text.py
+++ b/sphinx/writers/text.py
@@ -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:
diff --git a/tests/roots/test-ext-autodoc/target/name_conflict/__init__.py b/tests/roots/test-ext-autodoc/target/name_conflict/__init__.py
new file mode 100644
index 000000000..7ad521fc2
--- /dev/null
+++ b/tests/roots/test-ext-autodoc/target/name_conflict/__init__.py
@@ -0,0 +1,5 @@
+from .foo import bar
+
+class foo:
+ """docstring of target.name_conflict::foo."""
+ pass
diff --git a/tests/roots/test-ext-autodoc/target/name_conflict/foo.py b/tests/roots/test-ext-autodoc/target/name_conflict/foo.py
new file mode 100644
index 000000000..bb83ca0d4
--- /dev/null
+++ b/tests/roots/test-ext-autodoc/target/name_conflict/foo.py
@@ -0,0 +1,2 @@
+class bar:
+ """docstring of target.name_conflict.foo::bar."""
diff --git a/tests/test_build_latex.py b/tests/test_build_latex.py
index eaf77189b..818a493a5 100644
--- a/tests/test_build_latex.py
+++ b/tests/test_build_latex.py
@@ -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):
diff --git a/tests/test_build_texinfo.py b/tests/test_build_texinfo.py
index 378eaa192..9833218d7 100644
--- a/tests/test_build_texinfo.py
+++ b/tests/test_build_texinfo.py
@@ -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)
diff --git a/tests/test_domain_c.py b/tests/test_domain_c.py
index 10a20b9d1..347f8c7d6 100644
--- a/tests/test_domain_c.py
+++ b/tests/test_domain_c.py
@@ -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
diff --git a/tests/test_domain_cpp.py b/tests/test_domain_cpp.py
index 48227dddc..bf355ae1f 100644
--- a/tests/test_domain_cpp.py
+++ b/tests/test_domain_cpp.py
@@ -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
diff --git a/tests/test_ext_autodoc.py b/tests/test_ext_autodoc.py
index e4ec4a815..652c7f10b 100644
--- a/tests/test_ext_autodoc.py
+++ b/tests/test_ext_autodoc.py
@@ -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.',
+ '',
+ ]
diff --git a/tests/test_ext_autodoc_autofunction.py b/tests/test_ext_autodoc_autofunction.py
index 579ad9f48..da090d83a 100644
--- a/tests/test_ext_autodoc_autofunction.py
+++ b/tests/test_ext_autodoc_autofunction.py
@@ -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.',
'',
diff --git a/tests/test_ext_autodoc_configs.py b/tests/test_ext_autodoc_configs.py
index 674620df0..88519b5fd 100644
--- a/tests/test_ext_autodoc_configs.py
+++ b/tests/test_ext_autodoc_configs.py
@@ -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):
diff --git a/tests/test_ext_autosummary.py b/tests/test_ext_autosummary.py
index a65826141..3dd90b8ce 100644
--- a/tests/test_ext_autosummary.py
+++ b/tests/test_ext_autosummary.py
@@ -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 == ''
diff --git a/tox.ini b/tox.ini
index f59959d20..06f7a691f 100644
--- a/tox.ini
+++ b/tox.ini
@@ -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.
diff --git a/utils/release-checklist b/utils/release-checklist
index 5a60e59c8..582d26685 100644
--- a/utils/release-checklist
+++ b/utils/release-checklist
@@ -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``