From 58d422422729f67f73b2c99dac01cced8471691e Mon Sep 17 00:00:00 2001 From: danieleades <33452915+danieleades@users.noreply.github.com> Date: Tue, 23 Jul 2024 02:22:58 +0100 Subject: [PATCH] Disallow untyped calls (#12640) Co-authored-by: Adam Turner <9087854+aa-turner@users.noreply.github.com> --- pyproject.toml | 1 + sphinx/builders/_epub_base.py | 4 ++-- sphinx/environment/collectors/title.py | 2 +- sphinx/environment/collectors/toctree.py | 2 +- sphinx/locale/__init__.py | 3 ++- sphinx/transforms/__init__.py | 2 +- sphinx/transforms/references.py | 2 +- sphinx/writers/latex.py | 4 ++-- .../test_builders/test_build_html_5_output.py | 12 ++++++++-- tests/test_builders/test_build_linkcheck.py | 22 ++++++++++++------- tests/test_builders/test_build_text.py | 9 +++++++- tests/test_extensions/test_ext_math.py | 2 +- tests/test_extensions/test_ext_viewcode.py | 10 ++++++++- tests/test_intl/test_locale.py | 10 ++++++++- tests/test_util/test_util_docutils.py | 12 +++++++--- tests/test_util/test_util_inventory.py | 11 ++++++++-- 16 files changed, 80 insertions(+), 28 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 6b802573c..f6e166ff7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -211,6 +211,7 @@ disallow_incomplete_defs = true disallow_subclassing_any = true disallow_untyped_defs = true python_version = "3.10" +disallow_untyped_calls = true show_column_numbers = true show_error_context = true strict_optional = true diff --git a/sphinx/builders/_epub_base.py b/sphinx/builders/_epub_base.py index 5e9d53a44..d8d7c7564 100644 --- a/sphinx/builders/_epub_base.py +++ b/sphinx/builders/_epub_base.py @@ -108,8 +108,8 @@ class NavPoint(NamedTuple): def sphinx_smarty_pants(t: str, language: str = 'en') -> str: t = t.replace('"', '"') - t = smartquotes.educateDashesOldSchool(t) - t = smartquotes.educateQuotes(t, language) + t = smartquotes.educateDashesOldSchool(t) # type: ignore[no-untyped-call] + t = smartquotes.educateQuotes(t, language) # type: ignore[no-untyped-call] t = t.replace('"', '"') return t diff --git a/sphinx/environment/collectors/title.py b/sphinx/environment/collectors/title.py index 076055765..76e3f0379 100644 --- a/sphinx/environment/collectors/title.py +++ b/sphinx/environment/collectors/title.py @@ -43,7 +43,7 @@ class TitleCollector(EnvironmentCollector): for node in doctree.findall(nodes.section): visitor = SphinxContentsFilter(doctree) node[0].walkabout(visitor) - titlenode += visitor.get_entry_text() + titlenode += visitor.get_entry_text() # type: ignore[no-untyped-call] break else: # document has no title diff --git a/sphinx/environment/collectors/toctree.py b/sphinx/environment/collectors/toctree.py index fda02c61c..e3f80f251 100644 --- a/sphinx/environment/collectors/toctree.py +++ b/sphinx/environment/collectors/toctree.py @@ -78,7 +78,7 @@ class TocTreeCollector(EnvironmentCollector): # and unnecessary stuff visitor = SphinxContentsFilter(doctree) title.walkabout(visitor) - nodetext = visitor.get_entry_text() + nodetext = visitor.get_entry_text() # type: ignore[no-untyped-call] anchorname = _make_anchor_name(sectionnode['ids'], numentries) # make these nodes: # list_item -> compact_paragraph -> reference diff --git a/sphinx/locale/__init__.py b/sphinx/locale/__init__.py index 44640a934..c0b4ee9ad 100644 --- a/sphinx/locale/__init__.py +++ b/sphinx/locale/__init__.py @@ -9,6 +9,7 @@ from os import path from typing import TYPE_CHECKING if TYPE_CHECKING: + import os from collections.abc import Callable, Iterable from typing import Any @@ -98,7 +99,7 @@ translators: dict[tuple[str, str], NullTranslations] = {} def init( - locale_dirs: Iterable[str | None], + locale_dirs: Iterable[str | os.PathLike[str] | None], language: str | None, catalog: str = 'sphinx', namespace: str = 'general', diff --git a/sphinx/transforms/__init__.py b/sphinx/transforms/__init__.py index fd7dd71fa..7ccef6aac 100644 --- a/sphinx/transforms/__init__.py +++ b/sphinx/transforms/__init__.py @@ -364,7 +364,7 @@ class SphinxSmartQuotes(SmartQuotes, SphinxTransform): # override default settings with :confval:`smartquotes_action` self.smartquotes_action = self.config.smartquotes_action - super().apply() + super().apply() # type: ignore[no-untyped-call] def is_available(self) -> bool: builders = self.config.smartquotes_excludes.get('builders', []) diff --git a/sphinx/transforms/references.py b/sphinx/transforms/references.py index e0dfeb190..6f935aa2a 100644 --- a/sphinx/transforms/references.py +++ b/sphinx/transforms/references.py @@ -23,7 +23,7 @@ class SphinxDanglingReferences(DanglingReferences): # suppress INFO level messages for a while reporter.report_level = max(reporter.WARNING_LEVEL, reporter.report_level) - super().apply() + super().apply() # type: ignore[no-untyped-call] finally: reporter.report_level = report_level diff --git a/sphinx/writers/latex.py b/sphinx/writers/latex.py index 4badfa87f..7cd6350c3 100644 --- a/sphinx/writers/latex.py +++ b/sphinx/writers/latex.py @@ -1276,8 +1276,8 @@ class LaTeXTranslator(SphinxTranslator): else: return get_nested_level(node.parent) - enum = "enum%s" % toRoman(get_nested_level(node)).lower() - enumnext = "enum%s" % toRoman(get_nested_level(node) + 1).lower() + enum = "enum%s" % toRoman(get_nested_level(node)).lower() # type: ignore[no-untyped-call] + enumnext = "enum%s" % toRoman(get_nested_level(node) + 1).lower() # type: ignore[no-untyped-call] style = ENUMERATE_LIST_STYLE.get(get_enumtype(node)) prefix = node.get('prefix', '') suffix = node.get('suffix', '.') diff --git a/tests/test_builders/test_build_html_5_output.py b/tests/test_builders/test_build_html_5_output.py index 388c32466..98d1c9611 100644 --- a/tests/test_builders/test_build_html_5_output.py +++ b/tests/test_builders/test_build_html_5_output.py @@ -1,17 +1,25 @@ """Test the HTML builder and check output against XPath.""" +from __future__ import annotations + import re +from typing import TYPE_CHECKING import pytest from docutils import nodes from tests.test_builders.xpath_util import check_xpath +if TYPE_CHECKING: + from collections.abc import Callable, Iterable + from typing import Literal + from xml.etree.ElementTree import Element -def tail_check(check): + +def tail_check(check: str) -> Callable[[Iterable[Element]], Literal[True]]: rex = re.compile(check) - def checker(nodes): + def checker(nodes: Iterable[Element]) -> Literal[True]: for node in nodes: if node.tail and rex.search(node.tail): return True diff --git a/tests/test_builders/test_build_linkcheck.py b/tests/test_builders/test_build_linkcheck.py index d3775c760..98621a5ff 100644 --- a/tests/test_builders/test_build_linkcheck.py +++ b/tests/test_builders/test_build_linkcheck.py @@ -37,6 +37,9 @@ ts_re = re.compile(r".*\[(?P.*)\].*") if TYPE_CHECKING: from collections.abc import Callable, Iterable from io import StringIO + from typing import Any + + from urllib3 import HTTPConnectionPool from sphinx.application import Sphinx @@ -77,8 +80,8 @@ class DefaultsHandler(BaseHTTPRequestHandler): class ConnectionMeasurement: """Measure the number of distinct host connections created during linkchecking""" - def __init__(self): - self.connections = set() + def __init__(self) -> None: + self.connections: set[HTTPConnectionPool] = set() self.urllib3_connection_from_url = PoolManager.connection_from_url self.patcher = mock.patch.object( target=PoolManager, @@ -86,7 +89,7 @@ class ConnectionMeasurement: new=self._collect_connections(), ) - def _collect_connections(self): + def _collect_connections(self) -> Callable[[object, str], HTTPConnectionPool]: def connection_collector(obj, url): connection = self.urllib3_connection_from_url(obj, url) self.connections.add(connection) @@ -436,7 +439,10 @@ def test_decoding_error_anchor_ignored(app): assert row['status'] == 'ignored' -def custom_handler(valid_credentials=(), success_criteria=lambda _: True): +def custom_handler( + valid_credentials: tuple[str, str] | None = None, + success_criteria: Callable[[Any], bool] = lambda _: True +) -> type[BaseHTTPRequestHandler]: """ Returns an HTTP request handler that authenticates the client and then determines an appropriate HTTP response code, based on caller-provided credentials and optional @@ -591,11 +597,11 @@ def test_linkcheck_request_headers_default(app: Sphinx) -> None: assert content["status"] == "working" -def make_redirect_handler(*, support_head): +def make_redirect_handler(*, support_head: bool) -> type[BaseHTTPRequestHandler]: class RedirectOnceHandler(BaseHTTPRequestHandler): protocol_version = "HTTP/1.1" - def do_HEAD(self): + def do_HEAD(self) -> None: if support_head: self.do_GET() else: @@ -603,7 +609,7 @@ def make_redirect_handler(*, support_head): self.send_header("Content-Length", "0") self.end_headers() - def do_GET(self): + def do_GET(self) -> None: if self.path == "/?redirected=1": self.send_response(204, "No content") else: @@ -843,7 +849,7 @@ def test_TooManyRedirects_on_HEAD(app, monkeypatch): } -def make_retry_after_handler(responses): +def make_retry_after_handler(responses: list[tuple[int, str | None]]) -> type[BaseHTTPRequestHandler]: class RetryAfterHandler(BaseHTTPRequestHandler): protocol_version = "HTTP/1.1" diff --git a/tests/test_builders/test_build_text.py b/tests/test_builders/test_build_text.py index 6dc0d0375..d273ca745 100644 --- a/tests/test_builders/test_build_text.py +++ b/tests/test_builders/test_build_text.py @@ -1,12 +1,19 @@ """Test the build process with Text builder with the test root.""" +from __future__ import annotations + +from typing import TYPE_CHECKING + import pytest from docutils.utils import column_width from sphinx.writers.text import MAXWIDTH, Cell, Table +if TYPE_CHECKING: + from typing import Any -def with_text_app(*args, **kw): + +def with_text_app(*args: Any, **kw: Any) -> pytest.MarkDecorator: default_kw = { 'buildername': 'text', 'testroot': 'build-text', diff --git a/tests/test_extensions/test_ext_math.py b/tests/test_extensions/test_ext_math.py index 80a5ae71a..34505bf83 100644 --- a/tests/test_extensions/test_ext_math.py +++ b/tests/test_extensions/test_ext_math.py @@ -12,7 +12,7 @@ from sphinx.ext.mathjax import MATHJAX_URL from sphinx.testing.util import assert_node -def has_binary(binary): +def has_binary(binary: str) -> bool: try: subprocess.check_output([binary]) except FileNotFoundError: diff --git a/tests/test_extensions/test_ext_viewcode.py b/tests/test_extensions/test_ext_viewcode.py index 800904a55..bc8427d5e 100644 --- a/tests/test_extensions/test_ext_viewcode.py +++ b/tests/test_extensions/test_ext_viewcode.py @@ -1,12 +1,20 @@ """Test sphinx.ext.viewcode extension.""" +from __future__ import annotations + import re import shutil +from typing import TYPE_CHECKING import pytest +if TYPE_CHECKING: + from io import StringIO -def check_viewcode_output(app, warning): + from sphinx.application import Sphinx + + +def check_viewcode_output(app: Sphinx, warning: StringIO) -> str: warnings = re.sub(r'\\+', '/', warning.getvalue()) assert re.findall( r"index.rst:\d+: WARNING: Object named 'func1' not found in include " + diff --git a/tests/test_intl/test_locale.py b/tests/test_intl/test_locale.py index 11dd95db9..400b6e7ca 100644 --- a/tests/test_intl/test_locale.py +++ b/tests/test_intl/test_locale.py @@ -1,9 +1,17 @@ """Test locale.""" +from __future__ import annotations + +from typing import TYPE_CHECKING + import pytest from sphinx import locale +if TYPE_CHECKING: + from collections.abc import Callable + from pathlib import Path + @pytest.fixture(autouse=True) def _cleanup_translations(): @@ -57,7 +65,7 @@ def test_add_message_catalog(app, rootdir): assert _('Hello reST') == 'Hello reST' -def _empty_language_translation(rootdir): +def _empty_language_translation(rootdir: Path) -> Callable[[str], str]: locale_dirs, catalog = [rootdir / 'test-locale' / 'locale1'], 'myext' locale.translators.clear() locale.init(locale_dirs, language=None, catalog=catalog) diff --git a/tests/test_util/test_util_docutils.py b/tests/test_util/test_util_docutils.py index 69999eb97..cefee412f 100644 --- a/tests/test_util/test_util_docutils.py +++ b/tests/test_util/test_util_docutils.py @@ -1,6 +1,9 @@ """Tests util.utils functions.""" +from __future__ import annotations + import os +from typing import TYPE_CHECKING from docutils import nodes @@ -12,6 +15,9 @@ from sphinx.util.docutils import ( register_node, ) +if TYPE_CHECKING: + from sphinx.builders import Builder + def test_register_node(): class custom_node(nodes.Element): @@ -66,9 +72,9 @@ def test_SphinxTranslator(app): pass class MyTranslator(SphinxTranslator): - def __init__(self, *args): - self.called = [] - super().__init__(*args) + def __init__(self, document: nodes.document, builder: Builder): + self.called: list[str] = [] + super().__init__(document, builder) def visit_document(self, node): pass diff --git a/tests/test_util/test_util_inventory.py b/tests/test_util/test_util_inventory.py index 211dc17e7..4de8f1d0f 100644 --- a/tests/test_util/test_util_inventory.py +++ b/tests/test_util/test_util_inventory.py @@ -1,7 +1,11 @@ """Test inventory util functions.""" + +from __future__ import annotations + import os import posixpath from io import BytesIO +from typing import TYPE_CHECKING import sphinx.locale from sphinx.testing.util import SphinxTestApp @@ -14,6 +18,9 @@ from tests.test_util.intersphinx_data import ( INVENTORY_V2_NO_VERSION, ) +if TYPE_CHECKING: + from pathlib import Path + def test_read_inventory_v1(): f = BytesIO(INVENTORY_V1) @@ -67,7 +74,7 @@ def test_ambiguous_definition_warning(warning, status): assert mult_defs_b in status.getvalue().lower() -def _write_appconfig(dir, language, prefix=None): +def _write_appconfig(dir: Path, language: str, prefix: str | None = None) -> Path: prefix = prefix or language os.makedirs(dir / prefix, exist_ok=True) (dir / prefix / 'conf.py').write_text(f'language = "{language}"', encoding='utf8') @@ -77,7 +84,7 @@ def _write_appconfig(dir, language, prefix=None): return dir / prefix -def _build_inventory(srcdir): +def _build_inventory(srcdir: Path) -> Path: app = SphinxTestApp(srcdir=srcdir) app.build() sphinx.locale.translators.clear()