Skip to content

Commit 21c8513

Browse files
authored
Avoid self.app in transforms (#13628)
1 parent 8f18b57 commit 21c8513

File tree

11 files changed

+91
-77
lines changed

11 files changed

+91
-77
lines changed

sphinx/application.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,8 @@ def _post_init_env(self) -> None:
399399
if self._fresh_env_used:
400400
self.env.find_files(self.config, self.builder)
401401

402+
self.env._builder_cls = self.builder.__class__
403+
402404
def preload_builder(self, name: str) -> None:
403405
self.registry.preload_builder(self, name)
404406

sphinx/builders/linkcheck.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -259,11 +259,11 @@ def _add_uri(self, uri: str, node: nodes.Element) -> None:
259259
:param uri: URI to add
260260
:param node: A node class where the URI was found
261261
"""
262-
builder = cast('CheckExternalLinksBuilder', self.app.builder)
262+
builder = cast('CheckExternalLinksBuilder', self.env._app.builder)
263263
hyperlinks = builder.hyperlinks
264264
docname = self.env.docname
265265

266-
if newuri := self.app.events.emit_firstresult('linkcheck-process-uri', uri):
266+
if newuri := self.env.events.emit_firstresult('linkcheck-process-uri', uri):
267267
uri = newuri
268268

269269
try:

sphinx/environment/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,9 @@ class BuildEnvironment:
107107
srcdir = _StrPathProperty()
108108
doctreedir = _StrPathProperty()
109109

110+
# builder is created after the environment.
111+
_builder_cls: type[Builder]
112+
110113
def __init__(self, app: Sphinx) -> None:
111114
self._app: Sphinx = app
112115
self.doctreedir = app.doctreedir

sphinx/ext/extlinks.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ def check_uri(self, refnode: nodes.reference) -> None:
6868
uri = refnode['refuri']
6969
title = refnode.astext()
7070

71-
for alias, (base_uri, _caption) in self.app.config.extlinks.items():
71+
for alias, (base_uri, _caption) in self.config.extlinks.items():
7272
uri_pattern = re.compile(re.escape(base_uri).replace('%s', '(?P<value>.+)'))
7373

7474
match = uri_pattern.match(uri)

sphinx/ext/viewcode.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -103,11 +103,11 @@ def _get_full_modname(modname: str, attribute: str) -> str | None:
103103
return None
104104

105105

106-
def is_supported_builder(builder: Builder) -> bool:
106+
def is_supported_builder(builder: type[Builder], viewcode_enable_epub: bool) -> bool:
107107
return (
108108
builder.format == 'html'
109109
and builder.name != 'singlehtml'
110-
and (not builder.name.startswith('epub') or builder.config.viewcode_enable_epub)
110+
and (not builder.name.startswith('epub') or viewcode_enable_epub)
111111
)
112112

113113

@@ -220,7 +220,9 @@ class ViewcodeAnchorTransform(SphinxPostTransform):
220220
default_priority = 100
221221

222222
def run(self, **kwargs: Any) -> None:
223-
if is_supported_builder(self.app.builder):
223+
if is_supported_builder(
224+
self.env._builder_cls, self.config.viewcode_enable_epub
225+
):
224226
self.convert_viewcode_anchors()
225227
else:
226228
self.remove_viewcode_anchors()
@@ -229,7 +231,7 @@ def convert_viewcode_anchors(self) -> None:
229231
for node in self.document.findall(viewcode_anchor):
230232
anchor = nodes.inline('', _('[source]'), classes=['viewcode-link'])
231233
refnode = make_refnode(
232-
self.app.builder,
234+
self.env._app.builder,
233235
node['refdoc'],
234236
node['reftarget'],
235237
node['refid'],
@@ -281,7 +283,7 @@ def collect_pages(app: Sphinx) -> Iterator[tuple[str, dict[str, Any], str]]:
281283
env = app.env
282284
if not hasattr(env, '_viewcode_modules'):
283285
return
284-
if not is_supported_builder(app.builder):
286+
if not is_supported_builder(env._builder_cls, env.config.viewcode_enable_epub):
285287
return
286288
highlighter = app.builder.highlighter # type: ignore[attr-defined]
287289
urito = app.builder.get_relative_uri

sphinx/io.py

Lines changed: 1 addition & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,9 @@
1010
from docutils.transforms.references import DanglingReferences
1111
from docutils.writers import UnfilteredWriter
1212

13-
from sphinx.transforms import AutoIndexUpgrader, DoctreeReadEvent, SphinxTransformer
14-
from sphinx.transforms.i18n import (
15-
Locale,
16-
PreserveTranslatableMessages,
17-
RemoveTranslatableInline,
18-
)
19-
from sphinx.transforms.references import SphinxDomains
13+
from sphinx.transforms import SphinxTransformer
2014
from sphinx.util import logging
2115
from sphinx.util.docutils import LoggingReporter
22-
from sphinx.versioning import UIDTransform
2316

2417
if TYPE_CHECKING:
2518
from typing import Any
@@ -113,32 +106,6 @@ def read_source(self, env: BuildEnvironment) -> str:
113106
return arg[0]
114107

115108

116-
class SphinxI18nReader(SphinxBaseReader):
117-
"""A document reader for i18n.
118-
119-
This returns the source line number of original text as current source line number
120-
to let users know where the error happened.
121-
Because the translated texts are partial and they don't have correct line numbers.
122-
"""
123-
124-
def setup(self, app: Sphinx) -> None:
125-
super().setup(app)
126-
127-
self.transforms = self.transforms + app.registry.get_transforms()
128-
unused = [
129-
PreserveTranslatableMessages,
130-
Locale,
131-
RemoveTranslatableInline,
132-
AutoIndexUpgrader,
133-
SphinxDomains,
134-
DoctreeReadEvent,
135-
UIDTransform,
136-
]
137-
for transform in unused:
138-
if transform in self.transforms:
139-
self.transforms.remove(transform)
140-
141-
142109
class SphinxDummyWriter(UnfilteredWriter): # type: ignore[type-arg]
143110
"""Dummy writer module used for generating doctree."""
144111

sphinx/transforms/__init__.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,9 @@ class SphinxTransform(Transform):
6363
@property
6464
def app(self) -> Sphinx:
6565
"""Reference to the :class:`.Sphinx` object."""
66+
cls_module = self.__class__.__module__
6667
cls_name = self.__class__.__qualname__
67-
_deprecation_warning(__name__, f'{cls_name}.app', remove=(10, 0))
68+
_deprecation_warning(cls_module, f'{cls_name}.app', remove=(10, 0))
6869
return self.env.app
6970

7071
@property
@@ -382,7 +383,7 @@ def is_available(self) -> bool:
382383
if self.config.smartquotes is False:
383384
# disabled by confval smartquotes
384385
return False
385-
if self.app.builder.name in builders:
386+
if self.env._builder_cls.name in builders:
386387
# disabled by confval smartquotes_excludes['builders']
387388
return False
388389
if self.config.language in languages:
@@ -412,7 +413,7 @@ class DoctreeReadEvent(SphinxTransform):
412413
default_priority = 880
413414

414415
def apply(self, **kwargs: Any) -> None:
415-
self.app.events.emit('doctree-read', self.document)
416+
self.env.events.emit('doctree-read', self.document)
416417

417418

418419
class GlossarySorter(SphinxTransform):

sphinx/transforms/i18n.py

Lines changed: 52 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import contextlib
66
from re import DOTALL, match
77
from textwrap import indent
8+
from types import SimpleNamespace
89
from typing import TYPE_CHECKING, Any, TypeVar
910

1011
from docutils import nodes
@@ -13,9 +14,11 @@
1314
from sphinx import addnodes
1415
from sphinx.domains.std import make_glossary_term, split_term_classifiers
1516
from sphinx.errors import ConfigError
17+
from sphinx.io import SphinxBaseReader
1618
from sphinx.locale import __
1719
from sphinx.locale import init as init_locale
18-
from sphinx.transforms import SphinxTransform
20+
from sphinx.transforms import AutoIndexUpgrader, DoctreeReadEvent, SphinxTransform
21+
from sphinx.transforms.references import SphinxDomains
1922
from sphinx.util import get_filetype, logging
2023
from sphinx.util.i18n import docname_to_domain
2124
from sphinx.util.index_entries import split_index_msg
@@ -26,12 +29,15 @@
2629
extract_messages,
2730
traverse_translatable_index,
2831
)
32+
from sphinx.versioning import UIDTransform
2933

3034
if TYPE_CHECKING:
3135
from collections.abc import Sequence
3236

3337
from sphinx.application import Sphinx
3438
from sphinx.config import Config
39+
from sphinx.environment import BuildEnvironment
40+
from sphinx.registry import SphinxComponentRegistry
3541
from sphinx.util.typing import ExtensionMetadata
3642

3743

@@ -47,36 +53,65 @@
4753
N = TypeVar('N', bound=nodes.Node)
4854

4955

56+
class _SphinxI18nReader(SphinxBaseReader):
57+
"""A document reader for internationalisation (i18n).
58+
59+
This returns the source line number of the original text
60+
as the current source line number to let users know where
61+
the error happened, because the translated texts are
62+
partial and they don't have correct line numbers.
63+
"""
64+
65+
def __init__(
66+
self, *args: Any, registry: SphinxComponentRegistry, **kwargs: Any
67+
) -> None:
68+
super().__init__(*args, **kwargs)
69+
unused = frozenset({
70+
PreserveTranslatableMessages,
71+
Locale,
72+
RemoveTranslatableInline,
73+
AutoIndexUpgrader,
74+
SphinxDomains,
75+
DoctreeReadEvent,
76+
UIDTransform,
77+
})
78+
transforms = self.transforms + registry.get_transforms()
79+
self.transforms = [
80+
transform for transform in transforms if transform not in unused
81+
]
82+
83+
5084
def publish_msgstr(
51-
app: Sphinx,
5285
source: str,
5386
source_path: str,
5487
source_line: int,
5588
config: Config,
5689
settings: Any,
90+
*,
91+
env: BuildEnvironment,
92+
registry: SphinxComponentRegistry,
5793
) -> nodes.Element:
5894
"""Publish msgstr (single line) into docutils document
5995
60-
:param sphinx.application.Sphinx app: sphinx application
6196
:param str source: source text
6297
:param str source_path: source path for warning indication
6398
:param source_line: source line for warning indication
6499
:param sphinx.config.Config config: sphinx config
65100
:param docutils.frontend.Values settings: docutils settings
66101
:return: document
67102
:rtype: docutils.nodes.document
103+
:param sphinx.environment.BuildEnvironment env: sphinx environment
104+
:param sphinx.registry.SphinxComponentRegistry registry: sphinx registry
68105
"""
69106
try:
70107
# clear rst_prolog temporarily
71108
rst_prolog = config.rst_prolog
72109
config.rst_prolog = None
73110

74-
from sphinx.io import SphinxI18nReader
75-
76-
reader = SphinxI18nReader()
77-
reader.setup(app)
111+
reader = _SphinxI18nReader(registry=registry)
112+
app = SimpleNamespace(config=config, env=env, registry=registry)
78113
filetype = get_filetype(config.source_suffix, source_path)
79-
parser = app.registry.create_source_parser(app, filetype)
114+
parser = registry.create_source_parser(app, filetype) # type: ignore[arg-type]
80115
doc = reader.read(
81116
source=StringInput(
82117
source=source, source_path=f'{source_path}:{source_line}:<translated>'
@@ -436,12 +471,13 @@ def apply(self, **kwargs: Any) -> None:
436471
msgstr = '::\n\n' + indent(msgstr, ' ' * 3)
437472

438473
patch = publish_msgstr(
439-
self.app,
440474
msgstr,
441475
source,
442476
node.line, # type: ignore[arg-type]
443477
self.config,
444478
settings,
479+
env=self.env,
480+
registry=self.env._registry,
445481
)
446482
# FIXME: no warnings about inconsistent references in this part
447483
# XXX doctest and other block markup
@@ -456,12 +492,13 @@ def apply(self, **kwargs: Any) -> None:
456492
for _id in node['ids']:
457493
term, first_classifier = split_term_classifiers(msgstr)
458494
patch = publish_msgstr(
459-
self.app,
460495
term or '',
461496
source,
462497
node.line, # type: ignore[arg-type]
463498
self.config,
464499
settings,
500+
env=self.env,
501+
registry=self.env._registry,
465502
)
466503
updater.patch = make_glossary_term(
467504
self.env,
@@ -533,12 +570,13 @@ def apply(self, **kwargs: Any) -> None:
533570
msgstr = msgstr + '\n' + '=' * len(msgstr) * 2
534571

535572
patch = publish_msgstr(
536-
self.app,
537573
msgstr,
538574
source,
539575
node.line, # type: ignore[arg-type]
540576
self.config,
541577
settings,
578+
env=self.env,
579+
registry=self.env._registry,
542580
)
543581
# Structural Subelements phase2
544582
if isinstance(node, nodes.title):
@@ -612,7 +650,7 @@ class TranslationProgressTotaliser(SphinxTransform):
612650
def apply(self, **kwargs: Any) -> None:
613651
from sphinx.builders.gettext import MessageCatalogBuilder
614652

615-
if isinstance(self.app.builder, MessageCatalogBuilder):
653+
if issubclass(self.env._builder_cls, MessageCatalogBuilder):
616654
return
617655

618656
total = translated = 0
@@ -635,7 +673,7 @@ class AddTranslationClasses(SphinxTransform):
635673
def apply(self, **kwargs: Any) -> None:
636674
from sphinx.builders.gettext import MessageCatalogBuilder
637675

638-
if isinstance(self.app.builder, MessageCatalogBuilder):
676+
if issubclass(self.env._builder_cls, MessageCatalogBuilder):
639677
return
640678

641679
if not self.config.translation_progress_classes:
@@ -673,7 +711,7 @@ class RemoveTranslatableInline(SphinxTransform):
673711
def apply(self, **kwargs: Any) -> None:
674712
from sphinx.builders.gettext import MessageCatalogBuilder
675713

676-
if isinstance(self.app.builder, MessageCatalogBuilder):
714+
if issubclass(self.env._builder_cls, MessageCatalogBuilder):
677715
return
678716

679717
matcher = NodeMatcher(nodes.inline, translatable=Any)

0 commit comments

Comments
 (0)