diff --git a/CHANGES b/CHANGES index e8c0561ab..9581b1f93 100644 --- a/CHANGES +++ b/CHANGES @@ -20,6 +20,8 @@ Incompatible changes Deprecated ---------- +* ``sphinx.domains.std.StandardDomain.add_object()`` + Features added -------------- @@ -27,6 +29,8 @@ Features added old stub file * #5923: autodoc: ``:inherited-members:`` option takes a name of anchestor class not to document inherited members of the class and uppers +* #6558: glossary: emit a warning for duplicated glossary entry +* #6558: std domain: emit a warning for duplicated generic objects Bugs fixed ---------- diff --git a/doc/extdev/deprecated.rst b/doc/extdev/deprecated.rst index 6159ae4f7..52dcddfdb 100644 --- a/doc/extdev/deprecated.rst +++ b/doc/extdev/deprecated.rst @@ -26,6 +26,11 @@ The following is a list of deprecated interfaces. - (will be) Removed - Alternatives + * - ``sphinx.domains.std.StandardDomain.add_object()`` + - 3.0 + - 5.0 + - ``sphinx.domains.std.StandardDomain.note_object()`` + * - ``sphinx.environment.BuildEnvironment.indexentries`` - 2.4 - 4.0 diff --git a/sphinx/domains/std.py b/sphinx/domains/std.py index 10ca58c5b..dc35b1c2b 100644 --- a/sphinx/domains/std.py +++ b/sphinx/domains/std.py @@ -22,7 +22,7 @@ from docutils.statemachine import StringList from sphinx import addnodes from sphinx.addnodes import desc_signature, pending_xref -from sphinx.deprecation import RemovedInSphinx40Warning +from sphinx.deprecation import RemovedInSphinx40Warning, RemovedInSphinx50Warning from sphinx.directives import ObjectDescription from sphinx.domains import Domain, ObjType from sphinx.locale import _, __ @@ -81,7 +81,8 @@ class GenericObject(ObjectDescription): targetname, '', None)) std = cast(StandardDomain, self.env.get_domain('std')) - std.add_object(self.objtype, name, self.env.docname, targetname) + std.note_object(self.objtype, name, targetname, + location=(self.env.docname, self.lineno)) class EnvVar(GenericObject): @@ -143,7 +144,7 @@ class Target(SphinxDirective): _, name = self.name.split(':', 1) std = cast(StandardDomain, self.env.get_domain('std')) - std.add_object(name, fullname, self.env.docname, targetname) + std.note_object(name, fullname, targetname, location=(self.env.docname, self.lineno)) return ret @@ -261,7 +262,7 @@ def make_glossary_term(env: "BuildEnvironment", textnodes: Iterable[Node], index gloss_entries.add(new_id) std = cast(StandardDomain, env.get_domain('std')) - std.add_object('term', termtext.lower(), env.docname, new_id) + std.note_object('term', termtext.lower(), new_id, location=(env.docname, lineno)) # add an index entry too indexnode = addnodes.index() @@ -443,7 +444,8 @@ class ProductionList(SphinxDirective): if idname not in self.state.document.ids: subnode['ids'].append(idname) self.state.document.note_implicit_target(subnode, subnode) - domain.add_object('token', subnode['tokenname'], self.env.docname, idname) + domain.note_object('token', subnode['tokenname'], idname, + location=(self.env.docname, self.lineno)) subnode.extend(token_xrefs(tokens)) node.append(subnode) return [node] @@ -539,6 +541,23 @@ class StandardDomain(Domain): def objects(self) -> Dict[Tuple[str, str], Tuple[str, str]]: return self.data.setdefault('objects', {}) # (objtype, name) -> docname, labelid + def note_object(self, objtype: str, name: str, labelid: str, location: Any = None + ) -> None: + """Note a generic object for cross reference. + + .. versionadded:: 3.0 + """ + if (objtype, name) in self.objects: + docname = self.objects[objtype, name][0] + logger.warning(__('duplicate %s description of %s, other instance in %s'), + objtype, name, docname, location=location) + self.objects[objtype, name] = (self.env.docname, labelid) + + def add_object(self, objtype: str, name: str, docname: str, labelid: str) -> None: + warnings.warn('StandardDomain.add_object() is deprecated.', + RemovedInSphinx50Warning) + self.objects[objtype, name] = (docname, labelid) + @property def progoptions(self) -> Dict[Tuple[str, str], Tuple[str, str]]: return self.data.setdefault('progoptions', {}) # (program, name) -> docname, labelid @@ -620,9 +639,6 @@ class StandardDomain(Domain): continue self.labels[name] = docname, labelid, sectname - def add_object(self, objtype: str, name: str, docname: str, labelid: str) -> None: - self.objects[objtype, name] = (docname, labelid) - def add_program_option(self, program: str, name: str, docname: str, labelid: str) -> None: self.progoptions[program, name] = (docname, labelid) diff --git a/tests/test_domain_std.py b/tests/test_domain_std.py index c7a7b496a..db1346bcf 100644 --- a/tests/test_domain_std.py +++ b/tests/test_domain_std.py @@ -172,6 +172,15 @@ def test_glossary_warning(app, status, warning): assert ("case3.rst:4: WARNING: glossary term must be preceded by empty line" in warning.getvalue()) + # duplicated terms + text = (".. glossary::\n" + "\n" + " term-case4\n" + " term-case4\n") + restructuredtext.parse(app, text, "case4") + assert ("case4.txt:3: WARNING: duplicate term description of term-case4, " + "other instance in case4" in warning.getvalue()) + def test_glossary_comment(app): text = (".. glossary::\n"