Merge pull request #3520 from agjohnson/fix-domain-py-nesting

Fix Python domain nesting
This commit is contained in:
Takeshi KOMIYA 2017-03-18 17:33:56 +09:00 committed by GitHub
commit a67c7855aa
2 changed files with 58 additions and 20 deletions

View File

@ -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,55 @@ 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
:py:class:`PyObject` represents Python language constructs. For
constructs that are nestable, such as a Python classes, this method will
build up a stack of the nesting heirarchy so that it can be later
de-nested correctly, in :py:meth:`after_content`.
For constructs that aren't nestable, the stack is bypassed, and instead
only the most recent object is tracked. This object prefix name will be
removed with :py:meth:`after_content`.
"""
if self.names:
# fullname and name_prefix come from the `handle_signature` method.
# fullname represents the full object name that is constructed using
# object nesting and explicit prefixes. `name_prefix` is the
# explicit prefix given in a signature
(fullname, name_prefix) = self.names[-1]
if self.allow_nesting:
prefix = fullname
elif name_prefix:
prefix = name_prefix.strip('.')
else:
prefix = None
if prefix:
self.env.ref_context['py:class'] = prefix
if self.allow_nesting:
classes = self.env.ref_context.setdefault('py:classes', [])
classes.append(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`.
"""
classes = self.env.ref_context.setdefault('py:classes', [])
if self.allow_nesting:
try:
classes.pop()
except IndexError:
pass
self.env.ref_context['py:class'] = (classes[-1] if len(classes) > 0
else None)
class PyModulelevel(PyObject):
@ -319,6 +367,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 +382,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 +454,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):
"""

View File

@ -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')