diff --git a/pyproject.toml b/pyproject.toml
index 17cb0463ae5..b432c46be32 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -97,7 +97,7 @@ lint = [
"sphinx-lint>=0.9",
"types-colorama==0.4.15.20240311",
"types-defusedxml==0.7.0.20250516",
- "types-docutils==0.21.0.20250514",
+ "types-docutils==0.21.0.20250525",
"types-Pillow==10.2.0.20240822",
"types-Pygments==2.19.0.20250516",
"types-requests==2.32.0.20250602", # align with requests
@@ -166,7 +166,7 @@ type-stubs = [
# align with versions used elsewhere
"types-colorama==0.4.15.20240311",
"types-defusedxml==0.7.0.20250516",
- "types-docutils==0.21.0.20250514",
+ "types-docutils==0.21.0.20250525",
"types-Pillow==10.2.0.20240822",
"types-Pygments==2.19.0.20250516",
"types-requests==2.32.0.20250602",
diff --git a/sphinx/builders/_epub_base.py b/sphinx/builders/_epub_base.py
index 10ae0820c5b..28a7553da51 100644
--- a/sphinx/builders/_epub_base.py
+++ b/sphinx/builders/_epub_base.py
@@ -114,8 +114,8 @@ class NavPoint(NamedTuple):
def sphinx_smarty_pants(t: str, language: str = 'en') -> str:
t = t.replace('"', '"')
- t = smartquotes.educateDashesOldSchool(t) # type: ignore[no-untyped-call]
- t = smartquotes.educateQuotes(t, language) # type: ignore[no-untyped-call]
+ t = smartquotes.educateDashesOldSchool(t)
+ t = smartquotes.educateQuotes(t, language)
t = t.replace('"', '"')
return t
diff --git a/sphinx/util/rst.py b/sphinx/util/rst.py
index c848a9b3657..7e6853a81ef 100644
--- a/sphinx/util/rst.py
+++ b/sphinx/util/rst.py
@@ -9,7 +9,7 @@
from unicodedata import east_asian_width
from docutils.parsers.rst import roles
-from docutils.parsers.rst.languages import en as english # type: ignore[attr-defined]
+from docutils.parsers.rst.languages import en as english
from docutils.parsers.rst.states import Body
from docutils.utils import Reporter
from jinja2 import pass_environment
@@ -66,7 +66,7 @@ def heading(env: Environment, text: str, level: int = 1) -> str:
def default_role(docname: str, name: str) -> Iterator[None]:
if name:
dummy_reporter = Reporter('', 4, 4)
- role_fn, _ = roles.role(name, english, 0, dummy_reporter)
+ role_fn, _ = roles.role(name, english, 0, dummy_reporter) # type: ignore[arg-type]
if role_fn:
docutils.register_role('', role_fn) # type: ignore[arg-type]
else:
diff --git a/sphinx/writers/html5.py b/sphinx/writers/html5.py
index b39b463d6db..464da088184 100644
--- a/sphinx/writers/html5.py
+++ b/sphinx/writers/html5.py
@@ -17,7 +17,22 @@
from sphinx.util.images import get_image_size
if TYPE_CHECKING:
- from docutils.nodes import Element, Node, Text
+ from docutils.nodes import (
+ Element,
+ Node,
+ Text,
+ bullet_list,
+ caption,
+ emphasis,
+ field_list,
+ figure,
+ image,
+ literal_block,
+ reference,
+ strong,
+ table,
+ title,
+ )
from sphinx.builders import Builder
from sphinx.builders.html import StandaloneHTMLBuilder
@@ -357,7 +372,7 @@ def visit_reference(self, node: Element) -> None:
def visit_number_reference(self, node: Element) -> None:
self.visit_reference(node)
- def depart_number_reference(self, node: Element) -> None:
+ def depart_number_reference(self, node: reference) -> None:
self.depart_reference(node)
# overwritten -- we don't want source comments to show up in the HTML
@@ -451,7 +466,7 @@ def add_permalink_ref(self, node: Element, title: str) -> None:
)
# overwritten
- def visit_bullet_list(self, node: Element) -> None:
+ def visit_bullet_list(self, node: bullet_list) -> None:
if len(node) == 1 and isinstance(node[0], addnodes.toctree):
# avoid emitting empty
raise nodes.SkipNode
@@ -498,7 +513,7 @@ def depart_term(self, node: Element) -> None:
self.body.append('')
# overwritten
- def visit_title(self, node: Element) -> None:
+ def visit_title(self, node: title) -> None:
if (
isinstance(node.parent, addnodes.compact_paragraph)
and node.parent.get('toctree')
@@ -535,7 +550,7 @@ def visit_title(self, node: Element) -> None:
self.body.pop()
self.context[-1] = '\n'
- def depart_title(self, node: Element) -> None:
+ def depart_title(self, node: title) -> None:
close_tag = self.context[-1]
if (
self.config.html_permalinks
@@ -586,7 +601,7 @@ def depart_rubric(self, node: nodes.rubric) -> None:
super().depart_rubric(node)
# overwritten
- def visit_literal_block(self, node: Element) -> None:
+ def visit_literal_block(self, node: literal_block) -> None:
if node.rawsource != node.astext():
# most probably a parsed-literal block -- don't highlight
return super().visit_literal_block(node)
@@ -614,7 +629,7 @@ def visit_literal_block(self, node: Element) -> None:
self.body.append(starttag + highlighted + '\n')
raise nodes.SkipNode
- def visit_caption(self, node: Element) -> None:
+ def visit_caption(self, node: caption) -> None:
if (
isinstance(node.parent, nodes.container)
and node.parent.get('literal_block')
@@ -625,7 +640,7 @@ def visit_caption(self, node: Element) -> None:
self.add_fignumber(node.parent)
self.body.append(self.starttag(node, 'span', '', CLASS='caption-text'))
- def depart_caption(self, node: Element) -> None:
+ def depart_caption(self, node: caption) -> None:
self.body.append('')
# append permalink if available
@@ -648,7 +663,7 @@ def depart_caption(self, node: Element) -> None:
super().depart_caption(node)
def visit_doctest_block(self, node: Element) -> None:
- self.visit_literal_block(node)
+ self.visit_literal_block(node) # type: ignore[arg-type]
# overwritten to add the (for XHTML compliance)
def visit_block_quote(self, node: Element) -> None:
@@ -740,14 +755,14 @@ def depart_download_reference(self, node: Element) -> None:
self.body.append(self.context.pop())
# overwritten
- def visit_figure(self, node: Element) -> None:
+ def visit_figure(self, node: figure) -> None:
# set align=default if align not specified to give a default style
node.setdefault('align', 'default')
return super().visit_figure(node)
# overwritten
- def visit_image(self, node: Element) -> None:
+ def visit_image(self, node: image) -> None:
olduri = node['uri']
# rewrite the URI if the environment knows about it
if olduri in self.builder.images:
@@ -775,7 +790,7 @@ def visit_image(self, node: Element) -> None:
super().visit_image(node)
# overwritten
- def depart_image(self, node: Element) -> None:
+ def depart_image(self, node: image) -> None:
if node['uri'].lower().endswith(('svg', 'svgz')):
pass
else:
@@ -892,16 +907,16 @@ def visit_tip(self, node: Element) -> None:
def depart_tip(self, node: Element) -> None:
self.depart_admonition(node)
- def visit_literal_emphasis(self, node: Element) -> None:
+ def visit_literal_emphasis(self, node: emphasis) -> None:
return self.visit_emphasis(node)
- def depart_literal_emphasis(self, node: Element) -> None:
+ def depart_literal_emphasis(self, node: emphasis) -> None:
return self.depart_emphasis(node)
- def visit_literal_strong(self, node: Element) -> None:
+ def visit_literal_strong(self, node: strong) -> None:
return self.visit_strong(node)
- def depart_literal_strong(self, node: Element) -> None:
+ def depart_literal_strong(self, node: strong) -> None:
return self.depart_strong(node)
def visit_abbreviation(self, node: Element) -> None:
@@ -913,15 +928,15 @@ def visit_abbreviation(self, node: Element) -> None:
def depart_abbreviation(self, node: Element) -> None:
self.body.append('')
- def visit_manpage(self, node: Element) -> None:
+ def visit_manpage(self, node: emphasis) -> None:
self.visit_literal_emphasis(node)
- def depart_manpage(self, node: Element) -> None:
+ def depart_manpage(self, node: emphasis) -> None:
self.depart_literal_emphasis(node)
# overwritten to add even/odd classes
- def visit_table(self, node: Element) -> None:
+ def visit_table(self, node: table) -> None:
self._table_row_indices.append(0)
atts = {}
@@ -936,7 +951,7 @@ def visit_table(self, node: Element) -> None:
tag = self.starttag(node, 'table', CLASS=' '.join(classes), **atts)
self.body.append(tag)
- def depart_table(self, node: Element) -> None:
+ def depart_table(self, node: table) -> None:
self._table_row_indices.pop()
super().depart_table(node)
@@ -949,11 +964,11 @@ def visit_row(self, node: Element) -> None:
self.body.append(self.starttag(node, 'tr', ''))
node.column = 0 # type: ignore[attr-defined]
- def visit_field_list(self, node: Element) -> None:
+ def visit_field_list(self, node: field_list) -> None:
self._fieldlist_row_indices.append(0)
return super().visit_field_list(node)
- def depart_field_list(self, node: Element) -> None:
+ def depart_field_list(self, node: field_list) -> None:
self._fieldlist_row_indices.pop()
return super().depart_field_list(node)
diff --git a/sphinx/writers/manpage.py b/sphinx/writers/manpage.py
index 171761fa2b0..e434b5f4c50 100644
--- a/sphinx/writers/manpage.py
+++ b/sphinx/writers/manpage.py
@@ -19,7 +19,21 @@
from collections.abc import Iterable
from typing import Any
- from docutils.nodes import Element
+ from docutils.nodes import (
+ Element,
+ admonition,
+ bullet_list,
+ caption,
+ definition,
+ definition_list,
+ emphasis,
+ footnote,
+ paragraph,
+ reference,
+ strong,
+ term,
+ title,
+ )
from sphinx.builders import Builder
@@ -71,7 +85,7 @@ def apply(self, **kwargs: Any) -> None:
node.parent.remove(node)
-class ManualPageTranslator(SphinxTranslator, BaseTranslator): # type: ignore[misc]
+class ManualPageTranslator(SphinxTranslator, BaseTranslator):
"""Custom man page translator."""
_docinfo: dict[str, Any] = {}
@@ -130,17 +144,17 @@ def depart_start_of_file(self, node: Element) -> None:
# Top-level nodes for descriptions
##################################
- def visit_desc(self, node: Element) -> None:
+ def visit_desc(self, node: definition_list) -> None:
self.visit_definition_list(node)
- def depart_desc(self, node: Element) -> None:
+ def depart_desc(self, node: definition_list) -> None:
self.depart_definition_list(node)
- def visit_desc_signature(self, node: Element) -> None:
- self.visit_definition_list_item(node)
+ def visit_desc_signature(self, node: term) -> None:
+ self.visit_definition_list_item(node) # type: ignore[arg-type]
self.visit_term(node)
- def depart_desc_signature(self, node: Element) -> None:
+ def depart_desc_signature(self, node: term) -> None:
self.depart_term(node)
def visit_desc_signature_line(self, node: Element) -> None:
@@ -149,10 +163,10 @@ def visit_desc_signature_line(self, node: Element) -> None:
def depart_desc_signature_line(self, node: Element) -> None:
self.body.append(' ')
- def visit_desc_content(self, node: Element) -> None:
+ def visit_desc_content(self, node: definition) -> None:
self.visit_definition(node)
- def depart_desc_content(self, node: Element) -> None:
+ def depart_desc_content(self, node: definition) -> None:
self.depart_definition(node)
def visit_desc_inline(self, node: Element) -> None:
@@ -231,25 +245,25 @@ def depart_desc_annotation(self, node: Element) -> None:
##############################################
- def visit_versionmodified(self, node: Element) -> None:
+ def visit_versionmodified(self, node: paragraph) -> None:
self.visit_paragraph(node)
- def depart_versionmodified(self, node: Element) -> None:
+ def depart_versionmodified(self, node: paragraph) -> None:
self.depart_paragraph(node)
# overwritten -- don't make whole of term bold if it includes strong node
- def visit_term(self, node: Element) -> None:
+ def visit_term(self, node: term) -> None:
if any(node.findall(nodes.strong)):
self.body.append('\n')
else:
super().visit_term(node)
# overwritten -- we don't want source comments to show up
- def visit_comment(self, node: Element) -> None:
+ def visit_comment(self, node: Element) -> None: # type: ignore[override]
raise nodes.SkipNode
# overwritten -- added ensure_eol()
- def visit_footnote(self, node: Element) -> None:
+ def visit_footnote(self, node: footnote) -> None:
self.ensure_eol()
super().visit_footnote(node)
@@ -264,10 +278,10 @@ def visit_rubric(self, node: Element) -> None:
def depart_rubric(self, node: Element) -> None:
self.body.append('\n')
- def visit_seealso(self, node: Element) -> None:
+ def visit_seealso(self, node: admonition) -> None:
self.visit_admonition(node, 'seealso')
- def depart_seealso(self, node: Element) -> None:
+ def depart_seealso(self, node: admonition) -> None:
self.depart_admonition(node)
def visit_productionlist(self, node: Element) -> None:
@@ -291,7 +305,7 @@ def visit_image(self, node: Element) -> None:
raise nodes.SkipNode
# overwritten -- don't visit inner marked up nodes
- def visit_reference(self, node: Element) -> None:
+ def visit_reference(self, node: reference) -> None:
uri = node.get('refuri', '')
is_safe_to_click = uri.startswith(('mailto:', 'http:', 'https:', 'ftp:'))
if is_safe_to_click:
@@ -301,7 +315,7 @@ def visit_reference(self, node: Element) -> None:
self.body.append(self.defs['reference'][0])
# avoid repeating escaping code... fine since
# visit_Text calls astext() and only works on that afterwards
- self.visit_Text(node)
+ self.visit_Text(node) # type: ignore[arg-type]
self.body.append(self.defs['reference'][1])
if uri and not uri.startswith('#'):
@@ -369,10 +383,10 @@ def visit_acks(self, node: Element) -> None:
self.body.append('\n')
raise nodes.SkipNode
- def visit_hlist(self, node: Element) -> None:
+ def visit_hlist(self, node: bullet_list) -> None:
self.visit_bullet_list(node)
- def depart_hlist(self, node: Element) -> None:
+ def depart_hlist(self, node: bullet_list) -> None:
self.depart_bullet_list(node)
def visit_hlistcol(self, node: Element) -> None:
@@ -381,16 +395,16 @@ def visit_hlistcol(self, node: Element) -> None:
def depart_hlistcol(self, node: Element) -> None:
pass
- def visit_literal_emphasis(self, node: Element) -> None:
+ def visit_literal_emphasis(self, node: emphasis) -> None:
return self.visit_emphasis(node)
- def depart_literal_emphasis(self, node: Element) -> None:
+ def depart_literal_emphasis(self, node: emphasis) -> None:
return self.depart_emphasis(node)
- def visit_literal_strong(self, node: Element) -> None:
+ def visit_literal_strong(self, node: strong) -> None:
return self.visit_strong(node)
- def depart_literal_strong(self, node: Element) -> None:
+ def depart_literal_strong(self, node: strong) -> None:
return self.depart_strong(node)
def visit_abbreviation(self, node: Element) -> None:
@@ -399,14 +413,14 @@ def visit_abbreviation(self, node: Element) -> None:
def depart_abbreviation(self, node: Element) -> None:
pass
- def visit_manpage(self, node: Element) -> None:
+ def visit_manpage(self, node: strong) -> None:
return self.visit_strong(node)
- def depart_manpage(self, node: Element) -> None:
+ def depart_manpage(self, node: strong) -> None:
return self.depart_strong(node)
# overwritten: handle section titles better than in 0.6 release
- def visit_caption(self, node: Element) -> None:
+ def visit_caption(self, node: caption) -> None:
if (
isinstance(node.parent, nodes.container)
and node.parent.get('literal_block')
@@ -415,7 +429,7 @@ def visit_caption(self, node: Element) -> None:
else:
super().visit_caption(node)
- def depart_caption(self, node: Element) -> None:
+ def depart_caption(self, node: caption) -> None:
if (
isinstance(node.parent, nodes.container)
and node.parent.get('literal_block')
@@ -425,7 +439,7 @@ def depart_caption(self, node: Element) -> None:
super().depart_caption(node)
# overwritten: handle section titles better than in 0.6 release
- def visit_title(self, node: Element) -> None:
+ def visit_title(self, node: title) -> None:
if isinstance(node.parent, addnodes.seealso):
self.body.append('.IP "')
return None
@@ -438,7 +452,7 @@ def visit_title(self, node: Element) -> None:
raise nodes.SkipNode
return super().visit_title(node)
- def depart_title(self, node: Element) -> None:
+ def depart_title(self, node: title) -> None:
if isinstance(node.parent, addnodes.seealso):
self.body.append('"\n')
return None
diff --git a/tests/test_util/test_util_docutils_sphinx_directive.py b/tests/test_util/test_util_docutils_sphinx_directive.py
index 8c24a3c4a83..eb1e4aea16a 100644
--- a/tests/test_util/test_util_docutils_sphinx_directive.py
+++ b/tests/test_util/test_util_docutils_sphinx_directive.py
@@ -3,7 +3,7 @@
from types import SimpleNamespace
from docutils import nodes
-from docutils.parsers.rst.languages import en as english # type: ignore[attr-defined]
+from docutils.parsers.rst.languages import en as english
from docutils.parsers.rst.states import (
Inliner,
RSTState,