summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAdam Turner <9087854+aa-turner@users.noreply.github.com>2022-09-25 21:36:30 +0100
committerAdam Turner <9087854+aa-turner@users.noreply.github.com>2022-09-25 21:36:30 +0100
commitf01d50d695bf55f1af34b87c4e6c84f76dd9a36d (patch)
treee98dc537312410d5661ae76c72306d3a8d2a8e49
parent7ad0fcf22dbff5ee80a644e46429ff2ec3f25980 (diff)
parenteb5b3aa25dbc918c8250a2336aaaf2b753ad36b5 (diff)
downloadsphinx-git-f01d50d695bf55f1af34b87c4e6c84f76dd9a36d.tar.gz
Merge branch '5.x'
# Conflicts: # CHANGES # doc/conf.py # sphinx/__init__.py # sphinx/builders/html/__init__.py # sphinx/domains/python.py # tests/test_build_html.py
-rw-r--r--CHANGES73
-rw-r--r--doc/changes.rst7
-rw-r--r--doc/conf.py1
-rw-r--r--doc/extdev/deprecated.rst5
-rw-r--r--doc/latex.rst6
-rw-r--r--karma.conf.js2
-rw-r--r--pyproject.toml25
-rw-r--r--sphinx/addnodes.py43
-rw-r--r--sphinx/application.py90
-rw-r--r--sphinx/builders/__init__.py4
-rw-r--r--sphinx/builders/html/__init__.py13
-rw-r--r--sphinx/builders/latex/transforms.py2
-rw-r--r--sphinx/domains/std.py6
-rw-r--r--sphinx/ext/autodoc/__init__.py3
-rw-r--r--sphinx/ext/autosummary/__init__.py3
-rw-r--r--sphinx/ext/doctest.py10
-rw-r--r--sphinx/ext/viewcode.py2
-rw-r--r--sphinx/extension.py9
-rw-r--r--sphinx/highlighting.py2
-rw-r--r--sphinx/search/__init__.py8
-rw-r--r--sphinx/themes/basic/static/doctools.js130
-rw-r--r--sphinx/themes/basic/static/searchtools.js31
-rw-r--r--sphinx/themes/basic/static/sphinx_highlight.js144
-rw-r--r--sphinx/transforms/post_transforms/code.py4
-rw-r--r--sphinx/writers/html.py1
-rw-r--r--tests/js/documentation_options.js1
-rw-r--r--tests/js/sphinx_highlight.js (renamed from tests/js/doctools.js)2
-rw-r--r--tests/roots/test-environment-record-dependencies/api.rst4
-rw-r--r--tests/roots/test-environment-record-dependencies/conf.py5
-rw-r--r--tests/roots/test-environment-record-dependencies/example_module.py2
-rw-r--r--tests/roots/test-environment-record-dependencies/index.rst3
-rw-r--r--tests/test_build_html.py13
-rw-r--r--tests/test_environment_record_dependencies.py10
-rw-r--r--tests/test_ext_doctest.py6
-rw-r--r--tests/test_highlighting.py23
-rw-r--r--utils/release-checklist10
36 files changed, 436 insertions, 267 deletions
diff --git a/CHANGES b/CHANGES
index ade0ffdaa..1b7f015cd 100644
--- a/CHANGES
+++ b/CHANGES
@@ -21,16 +21,12 @@ Bugs fixed
Testing
--------
-Release 5.2.0 (in development)
+Release 5.3.0 (in development)
==============================
Dependencies
------------
-* #10356: Sphinx now uses declarative metadata with ``pyproject.toml`` to
- create packages, using PyPA's ``build`` project as a build backend. Patch by
- Adam Turner.
-
Incompatible changes
--------------------
@@ -40,57 +36,72 @@ Deprecated
Features added
--------------
-* #10286: C++, support requires clauses not just between the template
- parameter lists and the declaration.
-* #10755: linkcheck: Check the source URL of raw directives that use the ``url``
- option.
-* #10781: Allow :rst:role:`ref` role to be used with definitions and fields.
-* #10717: HTML Search: Increase priority for full title and
- subtitle matches in search results
-* #10718: HTML Search: Save search result score to the HTML element for debugging
-* #10673: Make toctree accept 'genindex', 'modindex' and 'search' docnames
-* #6316, #10804: Add domain objects to the table of contents. Patch by Adam Turner
-* #6692: HTML Search: Include explicit :rst:dir:`index` directive index entries
- in the search index and search results. Patch by Adam Turner
-* #10816: imgmath: Allow embedding images in HTML as base64
-
Bugs fixed
----------
-* #10257: C++, ensure consistent non-specialization template argument
- representation.
-* #10729: C++, fix parsing of certain non-type template parameter packs.
-* #10715: Revert #10520: "Fix" use of sidebar classes in ``agogo.css_t``
-
Testing
--------
-Release 5.1.2 (in development)
-==============================
+Release 5.2.1 (released Sep 24, 2022)
+=====================================
+
+Bugs fixed
+----------
+
+* #10861: Always normalise the ``pycon3`` lexer to ``pycon``.
+* Fix using ``sphinx.ext.autosummary`` with modules containing titles in the
+ module-level docstring.
+
+Release 5.2.0.post0 (released Sep 24, 2022)
+===========================================
+
+* Recreated source tarballs for Debian maintainers.
+
+Release 5.2.0 (released Sep 24, 2022)
+=====================================
Dependencies
------------
-Incompatible changes
---------------------
+* #10356: Sphinx now uses declarative metadata with ``pyproject.toml`` to
+ create packages, using PyPA's ``flit`` project as a build backend. Patch by
+ Adam Turner.
Deprecated
----------
+* #10843: Support for HTML 4 output. Patch by Adam Turner.
+
Features added
--------------
* #10738: napoleon: Add support for docstring types using 'of', like
``type of type``. Example: ``tuple of int``.
+* #10286: C++, support requires clauses not just between the template
+ parameter lists and the declaration.
+* #10755: linkcheck: Check the source URL of raw directives that use the ``url``
+ option.
+* #10781: Allow :rst:role:`ref` role to be used with definitions and fields.
+* #10717: HTML Search: Increase priority for full title and
+ subtitle matches in search results
+* #10718: HTML Search: Save search result score to the HTML element for debugging
+* #10673: Make toctree accept 'genindex', 'modindex' and 'search' docnames
+* #6316, #10804: Add domain objects to the table of contents. Patch by Adam Turner
+* #6692: HTML Search: Include explicit :rst:dir:`index` directive index entries
+ in the search index and search results. Patch by Adam Turner
+* #10816: imgmath: Allow embedding images in HTML as base64
+* #10854: HTML Search: Use browser localstorage for highlight control, stop
+ storing highlight parameters in URL query strings. Patch by Adam Turner.
Bugs fixed
----------
* #10723: LaTeX: 5.1.0 has made the 'sphinxsetup' ``verbatimwithframe=false``
become without effect.
-
-Testing
---------
+* #10257: C++, ensure consistent non-specialization template argument
+ representation.
+* #10729: C++, fix parsing of certain non-type template parameter packs.
+* #10715: Revert #10520: "Fix" use of sidebar classes in ``agogo.css_t``
Release 5.1.1 (released Jul 26, 2022)
=====================================
diff --git a/doc/changes.rst b/doc/changes.rst
index 3fe902404..96853fe09 100644
--- a/doc/changes.rst
+++ b/doc/changes.rst
@@ -11,5 +11,12 @@ Changelog
.. raw:: latex
\addtocontents{toc}{\protect\setcounter{tocdepth}{1}}%
+ \makeatletter
+ \addtocontents{toc}%
+ {\def\string\l@section{\string\@dottedtocline{1}{1.5em}{3.3em}}}
+ \addtocontents{toc}%
+ {\def\string\l@subsection{\string\@dottedtocline{2}{4.8em}{4em}}}
+ \makeatother
+
.. include:: ../CHANGES
diff --git a/doc/conf.py b/doc/conf.py
index 58e8e6c27..f903fd570 100644
--- a/doc/conf.py
+++ b/doc/conf.py
@@ -48,6 +48,7 @@ epub_post_files = [('usage/installation.xhtml', 'Installing Sphinx'),
('develop.xhtml', 'Sphinx development')]
epub_exclude_files = ['_static/opensearch.xml', '_static/doctools.js',
'_static/searchtools.js',
+ '_static/sphinx_highlight.js',
'_static/basic.css',
'_static/language_data.js',
'search.html', '_static/websupport.js']
diff --git a/doc/extdev/deprecated.rst b/doc/extdev/deprecated.rst
index 18b0e6d04..8c850ceb6 100644
--- a/doc/extdev/deprecated.rst
+++ b/doc/extdev/deprecated.rst
@@ -22,6 +22,11 @@ The following is a list of deprecated interfaces.
- (will be) Removed
- Alternatives
+ * - HTML 4 support
+ - 5.2
+ - 7.0
+ - N/A
+
* - ``sphinx.util.path_stabilize``
- 5.1
- 7.0
diff --git a/doc/latex.rst b/doc/latex.rst
index 84dd2e433..7fa90fe89 100644
--- a/doc/latex.rst
+++ b/doc/latex.rst
@@ -1300,6 +1300,12 @@ Macros
``\sphinxtableofcontentshook``. This macro is also executed by the
``'howto'`` docclass, but defaults to empty with it.
+ .. hint::
+
+ If adding to preamble the loading of ``tocloft`` package, also add to
+ preamble ``\renewcommand\sphinxtableofcontentshook{}`` else it will reset
+ ``\l@section`` and ``\l@subsection`` cancelling ``tocloft`` customization.
+
- ``\sphinxmaketitle``: Used as the default setting of the ``'maketitle'``
:confval:`latex_elements` key.
Defined in the class files :file:`sphinxmanual.cls` and
diff --git a/karma.conf.js b/karma.conf.js
index 082584cf7..8a18e80ba 100644
--- a/karma.conf.js
+++ b/karma.conf.js
@@ -15,8 +15,10 @@ module.exports = function(config) {
// list of files / patterns to load in the browser
files: [
+ 'tests/js/documentation_options.js',
'sphinx/themes/basic/static/doctools.js',
'sphinx/themes/basic/static/searchtools.js',
+ 'sphinx/themes/basic/static/sphinx_highlight.js',
'tests/js/*.js'
],
diff --git a/pyproject.toml b/pyproject.toml
index 0119c99b8..8321a3310 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -150,6 +150,7 @@ show_error_context = true
strict_optional = true
warn_redundant_casts = true
warn_unused_ignores = true
+disallow_any_generics = true
[[tool.mypy.overrides]]
module = [
@@ -181,6 +182,30 @@ module = [
]
strict_optional = false
+[[tool.mypy.overrides]]
+module = [
+ "sphinx.application",
+ "sphinx.builders.*",
+ "sphinx.cmd.*",
+ "sphinx.config",
+ "sphinx.deprecation",
+ "sphinx.domains.*",
+ "sphinx.environment.*",
+ "sphinx.events",
+ "sphinx.ext.*",
+ "sphinx.highlighting",
+ "sphinx.jinja2glue",
+ "sphinx.locale",
+ "sphinx.pycode.*",
+ "sphinx.registry",
+ "sphinx.roles",
+ "sphinx.search.*",
+ "sphinx.testing.*",
+ "sphinx.util.*",
+ "sphinx.writers.*",
+]
+disallow_any_generics = false
+
[tool.pytest.ini_options]
filterwarnings = [
"all",
diff --git a/sphinx/addnodes.py b/sphinx/addnodes.py
index e23f570d7..e22ce5fa2 100644
--- a/sphinx/addnodes.py
+++ b/sphinx/addnodes.py
@@ -1,4 +1,4 @@
-"""Additional docutils nodes."""
+"""Document tree nodes that Sphinx defines on top of those in Docutils."""
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Sequence
@@ -119,7 +119,7 @@ class toctree(nodes.General, nodes.Element, translatable):
#############################################################
class _desc_classes_injector(nodes.Element, not_smartquotable):
- """Helper base class for injecting a fixes list of classes.
+ """Helper base class for injecting a fixed list of classes.
Use as the first base class.
"""
@@ -390,7 +390,7 @@ class index(nodes.Invisible, nodes.Inline, nodes.TextElement):
class centered(nodes.Part, nodes.TextElement):
- """Deprecated."""
+ """This node is deprecated."""
class acks(nodes.Element):
@@ -455,13 +455,18 @@ class pending_xref(nodes.Inline, nodes.Element):
class pending_xref_condition(nodes.Inline, nodes.TextElement):
- """Node for cross-references that are used to choose appropriate
- content of the reference by conditions on the resolving phase.
+ """Node representing a potential way to create a cross-reference and the
+ condition in which this way should be used.
- When the :py:class:`pending_xref` node contains one or more
- **pending_xref_condition** nodes, the cross-reference resolver
- should choose the content of the reference using defined conditions
- in ``condition`` attribute of each pending_xref_condition nodes::
+ This node is only allowed to be placed under a :py:class:`pending_xref`
+ node. A **pending_xref** node must contain either no **pending_xref_condition**
+ nodes or it must only contains **pending_xref_condition** nodes.
+
+ The cross-reference resolver will replace a :py:class:`pending_xref` which
+ contains **pending_xref_condition** nodes by the content of exactly one of
+ those **pending_xref_condition** nodes' content. It uses the **condition**
+ attribute to decide which **pending_xref_condition** node's content to
+ use. For example, let us consider how the cross-reference resolver acts on::
<pending_xref refdomain="py" reftarget="io.StringIO ...>
<pending_xref_condition condition="resolved">
@@ -471,32 +476,26 @@ class pending_xref_condition(nodes.Inline, nodes.TextElement):
<literal>
io.StringIO
- After the processing of cross-reference resolver, one of the content node
- under pending_xref_condition node is chosen by its condition and to be
- removed all of pending_xref_condition nodes::
+ If the cross-reference resolver successfully resolves the cross-reference,
+ then it rewrites the **pending_xref** as::
- # When resolved the cross-reference successfully
<reference>
<literal>
StringIO
- # When resolution is failed
+ Otherwise, if the cross-reference resolution failed, it rewrites the
+ **pending_xref** as::
+
<reference>
<literal>
io.StringIO
- .. note:: This node is only allowed to be placed under pending_xref node.
- It is not allows to place it under other nodes. In addition,
- pending_xref node must contain only pending_xref_condition
- nodes if it contains one or more pending_xref_condition nodes.
-
- The pending_xref_condition node should have **condition** attribute.
+ The **pending_xref_condition** node should have **condition** attribute.
Domains can be store their individual conditions into the attribute to
filter contents on resolving phase. As a reserved condition name,
``condition="*"`` is used for the fallback of resolution failure.
Additionally, as a recommended condition name, ``condition="resolved"``
- is used for the representation of resolstion success in the intersphinx
- module.
+ represents a resolution success in the intersphinx module.
.. versionadded:: 4.0
"""
diff --git a/sphinx/application.py b/sphinx/application.py
index 254678569..8a349ee48 100644
--- a/sphinx/application.py
+++ b/sphinx/application.py
@@ -636,8 +636,9 @@ class Sphinx:
:param name: The name of the directive
:param cls: A directive class
- :param override: If true, install the directive forcedly even if another directive
+ :param override: If false, do not install it if another directive
is already installed as the same name
+ If true, unconditionally install the directive.
For example, a custom directive named ``my-directive`` would be added
like this:
@@ -684,8 +685,9 @@ class Sphinx:
:param name: The name of role
:param role: A role function
- :param override: If true, install the role forcedly even if another role is already
- installed as the same name
+ :param override: If false, do not install it if another role
+ is already installed as the same name
+ If true, unconditionally install the role.
For more details about role functions, see `the Docutils docs
<https://docutils.sourceforge.io/docs/howto/rst-roles.html>`__ .
@@ -705,8 +707,9 @@ class Sphinx:
Register a Docutils role that does nothing but wrap its contents in the
node given by *nodeclass*.
- If *override* is True, the given *nodeclass* is forcedly installed even if
- a role named as *name* is already installed.
+ :param override: If false, do not install it if another role
+ is already installed as the same name
+ If true, unconditionally install the role.
.. versionadded:: 0.6
.. versionchanged:: 1.8
@@ -725,8 +728,9 @@ class Sphinx:
"""Register a domain.
:param domain: A domain class
- :param override: If true, install the domain forcedly even if another domain
+ :param override: If false, do not install it if another domain
is already installed as the same name
+ If true, unconditionally install the domain.
.. versionadded:: 1.0
.. versionchanged:: 1.8
@@ -744,8 +748,9 @@ class Sphinx:
:param domain: The name of target domain
:param name: A name of directive
:param cls: A directive class
- :param override: If true, install the directive forcedly even if another directive
+ :param override: If false, do not install it if another directive
is already installed as the same name
+ If true, unconditionally install the directive.
.. versionadded:: 1.0
.. versionchanged:: 1.8
@@ -763,8 +768,9 @@ class Sphinx:
:param domain: The name of the target domain
:param name: The name of the role
:param role: The role function
- :param override: If true, install the role forcedly even if another role is already
- installed as the same name
+ :param override: If false, do not install it if another role
+ is already installed as the same name
+ If true, unconditionally install the role.
.. versionadded:: 1.0
.. versionchanged:: 1.8
@@ -780,8 +786,9 @@ class Sphinx:
:param domain: The name of the target domain
:param index: The index class
- :param override: If true, install the index forcedly even if another index is
- already installed as the same name
+ :param override: If false, do not install it if another index
+ is already installed as the same name
+ If true, unconditionally install the index.
.. versionadded:: 1.0
.. versionchanged:: 1.8
@@ -886,8 +893,10 @@ class Sphinx:
(Of course, the element following the ``topic`` directive needn't be a
section.)
- If *override* is True, the given crossref_type is forcedly installed even if
- a crossref_type having the same name is already installed.
+
+ :param override: If false, do not install it if another cross-reference type
+ is already installed as the same name
+ If true, unconditionally install the cross-reference type.
.. versionchanged:: 1.8
Add *override* keyword.
@@ -946,20 +955,22 @@ class Sphinx:
loading_method: Optional[str] = None, **kwargs: Any) -> None:
"""Register a JavaScript file to include in the HTML output.
- :param filename: The filename of the JavaScript file. It must be relative to the HTML
- static path, a full URI with scheme, or ``None`` value. The ``None``
- value is used to create inline ``<script>`` tag. See the description
- of *kwargs* below.
- :param priority: The priority to determine the order of ``<script>`` tag for
- JavaScript files. See list of "prority range for JavaScript
- files" below. If the priority of the JavaScript files it the same
- as others, the JavaScript files will be loaded in order of
- registration.
- :param loading_method: The loading method of the JavaScript file. ``'async'`` or
- ``'defer'`` is allowed.
- :param kwargs: Extra keyword arguments are included as attributes of the ``<script>``
- tag. A special keyword argument ``body`` is given, its value will be
- added between the ``<script>`` tag.
+ :param filename: The name of a JavaScript file that the default HTML
+ template will include. It must be relative to the HTML
+ static path, or a full URI with scheme, or ``None`` .
+ The ``None`` value is used to create an inline
+ ``<script>`` tag. See the description of *kwargs*
+ below.
+ :param priority: Files are included in ascending order of priority. If
+ multiple JavaScript files have the same priority,
+ those files will be included in order of registration.
+ See list of "prority range for JavaScript files" below.
+ :param loading_method: The loading method for the JavaScript file.
+ Either ``'async'`` or ``'defer'`` are allowed.
+ :param kwargs: Extra keyword arguments are included as attributes of the
+ ``<script>`` tag. If the special keyword argument
+ ``body`` is given, its value will be added as the content
+ of the ``<script>`` tag.
Example::
@@ -1012,14 +1023,15 @@ class Sphinx:
def add_css_file(self, filename: str, priority: int = 500, **kwargs: Any) -> None:
"""Register a stylesheet to include in the HTML output.
- :param filename: The filename of the CSS file. It must be relative to the HTML
+ :param filename: The name of a CSS file that the default HTML
+ template will include. It must be relative to the HTML
static path, or a full URI with scheme.
- :param priority: The priority to determine the order of ``<link>`` tag for the
- CSS files. See list of "prority range for CSS files" below.
- If the priority of the CSS files it the same as others, the
- CSS files will be loaded in order of registration.
- :param kwargs: Extra keyword arguments are included as attributes of the ``<link>``
- tag.
+ :param priority: Files are included in ascending order of priority. If
+ multiple CSS files have the same priority,
+ those files will be included in order of registration.
+ See list of "prority range for CSS files" below.
+ :param kwargs: Extra keyword arguments are included as attributes of the
+ ``<link>`` tag.
Example::
@@ -1167,8 +1179,9 @@ class Sphinx:
Same as :confval:`source_suffix`. The users can override this
using the config setting.
- If *override* is True, the given *suffix* is forcedly installed even if
- the same suffix is already installed.
+ :param override: If false, do not install it the same suffix
+ is already installed.
+ If true, unconditionally install the suffix.
.. versionadded:: 1.8
"""
@@ -1177,8 +1190,9 @@ class Sphinx:
def add_source_parser(self, parser: Type[Parser], override: bool = False) -> None:
"""Register a parser class.
- If *override* is True, the given *parser* is forcedly installed even if
- a parser for the same suffix is already installed.
+ :param override: If false, do not install it if another parser
+ is already installed for the same suffix.
+ If true, unconditionally install the parser.
.. versionadded:: 1.4
.. versionchanged:: 1.8
diff --git a/sphinx/builders/__init__.py b/sphinx/builders/__init__.py
index 2aede5c24..dd0c4328e 100644
--- a/sphinx/builders/__init__.py
+++ b/sphinx/builders/__init__.py
@@ -10,6 +10,7 @@ from typing import (TYPE_CHECKING, Any, Dict, Iterable, List, Optional, Sequence
from docutils import nodes
from docutils.nodes import Node
+from docutils.utils import DependencyList
from sphinx.config import Config
from sphinx.deprecation import RemovedInSphinx70Warning
@@ -490,6 +491,9 @@ class Builder:
filename = self.env.doc2path(docname)
filetype = get_filetype(self.app.config.source_suffix, filename)
publisher = self.app.registry.get_publisher(self.app, filetype)
+ # record_dependencies is mutable even though it is in settings,
+ # explicitly re-initialise for each document
+ publisher.settings.record_dependencies = DependencyList()
with sphinx_domains(self.env), rst.default_role(docname, self.config.default_role):
# set up error_handler for the target document
codecs.register_error('sphinx', UnicodeDecodeErrorHandler(docname)) # type: ignore
diff --git a/sphinx/builders/html/__init__.py b/sphinx/builders/html/__init__.py
index 42e1108c1..71952199b 100644
--- a/sphinx/builders/html/__init__.py
+++ b/sphinx/builders/html/__init__.py
@@ -349,6 +349,7 @@ class StandaloneHTMLBuilder(Builder):
self.add_js_file('documentation_options.js', id="documentation_options",
data_url_root='', priority=200)
self.add_js_file('doctools.js', priority=200)
+ self.add_js_file('sphinx_highlight.js', priority=200)
for filename, attrs in self.app.registry.js_files:
self.add_js_file(filename, **attrs)
@@ -369,7 +370,7 @@ class StandaloneHTMLBuilder(Builder):
@property
def default_translator_class(self) -> Type[nodes.NodeVisitor]: # type: ignore
if self.config.html4_writer:
- return HTMLTranslator
+ return HTMLTranslator # RemovedInSphinx70Warning
else:
return HTML5Translator
@@ -1303,6 +1304,15 @@ def validate_html_favicon(app: Sphinx, config: Config) -> None:
config.html_favicon = None # type: ignore
+def deprecate_html_4(_app: Sphinx, config: Config) -> None:
+ """Warn on HTML 4."""
+ # RemovedInSphinx70Warning
+ if config.html4_writer:
+ logger.warning(_('Support for emitting HTML 4 output is deprecated and '
+ 'will be removed in Sphinx 7. ("html4_writer=True '
+ 'detected in configuration options)'))
+
+
# for compatibility
import sphinxcontrib.serializinghtml # NOQA
@@ -1375,6 +1385,7 @@ def setup(app: Sphinx) -> Dict[str, Any]:
app.connect('config-inited', validate_html_static_path, priority=800)
app.connect('config-inited', validate_html_logo, priority=800)
app.connect('config-inited', validate_html_favicon, priority=800)
+ app.connect('config-inited', deprecate_html_4, priority=800)
app.connect('builder-inited', validate_math_renderer)
app.connect('html-page-context', setup_css_tag_helper)
app.connect('html-page-context', setup_js_tag_helper)
diff --git a/sphinx/builders/latex/transforms.py b/sphinx/builders/latex/transforms.py
index e996250f7..de3f3ff03 100644
--- a/sphinx/builders/latex/transforms.py
+++ b/sphinx/builders/latex/transforms.py
@@ -30,7 +30,7 @@ class FootnoteDocnameUpdater(SphinxTransform):
class SubstitutionDefinitionsRemover(SphinxPostTransform):
- """Remove ``substitution_definition node from doctrees."""
+ """Remove ``substitution_definition`` nodes from doctrees."""
# should be invoked after Substitutions process
default_priority = Substitutions.default_priority + 1
diff --git a/sphinx/domains/std.py b/sphinx/domains/std.py
index 6218de6ab..68cab115c 100644
--- a/sphinx/domains/std.py
+++ b/sphinx/domains/std.py
@@ -493,12 +493,12 @@ class ProductionList(SphinxDirective):
lines = nl_escape_re.sub('', self.arguments[0]).split('\n')
productionGroup = ""
- i = 0
+ first_rule_seen = False
for rule in lines:
- if i == 0 and ':' not in rule:
+ if not first_rule_seen and ':' not in rule:
productionGroup = rule.strip()
continue
- i += 1
+ first_rule_seen = True
try:
name, tokens = rule.split(':', 1)
except ValueError:
diff --git a/sphinx/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py
index 511cb0cfb..cb230f3e2 100644
--- a/sphinx/ext/autodoc/__init__.py
+++ b/sphinx/ext/autodoc/__init__.py
@@ -939,6 +939,7 @@ class ModuleDocumenter(Documenter):
objtype = 'module'
content_indent = ''
titles_allowed = True
+ _extra_indent = ' '
option_spec: OptionSpec = {
'members': members_option, 'undoc-members': bool_option,
@@ -958,7 +959,7 @@ class ModuleDocumenter(Documenter):
def add_content(self, more_content: Optional[StringList]) -> None:
old_indent = self.indent
- self.indent += ' '
+ self.indent += self._extra_indent
super().add_content(None)
self.indent = old_indent
if more_content:
diff --git a/sphinx/ext/autosummary/__init__.py b/sphinx/ext/autosummary/__init__.py
index 9edb58505..3cb2e54a7 100644
--- a/sphinx/ext/autosummary/__init__.py
+++ b/sphinx/ext/autosummary/__init__.py
@@ -362,6 +362,9 @@ class Autosummary(SphinxDirective):
# -- Grab the summary
+ # bodge for ModuleDocumenter
+ documenter._extra_indent = '' # type: ignore[attr-defined]
+
documenter.add_content(None)
summary = extract_summary(self.bridge.result.data[:], self.state.document)
diff --git a/sphinx/ext/doctest.py b/sphinx/ext/doctest.py
index 8e060907f..966ef7084 100644
--- a/sphinx/ext/doctest.py
+++ b/sphinx/ext/doctest.py
@@ -94,15 +94,9 @@ class TestDirective(SphinxDirective):
# only save if it differs from code
node['test'] = test
if self.name == 'doctest':
- if self.config.highlight_language in ('py', 'python'):
- node['language'] = 'pycon'
- else:
- node['language'] = 'pycon3' # default
+ node['language'] = 'pycon'
elif self.name == 'testcode':
- if self.config.highlight_language in ('py', 'python'):
- node['language'] = 'python'
- else:
- node['language'] = 'python3' # default
+ node['language'] = 'python'
elif self.name == 'testoutput':
# don't try to highlight output
node['language'] = 'none'
diff --git a/sphinx/ext/viewcode.py b/sphinx/ext/viewcode.py
index 279997b43..d61837945 100644
--- a/sphinx/ext/viewcode.py
+++ b/sphinx/ext/viewcode.py
@@ -244,7 +244,7 @@ def collect_pages(app: Sphinx) -> Generator[Tuple[str, Dict[str, Any], str], Non
# construct a page name for the highlighted source
pagename = posixpath.join(OUTPUT_DIRNAME, modname.replace('.', '/'))
# highlight the source using the builder's highlighter
- if env.config.highlight_language in ('python3', 'default', 'none'):
+ if env.config.highlight_language in {'default', 'none'}:
lexer = env.config.highlight_language
else:
lexer = 'python'
diff --git a/sphinx/extension.py b/sphinx/extension.py
index 356b4ab9d..2a984f5b4 100644
--- a/sphinx/extension.py
+++ b/sphinx/extension.py
@@ -34,7 +34,14 @@ class Extension:
def verify_needs_extensions(app: "Sphinx", config: Config) -> None:
- """Verify the required Sphinx extensions are loaded."""
+ """Check that extensions mentioned in :confval:`needs_extensions` satisfy the version
+ requirement, and warn if an extension is not loaded.
+
+ Warns if an extension in :confval:`needs_extension` is not loaded.
+
+ :raises VersionRequirementError: if the version of an extension in
+ :confval:`needs_extension` is unknown or older than the required version.
+ """
if config.needs_extensions is None:
return
diff --git a/sphinx/highlighting.py b/sphinx/highlighting.py
index 4a737eb40..7716f2a12 100644
--- a/sphinx/highlighting.py
+++ b/sphinx/highlighting.py
@@ -120,6 +120,8 @@ class PygmentsBridge:
lang = 'pycon'
else:
lang = 'python'
+ if lang == 'pycon3':
+ lang = 'pycon'
if lang in lexers:
# just return custom lexers here (without installing raiseonerror filter)
diff --git a/sphinx/search/__init__.py b/sphinx/search/__init__.py
index 0cff8fd25..5330d7e7c 100644
--- a/sphinx/search/__init__.py
+++ b/sphinx/search/__init__.py
@@ -290,9 +290,11 @@ class IndexBuilder:
self._titles = dict(zip(index2fn, frozen['titles']))
self._all_titles = {}
+ for docname in self._titles.keys():
+ self._all_titles[docname] = []
for title, doc_tuples in frozen['alltitles'].items():
for doc, titleid in doc_tuples:
- self._all_titles.setdefault(index2fn[doc], []).append((title, titleid))
+ self._all_titles[index2fn[doc]].append((title, titleid))
def load_terms(mapping: Dict[str, Any]) -> Dict[str, Set[str]]:
rv = {}
@@ -380,12 +382,12 @@ class IndexBuilder:
alltitles: Dict[str, List[Tuple[int, str]]] = {}
for docname, titlelist in self._all_titles.items():
for title, titleid in titlelist:
- alltitles.setdefault(title, []).append((fn2index[docname], titleid))
+ alltitles.setdefault(title, []).append((fn2index[docname], titleid))
index_entries: Dict[str, List[Tuple[int, str]]] = {}
for docname, entries in self._index_entries.items():
for entry, entry_id, main_entry in entries:
- index_entries.setdefault(entry.lower(), []).append((fn2index[docname], entry_id))
+ index_entries.setdefault(entry.lower(), []).append((fn2index[docname], entry_id))
return dict(docnames=docnames, filenames=filenames, titles=titles, terms=terms,
objects=objects, objtypes=objtypes, objnames=objnames,
diff --git a/sphinx/themes/basic/static/doctools.js b/sphinx/themes/basic/static/doctools.js
index c3db08d1c..527b876ca 100644
--- a/sphinx/themes/basic/static/doctools.js
+++ b/sphinx/themes/basic/static/doctools.js
@@ -10,6 +10,13 @@
*/
"use strict";
+const BLACKLISTED_KEY_CONTROL_ELEMENTS = new Set([
+ "TEXTAREA",
+ "INPUT",
+ "SELECT",
+ "BUTTON",
+]);
+
const _ready = (callback) => {
if (document.readyState !== "loading") {
callback();
@@ -19,72 +26,10 @@ const _ready = (callback) => {
};
/**
- * highlight a given string on a node by wrapping it in
- * span elements with the given class name.
- */
-const _highlight = (node, addItems, text, className) => {
- if (node.nodeType === Node.TEXT_NODE) {
- const val = node.nodeValue;
- const parent = node.parentNode;
- const pos = val.toLowerCase().indexOf(text);
- if (
- pos >= 0 &&
- !parent.classList.contains(className) &&
- !parent.classList.contains("nohighlight")
- ) {
- let span;
-
- const closestNode = parent.closest("body, svg, foreignObject");
- const isInSVG = closestNode && closestNode.matches("svg");
- if (isInSVG) {
- span = document.createElementNS("http://www.w3.org/2000/svg", "tspan");
- } else {
- span = document.createElement("span");
- span.classList.add(className);
- }
-
- span.appendChild(document.createTextNode(val.substr(pos, text.length)));
- parent.insertBefore(
- span,
- parent.insertBefore(
- document.createTextNode(val.substr(pos + text.length)),
- node.nextSibling
- )
- );
- node.nodeValue = val.substr(0, pos);
-
- if (isInSVG) {
- const rect = document.createElementNS(
- "http://www.w3.org/2000/svg",
- "rect"
- );
- const bbox = parent.getBBox();
- rect.x.baseVal.value = bbox.x;
- rect.y.baseVal.value = bbox.y;
- rect.width.baseVal.value = bbox.width;
- rect.height.baseVal.value = bbox.height;
- rect.setAttribute("class", className);
- addItems.push({ parent: parent, target: rect });
- }
- }
- } else if (node.matches && !node.matches("button, select, textarea")) {
- node.childNodes.forEach((el) => _highlight(el, addItems, text, className));
- }
-};
-const _highlightText = (thisNode, text, className) => {
- let addItems = [];
- _highlight(thisNode, addItems, text, className);
- addItems.forEach((obj) =>
- obj.parent.insertAdjacentElement("beforebegin", obj.target)
- );
-};
-
-/**
* Small JavaScript module for the documentation.
*/
const Documentation = {
init: () => {
- Documentation.highlightSearchWords();
Documentation.initDomainIndexTable();
Documentation.initOnKeyListeners();
},
@@ -127,51 +72,6 @@ const Documentation = {
},
/**
- * highlight the search words provided in the url in the text
- */
- highlightSearchWords: () => {
- const highlight =
- new URLSearchParams(window.location.search).get("highlight") || "";
- const terms = highlight.toLowerCase().split(/\s+/).filter(x => x);
- if (terms.length === 0) return; // nothing to do
-
- // There should never be more than one element matching "div.body"
- const divBody = document.querySelectorAll("div.body");
- const body = divBody.length ? divBody[0] : document.querySelector("body");
- window.setTimeout(() => {
- terms.forEach((term) => _highlightText(body, term, "highlighted"));
- }, 10);
-
- const searchBox = document.getElementById("searchbox");
- if (searchBox === null) return;
- searchBox.appendChild(
- document
- .createRange()
- .createContextualFragment(
- '<p class="highlight-link">' +
- '<a href="javascript:Documentation.hideSearchWords()">' +
- Documentation.gettext("Hide Search Matches") +
- "</a></p>"
- )
- );
- },
-
- /**
- * helper function to hide the search marks again
- */
- hideSearchWords: () => {
- document
- .querySelectorAll("#searchbox .highlight-link")
- .forEach((el) => el.remove());
- document
- .querySelectorAll("span.highlighted")
- .forEach((el) => el.classList.remove("highlighted"));
- const url = new URL(window.location);
- url.searchParams.delete("highlight");
- window.history.replaceState({}, "", url);
- },
-
- /**
* helper function to focus on search bar
*/
focusSearchBar: () => {
@@ -210,15 +110,11 @@ const Documentation = {
)
return;
- const blacklistedElements = new Set([
- "TEXTAREA",
- "INPUT",
- "SELECT",
- "BUTTON",
- ]);
document.addEventListener("keydown", (event) => {
- if (blacklistedElements.has(document.activeElement.tagName)) return; // bail for input elements
- if (event.altKey || event.ctrlKey || event.metaKey) return; // bail with special keys
+ // bail for input elements
+ if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return;
+ // bail with special keys
+ if (event.altKey || event.ctrlKey || event.metaKey) return;
if (!event.shiftKey) {
switch (event.key) {
@@ -240,10 +136,6 @@ const Documentation = {
event.preventDefault();
}
break;
- case "Escape":
- if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) break;
- Documentation.hideSearchWords();
- event.preventDefault();
}
}
diff --git a/sphinx/themes/basic/static/searchtools.js b/sphinx/themes/basic/static/searchtools.js
index 0073f7e94..e89e34d4e 100644
--- a/sphinx/themes/basic/static/searchtools.js
+++ b/sphinx/themes/basic/static/searchtools.js
@@ -57,7 +57,7 @@ const _removeChildren = (element) => {
const _escapeRegExp = (string) =>
string.replace(/[.*+\-?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string
-const _displayItem = (item, highlightTerms, searchTerms) => {
+const _displayItem = (item, searchTerms) => {
const docBuilder = DOCUMENTATION_OPTIONS.BUILDER;
const docUrlRoot = DOCUMENTATION_OPTIONS.URL_ROOT;
const docFileSuffix = DOCUMENTATION_OPTIONS.FILE_SUFFIX;
@@ -82,10 +82,8 @@ const _displayItem = (item, highlightTerms, searchTerms) => {
requestUrl = docUrlRoot + docName + docFileSuffix;
linkUrl = docName + docLinkSuffix;
}
- const params = new URLSearchParams();
- params.set("highlight", [...highlightTerms].join(" "));
let linkEl = listItem.appendChild(document.createElement("a"));
- linkEl.href = linkUrl + "?" + params.toString() + anchor;
+ linkEl.href = linkUrl + anchor;
linkEl.dataset.score = score;
linkEl.innerHTML = title;
if (descr)
@@ -97,7 +95,7 @@ const _displayItem = (item, highlightTerms, searchTerms) => {
.then((data) => {
if (data)
listItem.appendChild(
- Search.makeSearchSummary(data, searchTerms, highlightTerms)
+ Search.makeSearchSummary(data, searchTerms)
);
});
Search.output.appendChild(listItem);
@@ -117,15 +115,14 @@ const _finishSearch = (resultCount) => {
const _displayNextItem = (
results,
resultCount,
- highlightTerms,
searchTerms
) => {
// results left, load the summary and display it
// this is intended to be dynamic (don't sub resultsCount)
if (results.length) {
- _displayItem(results.pop(), highlightTerms, searchTerms);
+ _displayItem(results.pop(), searchTerms);
setTimeout(
- () => _displayNextItem(results, resultCount, highlightTerms, searchTerms),
+ () => _displayNextItem(results, resultCount, searchTerms),
5
);
}
@@ -271,6 +268,10 @@ const Search = {
}
});
+ if (SPHINX_HIGHLIGHT_ENABLED) { // set in sphinx_highlight.js
+ localStorage.setItem("sphinx_highlight_terms", [...highlightTerms].join(" "))
+ }
+
// console.debug("SEARCH: searching for:");
// console.info("required: ", [...searchTerms]);
// console.info("excluded: ", [...excludedTerms]);
@@ -286,7 +287,7 @@ const Search = {
let score = Math.round(100 * queryLower.length / title.length)
results.push([
docNames[file],
- `${titles[file]} > ${title}`,
+ titles[file] !== title ? `${titles[file]} > ${title}` : title,
id !== null ? "#" + id : "",
null,
score,
@@ -359,7 +360,7 @@ const Search = {
// console.info("search results:", Search.lastresults);
// print the results
- _displayNextItem(results, results.length, highlightTerms, searchTerms);
+ _displayNextItem(results, results.length, searchTerms);
},
/**
@@ -538,11 +539,9 @@ const Search = {
/**
* helper function to return a node containing the
* search summary for a given text. keywords is a list
- * of stemmed words, highlightWords is the list of normal, unstemmed
- * words. the first one is used to find the occurrence, the
- * latter for highlighting it.
+ * of stemmed words.
*/
- makeSearchSummary: (htmlText, keywords, highlightWords) => {
+ makeSearchSummary: (htmlText, keywords) => {
const text = Search.htmlToText(htmlText);
if (text === "") return null;
@@ -560,10 +559,6 @@ const Search = {
summary.classList.add("context");
summary.textContent = top + text.substr(startWithContext, 240).trim() + tail;
- highlightWords.forEach((highlightWord) =>
- _highlightText(summary, highlightWord, "highlighted")
- );
-
return summary;
},
};
diff --git a/sphinx/themes/basic/static/sphinx_highlight.js b/sphinx/themes/basic/static/sphinx_highlight.js
new file mode 100644
index 000000000..aae669d7e
--- /dev/null
+++ b/sphinx/themes/basic/static/sphinx_highlight.js
@@ -0,0 +1,144 @@
+/* Highlighting utilities for Sphinx HTML documentation. */
+"use strict";
+
+const SPHINX_HIGHLIGHT_ENABLED = true
+
+/**
+ * highlight a given string on a node by wrapping it in
+ * span elements with the given class name.
+ */
+const _highlight = (node, addItems, text, className) => {
+ if (node.nodeType === Node.TEXT_NODE) {
+ const val = node.nodeValue;
+ const parent = node.parentNode;
+ const pos = val.toLowerCase().indexOf(text);
+ if (
+ pos >= 0 &&
+ !parent.classList.contains(className) &&
+ !parent.classList.contains("nohighlight")
+ ) {
+ let span;
+
+ const closestNode = parent.closest("body, svg, foreignObject");
+ const isInSVG = closestNode && closestNode.matches("svg");
+ if (isInSVG) {
+ span = document.createElementNS("http://www.w3.org/2000/svg", "tspan");
+ } else {
+ span = document.createElement("span");
+ span.classList.add(className);
+ }
+
+ span.appendChild(document.createTextNode(val.substr(pos, text.length)));
+ parent.insertBefore(
+ span,
+ parent.insertBefore(
+ document.createTextNode(val.substr(pos + text.length)),
+ node.nextSibling
+ )
+ );
+ node.nodeValue = val.substr(0, pos);
+
+ if (isInSVG) {
+ const rect = document.createElementNS(
+ "http://www.w3.org/2000/svg",
+ "rect"
+ );
+ const bbox = parent.getBBox();
+ rect.x.baseVal.value = bbox.x;
+ rect.y.baseVal.value = bbox.y;
+ rect.width.baseVal.value = bbox.width;
+ rect.height.baseVal.value = bbox.height;
+ rect.setAttribute("class", className);
+ addItems.push({ parent: parent, target: rect });
+ }
+ }
+ } else if (node.matches && !node.matches("button, select, textarea")) {
+ node.childNodes.forEach((el) => _highlight(el, addItems, text, className));
+ }
+};
+const _highlightText = (thisNode, text, className) => {
+ let addItems = [];
+ _highlight(thisNode, addItems, text, className);
+ addItems.forEach((obj) =>
+ obj.parent.insertAdjacentElement("beforebegin", obj.target)
+ );
+};
+
+/**
+ * Small JavaScript module for the documentation.
+ */
+const SphinxHighlight = {
+
+ /**
+ * highlight the search words provided in localstorage in the text
+ */
+ highlightSearchWords: () => {
+ if (!SPHINX_HIGHLIGHT_ENABLED) return; // bail if no highlight
+
+ // get and clear terms from localstorage
+ const url = new URL(window.location);
+ const highlight =
+ localStorage.getItem("sphinx_highlight_terms")
+ || url.searchParams.get("highlight")
+ || "";
+ localStorage.removeItem("sphinx_highlight_terms")
+ url.searchParams.delete("highlight");
+ window.history.replaceState({}, "", url);
+
+ // get individual terms from highlight string
+ const terms = highlight.toLowerCase().split(/\s+/).filter(x => x);
+ if (terms.length === 0) return; // nothing to do
+
+ // There should never be more than one element matching "div.body"
+ const divBody = document.querySelectorAll("div.body");
+ const body = divBody.length ? divBody[0] : document.querySelector("body");
+ window.setTimeout(() => {
+ terms.forEach((term) => _highlightText(body, term, "highlighted"));
+ }, 10);
+
+ const searchBox = document.getElementById("searchbox");
+ if (searchBox === null) return;
+ searchBox.appendChild(
+ document
+ .createRange()
+ .createContextualFragment(
+ '<p class="highlight-link">' +
+ '<a href="javascript:SphinxHighlight.hideSearchWords()">' +
+ _("Hide Search Matches") +
+ "</a></p>"
+ )
+ );
+ },
+
+ /**
+ * helper function to hide the search marks again
+ */
+ hideSearchWords: () => {
+ document
+ .querySelectorAll("#searchbox .highlight-link")
+ .forEach((el) => el.remove());
+ document
+ .querySelectorAll("span.highlighted")
+ .forEach((el) => el.classList.remove("highlighted"));
+ localStorage.removeItem("sphinx_highlight_terms")
+ },
+
+ initEscapeListener: () => {
+ // only install a listener if it is really needed
+ if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) return;
+
+ document.addEventListener("keydown", (event) => {
+ // bail for input elements
+ if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return;
+ // bail with special keys
+ if (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey) return;
+ if (DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS && (event.key === "Escape")) {
+ SphinxHighlight.hideSearchWords();
+ event.preventDefault();
+ }
+ });
+ },
+};
+
+_ready(SphinxHighlight.highlightSearchWords);
+_ready(SphinxHighlight.initEscapeListener);
diff --git a/sphinx/transforms/post_transforms/code.py b/sphinx/transforms/post_transforms/code.py
index 3c3f54328..5a5980c4a 100644
--- a/sphinx/transforms/post_transforms/code.py
+++ b/sphinx/transforms/post_transforms/code.py
@@ -109,9 +109,9 @@ class TrimDoctestFlagsTransform(SphinxTransform):
return False # skip parsed-literal node
language = node.get('language')
- if language in ('pycon', 'pycon3'):
+ if language in {'pycon', 'pycon3'}:
return True
- elif language in ('py', 'py3', 'python', 'python3', 'default'):
+ elif language in {'py', 'python', 'py3', 'python3', 'default'}:
return node.rawsource.startswith('>>>')
elif language == 'guess':
try:
diff --git a/sphinx/writers/html.py b/sphinx/writers/html.py
index 1e12eb908..329d64394 100644
--- a/sphinx/writers/html.py
+++ b/sphinx/writers/html.py
@@ -65,6 +65,7 @@ class HTMLWriter(Writer):
self.clean_meta = ''.join(self.visitor.meta[2:])
+# RemovedInSphinx70Warning
class HTMLTranslator(SphinxTranslator, BaseTranslator):
"""
Our custom HTML translator.
diff --git a/tests/js/documentation_options.js b/tests/js/documentation_options.js
new file mode 100644
index 000000000..e736460a0
--- /dev/null
+++ b/tests/js/documentation_options.js
@@ -0,0 +1 @@
+const DOCUMENTATION_OPTIONS = {};
diff --git a/tests/js/doctools.js b/tests/js/sphinx_highlight.js
index 7268a6a8c..1f52eabb9 100644
--- a/tests/js/doctools.js
+++ b/tests/js/sphinx_highlight.js
@@ -1,5 +1,3 @@
-const DOCUMENTATION_OPTIONS = {};
-
describe('highlightText', function() {
const cyrillicTerm = 'шеллы';
diff --git a/tests/roots/test-environment-record-dependencies/api.rst b/tests/roots/test-environment-record-dependencies/api.rst
new file mode 100644
index 000000000..acfb89696
--- /dev/null
+++ b/tests/roots/test-environment-record-dependencies/api.rst
@@ -0,0 +1,4 @@
+API
+===
+
+.. automodule:: example_module
diff --git a/tests/roots/test-environment-record-dependencies/conf.py b/tests/roots/test-environment-record-dependencies/conf.py
new file mode 100644
index 000000000..107480e40
--- /dev/null
+++ b/tests/roots/test-environment-record-dependencies/conf.py
@@ -0,0 +1,5 @@
+import os
+import sys
+
+sys.path.insert(0, os.path.abspath('.'))
+extensions = ['sphinx.ext.autodoc']
diff --git a/tests/roots/test-environment-record-dependencies/example_module.py b/tests/roots/test-environment-record-dependencies/example_module.py
new file mode 100644
index 000000000..d12dc742d
--- /dev/null
+++ b/tests/roots/test-environment-record-dependencies/example_module.py
@@ -0,0 +1,2 @@
+def example_function():
+ return 42
diff --git a/tests/roots/test-environment-record-dependencies/index.rst b/tests/roots/test-environment-record-dependencies/index.rst
new file mode 100644
index 000000000..21d88a053
--- /dev/null
+++ b/tests/roots/test-environment-record-dependencies/index.rst
@@ -0,0 +1,3 @@
+.. toctree::
+
+ api
diff --git a/tests/test_build_html.py b/tests/test_build_html.py
index f443bdf4d..072f187ba 100644
--- a/tests/test_build_html.py
+++ b/tests/test_build_html.py
@@ -131,6 +131,16 @@ def test_html4_output(app, status, warning):
app.build()
+def test_html4_deprecation(make_app, tempdir):
+ (tempdir / 'conf.py').write_text('', encoding='utf-8')
+ app = make_app(
+ buildername='html',
+ srcdir=tempdir,
+ confoverrides={'html4_writer': True},
+ )
+ assert 'HTML 4 output is deprecated and will be removed' in app._warning.getvalue()
+
+
@pytest.mark.parametrize("fname,expect", flat_dict({
'images.html': [
(".//img[@src='_images/img.png']", ''),
@@ -1222,7 +1232,8 @@ def test_assets_order(app):
# js_files
expected = ['_static/early.js',
- '_static/doctools.js', 'https://example.com/script.js', '_static/normal.js',
+ '_static/doctools.js', '_static/sphinx_highlight.js',
+ 'https://example.com/script.js', '_static/normal.js',
'_static/late.js', '_static/js/custom.js', '_static/lazy.js']
pattern = '.*'.join('src="%s"' % f for f in expected)
assert re.search(pattern, content, re.S)
diff --git a/tests/test_environment_record_dependencies.py b/tests/test_environment_record_dependencies.py
new file mode 100644
index 000000000..0a17253c0
--- /dev/null
+++ b/tests/test_environment_record_dependencies.py
@@ -0,0 +1,10 @@
+"""Tests for ``record_dependencies``."""
+
+import pytest
+
+
+@pytest.mark.sphinx('html', testroot='environment-record-dependencies')
+def test_record_dependencies_cleared(app):
+ app.builder.read()
+ assert app.env.dependencies['index'] == set()
+ assert app.env.dependencies['api'] == {'example_module.py'}
diff --git a/tests/test_ext_doctest.py b/tests/test_ext_doctest.py
index 6ec0495ef..6c628904f 100644
--- a/tests/test_ext_doctest.py
+++ b/tests/test_ext_doctest.py
@@ -29,16 +29,16 @@ def test_highlight_language_default(app, status, warning):
app.build()
doctree = app.env.get_doctree('doctest')
for node in doctree.findall(nodes.literal_block):
- assert node['language'] in ('python3', 'pycon3', 'none')
+ assert node['language'] in {'python', 'pycon', 'none'}
@pytest.mark.sphinx('dummy', testroot='ext-doctest',
confoverrides={'highlight_language': 'python'})
-def test_highlight_language_python2(app, status, warning):
+def test_highlight_language_python3(app, status, warning):
app.build()
doctree = app.env.get_doctree('doctest')
for node in doctree.findall(nodes.literal_block):
- assert node['language'] in ('python', 'pycon', 'none')
+ assert node['language'] in {'python', 'pycon', 'none'}
def test_is_allowed_version():
diff --git a/tests/test_highlighting.py b/tests/test_highlighting.py
index 92276a21c..d7e3625c4 100644
--- a/tests/test_highlighting.py
+++ b/tests/test_highlighting.py
@@ -81,14 +81,23 @@ def test_default_highlight(logger):
ret = bridge.highlight_block('reST ``like`` text', 'default')
assert ret == '<div class="highlight"><pre><span></span>reST ``like`` text\n</pre></div>\n'
- # python3: highlights as python3
- ret = bridge.highlight_block('print "Hello sphinx world"', 'python3')
- assert ret == ('<div class="highlight"><pre><span></span><span class="nb">print</span> '
- '<span class="s2">&quot;Hello sphinx world&quot;</span>\n</pre></div>\n')
+ # python: highlights as python3
+ ret = bridge.highlight_block('print("Hello sphinx world")', 'python')
+ assert ret == ('<div class="highlight"><pre><span></span><span class="nb">print</span>'
+ '<span class="p">(</span>'
+ '<span class="s2">&quot;Hello sphinx world&quot;</span>'
+ '<span class="p">)</span>\n</pre></div>\n')
- # python3: raises error if highlighting failed
- ret = bridge.highlight_block('reST ``like`` text', 'python3')
+ # python3: highlights as python3
+ ret = bridge.highlight_block('print("Hello sphinx world")', 'python3')
+ assert ret == ('<div class="highlight"><pre><span></span><span class="nb">print</span>'
+ '<span class="p">(</span>'
+ '<span class="s2">&quot;Hello sphinx world&quot;</span>'
+ '<span class="p">)</span>\n</pre></div>\n')
+
+ # python: raises error if highlighting failed
+ ret = bridge.highlight_block('reST ``like`` text', 'python')
logger.warning.assert_called_with('Could not lex literal_block as "%s". '
- 'Highlighting skipped.', 'python3',
+ 'Highlighting skipped.', 'python',
type='misc', subtype='highlighting_failure',
location=None)
diff --git a/utils/release-checklist b/utils/release-checklist
index 0d1733229..febbbae56 100644
--- a/utils/release-checklist
+++ b/utils/release-checklist
@@ -14,7 +14,7 @@ for stable releases
* ``twine upload dist/Sphinx-* --sign --identity [your GPG key]``
* open https://pypi.org/project/Sphinx/ and check there are no obvious errors
* ``sh utils/bump_docker.sh X.Y.Z``
-* ``git tag vX.Y.Z``
+* ``git tag vX.Y.Z -m "Sphinx X.Y.Z"``
* ``python utils/bump_version.py --in-develop X.Y.Zb0`` (ex. 1.5.3b0)
* Check diff by ``git diff``
* ``git commit -am 'Bump version'``
@@ -37,7 +37,7 @@ for first beta releases
* ``python -m build .``
* ``twine upload dist/Sphinx-* --sign --identity [your GPG key]``
* open https://pypi.org/project/Sphinx/ and check there are no obvious errors
-* ``git tag vX.Y.0b1``
+* ``git tag vX.Y.0b1 -m "Sphinx X.Y.0b1"``
* ``python utils/bump_version.py --in-develop X.Y.0b2`` (ex. 1.6.0b2)
* Check diff by ``git diff``
* ``git commit -am 'Bump version'``
@@ -65,7 +65,7 @@ for other beta releases
* ``python -m build .``
* ``twine upload dist/Sphinx-* --sign --identity [your GPG key]``
* open https://pypi.org/project/Sphinx/ and check there are no obvious errors
-* ``git tag vX.Y.0bN``
+* ``git tag vX.Y.0bN -m "Sphinx X.Y.0bN"``
* ``python utils/bump_version.py --in-develop X.Y.0bM`` (ex. 1.6.0b3)
* Check diff by `git diff``
* ``git commit -am 'Bump version'``
@@ -91,7 +91,7 @@ for major releases
* ``twine upload dist/Sphinx-* --sign --identity [your GPG key]``
* open https://pypi.org/project/Sphinx/ and check there are no obvious errors
* ``sh utils/bump_docker.sh X.Y.Z``
-* ``git tag vX.Y.0``
+* ``git tag vX.Y.0 -m "Sphinx X.Y.0"``
* ``python utils/bump_version.py --in-develop X.Y.1b0`` (ex. 1.6.1b0)
* Check diff by ``git diff``
* ``git commit -am 'Bump version'``
@@ -101,7 +101,7 @@ for major releases
* ``git push origin master``
* open https://github.com/sphinx-doc/sphinx/settings/branches and make ``A.B`` branch *not* protected
* ``git checkout A.B`` (checkout old stable)
-* Run ``git tag A.B`` to paste a tag instead branch
+* Run ``git tag A.B -m "Sphinx A.B"`` to paste a tag instead branch
* Run ``git push origin :A.B --tags`` to remove old stable branch
* open https://readthedocs.org/dashboard/sphinx/versions/ and enable the released version
* Add new version/milestone to tracker categories