diff --git a/CHANGES b/CHANGES index 3dcbe2b1f..d426fa48c 100644 --- a/CHANGES +++ b/CHANGES @@ -70,6 +70,7 @@ Features added * C++, parse trailing return types. * #7143: py domain: Add ``:final:`` option to :rst:dir:`py:class:`, :rst:dir:`py:exception:` and :rst:dir:`py:method:` directives +* #7596: py domain: Change a type annotation for variables to a hyperlink * #7582: napoleon: a type for attribute are represented like type annotation Bugs fixed diff --git a/sphinx/domains/python.py b/sphinx/domains/python.py index 39c7de142..fc1136ae2 100644 --- a/sphinx/domains/python.py +++ b/sphinx/domains/python.py @@ -77,17 +77,19 @@ ModuleEntry = NamedTuple('ModuleEntry', [('docname', str), ('deprecated', bool)]) +def type_to_xref(text: str) -> addnodes.pending_xref: + """Convert a type string to a cross reference node.""" + if text == 'None': + reftype = 'obj' + else: + reftype = 'class' + + return pending_xref('', nodes.Text(text), + refdomain='py', reftype=reftype, reftarget=text) + + def _parse_annotation(annotation: str) -> List[Node]: """Parse type annotation.""" - def make_xref(text: str) -> addnodes.pending_xref: - if text == 'None': - reftype = 'obj' - else: - reftype = 'class' - - return pending_xref('', nodes.Text(text), - refdomain='py', reftype=reftype, reftarget=text) - def unparse(node: ast.AST) -> List[Node]: if isinstance(node, ast.Attribute): return [nodes.Text("%s.%s" % (unparse(node.value)[0], node.attr))] @@ -133,10 +135,10 @@ def _parse_annotation(annotation: str) -> List[Node]: result = unparse(tree) for i, node in enumerate(result): if isinstance(node, nodes.Text): - result[i] = make_xref(str(node)) + result[i] = type_to_xref(str(node)) return result except SyntaxError: - return [make_xref(annotation)] + return [type_to_xref(annotation)] def _parse_arglist(arglist: str) -> addnodes.desc_parameterlist: @@ -621,7 +623,7 @@ class PyVariable(PyObject): typ = self.options.get('type') if typ: - signode += addnodes.desc_annotation(typ, ': ' + typ) + signode += addnodes.desc_annotation(typ, '', nodes.Text(': '), type_to_xref(typ)) value = self.options.get('value') if value: @@ -866,7 +868,7 @@ class PyAttribute(PyObject): typ = self.options.get('type') if typ: - signode += addnodes.desc_annotation(typ, ': ' + typ) + signode += addnodes.desc_annotation(typ, '', nodes.Text(': '), type_to_xref(typ)) value = self.options.get('value') if value: diff --git a/tests/test_domain_py.py b/tests/test_domain_py.py index 5a1d73cfe..08b3da21e 100644 --- a/tests/test_domain_py.py +++ b/tests/test_domain_py.py @@ -420,7 +420,8 @@ def test_pydata_signature(app): doctree = restructuredtext.parse(app, text) assert_node(doctree, (addnodes.index, [desc, ([desc_signature, ([desc_name, "version"], - [desc_annotation, ": int"], + [desc_annotation, (": ", + [pending_xref, "int"])], [desc_annotation, " = 1"])], desc_content)])) assert_node(doctree[1], addnodes.desc, desctype="data", @@ -690,7 +691,8 @@ def test_pyattribute(app): assert_node(doctree[1][1][0], addnodes.index, entries=[('single', 'attr (Class attribute)', 'Class.attr', '', None)]) assert_node(doctree[1][1][1], ([desc_signature, ([desc_name, "attr"], - [desc_annotation, ": str"], + [desc_annotation, (": ", + [pending_xref, "str"])], [desc_annotation, " = ''"])], [desc_content, ()])) assert 'Class.attr' in domain.objects