diff --git a/AUTHORS.rst b/AUTHORS.rst index fd129bb8f72..8d3e87ef22e 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -56,6 +56,7 @@ Contributors * Erik Bedard -- config options for :mod:`sphinx.ext.duration` * Etienne Desautels -- apidoc module * Ezio Melotti -- collapsible sidebar JavaScript +* Fazeel Usmani -- parser API fixes * Filip Vavera -- napoleon todo directive * Florian Best -- log improvements * Glenn Matthews -- python domain signature improvements diff --git a/CHANGES.rst b/CHANGES.rst index 6f7279e8011..17b1a781084 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,3 +1,16 @@ +Release 9.2.0 (in development) +============================== + +Deprecated +---------- + +* #14371: Un-deprecate :py:attr:`!Parser.config` and :py:attr:`!Parser.env`. + These remain the supported way for a custom parser to read the build + configuration and environment. + :py:meth:`!Parser.set_application` continues to be deprecated and is + scheduled for removal in Sphinx 10. + Patch by Fazeel Usmani. + Release 9.1.0 (released Dec 31, 2025) ===================================== diff --git a/doc/extdev/parserapi.rst b/doc/extdev/parserapi.rst index b5480996065..f9347a34dc7 100644 --- a/doc/extdev/parserapi.rst +++ b/doc/extdev/parserapi.rst @@ -36,3 +36,36 @@ to configure their settings appropriately. .. autoclass:: Parser :members: + +Accessing the Sphinx config and environment +------------------------------------------- + +A custom parser can read Sphinx :class:`~sphinx.config.Config` values and the +:class:`~sphinx.environment.BuildEnvironment` through the inherited +``Parser.config`` and ``Parser.env`` properties. Sphinx populates the +backing ``_config`` and ``_env`` attributes when it instantiates a parser +registered via :meth:`.Sphinx.add_source_parser`, so they are available +from the start of ``parse()``. + +.. code-block:: python + + from sphinx.parsers import Parser + + + class MyParser(Parser): + supported = ('mytype',) + + def parse(self, inputstring, document): + # ``self.config`` and ``self.env`` are populated by Sphinx + encoding = self.config.source_encoding + docname = self.env.docname + ... + +.. versionchanged:: 9.2 + ``Parser.config`` and ``Parser.env`` are no longer deprecated. + +.. deprecated:: 9.0 + The ``Parser.set_application`` hook is deprecated and will be removed in + Sphinx 10. Sphinx now populates ``_config`` and ``_env`` directly when + the parser instance is created, so custom parsers no longer need to + override ``set_application`` to capture these objects. diff --git a/sphinx/parsers.py b/sphinx/parsers.py index 5731ccd6f66..70abe8ac8c3 100644 --- a/sphinx/parsers.py +++ b/sphinx/parsers.py @@ -36,24 +36,40 @@ class Parser(docutils.parsers.Parser): @property def config(self) -> Config: - """The config object.""" - cls_module = self.__class__.__module__ - cls_name = self.__class__.__qualname__ - _deprecation_warning(cls_module, f'{cls_name}.config', remove=(10, 0)) + """The config object. + + Populated by Sphinx when it instantiates a parser registered via + :meth:`.Sphinx.add_source_parser`, so this is available from the + start of ``parse()``. + + .. versionchanged:: 9.2 + No longer deprecated. + """ return self._config @property def env(self) -> BuildEnvironment: - """The environment object.""" - cls_module = self.__class__.__module__ - cls_name = self.__class__.__qualname__ - _deprecation_warning(cls_module, f'{cls_name}.env', remove=(10, 0)) + """The environment object. + + Populated by Sphinx when it instantiates a parser registered via + :meth:`.Sphinx.add_source_parser`, so this is available from the + start of ``parse()``. + + .. versionchanged:: 9.2 + No longer deprecated. + """ return self._env def set_application(self, app: Sphinx) -> None: - """set_application will be called from Sphinx to set app and other instance variables + """Legacy compatibility hook for receiving the Sphinx application. :param sphinx.application.Sphinx app: Sphinx application object + + .. deprecated:: 9.0 + Sphinx now makes ``config`` and ``env`` available on the parser + instance automatically, so custom parsers no longer need to + override this method to capture the application. It will be + removed in Sphinx 10. """ cls_module = self.__class__.__module__ cls_name = self.__class__.__qualname__ diff --git a/tests/test_markup/test_parser.py b/tests/test_markup/test_parser.py index 215fdf04dd6..34ae11be8ba 100644 --- a/tests/test_markup/test_parser.py +++ b/tests/test_markup/test_parser.py @@ -2,12 +2,14 @@ from __future__ import annotations +import warnings from typing import TYPE_CHECKING from unittest.mock import Mock, patch import pytest -from sphinx.parsers import RSTParser +from sphinx.deprecation import RemovedInSphinx10Warning +from sphinx.parsers import Parser, RSTParser from sphinx.util.docutils import new_document if TYPE_CHECKING: @@ -68,3 +70,36 @@ def test_RSTParser_prolog_epilog(RSTStateMachine: Mock, app: SphinxTestApp) -> N ('dummy.rst', 0, ' hello Sphinx world'), ('dummy.rst', 1, ' Sphinx is a document generator'), ] + + +@pytest.mark.sphinx('html', testroot='basic') +def test_parser_config_env_populated_by_registry(app: SphinxTestApp) -> None: + """Parsers built via the registry expose config/env without warning (#14371). + + This goes through the real construction path in + :meth:`.SphinxComponentRegistry.create_source_parser` so a regression in + that code path (for example, removal of the ``isinstance(..., SphinxParser)`` + gate) would be caught here. + """ + parser = app.registry.create_source_parser( + 'restructuredtext', config=app.config, env=app.env + ) + # ``create_source_parser`` is typed as ``docutils.parsers.Parser``; narrow + # to the Sphinx base class so ``config`` / ``env`` access type-checks and + # so a future regression that stops wrapping Sphinx parsers is caught here. + assert isinstance(parser, Parser) + + with warnings.catch_warnings(): + warnings.simplefilter('error', RemovedInSphinx10Warning) + assert parser.config is app.config + assert parser.env is app.env + + +@pytest.mark.sphinx('html', testroot='basic') +def test_parser_set_application_still_deprecated(app: SphinxTestApp) -> None: + """``Parser.set_application`` must still emit a deprecation warning (#14371).""" + parser = RSTParser() + with pytest.warns(RemovedInSphinx10Warning, match='set_application'): + parser.set_application(app) + assert parser.config is app.config + assert parser.env is app.env