From f0cc20a06557dc295dab93ad856eaaf2ddb2f6b5 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sun, 14 Jun 2020 12:41:01 +0900 Subject: [PATCH 01/34] Run lint tools on GH Actions --- .github/workflows/docslint.yml | 16 ++++++++++++++++ .github/workflows/flake8.yml | 18 ++++++++++++++++++ .github/workflows/mypy.yml | 16 ++++++++++++++++ .travis.yml | 6 ------ 4 files changed, 50 insertions(+), 6 deletions(-) create mode 100644 .github/workflows/docslint.yml create mode 100644 .github/workflows/flake8.yml create mode 100644 .github/workflows/mypy.yml diff --git a/.github/workflows/docslint.yml b/.github/workflows/docslint.yml new file mode 100644 index 000000000..f0b3c38ea --- /dev/null +++ b/.github/workflows/docslint.yml @@ -0,0 +1,16 @@ +name: Lint the format of 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 + - name: Install dependencies + run: pip install -U tox + - name: Run Tox + run: tox -e docslint diff --git a/.github/workflows/flake8.yml b/.github/workflows/flake8.yml new file mode 100644 index 000000000..e6feb71b6 --- /dev/null +++ b/.github/workflows/flake8.yml @@ -0,0 +1,18 @@ +name: Lint source code (flake8) + +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: pip install -U tox + - name: Run Tox + run: tox -e flake8 diff --git a/.github/workflows/mypy.yml b/.github/workflows/mypy.yml new file mode 100644 index 000000000..1d8bfc90a --- /dev/null +++ b/.github/workflows/mypy.yml @@ -0,0 +1,16 @@ +name: Lint source code (mypy) + +on: [push, pull_request] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v1 + - name: Install dependencies + run: pip install -U tox + - name: Run Tox + run: tox -e mypy diff --git a/.travis.yml b/.travis.yml index 3ceb2e3f2..340022cd8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -29,12 +29,6 @@ jobs: - TOXENV=du16 - 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' From b0490ce8129ba971bc4fceae836f8e725e6b6b57 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sun, 14 Jun 2020 15:33:22 +0900 Subject: [PATCH 02/34] github actions: Rename flake8.yml to lint.yml --- .github/workflows/{flake8.yml => lint.yml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/workflows/{flake8.yml => lint.yml} (100%) diff --git a/.github/workflows/flake8.yml b/.github/workflows/lint.yml similarity index 100% rename from .github/workflows/flake8.yml rename to .github/workflows/lint.yml From 0e271f28a490d4335b703fb173193443b080fd2e Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sun, 14 Jun 2020 15:36:53 +0900 Subject: [PATCH 03/34] github actions: Merge workflows for linting to lint.yml --- .github/workflows/docslint.yml | 16 ---------------- .github/workflows/lint.yml | 8 ++++++-- .github/workflows/mypy.yml | 16 ---------------- 3 files changed, 6 insertions(+), 34 deletions(-) delete mode 100644 .github/workflows/docslint.yml delete mode 100644 .github/workflows/mypy.yml diff --git a/.github/workflows/docslint.yml b/.github/workflows/docslint.yml deleted file mode 100644 index f0b3c38ea..000000000 --- a/.github/workflows/docslint.yml +++ /dev/null @@ -1,16 +0,0 @@ -name: Lint the format of 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 - - name: Install dependencies - run: pip install -U tox - - name: Run Tox - run: tox -e docslint diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index e6feb71b6..0c09c778b 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -1,10 +1,14 @@ -name: Lint source code (flake8) +name: Lint source code on: [push, pull_request] jobs: build: runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + tool: [docslint, flake8, mypy] steps: - uses: actions/checkout@v2 @@ -15,4 +19,4 @@ jobs: - name: Install dependencies run: pip install -U tox - name: Run Tox - run: tox -e flake8 + run: tox -e ${{ matrix.tool }} diff --git a/.github/workflows/mypy.yml b/.github/workflows/mypy.yml deleted file mode 100644 index 1d8bfc90a..000000000 --- a/.github/workflows/mypy.yml +++ /dev/null @@ -1,16 +0,0 @@ -name: Lint source code (mypy) - -on: [push, pull_request] - -jobs: - build: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2 - - name: Set up Python - uses: actions/setup-python@v1 - - name: Install dependencies - run: pip install -U tox - - name: Run Tox - run: tox -e mypy From 7167b689b998d8822a40085a9395e3e4fc375b9c Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sun, 14 Jun 2020 13:38:55 +0900 Subject: [PATCH 04/34] Do "twine check" on CI process --- .github/workflows/lint.yml | 2 +- tox.ini | 12 +++++++++++- utils/release-checklist | 4 ---- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 0c09c778b..fea1f17a2 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -8,7 +8,7 @@ jobs: strategy: fail-fast: false matrix: - tool: [docslint, flake8, mypy] + tool: [docslint, flake8, mypy, twine] steps: - uses: actions/checkout@v2 diff --git a/tox.ini b/tox.ini index d9f040544..ccfd60f84 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] minversion = 2.4.0 -envlist = docs,flake8,mypy,coverage,py{35,36,37,38,39},du{12,13,14,15} +envlist = docs,flake8,mypy,twine,coverage,py{35,36,37,38,39},du{12,13,14,15} [testenv] usedevelop = True @@ -88,6 +88,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`` From 53c1dff91c0b7100e1ce1b51acbf0fffbc10cf9c Mon Sep 17 00:00:00 2001 From: Ram Rachum Date: Sun, 14 Jun 2020 00:46:19 +0300 Subject: [PATCH 05/34] Fix exception causes all over the codebase --- sphinx/builders/gettext.py | 2 +- sphinx/builders/html/__init__.py | 4 +-- sphinx/builders/latex/theming.py | 8 +++--- sphinx/config.py | 14 +++++----- sphinx/domains/c.py | 13 ++++----- sphinx/domains/cpp.py | 27 ++++++++++--------- sphinx/domains/std.py | 4 +-- sphinx/environment/__init__.py | 7 ++--- sphinx/ext/autodoc/importer.py | 4 +-- sphinx/ext/autosummary/__init__.py | 2 +- sphinx/ext/graphviz.py | 8 +++--- sphinx/ext/imgconverter.py | 2 +- sphinx/ext/imgmath.py | 16 +++++------ sphinx/ext/intersphinx.py | 2 +- sphinx/pycode/__init__.py | 10 +++---- sphinx/registry.py | 21 ++++++++------- sphinx/search/ja.py | 4 +-- sphinx/setup_command.py | 2 +- sphinx/theming.py | 16 +++++------ sphinx/transforms/post_transforms/__init__.py | 4 +-- sphinx/util/__init__.py | 14 +++++----- sphinx/util/i18n.py | 2 +- sphinx/util/inspect.py | 8 +++--- sphinx/util/osutil.py | 4 +-- sphinx/util/pycompat.py | 7 ++--- sphinx/writers/texinfo.py | 4 +-- tests/test_build_latex.py | 6 ++--- tests/test_build_texinfo.py | 4 +-- 28 files changed, 114 insertions(+), 105 deletions(-) diff --git a/sphinx/builders/gettext.py b/sphinx/builders/gettext.py index 65f112510..8e30762e9 100644 --- a/sphinx/builders/gettext.py +++ b/sphinx/builders/gettext.py @@ -278,7 +278,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 5340c93f9..99d7aa13b 100644 --- a/sphinx/builders/html/__init__.py +++ b/sphinx/builders/html/__init__.py @@ -140,7 +140,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 = '' @@ -1015,7 +1015,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 92c203dfd..353268e38 100644 --- a/sphinx/config.py +++ b/sphinx/config.py @@ -196,9 +196,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): @@ -319,17 +319,17 @@ def eval_config_file(filename: str, tags: Tags) -> Dict[str, Any]: execfile_(filename, 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..b4cf32a99 100644 --- a/sphinx/domains/c.py +++ b/sphinx/domains/c.py @@ -2300,7 +2300,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 +2768,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 +2776,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 +2844,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 +2995,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 @@ -3132,7 +3133,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) diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py index 1783db491..c202786e8 100644 --- a/sphinx/domains/cpp.py +++ b/sphinx/domains/cpp.py @@ -4882,7 +4882,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 +5066,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 +5093,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 +5253,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 +5505,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 +5950,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 +5989,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 +6097,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 +6164,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 +6511,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 +6577,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 +6598,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: @@ -6790,7 +6791,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) diff --git a/sphinx/domains/std.py b/sphinx/domains/std.py index fbbed3a6b..016f84ebc 100644 --- a/sphinx/domains/std.py +++ b/sphinx/domains/std.py @@ -1061,10 +1061,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 6584ac6d8..1e58542bb 100644 --- a/sphinx/environment/__init__.py +++ b/sphinx/environment/__init__.py @@ -393,7 +393,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.""" @@ -511,8 +512,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/importer.py b/sphinx/ext/autodoc/importer.py index cdccf710d..e5e84eda3 100644 --- a/sphinx/ext/autodoc/importer.py +++ b/sphinx/ext/autodoc/importer.py @@ -33,7 +33,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 = '', @@ -98,7 +98,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/autosummary/__init__.py b/sphinx/ext/autosummary/__init__.py index 162b6868c..58e192bda 100644 --- a/sphinx/ext/autosummary/__init__.py +++ b/sphinx/ext/autosummary/__init__.py @@ -647,7 +647,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) ------------------------------------------- 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 2050e470e..d11c5d7c5 100644 --- a/sphinx/ext/imgmath.py +++ b/sphinx/ext/imgmath.py @@ -165,13 +165,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[bytes, bytes]: @@ -179,13 +179,13 @@ def convert_dvi_to_image(command: List[str], name: str) -> Tuple[bytes, bytes]: try: ret = subprocess.run(command, stdout=PIPE, stderr=PIPE, check=True) 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]: @@ -326,7 +326,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('%s' % @@ -352,7 +352,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('

') 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/pycode/__init__.py b/sphinx/pycode/__init__.py index 963680a54..29e53178f 100644 --- a/sphinx/pycode/__init__.py +++ b/sphinx/pycode/__init__.py @@ -37,7 +37,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): @@ -54,7 +54,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) @@ -92,7 +92,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 @@ -104,7 +104,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": @@ -169,7 +169,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 cad74559c..0aec0a9fd 100644 --- a/sphinx/registry.py +++ b/sphinx/registry.py @@ -139,9 +139,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) @@ -273,8 +273,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 @@ -311,9 +311,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, @@ -407,7 +409,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: @@ -423,7 +426,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/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 2c0076304..24beab856 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/theming.py b/sphinx/theming.py index 13a895393..087ee7f24 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 ca9bb028d..3bf049f98 100644 --- a/sphinx/util/__init__.py +++ b/sphinx/util/__init__.py @@ -285,21 +285,21 @@ def get_module_source(modname: str) -> Tuple[str, str]: 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 filename = getattr(mod, '__file__', None) loader = getattr(mod, '__loader__', None) if loader and getattr(loader, 'get_filename', None): try: filename = loader.get_filename(modname) except Exception as err: - raise PycodeError('error getting filename for %r' % filename, err) + raise PycodeError('error getting filename for %r' % filename, err) from err if filename is None and loader: try: filename = loader.get_source(modname) if filename: return 'string', filename except Exception as err: - raise PycodeError('error getting source for %r' % modname, err) + raise PycodeError('error getting source for %r' % modname, err) from err if filename is None: raise PycodeError('no source found for module %r' % modname) filename = path.normpath(path.abspath(filename)) @@ -456,8 +456,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 @@ -596,9 +596,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]: diff --git a/sphinx/util/i18n.py b/sphinx/util/i18n.py index 1cb75637c..499f2316f 100644 --- a/sphinx/util/i18n.py +++ b/sphinx/util/i18n.py @@ -312,7 +312,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 d4928c847..3077f9eb2 100644 --- a/sphinx/util/inspect.py +++ b/sphinx/util/inspect.py @@ -321,7 +321,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: @@ -336,7 +336,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 safe_getmembers(object: Any, predicate: Callable[[str], bool] = None, @@ -385,8 +385,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 23f5b0137..0390b038d 100644 --- a/sphinx/util/osutil.py +++ b/sphinx/util/osutil.py @@ -168,10 +168,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 664387cac..2173fce14 100644 --- a/sphinx/util/pycompat.py +++ b/sphinx/util/pycompat.py @@ -34,11 +34,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) @@ -49,7 +49,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 9c30244e9..b77bf4352 100644 --- a/sphinx/writers/texinfo.py +++ b/sphinx/writers/texinfo.py @@ -853,8 +853,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/tests/test_build_latex.py b/tests/test_build_latex.py index d3c491461..3bf6edd66 100644 --- a/tests/test_build_latex.py +++ b/tests/test_build_latex.py @@ -64,8 +64,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) @@ -1545,7 +1545,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) From 2049527f03024970855ea3b3c1b10a5de8c462c2 Mon Sep 17 00:00:00 2001 From: Emmanuel Arias Date: Mon, 15 Jun 2020 21:48:56 -0300 Subject: [PATCH 06/34] New year Update year Licence --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 9c0bf231ab68883fe4b2cdf26fcd3c0b5bec52f0 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Tue, 16 Jun 2020 23:18:17 +0900 Subject: [PATCH 07/34] Fix #7839: autosummary: cannot handle umlauts in function names --- CHANGES | 1 + sphinx/ext/autosummary/__init__.py | 3 ++- sphinx/ext/autosummary/generate.py | 8 ++++---- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/CHANGES b/CHANGES index 85a4f9def..b22e05b8f 100644 --- a/CHANGES +++ b/CHANGES @@ -16,6 +16,7 @@ Features added Bugs fixed ---------- +* #7839: autosummary: cannot handle umlauts in function names * #7715: LaTeX: ``numfig_secnum_depth > 1`` leads to wrong figure links Testing diff --git a/sphinx/ext/autosummary/__init__.py b/sphinx/ext/autosummary/__init__.py index 162b6868c..7eaefd4b1 100644 --- a/sphinx/ext/autosummary/__init__.py +++ b/sphinx/ext/autosummary/__init__.py @@ -755,7 +755,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 fe7daf214..7583d9894 100644 --- a/sphinx/ext/autosummary/generate.py +++ b/sphinx/ext/autosummary/generate.py @@ -347,7 +347,7 @@ def generate_autosummary_docs(sources: List[str], output_dir: str = None, info: Callable = None, 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 info: warnings.warn('info argument for generate_autosummary_docs() is deprecated.', RemovedInSphinx40Warning, stacklevel=2) @@ -415,17 +415,17 @@ def generate_autosummary_docs(sources: List[str], output_dir: str = None, 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) From 19974ed0171ad4cec4b20e314645429a27bc084c Mon Sep 17 00:00:00 2001 From: Ram Rachum Date: Wed, 17 Jun 2020 11:58:32 +0300 Subject: [PATCH 08/34] Use chain.from_iterable in text.py --- sphinx/writers/text.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sphinx/writers/text.py b/sphinx/writers/text.py index b2ccd7b89..485bc010c 100644 --- a/sphinx/writers/text.py +++ b/sphinx/writers/text.py @@ -223,7 +223,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: From 267eeabc008ce7ab1a0034cc9c7ce2eafc52514b Mon Sep 17 00:00:00 2001 From: Abramo Bagnara Date: Wed, 17 Jun 2020 19:03:05 +0200 Subject: [PATCH 09/34] Use well-formed XML syntax. --- sphinx/themes/basic/layout.html | 2 +- sphinx/themes/bizstyle/layout.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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 %} {%- endif %} - + {{- metatags }} {%- block htmltitle %} {{ title|striptags|e }}{{ titlesuffix }} 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 %} - + From 54147083438039eabffe0bd2fe31be3187b299ce Mon Sep 17 00:00:00 2001 From: Jakob Lykke Andersen Date: Sat, 20 Jun 2020 09:55:35 +0200 Subject: [PATCH 10/34] C,C++: support parameterized GNU style attributes Fixes sphinx-doc/sphinx#7853 --- CHANGES | 2 ++ sphinx/domains/c.py | 5 +++-- sphinx/domains/cpp.py | 5 +++-- sphinx/util/cfamily.py | 21 ++++++++++++--------- tests/test_domain_c.py | 2 ++ tests/test_domain_cpp.py | 2 ++ 6 files changed, 24 insertions(+), 13 deletions(-) diff --git a/CHANGES b/CHANGES index b22e05b8f..e274b8b09 100644 --- a/CHANGES +++ b/CHANGES @@ -13,6 +13,8 @@ Deprecated Features added -------------- +* #7853: C and C++, support parameterized GNU style attributes. + Bugs fixed ---------- diff --git a/sphinx/domains/c.py b/sphinx/domains/c.py index 36a8f1f65..932c53fdd 100644 --- a/sphinx/domains/c.py +++ b/sphinx/domains/c.py @@ -28,7 +28,8 @@ from sphinx.locale import _, __ from sphinx.roles import SphinxRole, XRefRole 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, @@ -1053,7 +1054,7 @@ class ASTDeclaratorParen(ASTDeclarator): # Initializer ################################################################################ -class ASTParenExprList(ASTBase): +class ASTParenExprList(ASTBaseParenExprList): def __init__(self, exprs: List[ASTExpression]) -> None: self.exprs = exprs diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py index 1783db491..0d76172a0 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 diff --git a/sphinx/util/cfamily.py b/sphinx/util/cfamily.py index edccf96a7..6c2e99c84 100644 --- a/sphinx/util/cfamily.py +++ b/sphinx/util/cfamily.py @@ -12,7 +12,7 @@ import re import warnings 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 @@ -148,16 +148,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) @@ -211,6 +209,11 @@ class ASTParenAttribute(ASTAttribute): ################################################################################ +class ASTBaseParenExprList(ASTBaseBase): + pass + + +################################################################################ class UnsupportedMultiCharacterCharLiteral(Exception): @property @@ -415,11 +418,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(')'): @@ -447,3 +447,6 @@ class BaseParser: return ASTParenAttribute(id, arg) return None + + def _parse_paren_expression_list(self) -> ASTBaseParenExprList: + raise NotImplementedError 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 961646131..a6c9eee9d 100644 --- a/tests/test_domain_cpp.py +++ b/tests/test_domain_cpp.py @@ -897,6 +897,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 From 275d93b5068a4b6af4c912d5bebb2df928416060 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sun, 21 Jun 2020 17:57:50 +0900 Subject: [PATCH 11/34] Update CHANGES for PR #7846 --- CHANGES | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES b/CHANGES index e274b8b09..b93bb05f0 100644 --- a/CHANGES +++ b/CHANGES @@ -20,6 +20,7 @@ Bugs fixed * #7839: autosummary: cannot handle umlauts in function names * #7715: LaTeX: ``numfig_secnum_depth > 1`` leads to wrong figure links +* #7846: html theme: XML-invalid files were generated Testing -------- From 42f29b6acf9661c34a44078b49de9e32a447def4 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sun, 21 Jun 2020 23:28:03 +0900 Subject: [PATCH 12/34] doc: Add modname separator tip for autodoc (refs: #7841) --- doc/usage/extensions/autodoc.rst | 6 ++++++ 1 file changed, 6 insertions(+) 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 From e860903cd82e93fc40abff0fc109388e08f90f15 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Mon, 22 Jun 2020 01:33:41 +0900 Subject: [PATCH 13/34] Fix #7844: autodoc: Failed to detect module when relative module name given --- CHANGES | 2 ++ sphinx/ext/autodoc/__init__.py | 50 ++++++++++++++++++++++------------ sphinx/util/__init__.py | 5 +++- tests/test_ext_autodoc.py | 48 ++++++++++++++++++++++++++++---- 4 files changed, 82 insertions(+), 23 deletions(-) diff --git a/CHANGES b/CHANGES index 1d7b119b4..c1b97b58d 100644 --- a/CHANGES +++ b/CHANGES @@ -16,6 +16,8 @@ Features added Bugs fixed ---------- +* #7844: autodoc: Failed to detect module when relative module name given + Testing -------- diff --git a/sphinx/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py index ab75aaf5a..23245ad9c 100644 --- a/sphinx/ext/autodoc/__init__.py +++ b/sphinx/ext/autodoc/__init__.py @@ -977,19 +977,29 @@ class ModuleLevelDocumenter(Documenter): if path: stripped = path.rstrip('.') modname, qualname = split_full_qualified_name(stripped) + if modname is None: + # if documenting a toplevel object without explicit module, + # it can be contained in another auto directive ... + modname = self.env.temp_data.get('autodoc:module') + # ... or in the scope of a module directive + if not modname: + modname = self.env.ref_context.get('py:module') + + if modname: + stripped = ".".join([modname, stripped]) + modname, qualname = split_full_qualified_name(stripped) + if qualname: parents = qualname.split(".") else: parents = [] - - if modname is None: - # if documenting a toplevel object without explicit module, - # it can be contained in another auto directive ... + else: modname = self.env.temp_data.get('autodoc:module') - # ... or in the scope of a module directive + parents = [] + if not modname: modname = self.env.ref_context.get('py:module') - # ... else, it stays None, which means invalid + return modname, parents + [base] @@ -1016,18 +1026,24 @@ class ClassLevelDocumenter(Documenter): 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(".") - - # if the module name is still missing, get it like above - if not modname: + modname, qualname = split_full_qualified_name(mod_cls) + if modname is None: + # if documenting a toplevel object without explicit module, + # it can be contained in another auto directive ... modname = self.env.temp_data.get('autodoc:module') - if not modname: - modname = self.env.ref_context.get('py:module') - # ... else, it stays None, which means invalid + # ... or in the scope of a module directive + if not modname: + modname = self.env.ref_context.get('py:module') + + if modname: + stripped = ".".join([modname, mod_cls]) + modname, qualname = split_full_qualified_name(stripped) + + if qualname: + parents = qualname.split(".") + else: + parents = [] + return modname, parents + [base] diff --git a/sphinx/util/__init__.py b/sphinx/util/__init__.py index b28c62fc3..aa77c344c 100644 --- a/sphinx/util/__init__.py +++ b/sphinx/util/__init__.py @@ -634,7 +634,10 @@ def split_full_qualified_name(name: str) -> Tuple[str, str]: if not inspect.ismodule(value): return ".".join(parts[:i]), ".".join(parts[i:]) 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/tests/test_ext_autodoc.py b/tests/test_ext_autodoc.py index e4ec4a815..8c34c4e9f 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' + directive.env.ref_context['py:module'] = 'sphinx.testing' verify('method', 'util.SphinxTestApp.cleanup', - ('foo', ['util', 'SphinxTestApp', 'cleanup'], None, None)) - directive.env.ref_context['py:module'] = 'util' + ('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): @@ -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' + actual = do_autodoc(app, 'class', 'classes.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' + actual = do_autodoc(app, 'method', 'methods.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() From 0be08012cd8d9ba16d15ffa4d3540f56d3ac9dfc Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sun, 21 Jun 2020 16:11:55 +0900 Subject: [PATCH 14/34] Fix #7856: autodoc: AttributeError is raised for non-class object --- CHANGES | 2 ++ sphinx/ext/autodoc/__init__.py | 7 ++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index c1b97b58d..f85970ffe 100644 --- a/CHANGES +++ b/CHANGES @@ -17,6 +17,8 @@ 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 Testing -------- diff --git a/sphinx/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py index 23245ad9c..bb89920be 100644 --- a/sphinx/ext/autodoc/__init__.py +++ b/sphinx/ext/autodoc/__init__.py @@ -826,7 +826,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) From 4e739b85eeb36b942efed0dff94d4962803ac9ca Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sun, 21 Jun 2020 17:53:04 +0900 Subject: [PATCH 15/34] Fix #7850: autodoc: KeyError is raised for invalid mark up --- CHANGES | 2 ++ sphinx/ext/autodoc/typehints.py | 15 ++++++++++----- tests/test_ext_autodoc_configs.py | 8 ++++++++ 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/CHANGES b/CHANGES index f85970ffe..0dcdae502 100644 --- a/CHANGES +++ b/CHANGES @@ -19,6 +19,8 @@ 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' Testing -------- 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/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): From 358611970dcc0a22f0be0ee75153806e736ff427 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Fri, 26 Jun 2020 23:31:00 +0900 Subject: [PATCH 16/34] Revert "viewcode: Fix viewcode raises NoUri error on resolving phase except on HTML builders" This reverts commit c2ef1ad7e507c86442eae76e1ad7182383e13c8d. The error was completely resolved by #7683. So this is no longer needed. --- sphinx/ext/viewcode.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) 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) From d50c7ff31944ad63e94b039b5465fdbe26bb04f9 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Fri, 26 Jun 2020 23:33:04 +0900 Subject: [PATCH 17/34] Fix #7806: viewcode: Failed to resolve viewcode references on 3rd party builders --- CHANGES | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES b/CHANGES index 0dcdae502..53a566d0f 100644 --- a/CHANGES +++ b/CHANGES @@ -21,6 +21,7 @@ Bugs fixed the autoclass directive * #7850: autodoc: KeyError is raised for invalid mark up when autodoc_typehints is 'description' +* #7806: viewcode: Failed to resolve viewcode references on 3rd party builders Testing -------- From 7efa9aebb1183dbc9c50a010c0449692c50b3b60 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sat, 27 Jun 2020 03:04:28 +0900 Subject: [PATCH 18/34] Fix #7869: abbr role wrongly copies an explanation from the previous one --- CHANGES | 2 ++ sphinx/roles.py | 5 +++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index b93bb05f0..64ca7b8f3 100644 --- a/CHANGES +++ b/CHANGES @@ -21,6 +21,8 @@ Bugs fixed * #7839: autosummary: cannot handle umlauts in function names * #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 Testing -------- diff --git a/sphinx/roles.py b/sphinx/roles.py index 57d11c269..2ff66d07b 100644 --- a/sphinx/roles.py +++ b/sphinx/roles.py @@ -530,14 +530,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)], [] def index_role(typ: str, rawtext: str, text: str, lineno: int, inliner: Inliner, From 12696cdfa2796e85ea0603966b8839e24650b0e3 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sat, 27 Jun 2020 22:19:05 +0900 Subject: [PATCH 19/34] doc: Fix URL --- CONTRIBUTING.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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. From 4fa596ba4c4cfdb857021e1c723579a51f8dd044 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sat, 27 Jun 2020 23:20:44 +0900 Subject: [PATCH 20/34] Fix #7866: autosummary: Failed to extract correct summary line A hyperlink target in the docstring causes a system_error because node_ids are cached expectedly during extracting a summary. --- CHANGES | 2 ++ sphinx/ext/autosummary/__init__.py | 15 +++++++++------ tests/test_ext_autosummary.py | 6 ++++++ 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/CHANGES b/CHANGES index b93bb05f0..10f5f5f26 100644 --- a/CHANGES +++ b/CHANGES @@ -19,6 +19,8 @@ Bugs fixed ---------- * #7839: autosummary: cannot handle umlauts in function names +* #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 diff --git a/sphinx/ext/autosummary/__init__.py b/sphinx/ext/autosummary/__init__.py index 7eaefd4b1..08f5bd3c4 100644 --- a/sphinx/ext/autosummary/__init__.py +++ b/sphinx/ext/autosummary/__init__.py @@ -497,6 +497,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(): @@ -514,11 +521,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() @@ -532,7 +535,7 @@ def extract_summary(doc: List[str], document: Any) -> str: while sentences: summary += sentences.pop(0) + '.' node[:] = [] - state_machine.run([summary], node) + node = parse(doc, document.settings) if not node.traverse(nodes.system_message): # considered as that splitting by period does not break inline markups break diff --git a/tests/test_ext_autosummary.py b/tests/test_ext_autosummary.py index a65826141..f716feaae 100644 --- a/tests/test_ext_autosummary.py +++ b/tests/test_ext_autosummary.py @@ -108,6 +108,12 @@ def test_extract_summary(capsys): '========='] assert extract_summary(doc, document) == 'blah blah' + # hyperlink target + doc = ['Do `this `_ and that. ' + 'blah blah blah.'] + assert (extract_summary(doc, document) == + 'Do `this `_ and that.') + _, err = capsys.readouterr() assert err == '' From 2d69b4211a702b27417bb28d0c2e1486b91b35f5 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sat, 27 Jun 2020 23:33:43 +0900 Subject: [PATCH 21/34] Fix #7865: autosummary: Failed to extract summary line when abbr. found --- CHANGES | 2 ++ sphinx/ext/autosummary/__init__.py | 10 +++++++--- tests/test_ext_autosummary.py | 2 +- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/CHANGES b/CHANGES index b93bb05f0..f9af48bb8 100644 --- a/CHANGES +++ b/CHANGES @@ -19,6 +19,8 @@ Bugs fixed ---------- * #7839: autosummary: cannot handle umlauts in function names +* #7865: autosummary: Failed to extract summary line when abbreviations found + * #7715: LaTeX: ``numfig_secnum_depth > 1`` leads to wrong figure links * #7846: html theme: XML-invalid files were generated diff --git a/sphinx/ext/autosummary/__init__.py b/sphinx/ext/autosummary/__init__.py index 7eaefd4b1..570419b06 100644 --- a/sphinx/ext/autosummary/__init__.py +++ b/sphinx/ext/autosummary/__init__.py @@ -99,6 +99,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 ------------------------------------------------------ @@ -529,11 +531,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): + 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 diff --git a/tests/test_ext_autosummary.py b/tests/test_ext_autosummary.py index a65826141..73fab7cb9 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::'] From 25e8171a0a8636eec93b6b60d87822e1bd3ffb74 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sun, 28 Jun 2020 15:22:23 +0900 Subject: [PATCH 22/34] Fix #7812: autosummary: generates broken stub files (again) --- CHANGES | 2 ++ sphinx/ext/autosummary/generate.py | 13 +++++++++---- sphinx/util/__init__.py | 15 +-------------- 3 files changed, 12 insertions(+), 18 deletions(-) diff --git a/CHANGES b/CHANGES index 0dcdae502..86f31a875 100644 --- a/CHANGES +++ b/CHANGES @@ -21,6 +21,8 @@ Bugs fixed the autoclass directive * #7850: autodoc: KeyError is raised for invalid mark up when autodoc_typehints is 'description' +* #7812: autosummary: generates broken stub files if the target code contains + an attribute and module that are same name Testing -------- diff --git a/sphinx/ext/autosummary/generate.py b/sphinx/ext/autosummary/generate.py index fe7daf214..40fdb5181 100644 --- a/sphinx/ext/autosummary/generate.py +++ b/sphinx/ext/autosummary/generate.py @@ -230,7 +230,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: @@ -319,7 +320,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] @@ -401,7 +404,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: _warn(__('[autosummary] failed to import %r: %s') % (entry.name, e)) continue @@ -411,7 +415,8 @@ 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): diff --git a/sphinx/util/__init__.py b/sphinx/util/__init__.py index aa77c344c..1ca82ad21 100644 --- a/sphinx/util/__init__.py +++ b/sphinx/util/__init__.py @@ -615,24 +615,11 @@ 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: if parts[:i - 1]: return ".".join(parts[:i - 1]), ".".join(parts[i - 1:]) From 67cb8e81b2ba5fecc576db3aff52c172d44fab84 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Mon, 29 Jun 2020 02:08:22 +0900 Subject: [PATCH 23/34] CI: Build document on GitHub Actions --- .github/workflows/builddoc.yml | 21 +++++++++++++++++++++ .travis.yml | 2 -- 2 files changed, 21 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/builddoc.yml 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/.travis.yml b/.travis.yml index 340022cd8..d4cbfd582 100644 --- a/.travis.yml +++ b/.travis.yml @@ -27,8 +27,6 @@ jobs: - python: 'nightly' env: - TOXENV=du16 - - python: '3.6' - env: TOXENV=docs - language: node_js node_js: '10.7' From 1a865069dbc5f753684347d7fd33a2045806c4bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Gia=20Phong?= Date: Mon, 29 Jun 2020 16:15:49 +0700 Subject: [PATCH 24/34] napoleon: Add aliases Warn and Raise --- CHANGES | 1 + doc/usage/extensions/napoleon.rst | 2 ++ sphinx/ext/napoleon/docstring.py | 2 ++ 3 files changed, 5 insertions(+) diff --git a/CHANGES b/CHANGES index b0dc46b9a..e6888146c 100644 --- a/CHANGES +++ b/CHANGES @@ -14,6 +14,7 @@ Features added -------------- * #7853: C and C++, support parameterized GNU style attributes. +* #7888: napoleon: Add aliases Warn and Raise. Bugs fixed ---------- 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/sphinx/ext/napoleon/docstring.py b/sphinx/ext/napoleon/docstring.py index 769d1ed22..5857fcf92 100644 --- a/sphinx/ext/napoleon/docstring.py +++ b/sphinx/ext/napoleon/docstring.py @@ -162,6 +162,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, @@ -169,6 +170,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, From 9cf95615d0a05397f33633b01a1c20ae3588f187 Mon Sep 17 00:00:00 2001 From: Harrissou Sant-anna Date: Tue, 30 Jun 2020 03:06:11 +0200 Subject: [PATCH 25/34] Fix sentence --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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) From 7cbf54c2106177d7d09783aec868eb9d984c06f1 Mon Sep 17 00:00:00 2001 From: Jakob Lykke Andersen Date: Sun, 28 Jun 2020 11:28:27 +0200 Subject: [PATCH 26/34] C, add c:alias directive --- CHANGES | 2 + doc/usage/restructuredtext/domains.rst | 35 ++++++++ sphinx/domains/c.py | 108 ++++++++++++++++++++++++- 3 files changed, 144 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index e6888146c..8aa5184b5 100644 --- a/CHANGES +++ b/CHANGES @@ -15,6 +15,8 @@ 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 ---------- diff --git a/doc/usage/restructuredtext/domains.rst b/doc/usage/restructuredtext/domains.rst index 9559acf22..884ac367e 100644 --- a/doc/usage/restructuredtext/domains.rst +++ b/doc/usage/restructuredtext/domains.rst @@ -699,6 +699,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 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/sphinx/domains/c.py b/sphinx/domains/c.py index fb7917bb7..210757f8c 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,6 +26,8 @@ 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, ASTBaseParenExprList, @@ -41,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 = [ @@ -3311,6 +3314,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]: @@ -3394,6 +3497,8 @@ class CDomain(Domain): 'namespace': CNamespaceObject, 'namespace-push': CNamespacePushObject, 'namespace-pop': CNamespacePopObject, + # other + 'alias': CAliasObject } roles = { 'member': CXRefRole(), @@ -3541,6 +3646,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', From ff5eb7cc2b39653153d6b94c1fcec7845873114b Mon Sep 17 00:00:00 2001 From: Jakob Lykke Andersen Date: Tue, 30 Jun 2020 13:52:18 +0200 Subject: [PATCH 27/34] C, C++: remove noindex option The option did nothing. Improve error messages on duplicates. --- CHANGES | 2 ++ doc/usage/restructuredtext/domains.rst | 1 - sphinx/domains/c.py | 33 +++++++++++++------------- sphinx/domains/cpp.py | 21 ++++++++-------- 4 files changed, 28 insertions(+), 29 deletions(-) diff --git a/CHANGES b/CHANGES index e6888146c..204501ccd 100644 --- a/CHANGES +++ b/CHANGES @@ -27,6 +27,8 @@ Bugs fixed * #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 -------- diff --git a/doc/usage/restructuredtext/domains.rst b/doc/usage/restructuredtext/domains.rst index 9559acf22..01a103cb8 100644 --- a/doc/usage/restructuredtext/domains.rst +++ b/doc/usage/restructuredtext/domains.rst @@ -1038,7 +1038,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/domains/c.py b/sphinx/domains/c.py index fb7917bb7..16866bf91 100644 --- a/sphinx/domains/c.py +++ b/sphinx/domains/c.py @@ -1884,7 +1884,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) @@ -3019,6 +3019,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 @@ -3085,7 +3092,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)) @@ -3150,7 +3158,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) @@ -3418,14 +3429,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) @@ -3471,13 +3474,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, diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py index d5aab9886..310691d49 100644 --- a/sphinx/domains/cpp.py +++ b/sphinx/domains/cpp.py @@ -4455,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) @@ -6624,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' @@ -6808,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) @@ -7083,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() @@ -7275,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)) From 853060a64673453d53259d5537ea21ee2a0ee136 Mon Sep 17 00:00:00 2001 From: Chris Holdgraf Date: Thu, 2 Jul 2020 13:42:53 -0700 Subject: [PATCH 28/34] events --- doc/extdev/appapi.rst | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/doc/extdev/appapi.rst b/doc/extdev/appapi.rst index d5b1a8a98..cd7205cd0 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. call builder + + 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 From ef03bcc6ea47d83132b54c4b3894990c086af5b9 Mon Sep 17 00:00:00 2001 From: Chris Holdgraf Date: Thu, 2 Jul 2020 13:46:44 -0700 Subject: [PATCH 29/34] output files comment --- doc/extdev/appapi.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/extdev/appapi.rst b/doc/extdev/appapi.rst index cd7205cd0..036b57ec1 100644 --- a/doc/extdev/appapi.rst +++ b/doc/extdev/appapi.rst @@ -185,7 +185,7 @@ type for that event:: 13. event.doctree-resolved(app, doctree, docname) - (for any reference node that fails to resolve) event.missing-reference(env, node, contnode) - 14. call builder + 14. Generate output files 15. event.build-finished(app, exception) From 9dcd88709cf288816d3d29c2894188dddc7c503e Mon Sep 17 00:00:00 2001 From: Tim Metzler <33530562+tmetzl@users.noreply.github.com> Date: Fri, 3 Jul 2020 14:11:53 +0200 Subject: [PATCH 30/34] Clarify language specific figures format I recently used sphinx for multilanguage documentation and found the explanation of the language specific figure format misleading. I replaced the underscores by periods and added a small example. --- doc/usage/configuration.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/doc/usage/configuration.rst b/doc/usage/configuration.rst index cdcc2a561..de2fa7ea7 100644 --- a/doc/usage/configuration.rst +++ b/doc/usage/configuration.rst @@ -660,8 +660,9 @@ 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 + 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. From 3982d526b2a1c58658c1d24abf95dea27869ed90 Mon Sep 17 00:00:00 2001 From: Tim Metzler <33530562+tmetzl@users.noreply.github.com> Date: Fri, 3 Jul 2020 14:27:14 +0200 Subject: [PATCH 31/34] Shorten line to less than 85 characters --- doc/usage/configuration.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/usage/configuration.rst b/doc/usage/configuration.rst index de2fa7ea7..2ed2c3269 100644 --- a/doc/usage/configuration.rst +++ b/doc/usage/configuration.rst @@ -662,9 +662,9 @@ documentation on :ref:`intl` for details. sets obtained from :confval:`locale_dirs`. Sphinx will search 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. + 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 From 5ba5cb60e31ec8095b5cd5190ef270388d092739 Mon Sep 17 00:00:00 2001 From: Tim Metzler <33530562+tmetzl@users.noreply.github.com> Date: Fri, 3 Jul 2020 19:08:53 +0200 Subject: [PATCH 32/34] Use double backticks for filename example --- doc/usage/configuration.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/usage/configuration.rst b/doc/usage/configuration.rst index 2ed2c3269..ba377e1f6 100644 --- a/doc/usage/configuration.rst +++ b/doc/usage/configuration.rst @@ -661,7 +661,7 @@ documentation on :ref:`intl` for details. 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` - (e.g. the German version of `myfigure.png` will be `myfigure.de.png`) + (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. From df6333a25003f5fa9f8cdfc0a14c953a9ac0d663 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sat, 4 Jul 2020 20:46:01 +0900 Subject: [PATCH 33/34] CI: Do testing more verbose --- .circleci/config.yml | 2 +- .github/workflows/main.yml | 2 +- .travis.yml | 2 +- 3 files changed, 3 insertions(+), 3 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/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 d4cbfd582..d73be03ec 100644 --- a/.travis.yml +++ b/.travis.yml @@ -39,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: From 659846b8054dbb1517874fc2d08b74eaded14bf3 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sat, 27 Jun 2020 22:05:02 +0900 Subject: [PATCH 34/34] Fix #7812: autodoc: crashed when given name is conflicted Note: this partially reverts #7594 to avoid errors. --- CHANGES | 2 + sphinx/ext/autodoc/__init__.py | 52 +++++-------------- .../target/name_conflict/__init__.py | 5 ++ .../target/name_conflict/foo.py | 2 + tests/test_ext_autodoc.py | 43 +++++++++++---- tests/test_ext_autodoc_autofunction.py | 4 +- 6 files changed, 56 insertions(+), 52 deletions(-) create mode 100644 tests/roots/test-ext-autodoc/target/name_conflict/__init__.py create mode 100644 tests/roots/test-ext-autodoc/target/name_conflict/foo.py diff --git a/CHANGES b/CHANGES index 76dcd673d..985116851 100644 --- a/CHANGES +++ b/CHANGES @@ -21,6 +21,8 @@ Bugs fixed 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 diff --git a/sphinx/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py index bb89920be..85bea8c43 100644 --- a/sphinx/ext/autodoc/__init__.py +++ b/sphinx/ext/autodoc/__init__.py @@ -32,7 +32,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 @@ -980,31 +979,15 @@ class ModuleLevelDocumenter(Documenter): ) -> Tuple[str, List[str]]: if modname is None: if path: - stripped = path.rstrip('.') - modname, qualname = split_full_qualified_name(stripped) - if modname is None: - # if documenting a toplevel object without explicit module, - # it can be contained in another auto directive ... - modname = self.env.temp_data.get('autodoc:module') - # ... or in the scope of a module directive - if not modname: - modname = self.env.ref_context.get('py:module') - - if modname: - stripped = ".".join([modname, stripped]) - modname, qualname = split_full_qualified_name(stripped) - - if qualname: - parents = qualname.split(".") - else: - parents = [] + 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') - parents = [] - + # ... or in the scope of a module directive if not modname: modname = self.env.ref_context.get('py:module') - + # ... else, it stays None, which means invalid return modname, parents + [base] @@ -1030,25 +1013,14 @@ class ClassLevelDocumenter(Documenter): # ... if still None, there's no way to know if mod_cls is None: return None, [] - - modname, qualname = split_full_qualified_name(mod_cls) - if modname is None: - # if documenting a toplevel object without explicit module, - # it can be contained in another auto directive ... + 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') - # ... or in the scope of a module directive - if not modname: - modname = self.env.ref_context.get('py:module') - - if modname: - stripped = ".".join([modname, mod_cls]) - modname, qualname = split_full_qualified_name(stripped) - - if qualname: - parents = qualname.split(".") - else: - parents = [] - + if not modname: + modname = self.env.ref_context.get('py:module') + # ... else, it stays None, which means invalid return modname, parents + [base] 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_ext_autodoc.py b/tests/test_ext_autodoc.py index 8c34c4e9f..652c7f10b 100644 --- a/tests/test_ext_autodoc.py +++ b/tests/test_ext_autodoc.py @@ -121,8 +121,8 @@ def test_parse_name(app): verify('class', 'Base', ('test_ext_autodoc', ['Base'], None, None)) # for members - directive.env.ref_context['py:module'] = 'sphinx.testing' - verify('method', 'util.SphinxTestApp.cleanup', + 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' @@ -801,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', '', @@ -1884,8 +1884,8 @@ def test_overload(app): @pytest.mark.sphinx('html', testroot='ext-autodoc') def test_pymodule_for_ModuleLevelDocumenter(app): - app.env.ref_context['py:module'] = 'target' - actual = do_autodoc(app, 'class', 'classes.Foo') + app.env.ref_context['py:module'] = 'target.classes' + actual = do_autodoc(app, 'class', 'Foo') assert list(actual) == [ '', '.. py:class:: Foo()', @@ -1896,8 +1896,8 @@ def test_pymodule_for_ModuleLevelDocumenter(app): @pytest.mark.sphinx('html', testroot='ext-autodoc') def test_pymodule_for_ClassLevelDocumenter(app): - app.env.ref_context['py:module'] = 'target' - actual = do_autodoc(app, 'method', 'methods.Base.meth') + app.env.ref_context['py:module'] = 'target.methods' + actual = do_autodoc(app, 'method', 'Base.meth') assert list(actual) == [ '', '.. py:method:: Base.meth()', @@ -1937,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.', '',