From d02a466cb50f059d938ea892f26bf4af1bd68d47 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Fri, 23 Apr 2021 01:19:18 +0900 Subject: [PATCH] Fix #9121: py domain: duplicated warning for canonical and alias A duplicated warning is emitted when both canonical and its alias objects are defined on the same document. But it should not be emitted because they're the same object, not conflicted. --- CHANGES | 2 ++ sphinx/domains/python.py | 26 +++++++++++++++++--------- tests/test_domain_py.py | 33 +++++++++++++++++++++++++++++++++ 3 files changed, 52 insertions(+), 9 deletions(-) diff --git a/CHANGES b/CHANGES index 5edc80e38..71236232c 100644 --- a/CHANGES +++ b/CHANGES @@ -23,6 +23,8 @@ Features added * #9098: html: copy-range protection for doctests doesn't work in Safari * #9103: LaTeX: imgconverter: conversion runs even if not needed * #8127: py domain: Ellipsis in info-field-list causes nit-picky warning +* #9121: py domain: duplicated warning is emitted when both canonical and its + alias objects are defined on the document * #9023: More CSS classes on domain descriptions, see :ref:`nodes` for details. Bugs fixed diff --git a/sphinx/domains/python.py b/sphinx/domains/python.py index dbb315e6e..fb8a4d84f 100644 --- a/sphinx/domains/python.py +++ b/sphinx/domains/python.py @@ -68,7 +68,7 @@ class ObjectEntry(NamedTuple): docname: str node_id: str objtype: str - canonical: bool + aliased: bool class ModuleEntry(NamedTuple): @@ -505,7 +505,7 @@ class PyObject(ObjectDescription[Tuple[str, str]]): canonical_name = self.options.get('canonical') if canonical_name: - domain.note_object(canonical_name, self.objtype, node_id, canonical=True, + domain.note_object(canonical_name, self.objtype, node_id, aliased=True, location=signode) if 'noindexentry' not in self.options: @@ -1138,17 +1138,25 @@ class PythonDomain(Domain): return self.data.setdefault('objects', {}) # fullname -> ObjectEntry def note_object(self, name: str, objtype: str, node_id: str, - canonical: bool = False, location: Any = None) -> None: + aliased: bool = False, location: Any = None) -> None: """Note a python object for cross reference. .. versionadded:: 2.1 """ if name in self.objects: other = self.objects[name] - logger.warning(__('duplicate object description of %s, ' - 'other instance in %s, use :noindex: for one of them'), - name, other.docname, location=location) - self.objects[name] = ObjectEntry(self.env.docname, node_id, objtype, canonical) + if other.aliased and aliased is False: + # The original definition found. Override it! + pass + elif other.aliased is False and aliased: + # The original definition is already registered. + return + else: + # duplicated + logger.warning(__('duplicate object description of %s, ' + 'other instance in %s, use :noindex: for one of them'), + name, other.docname, location=location) + self.objects[name] = ObjectEntry(self.env.docname, node_id, objtype, aliased) @property def modules(self) -> Dict[str, ModuleEntry]: @@ -1326,8 +1334,8 @@ class PythonDomain(Domain): yield (modname, modname, 'module', mod.docname, mod.node_id, 0) for refname, obj in self.objects.items(): if obj.objtype != 'module': # modules are already handled - if obj.canonical: - # canonical names are not full-text searchable. + if obj.aliased: + # aliased names are not full-text searchable. yield (refname, refname, obj.objtype, obj.docname, obj.node_id, -1) else: yield (refname, refname, obj.objtype, obj.docname, obj.node_id, 1) diff --git a/tests/test_domain_py.py b/tests/test_domain_py.py index 214cb58a0..e5616a6eb 100644 --- a/tests/test_domain_py.py +++ b/tests/test_domain_py.py @@ -870,6 +870,39 @@ def test_canonical(app): assert domain.objects['_io.StringIO'] == ('index', 'io.StringIO', 'class', True) +def test_canonical_definition_overrides(app, warning): + text = (".. py:class:: io.StringIO\n" + " :canonical: _io.StringIO\n" + ".. py:class:: _io.StringIO\n") + restructuredtext.parse(app, text) + assert warning.getvalue() == "" + + domain = app.env.get_domain('py') + assert domain.objects['_io.StringIO'] == ('index', 'id0', 'class', False) + + +def test_canonical_definition_skip(app, warning): + text = (".. py:class:: _io.StringIO\n" + ".. py:class:: io.StringIO\n" + " :canonical: _io.StringIO\n") + + restructuredtext.parse(app, text) + assert warning.getvalue() == "" + + domain = app.env.get_domain('py') + assert domain.objects['_io.StringIO'] == ('index', 'io.StringIO', 'class', False) + + +def test_canonical_duplicated(app, warning): + text = (".. py:class:: mypackage.StringIO\n" + " :canonical: _io.StringIO\n" + ".. py:class:: io.StringIO\n" + " :canonical: _io.StringIO\n") + + restructuredtext.parse(app, text) + assert warning.getvalue() != "" + + def test_info_field_list(app): text = (".. py:module:: example\n" ".. py:class:: Class\n"