Merge branch '3.x'

This commit is contained in:
Takeshi KOMIYA 2020-11-05 02:21:47 +09:00
commit 8bf84167a3
18 changed files with 229 additions and 76 deletions

View File

@ -1,9 +1,47 @@
name: CI on Windows name: CI
on: [push, pull_request] on: [push, pull_request]
jobs: jobs:
build: ubuntu:
runs-on: ubuntu-16.04
strategy:
fail-fast: false
matrix:
name: [py36, py37, py38]
include:
- name: py36
python: 3.6
docutils: du13
- name: py37
python: 3.7
docutils: du14
- name: py38
python: 3.8
docutils: du15
coverage: "--cov ./ --cov-append --cov-config setup.cfg"
env:
PYTEST_ADDOPTS: ${{ matrix.coverage }}
steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python }}
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python }}
- name: Check Python version
run: python --version
- name: Install graphviz
run: sudo apt-get install graphviz
- name: Install dependencies
run: pip install -U tox codecov
- name: Run Tox
run: tox -e ${{ matrix.docutils }} -- -vv
- name: codecov
uses: codecov/codecov-action@v1
if: matrix.coverage
windows:
runs-on: windows-latest runs-on: windows-latest
strategy: strategy:
matrix: matrix:

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

@ -0,0 +1,21 @@
name: CI (node.js)
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
env:
node-version: 10.7
steps:
- uses: actions/checkout@v2
- name: Use Node.js ${{ env.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ env.node-version }}
- run: npm install
- name: Run headless test
uses: GabrielBB/xvfb-action@v1
with:
run: npm test

View File

@ -1,43 +0,0 @@
os: linux
dist: xenial
language: python
cache: pip
env:
global:
- PYTHONFAULTHANDLER=x
- SKIP_LATEX_BUILD=1
- IS_PYTHON=true
jobs:
include:
- python: '3.6'
env:
- TOXENV=du14
- python: '3.7'
env:
- TOXENV=du15
- python: '3.8'
env:
- TOXENV=du16
- PYTEST_ADDOPTS="--cov ./ --cov-append --cov-config setup.cfg"
- python: '3.9'
env:
- TOXENV=py39
- language: node_js
node_js: '10.7'
env: IS_PYTHON=false
services: xvfb
install:
- "sudo apt-get install graphviz"
- if [ $IS_PYTHON = true ]; then pip install -U tox codecov; fi
- if [ $IS_PYTHON = false ]; then npm install; fi
script:
- if [ $IS_PYTHON = true ]; then tox -- -vv; fi
- if [ $IS_PYTHON = false ]; then npm test; fi
after_success:
- if [[ -e .coverage ]]; then codecov -e $TOXENV; fi

View File

@ -64,9 +64,15 @@ Deprecated
Features added Features added
-------------- --------------
* #6914: Add a new event :event:`warn-missing-reference` to custom warning
messages when failed to resolve a cross-reference
* #6914: Emit a detailed warning when failed to resolve a ``:ref:`` reference
Bugs fixed Bugs fixed
---------- ----------
* #7613: autodoc: autodoc does not respect __signature__ of the class
Testing Testing
-------- --------

View File

@ -22,9 +22,9 @@ Extensions
To learn how to write your own extension, see :ref:`dev-extensions`. To learn how to write your own extension, see :ref:`dev-extensions`.
The `sphinx-contrib <https://bitbucket.org/birkenfeld/sphinx-contrib/>`_ The `sphinx-contrib <https://github.com/sphinx-contrib>`_ repository contains many
repository contains many contributed extensions. Some of them have their own contributed extensions. Some of them have their own releases on PyPI, others you
releases on PyPI, others you can install from a checkout. can install from a checkout.
This is the current list of contributed extensions in that repository: This is the current list of contributed extensions in that repository:

View File

@ -186,6 +186,7 @@ type for that event::
13. apply post-transforms (by priority): docutils.document -> docutils.document 13. apply post-transforms (by priority): docutils.document -> docutils.document
14. event.doctree-resolved(app, doctree, docname) 14. event.doctree-resolved(app, doctree, docname)
- (for any reference node that fails to resolve) event.missing-reference(env, node, contnode) - (for any reference node that fails to resolve) event.missing-reference(env, node, contnode)
- (for any reference node that fails to resolve) event.warn-missing-reference(domain, node)
15. Generate output files 15. Generate output files
16. event.build-finished(app, exception) 16. event.build-finished(app, exception)
@ -284,6 +285,14 @@ Here is a more detailed list of these events.
.. versionadded:: 0.5 .. versionadded:: 0.5
.. event:: warn-missing-reference (app, domain, node)
Emitted when a cross-reference to an object cannot be resolved even after
:event:`missing-reference`. If the event handler can emit warnings for
the missing reference, it should return ``True``.
.. versionadded:: 3.4
.. event:: doctree-resolved (app, doctree, docname) .. event:: doctree-resolved (app, doctree, docname)
Emitted when a doctree has been "resolved" by the environment, that is, all Emitted when a doctree has been "resolved" by the environment, that is, all

View File

@ -595,8 +595,6 @@ class StandardDomain(Domain):
dangling_warnings = { dangling_warnings = {
'term': 'term not in glossary: %(target)s', 'term': 'term not in glossary: %(target)s',
'ref': 'undefined label: %(target)s (if the link has no caption '
'the label must precede a section header)',
'numref': 'undefined label: %(target)s', 'numref': 'undefined label: %(target)s',
'keyword': 'unknown keyword: %(target)s', 'keyword': 'unknown keyword: %(target)s',
'doc': 'unknown document: %(target)s', 'doc': 'unknown document: %(target)s',
@ -1075,8 +1073,23 @@ class StandardDomain(Domain):
return None return None
def warn_missing_reference(app: "Sphinx", domain: Domain, node: pending_xref) -> bool:
if domain.name != 'std' or node['reftype'] != 'ref':
return None
else:
target = node['reftarget']
if target not in domain.anonlabels: # type: ignore
msg = __('undefined label: %s')
else:
msg = __('Failed to create a cross reference. A title or caption not found: %s')
logger.warning(msg % target, location=node, type='ref', subtype=node['reftype'])
return True
def setup(app: "Sphinx") -> Dict[str, Any]: def setup(app: "Sphinx") -> Dict[str, Any]:
app.add_domain(StandardDomain) app.add_domain(StandardDomain)
app.connect('warn-missing-reference', warn_missing_reference)
return { return {
'version': 'builtin', 'version': 'builtin',

View File

@ -45,6 +45,7 @@ core_events = {
'doctree-read': 'the doctree before being pickled', 'doctree-read': 'the doctree before being pickled',
'env-merge-info': 'env, read docnames, other env instance', 'env-merge-info': 'env, read docnames, other env instance',
'missing-reference': 'env, node, contnode', 'missing-reference': 'env, node, contnode',
'warn-missing-reference': 'domain, node',
'doctree-resolved': 'doctree, docname', 'doctree-resolved': 'doctree, docname',
'env-updated': 'env', 'env-updated': 'env',
'html-collect-pages': 'builder', 'html-collect-pages': 'builder',

View File

@ -1376,7 +1376,12 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type:
# This sequence is copied from inspect._signature_from_callable. # This sequence is copied from inspect._signature_from_callable.
# ValueError means that no signature could be found, so we keep going. # ValueError means that no signature could be found, so we keep going.
# First, let's see if it has an overloaded __call__ defined # First, we check the obj has a __signature__ attribute
if (hasattr(self.object, '__signature__') and
isinstance(self.object.__signature__, Signature)):
return None, None, self.object.__signature__
# Next, let's see if it has an overloaded __call__ defined
# in its metaclass # in its metaclass
call = get_user_defined_function_or_method(type(self.object), '__call__') call = get_user_defined_function_or_method(type(self.object), '__call__')

View File

@ -166,7 +166,10 @@ class ReferencesResolver(SphinxPostTransform):
warn = False warn = False
if not warn: if not warn:
return return
if domain and typ in domain.dangling_warnings:
if self.app.emit_firstresult('warn-missing-reference', domain, node):
return
elif domain and typ in domain.dangling_warnings:
msg = domain.dangling_warnings[typ] msg = domain.dangling_warnings[typ]
elif node.get('refdomain', 'std') not in ('', 'std'): elif node.get('refdomain', 'std') not in ('', 'std'):
msg = (__('%s:%s reference target not found: %%(target)s') % msg = (__('%s:%s reference target not found: %%(target)s') %

View File

@ -0,0 +1,7 @@
test-domain-py-xref-warning
===========================
.. _existing-label:
:ref:`no-label`
:ref:`existing-label`

View File

@ -1,3 +1,6 @@
from inspect import Parameter, Signature
class Foo: class Foo:
pass pass
@ -10,3 +13,11 @@ class Bar:
class Baz: class Baz:
def __new__(cls, x, y): def __new__(cls, x, y):
pass pass
class Qux:
__signature__ = Signature(parameters=[Parameter('foo', Parameter.POSITIONAL_OR_KEYWORD),
Parameter('bar', Parameter.POSITIONAL_OR_KEYWORD)])
def __init__(self, x, y):
pass

View File

@ -876,3 +876,11 @@ def test_noindexentry(app):
assert_node(doctree, (addnodes.index, desc, addnodes.index, desc)) assert_node(doctree, (addnodes.index, desc, addnodes.index, desc))
assert_node(doctree[0], addnodes.index, entries=[('single', 'f (built-in class)', 'f', '', None)]) assert_node(doctree[0], addnodes.index, entries=[('single', 'f (built-in class)', 'f', '', None)])
assert_node(doctree[2], addnodes.index, entries=[]) assert_node(doctree[2], addnodes.index, entries=[])
@pytest.mark.sphinx('dummy', testroot='domain-py-xref-warning')
def test_warn_missing_reference(app, status, warning):
app.build()
assert 'index.rst:6: WARNING: undefined label: no-label' in warning.getvalue()
assert ('index.rst:6: WARNING: Failed to create a cross reference. A title or caption not found: existing-label'
in warning.getvalue())

View File

@ -1832,19 +1832,26 @@ def test_autodoc_for_egged_code(app):
def test_singledispatch(app): def test_singledispatch(app):
options = {"members": None} options = {"members": None}
actual = do_autodoc(app, 'module', 'target.singledispatch', options) actual = do_autodoc(app, 'module', 'target.singledispatch', options)
assert list(actual) == [ if sys.version_info < (3, 6):
'', # check the result via "in" because the order of singledispatch signatures is
'.. py:module:: target.singledispatch', # usually changed (because dict is not OrderedDict yet!)
'', assert '.. py:function:: func(arg, kwarg=None)' in actual
'', assert ' func(arg: int, kwarg=None)' in actual
'.. py:function:: func(arg, kwarg=None)', assert ' func(arg: str, kwarg=None)' in actual
' func(arg: int, kwarg=None)', else:
' func(arg: str, kwarg=None)', assert list(actual) == [
' :module: target.singledispatch', '',
'', '.. py:module:: target.singledispatch',
' A function for general use.', '',
'', '',
] '.. py:function:: func(arg, kwarg=None)',
' func(arg: int, kwarg=None)',
' func(arg: str, kwarg=None)',
' :module: target.singledispatch',
'',
' A function for general use.',
'',
]
@pytest.mark.skipif(sys.version_info < (3, 8), @pytest.mark.skipif(sys.version_info < (3, 8),

View File

@ -0,0 +1,50 @@
"""
test_ext_autodoc_autoclass
~~~~~~~~~~~~~~~~~~~~~~~~~~
Test the autodoc extension. This tests mainly the Documenters; the auto
directives are tested in a test source file translated by test_build.
:copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
import pytest
from test_ext_autodoc import do_autodoc
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_classes(app):
actual = do_autodoc(app, 'function', 'target.classes.Foo')
assert list(actual) == [
'',
'.. py:function:: Foo()',
' :module: target.classes',
'',
]
actual = do_autodoc(app, 'function', 'target.classes.Bar')
assert list(actual) == [
'',
'.. py:function:: Bar(x, y)',
' :module: target.classes',
'',
]
actual = do_autodoc(app, 'function', 'target.classes.Baz')
assert list(actual) == [
'',
'.. py:function:: Baz(x, y)',
' :module: target.classes',
'',
]
actual = do_autodoc(app, 'function', 'target.classes.Qux')
assert list(actual) == [
'',
'.. py:function:: Qux(foo, bar)',
' :module: target.classes',
'',
]

View File

@ -9,6 +9,8 @@
:license: BSD, see LICENSE for details. :license: BSD, see LICENSE for details.
""" """
import sys
import pytest import pytest
from test_ext_autodoc import do_autodoc from test_ext_autodoc import do_autodoc
@ -40,6 +42,14 @@ def test_classes(app):
'', '',
] ]
actual = do_autodoc(app, 'function', 'target.classes.Qux')
assert list(actual) == [
'',
'.. py:function:: Qux(foo, bar)',
' :module: target.classes',
'',
]
@pytest.mark.sphinx('html', testroot='ext-autodoc') @pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_callable(app): def test_callable(app):
@ -108,16 +118,23 @@ def test_decorated(app):
def test_singledispatch(app): def test_singledispatch(app):
options = {} options = {}
actual = do_autodoc(app, 'function', 'target.singledispatch.func', options) actual = do_autodoc(app, 'function', 'target.singledispatch.func', options)
assert list(actual) == [ if sys.version_info < (3, 6):
'', # check the result via "in" because the order of singledispatch signatures is
'.. py:function:: func(arg, kwarg=None)', # usually changed (because dict is not OrderedDict yet!)
' func(arg: int, kwarg=None)', assert '.. py:function:: func(arg, kwarg=None)' in actual
' func(arg: str, kwarg=None)', assert ' func(arg: int, kwarg=None)' in actual
' :module: target.singledispatch', assert ' func(arg: str, kwarg=None)' in actual
'', else:
' A function for general use.', assert list(actual) == [
'', '',
] '.. py:function:: func(arg, kwarg=None)',
' func(arg: int, kwarg=None)',
' func(arg: str, kwarg=None)',
' :module: target.singledispatch',
'',
' A function for general use.',
'',
]
@pytest.mark.sphinx('html', testroot='ext-autodoc') @pytest.mark.sphinx('html', testroot='ext-autodoc')

View File

@ -24,7 +24,7 @@ extras =
test test
setenv = setenv =
PYTHONWARNINGS = all,ignore::ImportWarning:importlib._bootstrap_external,ignore::DeprecationWarning:site,ignore::DeprecationWarning:distutils PYTHONWARNINGS = all,ignore::ImportWarning:importlib._bootstrap_external,ignore::DeprecationWarning:site,ignore::DeprecationWarning:distutils
PYTEST_ADDOPTS = --color yes PYTEST_ADDOPTS = {env:PYTEST_ADDOPTS:} --color yes
commands= commands=
pytest --durations 25 {posargs} pytest --durations 25 {posargs}