diff options
| -rw-r--r-- | .travis.yml | 3 | ||||
| -rw-r--r-- | CHANGES | 12 | ||||
| -rw-r--r-- | EXAMPLES | 1 | ||||
| -rw-r--r-- | doc/_static/favicon.svg | 8 | ||||
| -rw-r--r-- | doc/conf.py | 1 | ||||
| -rw-r--r-- | doc/glossary.rst | 4 | ||||
| -rw-r--r-- | doc/usage/configuration.rst | 5 | ||||
| -rw-r--r-- | setup.py | 2 | ||||
| -rw-r--r-- | sphinx/builders/_epub_base.py | 7 | ||||
| -rw-r--r-- | sphinx/builders/linkcheck.py | 5 | ||||
| -rw-r--r-- | sphinx/domains/c.py | 64 | ||||
| -rw-r--r-- | sphinx/domains/cpp.py | 27 | ||||
| -rw-r--r-- | sphinx/ext/autodoc/__init__.py | 2 | ||||
| -rw-r--r-- | sphinx/ext/autodoc/importer.py | 5 | ||||
| -rw-r--r-- | sphinx/search/__init__.py | 10 | ||||
| -rw-r--r-- | sphinx/themes/basic/static/basic.css_t | 1 | ||||
| -rw-r--r-- | sphinx/util/typing.py | 4 | ||||
| -rw-r--r-- | sphinx/writers/texinfo.py | 2 | ||||
| -rw-r--r-- | tests/roots/test-domain-c/function_param_target.rst | 5 | ||||
| -rw-r--r-- | tests/roots/test-ext-autodoc/target/slots.py | 4 | ||||
| -rw-r--r-- | tests/roots/test-root/conf.py | 2 | ||||
| -rw-r--r-- | tests/roots/test-root/markup.txt | 4 | ||||
| -rw-r--r-- | tests/test_build_html.py | 2 | ||||
| -rw-r--r-- | tests/test_domain_c.py | 52 | ||||
| -rw-r--r-- | tests/test_domain_cpp.py | 15 | ||||
| -rw-r--r-- | tests/test_domain_py.py | 10 | ||||
| -rw-r--r-- | tests/test_ext_autodoc.py | 8 |
27 files changed, 203 insertions, 62 deletions
diff --git a/.travis.yml b/.travis.yml index d73be03ec..47a8e7c7a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -24,9 +24,6 @@ jobs: env: - TOXENV=du15 - PYTEST_ADDOPTS="--cov ./ --cov-append --cov-config setup.cfg" - - python: 'nightly' - env: - - TOXENV=du16 - language: node_js node_js: '10.7' @@ -26,6 +26,8 @@ Features added just before writing .tex file * #7996: manpage: Add :confval:`man_make_section_directory` to make a section directory on build man page +* #8289: epub: Allow to suppress "duplicated ToC entry found" warnings from epub + builder using :confval:`suppress_warnings`. Bugs fixed ---------- @@ -43,6 +45,7 @@ Bugs fixed * #7964: autodoc: Tuple in default value is wrongly rendered * #8200: autodoc: type aliases break type formatting of autoattribute * #7786: autodoc: can't detect overloaded methods defined in other file +* #8294: autodoc: single-string __slots__ is not handled correctly * #8192: napoleon: description is disappeared when it contains inline literals * #8142: napoleon: Potential of regex denial of service in google style docs * #8169: LaTeX: pxjahyper loaded even when latex_engine is not platex @@ -58,7 +61,11 @@ Bugs fixed * #8239: Failed to refer a token in productionlist if it is indented * #8268: linkcheck: Report HTTP errors when ``linkcheck_anchors`` is ``True`` * #8245: linkcheck: take source directory into account for local files +* #8321: linkcheck: ``tel:`` schema hyperlinks are detected as errors +* #8323: linkcheck: An exit status is incorrect when links having unsupported + schema found * #6914: figure numbers are unexpectedly assigned to uncaptioned items +* #8320: make "inline" line numbers un-selectable Testing -------- @@ -85,6 +92,11 @@ Bugs fixed * #8188: C, add missing items to internal object types dictionary, e.g., preventing intersphinx from resolving them. +* C, fix anon objects in intersphinx. +* #8270, C++, properly reject functions as duplicate declarations if a + non-function declaration of the same name already exists. +* C, fix references to function parameters. + Link to the function instead of a non-existing anchor. Testing @@ -230,6 +230,7 @@ Documentation using sphinx_rtd_theme * `MyHDL <http://docs.myhdl.org/>`__ * `Nextflow <https://www.nextflow.io/docs/latest/index.html>`__ * `NICOS <https://forge.frm2.tum.de/nicos/doc/nicos-master/>`__ (customized) +* `OpenFAST <https://openfast.readthedocs.io/>`__ * `Pelican <http://docs.getpelican.com/>`__ * `picamera <https://picamera.readthedocs.io/>`__ * `Pillow <https://pillow.readthedocs.io/>`__ diff --git a/doc/_static/favicon.svg b/doc/_static/favicon.svg new file mode 100644 index 000000000..c3e1acd35 --- /dev/null +++ b/doc/_static/favicon.svg @@ -0,0 +1,8 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"> + <style> + @media (prefers-color-scheme: dark) { + svg { fill: white; } + } + </style> + <path d="m 67.780707,71.526216 c 0,-2.720856 0.735772,-7.633735 1.635035,-10.917507 2.076574,-7.582764 3.222746,-16.97568 2.071477,-16.97568 -0.485619,0 -3.994408,3.173002 -7.797313,7.051115 -14.448869,14.734603 -29.952812,23.068339 -42.915946,23.068339 -7.400211,0 -12.4298817,-1.871115 -17.2867007,-6.430912 -2.94436186,-2.764297 -3.47532146,-4.129685 -3.47532146,-8.936928 0,-4.94488 0.4862322,-6.108589 3.78321146,-9.054437 2.987989,-2.669773 4.875111,-3.380296 8.9779137,-3.380296 3.163221,0.711278 5.032659,0.664017 6.063532,1.917191 1.045041,1.231842 1.406892,5.262673 0.143323,7.623675 -0.674746,1.260763 -2.435471,2.043539 -4.5966,2.043539 -2.040303,0 -3.203991,-0.483702 -2.786976,-1.15844 1.31395,-2.126021 -0.560952,-3.566616 -2.9664067,-2.279256 -2.907025,1.555792 -2.957418,7.069066 -0.08839,9.665535 4.0345357,3.651203 15.1912207,5.023925 21.9019857,2.694828 7.250749,-2.516503 16.739014,-8.578986 24.30831,-15.531674 l 6.657407,-6.115083 -8.688303,-0.05007 C 43.622519,44.707714 37.702703,43.621524 18.54695,38.489741 12.175528,36.782852 6.0502733,35.306342 4.9352743,35.208608 3.6710803,35.097791 2.841723,34.067882 2.9080043,32.476074 3.0199286,29.788108 4.4800823,27.78768 6.2067673,27.033038 7.2437505,26.579828 14.43583,25.894406 22.0605,23.866486 c 29.699148,-7.899023 31.502043,-6.781254 51.28707,-1.772167 6.461504,1.635896 13.942408,3.414988 17.256961,3.474566 5.106245,0.09178 6.211825,0.514653 7.240255,2.76932 0.66758,1.46355 1.21378,2.858905 1.21378,3.10079 0,0.241884 -2.89333,1.764397 -6.429613,3.383363 -12.984983,5.944723 -17.083271,9.093943 -12.855172,15.130399 1.753219,2.503069 1.718037,2.768923 -0.57922,4.37799 -1.345193,0.942203 -2.457238,2.856456 -2.471232,4.253898 -0.03777,3.776976 -2.424786,11.884847 -5.893734,15.080164 l -3.048923,2.808424 z m 6.632814,-34.658372 c 5.169656,-1.440693 8.302047,-3.07045 14.72913,-6.500861 -5.292267,-1.548658 -18.570782,-3.724097 -18.570782,-3.724097 -9.796513,-1.964547 -8.76916,-1.865132 -9.21348,0.29669 -0.176673,0.859598 -0.702644,2.763948 -1.872329,4.596663 -2.251474,3.527711 -10.489307,4.271075 -15.214327,2.009703 -1.482367,-0.709454 -2.971272,-3.416276 -2.950606,-5.336922 0.02911,-2.705486 -1.505386,-3.336055 -2.486689,-2.975309 -0.796428,0.292781 -3.384665,0.330004 -9.071284,1.864262 -18.784765,5.068157 -21.3552119,4.487473 -9.110967,6.223299 1.472409,0.208739 9.252992,2.381926 13.052028,3.39412 9.318588,2.482796 11.064717,2.665087 23.125496,2.414247 8.385835,-0.174409 11.891174,-0.675356 17.58381,-2.261795 z M 3.0589449,14.916483 C 3.2921927,12.514245 3.424378,11.992797 10.100599,10.647894 13.924923,9.8774962 23.355266,7.3808108 31.056903,5.0997052 c 17.703937,-5.2436279 22.73392,-5.2565016 41.092202,-0.105175 7.923233,2.2232606 16.798382,4.047803 19.72254,4.054541 4.567242,0.01054 6.941892,2.0284768 6.941892,2.0284768 2.101843,4.825342 1.718463,5.158474 -6.484103,5.158474 -5.714193,0 -10.641875,-0.963081 -18.245438,-3.565943 C 68.300078,10.69012 60.060462,8.8316882 55.557963,8.4915615 47.342337,7.8709375 47.353713,7.8687835 21.963188,14.855617 17.503192,16.082896 11.34213,17.454164 8.2719268,17.902883 l -5.5821654,0.81585 z" /> +</svg> diff --git a/doc/conf.py b/doc/conf.py index 74e5a8b80..8b242dc7e 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -28,6 +28,7 @@ html_sidebars = {'index': ['indexsidebar.html', 'searchbox.html']} html_additional_pages = {'index': 'index.html'} html_use_opensearch = 'https://www.sphinx-doc.org/en/master' html_baseurl = 'https://www.sphinx-doc.org/en/master/' +html_favicon = '_static/favicon.svg' htmlhelp_basename = 'Sphinxdoc' diff --git a/doc/glossary.rst b/doc/glossary.rst index d3367e5df..87b7014b6 100644 --- a/doc/glossary.rst +++ b/doc/glossary.rst @@ -9,8 +9,8 @@ Glossary A class (inheriting from :class:`~sphinx.builders.Builder`) that takes parsed documents and performs an action on them. Normally, builders translate the documents to an output format, but it is also possible to - use the builder builders that e.g. check for broken links in the - documentation, or build coverage information. + use builders that e.g. check for broken links in the documentation, or + build coverage information. See :doc:`/usage/builders/index` for an overview over Sphinx's built-in builders. diff --git a/doc/usage/configuration.rst b/doc/usage/configuration.rst index 270fcbf33..4881b8629 100644 --- a/doc/usage/configuration.rst +++ b/doc/usage/configuration.rst @@ -316,6 +316,7 @@ General configuration * ``toc.circular`` * ``toc.secnum`` * ``epub.unknown_project_files`` + * ``epub.duplicated_toc_entry`` * ``autosectionlabel.*`` You can choose from these types. @@ -340,6 +341,10 @@ General configuration Added ``autosectionlabel.*`` + .. versionchanged:: 3.3.0 + + Added ``epub.duplicated_toc_entry`` + .. confval:: needs_sphinx If set to a ``major.minor`` version string like ``'1.1'``, Sphinx will @@ -44,7 +44,7 @@ extras_require = { 'lint': [ 'flake8>=3.5.0', 'flake8-import-order', - 'mypy>=0.780', + 'mypy>=0.790', 'docutils-stubs', ], 'test': [ diff --git a/sphinx/builders/_epub_base.py b/sphinx/builders/_epub_base.py index 95f9ab8ed..b126182e9 100644 --- a/sphinx/builders/_epub_base.py +++ b/sphinx/builders/_epub_base.py @@ -208,7 +208,12 @@ class EpubBuilder(StandaloneHTMLBuilder): appeared = set() # type: Set[str] for node in nodes: if node['refuri'] in appeared: - logger.warning(__('duplicated ToC entry found: %s'), node['refuri']) + logger.warning( + __('duplicated ToC entry found: %s'), + node['refuri'], + type="epub", + subtype="duplicated_toc_entry", + ) else: appeared.add(node['refuri']) diff --git a/sphinx/builders/linkcheck.py b/sphinx/builders/linkcheck.py index a9e6b05b0..7652761b5 100644 --- a/sphinx/builders/linkcheck.py +++ b/sphinx/builders/linkcheck.py @@ -106,8 +106,7 @@ class CheckExternalLinksBuilder(Builder): self.rqueue = queue.Queue() # type: queue.Queue self.workers = [] # type: List[threading.Thread] for i in range(self.app.config.linkcheck_workers): - thread = threading.Thread(target=self.check_thread) - thread.setDaemon(True) + thread = threading.Thread(target=self.check_thread, daemon=True) thread.start() self.workers.append(thread) @@ -213,7 +212,7 @@ class CheckExternalLinksBuilder(Builder): def check(docname: str) -> Tuple[str, str, int]: # check for various conditions without bothering the network - if len(uri) == 0 or uri.startswith(('#', 'mailto:')): + if len(uri) == 0 or uri.startswith(('#', 'mailto:', 'tel:')): return 'unchecked', '', 0 elif not uri.startswith(('http:', 'https:')): if uri_re.match(uri): diff --git a/sphinx/domains/c.py b/sphinx/domains/c.py index 304c871ea..fafe827af 100644 --- a/sphinx/domains/c.py +++ b/sphinx/domains/c.py @@ -10,9 +10,8 @@ import re from typing import ( - Any, Callable, Dict, Generator, Iterator, List, Type, TypeVar, Tuple, Union + Any, Callable, cast, Dict, Generator, Iterator, List, Type, TypeVar, Tuple, Union ) -from typing import cast from docutils import nodes from docutils.nodes import Element, Node, TextElement, system_message @@ -47,6 +46,11 @@ from sphinx.util.nodes import make_refnode logger = logging.getLogger(__name__) T = TypeVar('T') +DeclarationType = Union[ + "ASTStruct", "ASTUnion", "ASTEnum", "ASTEnumerator", + "ASTType", "ASTTypeWithInit", "ASTMacro", +] + # https://en.cppreference.com/w/c/keyword _keywords = [ 'auto', 'break', 'case', 'char', 'const', 'continue', 'default', 'do', 'double', @@ -636,6 +640,10 @@ class ASTFunctionParameter(ASTBase): self.arg = arg self.ellipsis = ellipsis + def get_id(self, version: int, objectType: str, symbol: "Symbol") -> str: + # the anchor will be our parent + return symbol.parent.declaration.get_id(version, prefixed=False) + def _stringify(self, transform: StringifyTransform) -> str: if self.ellipsis: return '...' @@ -1149,6 +1157,9 @@ class ASTType(ASTBase): def name(self) -> ASTNestedName: return self.decl.name + def get_id(self, version: int, objectType: str, symbol: "Symbol") -> str: + return symbol.get_full_nested_name().get_id(version) + @property def function_params(self) -> List[ASTFunctionParameter]: return self.decl.function_params @@ -1191,6 +1202,9 @@ class ASTTypeWithInit(ASTBase): def name(self) -> ASTNestedName: return self.type.name + def get_id(self, version: int, objectType: str, symbol: "Symbol") -> str: + return self.type.get_id(version, objectType, symbol) + def _stringify(self, transform: StringifyTransform) -> str: res = [] res.append(transform(self.type)) @@ -1242,6 +1256,9 @@ class ASTMacro(ASTBase): def name(self) -> ASTNestedName: return self.ident + def get_id(self, version: int, objectType: str, symbol: "Symbol") -> str: + return symbol.get_full_nested_name().get_id(version) + def _stringify(self, transform: StringifyTransform) -> str: res = [] res.append(transform(self.ident)) @@ -1342,7 +1359,8 @@ class ASTEnumerator(ASTBase): class ASTDeclaration(ASTBaseBase): - def __init__(self, objectType: str, directiveType: str, declaration: Any, + def __init__(self, objectType: str, directiveType: str, + declaration: Union[DeclarationType, ASTFunctionParameter], semicolon: bool = False) -> None: self.objectType = objectType self.directiveType = directiveType @@ -1359,18 +1377,20 @@ class ASTDeclaration(ASTBaseBase): @property def name(self) -> ASTNestedName: - return self.declaration.name + decl = cast(DeclarationType, self.declaration) + return decl.name @property def function_params(self) -> List[ASTFunctionParameter]: if self.objectType != 'function': return None - return self.declaration.function_params + decl = cast(ASTType, self.declaration) + return decl.function_params def get_id(self, version: int, prefixed: bool = True) -> str: if self.objectType == 'enumerator' and self.enumeratorScopedSymbol: return self.enumeratorScopedSymbol.declaration.get_id(version, prefixed) - id_ = self.symbol.get_full_nested_name().get_id(version) + id_ = self.declaration.get_id(version, self.objectType, self.symbol) if prefixed: return _id_prefix[version] + id_ else: @@ -1413,7 +1433,8 @@ class ASTDeclaration(ASTBaseBase): elif self.objectType == 'enumerator': mainDeclNode += addnodes.desc_annotation('enumerator ', 'enumerator ') elif self.objectType == 'type': - prefix = self.declaration.get_type_declaration_prefix() + decl = cast(ASTType, self.declaration) + prefix = decl.get_type_declaration_prefix() prefix += ' ' mainDeclNode += addnodes.desc_annotation(prefix, prefix) else: @@ -2988,7 +3009,7 @@ class DefinitionParser(BaseParser): def parse_pre_v3_type_definition(self) -> ASTDeclaration: self.skip_ws() - declaration = None # type: Any + declaration = None # type: DeclarationType if self.skip_word('struct'): typ = 'struct' declaration = self._parse_struct() @@ -3011,7 +3032,7 @@ class DefinitionParser(BaseParser): 'macro', 'struct', 'union', 'enum', 'enumerator', 'type'): raise Exception('Internal error, unknown directiveType "%s".' % directiveType) - declaration = None # type: Any + declaration = None # type: DeclarationType if objectType == 'member': declaration = self._parse_type_with_init(named=True, outer='member') elif objectType == 'function': @@ -3158,10 +3179,6 @@ class CObject(ObjectDescription): self.state.document.note_explicit_target(signode) - domain = cast(CDomain, self.env.get_domain('c')) - if name not in domain.objects: - domain.objects[name] = (domain.env.docname, newestId, self.objtype) - if 'noindexentry' not in self.options: indexText = self.get_index_text(name) self.indexnode['entries'].append(('single', indexText, newestId, '', None)) @@ -3681,10 +3698,6 @@ class CDomain(Domain): 'objects': {}, # fullname -> docname, node_id, objtype } # type: Dict[str, Union[Symbol, Dict[str, Tuple[str, str, str]]]] - @property - def objects(self) -> Dict[str, Tuple[str, str, str]]: - return self.data.setdefault('objects', {}) # fullname -> docname, node_id, objtype - def clear_doc(self, docname: str) -> None: if Symbol.debug_show_tree: print("clear_doc:", docname) @@ -3700,9 +3713,6 @@ class CDomain(Domain): print(self.data['root_symbol'].dump(1)) print("\tafter end") print("clear_doc end:", docname) - for fullname, (fn, _id, _l) in list(self.objects.items()): - if fn == docname: - del self.objects[fullname] def process_doc(self, env: BuildEnvironment, docname: str, document: nodes.document) -> None: @@ -3788,8 +3798,18 @@ class CDomain(Domain): return [] def get_objects(self) -> Iterator[Tuple[str, str, str, str, str, int]]: - for refname, (docname, node_id, objtype) in list(self.objects.items()): - yield (refname, refname, objtype, docname, node_id, 1) + rootSymbol = self.data['root_symbol'] + for symbol in rootSymbol.get_all_symbols(): + if symbol.declaration is None: + continue + assert symbol.docname + fullNestedName = symbol.get_full_nested_name() + name = str(fullNestedName).lstrip('.') + dispname = fullNestedName.get_display_string().lstrip('.') + objectType = symbol.declaration.objectType + docname = symbol.docname + newestId = symbol.declaration.get_newest_id() + yield (name, dispname, objectType, docname, newestId, 1) def setup(app: Sphinx) -> Dict[str, Any]: diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py index 7b10f8166..0e996532f 100644 --- a/sphinx/domains/cpp.py +++ b/sphinx/domains/cpp.py @@ -1836,7 +1836,7 @@ class ASTFunctionParameter(ASTBase): # this is not part of the normal name mangling in C++ if symbol: # the anchor will be our parent - return symbol.parent.declaration.get_id(version, prefixed=None) + return symbol.parent.declaration.get_id(version, prefixed=False) # else, do the usual if self.ellipsis: return 'z' @@ -4107,7 +4107,7 @@ class Symbol: Symbol.debug_print("self:") print(self.to_string(Symbol.debug_indent + 1), end="") Symbol.debug_print("nestedName: ", nestedName) - Symbol.debug_print("templateDecls: ", templateDecls) + Symbol.debug_print("templateDecls: ", ",".join(str(t) for t in templateDecls)) Symbol.debug_print("strictTemplateParamArgLists:", strictTemplateParamArgLists) Symbol.debug_print("ancestorLookupType:", ancestorLookupType) Symbol.debug_print("templateShorthand: ", templateShorthand) @@ -4231,7 +4231,7 @@ class Symbol: Symbol.debug_indent += 1 Symbol.debug_print("_add_symbols:") Symbol.debug_indent += 1 - Symbol.debug_print("tdecls:", templateDecls) + Symbol.debug_print("tdecls:", ",".join(str(t) for t in templateDecls)) Symbol.debug_print("nn: ", nestedName) Symbol.debug_print("decl: ", declaration) Symbol.debug_print("doc: ", docname) @@ -4360,6 +4360,11 @@ class Symbol: if Symbol.debug_lookup: Symbol.debug_print("candId:", candId) for symbol in withDecl: + # but all existing must be functions as well, + # otherwise we declare it to be a duplicate + if symbol.declaration.objectType != 'function': + handleDuplicateDeclaration(symbol, candSymbol) + # (not reachable) oldId = symbol.declaration.get_newest_id() if Symbol.debug_lookup: Symbol.debug_print("oldId: ", oldId) @@ -4370,7 +4375,11 @@ class Symbol: # if there is an empty symbol, fill that one if len(noDecl) == 0: if Symbol.debug_lookup: - Symbol.debug_print("no match, no empty, candSybmol is not None?:", candSymbol is not None) # NOQA + Symbol.debug_print("no match, no empty") + if candSymbol is not None: + Symbol.debug_print("result is already created candSymbol") + else: + Symbol.debug_print("result is makeCandSymbol()") Symbol.debug_indent -= 2 if candSymbol is not None: return candSymbol @@ -6814,10 +6823,12 @@ class CPPObject(ObjectDescription): parentSymbol = env.temp_data['cpp:parent_symbol'] parentDecl = parentSymbol.declaration if parentDecl is not None and parentDecl.objectType == 'function': - logger.warning("C++ declarations inside functions are not supported." + - " Parent function is " + - str(parentSymbol.get_full_nested_name()), - location=self.get_source_info()) + msg = "C++ declarations inside functions are not supported." \ + " Parent function: {}\nDirective name: {}\nDirective arg: {}" + logger.warning(msg.format( + str(parentSymbol.get_full_nested_name()), + self.name, self.arguments[0] + ), location=self.get_source_info()) name = _make_phony_error_name() symbol = parentSymbol.add_name(name) env.temp_data['cpp:last_symbol'] = symbol diff --git a/sphinx/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py index b68fc73c9..978fd5df8 100644 --- a/sphinx/ext/autodoc/__init__.py +++ b/sphinx/ext/autodoc/__init__.py @@ -1764,7 +1764,7 @@ class TypeVarDocumenter(DataDocumenter): @classmethod def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any ) -> bool: - return isinstance(member, TypeVar) and isattr # type: ignore + return isinstance(member, TypeVar) and isattr def add_directive_header(self, sig: str) -> None: self.options = Options(self.options) diff --git a/sphinx/ext/autodoc/importer.py b/sphinx/ext/autodoc/importer.py index 133ce1439..52b07639a 100644 --- a/sphinx/ext/autodoc/importer.py +++ b/sphinx/ext/autodoc/importer.py @@ -206,7 +206,10 @@ def get_object_members(subject: Any, objpath: List[str], attrgetter: Callable, if isclass(subject) and getattr(subject, '__slots__', None) is not None: from sphinx.ext.autodoc import SLOTSATTR - for name in subject.__slots__: + slots = subject.__slots__ + if isinstance(slots, str): + slots = [slots] + for name in slots: members[name] = Attribute(name, True, SLOTSATTR) # other members diff --git a/sphinx/search/__init__.py b/sphinx/search/__init__.py index 4534dd333..048b333d5 100644 --- a/sphinx/search/__init__.py +++ b/sphinx/search/__init__.py @@ -297,8 +297,8 @@ class IndexBuilder: frozen.get('envversion') != self.env.version: raise ValueError('old format') index2fn = frozen['docnames'] - self._filenames = dict(zip(index2fn, frozen['filenames'])) # type: ignore - self._titles = dict(zip(index2fn, frozen['titles'])) # type: ignore + self._filenames = dict(zip(index2fn, frozen['filenames'])) + self._titles = dict(zip(index2fn, frozen['titles'])) def load_terms(mapping: Dict[str, Any]) -> Dict[str, Set[str]]: rv = {} @@ -359,13 +359,13 @@ class IndexBuilder: def get_terms(self, fn2index: Dict) -> Tuple[Dict[str, List[str]], Dict[str, List[str]]]: rvs = {}, {} # type: Tuple[Dict[str, List[str]], Dict[str, List[str]]] for rv, mapping in zip(rvs, (self._mapping, self._title_mapping)): - for k, v in mapping.items(): # type: ignore + for k, v in mapping.items(): if len(v) == 1: fn, = v if fn in fn2index: - rv[k] = fn2index[fn] # type: ignore + rv[k] = fn2index[fn] else: - rv[k] = sorted([fn2index[fn] for fn in v if fn in fn2index]) # type: ignore # NOQA + rv[k] = sorted([fn2index[fn] for fn in v if fn in fn2index]) return rvs def freeze(self) -> Dict[str, Any]: diff --git a/sphinx/themes/basic/static/basic.css_t b/sphinx/themes/basic/static/basic.css_t index 0a71a7a91..38b9fb553 100644 --- a/sphinx/themes/basic/static/basic.css_t +++ b/sphinx/themes/basic/static/basic.css_t @@ -764,6 +764,7 @@ div.code-block-caption code { } table.highlighttable td.linenos, +span.linenos, div.doctest > div.highlight span.gp { /* gp: Generic.Prompt */ user-select: none; } diff --git a/sphinx/util/typing.py b/sphinx/util/typing.py index 8eca67220..76438889b 100644 --- a/sphinx/util/typing.py +++ b/sphinx/util/typing.py @@ -57,7 +57,7 @@ Inventory = Dict[str, Dict[str, Tuple[str, str, str, str]]] def is_system_TypeVar(typ: Any) -> bool: """Check *typ* is system defined TypeVar.""" modname = getattr(typ, '__module__', '') - return modname == 'typing' and isinstance(typ, TypeVar) # type: ignore + return modname == 'typing' and isinstance(typ, TypeVar) def stringify(annotation: Any) -> str: @@ -68,7 +68,7 @@ def stringify(annotation: Any) -> str: return annotation[1:-2] else: return annotation - elif isinstance(annotation, TypeVar): # type: ignore + elif isinstance(annotation, TypeVar): return annotation.__name__ elif not annotation: return repr(annotation) diff --git a/sphinx/writers/texinfo.py b/sphinx/writers/texinfo.py index 5ad2831dd..a33d3363f 100644 --- a/sphinx/writers/texinfo.py +++ b/sphinx/writers/texinfo.py @@ -369,7 +369,7 @@ class TexinfoTranslator(SphinxTranslator): """Return an escaped string suitable for use as an argument to a Texinfo command.""" s = self.escape(s) - # commas are the argument delimeters + # commas are the argument delimiters s = s.replace(',', '@comma{}') # normalize white space s = ' '.join(s.split()).strip() diff --git a/tests/roots/test-domain-c/function_param_target.rst b/tests/roots/test-domain-c/function_param_target.rst new file mode 100644 index 000000000..05de01445 --- /dev/null +++ b/tests/roots/test-domain-c/function_param_target.rst @@ -0,0 +1,5 @@ +.. c:function:: void f(int i) + + - :c:var:`i` + +- :c:var:`f.i` diff --git a/tests/roots/test-ext-autodoc/target/slots.py b/tests/roots/test-ext-autodoc/target/slots.py index 44e750320..17e469cac 100644 --- a/tests/roots/test-ext-autodoc/target/slots.py +++ b/tests/roots/test-ext-autodoc/target/slots.py @@ -2,6 +2,10 @@ class Foo: __slots__ = ['attr'] +class FooSingleString: + __slots__ = 'attr' + + class Bar: __slots__ = {'attr1': 'docstring of attr1', 'attr2': 'docstring of attr2', diff --git a/tests/roots/test-root/conf.py b/tests/roots/test-root/conf.py index b3cc12ae0..c3a890045 100644 --- a/tests/roots/test-root/conf.py +++ b/tests/roots/test-root/conf.py @@ -32,8 +32,6 @@ pygments_style = 'sphinx' show_authors = True numfig = True -rst_epilog = '.. |subst| replace:: global substitution' - html_sidebars = {'**': ['localtoc.html', 'relations.html', 'sourcelink.html', 'customsb.html', 'searchbox.html'], 'index': ['contentssb.html', 'localtoc.html', 'globaltoc.html']} diff --git a/tests/roots/test-root/markup.txt b/tests/roots/test-root/markup.txt index e6adef55e..7cd3e95ea 100644 --- a/tests/roots/test-root/markup.txt +++ b/tests/roots/test-root/markup.txt @@ -22,7 +22,9 @@ Meta markup Generic reST ------------ -A |subst| (the definition is in rst_epilog). +A |subst|! + +.. |subst| replace:: global substitution .. highlight:: none diff --git a/tests/test_build_html.py b/tests/test_build_html.py index 8cd541481..1f9f8eac7 100644 --- a/tests/test_build_html.py +++ b/tests/test_build_html.py @@ -258,7 +258,7 @@ def test_html4_output(app, status, warning): (".//pre/strong", 'try_stmt'), (".//pre/a[@href='#grammar-token-try1_stmt']/code/span", 'try1_stmt'), # tests for ``only`` directive - (".//p", 'A global substitution.'), + (".//p", 'A global substitution!'), (".//p", 'In HTML.'), (".//p", 'In both.'), (".//p", 'Always present'), diff --git a/tests/test_domain_c.py b/tests/test_domain_c.py index b6f72287e..43d71f74e 100644 --- a/tests/test_domain_c.py +++ b/tests/test_domain_c.py @@ -9,6 +9,8 @@ """ import pytest +from xml.etree import ElementTree + from sphinx import addnodes from sphinx.addnodes import desc from sphinx.domains.c import DefinitionParser, DefinitionError @@ -529,6 +531,25 @@ def filter_warnings(warning, file): return res +def extract_role_links(app, filename): + t = (app.outdir / filename).read_text() + lis = [l for l in t.split('\n') if l.startswith("<li")] + entries = [] + for l in lis: + li = ElementTree.fromstring(l) + aList = list(li.iter('a')) + assert len(aList) == 1 + a = aList[0] + target = a.attrib['href'].lstrip('#') + title = a.attrib['title'] + assert len(a) == 1 + code = a[0] + assert code.tag == 'code' + text = ''.join(code.itertext()) + entries.append((target, title, text)) + return entries + + @pytest.mark.sphinx(testroot='domain-c', confoverrides={'nitpicky': True}) def test_build_domain_c(app, status, warning): app.builder.build_all() @@ -562,6 +583,26 @@ def test_build_domain_c_semicolon(app, status, warning): assert len(ws) == 0 +@pytest.mark.sphinx(testroot='domain-c', confoverrides={'nitpicky': True}) +def test_build_function_param_target(app, warning): + # the anchor for function parameters should be the function + app.builder.build_all() + ws = filter_warnings(warning, "function_param_target") + assert len(ws) == 0 + entries = extract_role_links(app, "function_param_target.html") + assert entries == [ + ('c.f', 'i', 'i'), + ('c.f', 'f.i', 'f.i'), + ] + + +def _get_obj(app, queryName): + domain = app.env.get_domain('c') + for name, dispname, objectType, docname, anchor, prio in domain.get_objects(): + if name == queryName: + return (docname, anchor, objectType) + return (queryName, "not", "found") + def test_cfunction(app): text = (".. c:function:: PyObject* " "PyType_GenericAlloc(PyTypeObject *type, Py_ssize_t nitems)") @@ -569,8 +610,7 @@ def test_cfunction(app): assert_node(doctree[1], addnodes.desc, desctype="function", domain="c", objtype="function", noindex=False) - domain = app.env.get_domain('c') - entry = domain.objects.get('PyType_GenericAlloc') + entry = _get_obj(app, 'PyType_GenericAlloc') assert entry == ('index', 'c.PyType_GenericAlloc', 'function') @@ -580,8 +620,7 @@ def test_cmember(app): assert_node(doctree[1], addnodes.desc, desctype="member", domain="c", objtype="member", noindex=False) - domain = app.env.get_domain('c') - entry = domain.objects.get('PyTypeObject.tp_bases') + entry = _get_obj(app, 'PyTypeObject.tp_bases') assert entry == ('index', 'c.PyTypeObject.tp_bases', 'member') @@ -591,9 +630,8 @@ def test_cvar(app): assert_node(doctree[1], addnodes.desc, desctype="var", domain="c", objtype="var", noindex=False) - domain = app.env.get_domain('c') - entry = domain.objects.get('PyClass_Type') - assert entry == ('index', 'c.PyClass_Type', 'var') + entry = _get_obj(app, 'PyClass_Type') + assert entry == ('index', 'c.PyClass_Type', 'member') def test_noindexentry(app): diff --git a/tests/test_domain_cpp.py b/tests/test_domain_cpp.py index 558d69911..ec7a2b262 100644 --- a/tests/test_domain_cpp.py +++ b/tests/test_domain_cpp.py @@ -1236,3 +1236,18 @@ def test_noindexentry(app): assert_node(doctree, (addnodes.index, desc, addnodes.index, desc)) assert_node(doctree[0], addnodes.index, entries=[('single', 'f (C++ function)', '_CPPv41fv', '', None)]) assert_node(doctree[2], addnodes.index, entries=[]) + + +def test_mix_decl_duplicate(app, warning): + # Issue 8270 + text = (".. cpp:struct:: A\n" + ".. cpp:function:: void A()\n" + ".. cpp:struct:: A\n") + restructuredtext.parse(app, text) + ws = warning.getvalue().split("\n") + assert len(ws) == 5 + assert "index.rst:2: WARNING: Duplicate C++ declaration, also defined in 'index'." in ws[0] + assert "Declaration is 'void A()'." in ws[1] + assert "index.rst:3: WARNING: Duplicate C++ declaration, also defined in 'index'." in ws[2] + assert "Declaration is 'A'." in ws[3] + assert ws[4] == ""
\ No newline at end of file diff --git a/tests/test_domain_py.py b/tests/test_domain_py.py index 8040af9cc..ceea51508 100644 --- a/tests/test_domain_py.py +++ b/tests/test_domain_py.py @@ -313,7 +313,7 @@ def test_pyfunction_signature(app): def test_pyfunction_signature_full(app): text = (".. py:function:: hello(a: str, b = 1, *args: str, " - "c: bool = True, **kwargs: str) -> str") + "c: bool = True, d: tuple = (1, 2), **kwargs: str) -> str") doctree = restructuredtext.parse(app, text) assert_node(doctree, (addnodes.index, [desc, ([desc_signature, ([desc_name, "hello"], @@ -343,6 +343,14 @@ def test_pyfunction_signature_full(app): [desc_sig_operator, "="], " ", [nodes.inline, "True"])], + [desc_parameter, ([desc_sig_name, "d"], + [desc_sig_punctuation, ":"], + " ", + [desc_sig_name, pending_xref, "tuple"], + " ", + [desc_sig_operator, "="], + " ", + [nodes.inline, "(1, 2)"])], [desc_parameter, ([desc_sig_operator, "**"], [desc_sig_name, "kwargs"], [desc_sig_punctuation, ":"], diff --git a/tests/test_ext_autodoc.py b/tests/test_ext_autodoc.py index 9cb54de5b..c0676f23f 100644 --- a/tests/test_ext_autodoc.py +++ b/tests/test_ext_autodoc.py @@ -1205,6 +1205,14 @@ def test_slots(app): ' .. py:attribute:: Foo.attr', ' :module: target.slots', '', + '', + '.. py:class:: FooSingleString()', + ' :module: target.slots', + '', + '', + ' .. py:attribute:: FooSingleString.attr', + ' :module: target.slots', + '', ] |
