diff --git a/sphinx/domains/c.py b/sphinx/domains/c.py index a0ca0873c..e319771be 100644 --- a/sphinx/domains/c.py +++ b/sphinx/domains/c.py @@ -11,6 +11,7 @@ import re import string from typing import Any, Dict, Iterator, List, Tuple +from typing import cast from docutils import nodes from docutils.nodes import Element @@ -22,12 +23,15 @@ from sphinx.builders import Builder from sphinx.directives import ObjectDescription from sphinx.domains import Domain, ObjType from sphinx.environment import BuildEnvironment -from sphinx.locale import _ +from sphinx.locale import _, __ from sphinx.roles import XRefRole +from sphinx.util import logging from sphinx.util.docfields import Field, TypedField from sphinx.util.nodes import make_refnode +logger = logging.getLogger(__name__) + # RE to split at word boundaries wsplit_re = re.compile(r'(\W+)') @@ -201,13 +205,9 @@ class CObject(ObjectDescription): signode['ids'].append(targetname) signode['first'] = (not self.names) self.state.document.note_explicit_target(signode) - inv = self.env.domaindata['c']['objects'] - if name in inv: - self.state_machine.reporter.warning( - 'duplicate C object description of %s, ' % name + - 'other instance in ' + self.env.doc2path(inv[name][0]), - line=self.lineno) - inv[name] = (self.env.docname, self.objtype) + + domain = cast(CDomain, self.env.get_domain('c')) + domain.note_object(name, self.objtype) indextext = self.get_index_text(name) if indextext: @@ -271,10 +271,22 @@ class CDomain(Domain): 'objects': {}, # fullname -> docname, objtype } # type: Dict[str, Dict[str, Tuple[str, Any]]] + @property + def objects(self) -> Dict[str, Tuple[str, str]]: + return self.data.setdefault('objects', {}) # fullname -> docname, objtype + + def note_object(self, name: str, objtype: str, location: Any = None) -> None: + if name in self.objects: + docname = self.objects[name][0] + logger.warning(__('duplicate C object description of %s, ' + 'other instance in %s, use :noindex: for one of them'), + name, docname, location=location) + self.objects[name] = (self.env.docname, objtype) + def clear_doc(self, docname: str) -> None: - for fullname, (fn, _l) in list(self.data['objects'].items()): + for fullname, (fn, _l) in list(self.objects.items()): if fn == docname: - del self.data['objects'][fullname] + del self.objects[fullname] def merge_domaindata(self, docnames: List[str], otherdata: Dict) -> None: # XXX check duplicates @@ -290,9 +302,9 @@ class CDomain(Domain): # becase TypedField can generate xrefs if target in CObject.stopwords: return contnode - if target not in self.data['objects']: + if target not in self.objects: return None - obj = self.data['objects'][target] + obj = self.objects[target] return make_refnode(builder, fromdocname, obj[0], 'c.' + target, contnode, target) @@ -301,15 +313,15 @@ class CDomain(Domain): ) -> List[Tuple[str, Element]]: # strip pointer asterisk target = target.rstrip(' *') - if target not in self.data['objects']: + if target not in self.objects: return [] - obj = self.data['objects'][target] + obj = self.objects[target] return [('c:' + self.role_for_objtype(obj[1]), make_refnode(builder, fromdocname, obj[0], 'c.' + target, contnode, target))] def get_objects(self) -> Iterator[Tuple[str, str, str, str, str, int]]: - for refname, (docname, type) in list(self.data['objects'].items()): + for refname, (docname, type) in list(self.objects.items()): yield (refname, refname, type, docname, 'c.' + refname, 1) diff --git a/sphinx/domains/changeset.py b/sphinx/domains/changeset.py index 8ff9e90fc..53cdec41c 100644 --- a/sphinx/domains/changeset.py +++ b/sphinx/domains/changeset.py @@ -117,22 +117,9 @@ class ChangeSetDomain(Domain): 'changes': {}, # version -> list of ChangeSet } # type: Dict - def clear_doc(self, docname: str) -> None: - for version, changes in self.data['changes'].items(): - for changeset in changes[:]: - if changeset.docname == docname: - changes.remove(changeset) - - def merge_domaindata(self, docnames: List[str], otherdata: Dict) -> None: - # XXX duplicates? - for version, otherchanges in otherdata['changes'].items(): - changes = self.data['changes'].setdefault(version, []) - for changeset in otherchanges: - if changeset.docname in docnames: - changes.append(changeset) - - def process_doc(self, env: "BuildEnvironment", docname: str, document: nodes.document) -> None: # NOQA - pass # nothing to do here. All changesets are registered on calling directive. + @property + def changesets(self) -> Dict[str, List[ChangeSet]]: + return self.data.setdefault('changes', {}) # version -> list of ChangeSet def note_changeset(self, node: addnodes.versionmodified) -> None: version = node['version'] @@ -140,10 +127,27 @@ class ChangeSetDomain(Domain): objname = self.env.temp_data.get('object') changeset = ChangeSet(node['type'], self.env.docname, node.line, module, objname, node.astext()) - self.data['changes'].setdefault(version, []).append(changeset) + self.changesets.setdefault(version, []).append(changeset) + + def clear_doc(self, docname: str) -> None: + for version, changes in self.changesets.items(): + for changeset in changes[:]: + if changeset.docname == docname: + changes.remove(changeset) + + def merge_domaindata(self, docnames: List[str], otherdata: Dict) -> None: + # XXX duplicates? + for version, otherchanges in otherdata['changes'].items(): + changes = self.changesets.setdefault(version, []) + for changeset in otherchanges: + if changeset.docname in docnames: + changes.append(changeset) + + def process_doc(self, env: "BuildEnvironment", docname: str, document: nodes.document) -> None: # NOQA + pass # nothing to do here. All changesets are registered on calling directive. def get_changesets_for(self, version: str) -> List[ChangeSet]: - return self.data['changes'].get(version, []) + return self.changesets.get(version, []) def setup(app: "Sphinx") -> Dict[str, Any]: diff --git a/sphinx/domains/javascript.py b/sphinx/domains/javascript.py index 8181ea525..121d5582d 100644 --- a/sphinx/domains/javascript.py +++ b/sphinx/domains/javascript.py @@ -9,6 +9,7 @@ """ from typing import Any, Dict, Iterator, List, Tuple +from typing import cast from docutils import nodes from docutils.nodes import Element, Node @@ -22,13 +23,17 @@ from sphinx.directives import ObjectDescription from sphinx.domains import Domain, ObjType from sphinx.domains.python import _pseudo_parse_arglist from sphinx.environment import BuildEnvironment -from sphinx.locale import _ +from sphinx.locale import _, __ from sphinx.roles import XRefRole +from sphinx.util import logging from sphinx.util.docfields import Field, GroupedField, TypedField from sphinx.util.docutils import SphinxDirective from sphinx.util.nodes import make_refnode +logger = logging.getLogger(__name__) + + class JSObject(ObjectDescription): """ Description of a JavaScript object. @@ -106,14 +111,10 @@ class JSObject(ObjectDescription): signode['ids'].append(fullname.replace('$', '_S_')) signode['first'] = not self.names self.state.document.note_explicit_target(signode) - objects = self.env.domaindata['js']['objects'] - if fullname in objects: - self.state_machine.reporter.warning( - 'duplicate object description of %s, ' % fullname + - 'other instance in ' + - self.env.doc2path(objects[fullname][0]), - line=self.lineno) - objects[fullname] = self.env.docname, self.objtype + + domain = cast(JavaScriptDomain, self.env.get_domain('js')) + domain.note_object(fullname, self.objtype, + location=(self.env.docname, self.lineno)) indextext = self.get_index_text(mod_name, name_obj) if indextext: @@ -248,10 +249,13 @@ class JSModule(SphinxDirective): noindex = 'noindex' in self.options ret = [] # type: List[Node] if not noindex: - self.env.domaindata['js']['modules'][mod_name] = self.env.docname + domain = cast(JavaScriptDomain, self.env.get_domain('js')) + + domain.note_module(mod_name) # Make a duplicate entry in 'objects' to facilitate searching for # the module in JavaScriptDomain.find_obj() - self.env.domaindata['js']['objects'][mod_name] = (self.env.docname, 'module') + domain.note_object(mod_name, 'module', location=(self.env.docname, self.lineno)) + targetnode = nodes.target('', '', ids=['module-' + mod_name], ismod=True) self.state.document.note_explicit_target(targetnode) @@ -314,31 +318,48 @@ class JavaScriptDomain(Domain): } initial_data = { 'objects': {}, # fullname -> docname, objtype - 'modules': {}, # mod_name -> docname + 'modules': {}, # modname -> docname } # type: Dict[str, Dict[str, Tuple[str, str]]] + @property + def objects(self) -> Dict[str, Tuple[str, str]]: + return self.data.setdefault('objects', {}) # fullname -> docname, objtype + + def note_object(self, fullname: str, objtype: str, location: Any = None) -> None: + if fullname in self.objects: + docname = self.objects[fullname][0] + logger.warning(__('duplicate object description of %s, other instance in %s'), + fullname, docname, location=location) + self.objects[fullname] = (self.env.docname, objtype) + + @property + def modules(self) -> Dict[str, str]: + return self.data.setdefault('modules', {}) # modname -> docname + + def note_module(self, modname: str) -> None: + self.modules[modname] = self.env.docname + def clear_doc(self, docname: str) -> None: - for fullname, (pkg_docname, _l) in list(self.data['objects'].items()): + for fullname, (pkg_docname, _l) in list(self.objects.items()): if pkg_docname == docname: - del self.data['objects'][fullname] - for mod_name, pkg_docname in list(self.data['modules'].items()): + del self.objects[fullname] + for modname, pkg_docname in list(self.modules.items()): if pkg_docname == docname: - del self.data['modules'][mod_name] + del self.modules[modname] def merge_domaindata(self, docnames: List[str], otherdata: Dict) -> None: # XXX check duplicates for fullname, (fn, objtype) in otherdata['objects'].items(): if fn in docnames: - self.data['objects'][fullname] = (fn, objtype) + self.objects[fullname] = (fn, objtype) for mod_name, pkg_docname in otherdata['modules'].items(): if pkg_docname in docnames: - self.data['modules'][mod_name] = pkg_docname + self.modules[mod_name] = pkg_docname def find_obj(self, env: BuildEnvironment, mod_name: str, prefix: str, name: str, typ: str, searchorder: int = 0) -> Tuple[str, Tuple[str, str]]: if name[-2:] == '()': name = name[:-2] - objects = self.data['objects'] searches = [] if mod_name and prefix: @@ -354,10 +375,10 @@ class JavaScriptDomain(Domain): newname = None for search_name in searches: - if search_name in objects: + if search_name in self.objects: newname = search_name - return newname, objects.get(newname) + return newname, self.objects.get(newname) def resolve_xref(self, env: BuildEnvironment, fromdocname: str, builder: Builder, typ: str, target: str, node: pending_xref, contnode: Element @@ -384,9 +405,8 @@ class JavaScriptDomain(Domain): name.replace('$', '_S_'), contnode, name))] def get_objects(self) -> Iterator[Tuple[str, str, str, str, str, int]]: - for refname, (docname, type) in list(self.data['objects'].items()): - yield refname, refname, type, docname, \ - refname.replace('$', '_S_'), 1 + for refname, (docname, type) in list(self.objects.items()): + yield refname, refname, type, docname, refname.replace('$', '_S_'), 1 def get_full_qualified_name(self, node: Element) -> str: modname = node.get('js:module')