mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
Fix Python domain nesting
Moved #3465 here, to address this in `stable` instead. This fixes a problem with the Python domain object nesting. Because only one object name was stored in `ref_context`, and reset to `None` in `after_content`, nesting broke if you put anything after a nested class: ```rst .. py:class:: Parent .. py:method:: foo() This wouldn't resolve: :py:meth:`bar` .. py:class:: Child In the `after_content` method, the object is reset to `None`, so anything after this in the same nesting is considered to be top level instead. .. py:method:: bar() This is top level, as the domain thinks the surrounding object is `None` ``` This depends on #3519 and can be rebased after that is merged into stable Fixes #3065 Refs #3067
This commit is contained in:
parent
e060f65bc5
commit
b0875d63fc
@ -138,6 +138,9 @@ class PyTypedField(PyXrefMixin, TypedField):
|
||||
class PyObject(ObjectDescription):
|
||||
"""
|
||||
Description of a general Python object.
|
||||
|
||||
:cvar allow_nesting: Class is an object that allows for nested namespaces
|
||||
:vartype allow_nesting: bool
|
||||
"""
|
||||
option_spec = {
|
||||
'noindex': directives.flag,
|
||||
@ -164,6 +167,8 @@ class PyObject(ObjectDescription):
|
||||
names=('rtype',), bodyrolename='obj'),
|
||||
]
|
||||
|
||||
allow_nesting = False
|
||||
|
||||
def get_signature_prefix(self, sig):
|
||||
"""May return a prefix to put before the object name in the
|
||||
signature.
|
||||
@ -285,12 +290,54 @@ class PyObject(ObjectDescription):
|
||||
fullname, '', None))
|
||||
|
||||
def before_content(self):
|
||||
# needed for automatic qualification of members (reset in subclasses)
|
||||
self.clsname_set = False
|
||||
# type: () -> None
|
||||
"""Handle object nesting before content
|
||||
|
||||
If this class is a nestable object, such as a class object, build up a
|
||||
representation of the nesting heirarchy so that de-nesting multiple
|
||||
levels works correctly.
|
||||
|
||||
If this class isn't a nestable object, just set the current class name
|
||||
using the object prefix, if any. This class name will be removed with
|
||||
:py:meth:`after_content`, and is not added to the list of nested
|
||||
classes.
|
||||
"""
|
||||
prefix = None
|
||||
if self.names:
|
||||
(cls_name, cls_name_prefix) = self.names.pop()
|
||||
prefix = cls_name_prefix.strip('.') if cls_name_prefix else None
|
||||
if self.allow_nesting:
|
||||
prefix = cls_name
|
||||
if prefix:
|
||||
self.env.ref_context['py:class'] = prefix
|
||||
if self.allow_nesting:
|
||||
try:
|
||||
self.env.ref_context['py:classes'].append(prefix)
|
||||
except (AttributeError, KeyError):
|
||||
self.env.ref_context['py:classes'] = [prefix]
|
||||
|
||||
def after_content(self):
|
||||
if self.clsname_set:
|
||||
self.env.ref_context.pop('py:class', None)
|
||||
# type: () -> None
|
||||
"""Handle object de-nesting after content
|
||||
|
||||
If this class is a nestable object, removing the last nested class prefix
|
||||
ends further nesting in the object.
|
||||
|
||||
If this class is not a nestable object, the list of classes should not
|
||||
be altered as we didn't affect the nesting levels in
|
||||
:py:meth:`before_content`.
|
||||
"""
|
||||
if self.allow_nesting:
|
||||
try:
|
||||
self.env.ref_context['py:classes'].pop()
|
||||
except (KeyError, IndexError):
|
||||
self.env.ref_context['py:classes'] = []
|
||||
try:
|
||||
cls_name = self.env.ref_context.get('py:classes', [])[-1]
|
||||
except IndexError:
|
||||
cls_name = None
|
||||
finally:
|
||||
self.env.ref_context['py:class'] = cls_name
|
||||
|
||||
|
||||
class PyModulelevel(PyObject):
|
||||
@ -319,6 +366,8 @@ class PyClasslike(PyObject):
|
||||
Description of a class-like object (classes, interfaces, exceptions).
|
||||
"""
|
||||
|
||||
allow_nesting = True
|
||||
|
||||
def get_signature_prefix(self, sig):
|
||||
return self.objtype + ' '
|
||||
|
||||
@ -332,12 +381,6 @@ class PyClasslike(PyObject):
|
||||
else:
|
||||
return ''
|
||||
|
||||
def before_content(self):
|
||||
PyObject.before_content(self)
|
||||
if self.names:
|
||||
self.env.ref_context['py:class'] = self.names[0][0]
|
||||
self.clsname_set = True
|
||||
|
||||
|
||||
class PyClassmember(PyObject):
|
||||
"""
|
||||
@ -410,13 +453,6 @@ class PyClassmember(PyObject):
|
||||
else:
|
||||
return ''
|
||||
|
||||
def before_content(self):
|
||||
PyObject.before_content(self)
|
||||
lastname = self.names and self.names[-1][1]
|
||||
if lastname and not self.env.ref_context.get('py:class'):
|
||||
self.env.ref_context['py:class'] = lastname.strip('.')
|
||||
self.clsname_set = True
|
||||
|
||||
|
||||
class PyDecoratorMixin(object):
|
||||
"""
|
||||
|
@ -82,8 +82,8 @@ def test_domain_py_xrefs(app, status, warning):
|
||||
u'subchild_2', u'meth')
|
||||
assert_refnode(refnodes[8], None, u'NestedParentA.NestedChildA',
|
||||
u'NestedParentA.child_1', u'meth')
|
||||
assert_refnode(refnodes[9], None, None, u'NestedChildA.subchild_1',
|
||||
u'meth')
|
||||
assert_refnode(refnodes[9], None, u'NestedParentA',
|
||||
u'NestedChildA.subchild_1', u'meth')
|
||||
assert_refnode(refnodes[10], None, u'NestedParentB', u'child_1', u'meth')
|
||||
assert_refnode(refnodes[11], None, u'NestedParentB', u'NestedParentB',
|
||||
u'class')
|
||||
@ -125,6 +125,7 @@ def test_domain_py_objects(app, status, warning):
|
||||
assert objects['module_a.submodule.ModTopLevel'] == ('module', 'class')
|
||||
assert objects['module_a.submodule.ModTopLevel.mod_child_1'] == ('module', 'method')
|
||||
assert objects['module_a.submodule.ModTopLevel.mod_child_2'] == ('module', 'method')
|
||||
assert 'ModTopLevel.ModNoModule' not in objects
|
||||
assert objects['ModNoModule'] == ('module', 'class')
|
||||
assert objects['module_b.submodule.ModTopLevel'] == ('module', 'class')
|
||||
|
||||
@ -136,7 +137,7 @@ def test_domain_py_objects(app, status, warning):
|
||||
assert objects['NestedParentA.NestedChildA'] == ('roles', 'class')
|
||||
assert objects['NestedParentA.NestedChildA.subchild_1'] == ('roles', 'method')
|
||||
assert objects['NestedParentA.NestedChildA.subchild_2'] == ('roles', 'method')
|
||||
assert objects['child_2'] == ('roles', 'method')
|
||||
assert objects['NestedParentA.child_2'] == ('roles', 'method')
|
||||
assert objects['NestedParentB'] == ('roles', 'class')
|
||||
assert objects['NestedParentB.child_1'] == ('roles', 'method')
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user