Skip to content

Commit 0fb1ae9

Browse files
ilia-katspre-commit-ci[bot]chrisjsewell
authored
👌 IMPROVE: MathJax 4 compatibility (Sphinx 9) (#1110)
Sphinx 9 introduced `mathjax4_config` alongside the existing `mathjax3_config`. This PR updates MyST-Parser's mathjax override logic to handle both. ### Changes - Remove dead Sphinx 3 / MathJax 2 code path (`mathjax_config['tex2jax']`) — MyST-Parser requires Sphinx ≥ 8 - Add `mathjax4_config` support: prefer whichever config the user has explicitly set, falling back to the Sphinx version's default (`mathjax4_config` for Sphinx 9+, `mathjax3_config` for 8) - Ensure `processHtmlClass` is always applied even when no user mathjax config is pre-set - Simplify `log_override_warning` to use the actual config attribute name - Fix typo in module docstring ("mrakdown-it-py" → "markdown-it-py") ### Tests added - `test_mathjax_default` — verifies `processHtmlClass` is set with no user config - `test_mathjax_no_override` — verifies `myst_update_mathjax=False` skips override - `test_mathjax4_warning` — verifies warning when overriding `mathjax4_config` (Sphinx 9+) - `test_mathjax3_config_on_sphinx9` — verifies explicit `mathjax3_config` is still respected on Sphinx 9 --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Chris Sewell <chrisj_sewell@hotmail.com>
1 parent f153b4b commit 0fb1ae9

6 files changed

Lines changed: 150 additions & 39 deletions

File tree

myst_parser/sphinx_ext/mathjax.py

Lines changed: 33 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
This fixes two issues:
44
55
1. Mathjax should not search for ``$`` delimiters, nor LaTeX amsmath environments,
6-
since we already achieve this with the dollarmath and amsmath mrakdown-it-py plugins
6+
since we already achieve this with the dollarmath and amsmath markdown-it-py plugins
77
2. amsmath math blocks should be wrapped in mathjax delimiters (default ``\\[...\\]``),
88
and assigned an equation number
99
@@ -20,17 +20,13 @@
2020
logger = logging.getLogger(__name__)
2121

2222

23-
def log_override_warning(app: Sphinx, version: int, current: str, new: str) -> None:
23+
def log_override_warning(app: Sphinx, config_name: str, current: str, new: str) -> None:
2424
"""Log a warning if MathJax configuration being overridden."""
2525
if logging.is_suppressed_warning("myst", "mathjax", app.config.suppress_warnings):
2626
return
27-
config_name = (
28-
"mathjax3_config['options']['processHtmlClass']"
29-
if version == 3
30-
else "mathjax_config['tex2jax']['processClass']"
31-
)
3227
logger.warning(
33-
f"`{config_name}` is being overridden by myst-parser: '{current}' -> '{new}'. "
28+
f"`{config_name}['options']['processHtmlClass']` "
29+
f"is being overridden by myst-parser: '{current}' -> '{new}'. "
3430
"Set `suppress_warnings=['myst.mathjax']` to ignore this warning, or "
3531
"`myst_update_mathjax=False` if this is undesirable."
3632
)
@@ -59,37 +55,35 @@ def override_mathjax(app: Sphinx):
5955

6056
mjax_classes = app.env.myst_config.mathjax_classes # type: ignore[attr-defined]
6157

62-
if "mathjax3_config" in app.config:
63-
# sphinx 4 + mathjax 3
64-
app.config.mathjax3_config = app.config.mathjax3_config or {}
65-
app.config.mathjax3_config.setdefault("options", {})
66-
if (
67-
"processHtmlClass" in app.config.mathjax3_config["options"]
68-
and app.config.mathjax3_config["options"]["processHtmlClass"]
69-
!= mjax_classes
70-
):
71-
log_override_warning(
72-
app,
73-
3,
74-
app.config.mathjax3_config["options"]["processHtmlClass"],
75-
mjax_classes,
76-
)
77-
app.config.mathjax3_config["options"]["processHtmlClass"] = mjax_classes
78-
elif "mathjax_config" in app.config:
79-
# sphinx 3 + mathjax 2
80-
app.config.mathjax_config = app.config.mathjax_config or {}
81-
app.config.mathjax_config.setdefault("tex2jax", {})
82-
if (
83-
"processClass" in app.config.mathjax_config["tex2jax"]
84-
and app.config.mathjax_config["tex2jax"]["processClass"] != mjax_classes
85-
):
86-
log_override_warning(
87-
app,
88-
2,
89-
app.config.mathjax_config["tex2jax"]["processClass"],
90-
mjax_classes,
91-
)
92-
app.config.mathjax_config["tex2jax"]["processClass"] = mjax_classes
58+
# Determine which mathjax config to modify:
59+
# prefer whichever the user has explicitly set, falling back to
60+
# the Sphinx version's default (mathjax4_config for Sphinx 9+, mathjax3_config for 8).
61+
# Note: if a user sets both mathjax3_config and mathjax4_config, we only update
62+
# the v4 config (Sphinx emits both but v4 clobbers v3 at runtime anyway).
63+
has_mjax4 = hasattr(app.config, "mathjax4_config")
64+
if has_mjax4 and app.config.mathjax4_config:
65+
config_attr = "mathjax4_config"
66+
elif app.config.mathjax3_config:
67+
config_attr = "mathjax3_config"
68+
elif has_mjax4:
69+
config_attr = "mathjax4_config"
70+
else:
71+
config_attr = "mathjax3_config"
72+
73+
config: dict = getattr(app.config, config_attr) or {} # type: ignore[type-arg]
74+
config.setdefault("options", {})
75+
if (
76+
"processHtmlClass" in config["options"]
77+
and config["options"]["processHtmlClass"] != mjax_classes
78+
):
79+
log_override_warning(
80+
app,
81+
config_attr,
82+
config["options"]["processHtmlClass"],
83+
mjax_classes,
84+
)
85+
config["options"]["processHtmlClass"] = mjax_classes
86+
setattr(app.config, config_attr, config)
9387

9488

9589
def html_visit_displaymath(self: HTMLTranslator, node: nodes.math_block) -> None:
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
extensions = ["myst_parser"]
2+
exclude_patterns = ["_build"]
3+
4+
mathjax4_config = {"options": {"processHtmlClass": "other"}}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Test
2+
3+
```{math}
4+
a = 1
5+
```
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
extensions = ["myst_parser"]
2+
exclude_patterns = ["_build"]
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Test
2+
3+
```{math}
4+
a = 1
5+
```

tests/test_sphinx/test_sphinx_builds.py

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -595,6 +595,107 @@ def test_mathjax_warning(
595595
)
596596

597597

598+
@pytest.mark.sphinx(
599+
buildername="html",
600+
srcdir=os.path.join(SOURCE_DIR, "mathjax_default"),
601+
freshenv=True,
602+
confoverrides={"myst_enable_extensions": ["dollarmath"]},
603+
)
604+
def test_mathjax_default(
605+
app,
606+
status,
607+
warning,
608+
):
609+
"""Test mathjax processHtmlClass is set even without user config."""
610+
app.build()
611+
assert "build succeeded" in status.getvalue()
612+
warnings = warning.getvalue().strip()
613+
assert "overridden by myst-parser" not in warnings
614+
# Verify processHtmlClass was applied to the appropriate config
615+
if hasattr(app.config, "mathjax4_config"):
616+
config = app.config.mathjax4_config
617+
else:
618+
config = app.config.mathjax3_config
619+
assert config["options"]["processHtmlClass"] == (
620+
"tex2jax_process|mathjax_process|math|output_area"
621+
)
622+
623+
624+
@pytest.mark.sphinx(
625+
buildername="html",
626+
srcdir=os.path.join(SOURCE_DIR, "mathjax_default"),
627+
freshenv=True,
628+
confoverrides={
629+
"myst_enable_extensions": ["dollarmath"],
630+
"myst_update_mathjax": False,
631+
},
632+
)
633+
def test_mathjax_no_override(
634+
app,
635+
status,
636+
warning,
637+
):
638+
"""Test that myst_update_mathjax=False prevents config override."""
639+
app.build()
640+
assert "build succeeded" in status.getvalue()
641+
# processHtmlClass should NOT have been set
642+
if hasattr(app.config, "mathjax4_config"):
643+
assert app.config.mathjax4_config is None
644+
else:
645+
assert app.config.mathjax3_config is None
646+
647+
648+
@pytest.mark.skipif(
649+
int(__import__("sphinx").__version__.split(".")[0]) < 9,
650+
reason="mathjax4_config not available before Sphinx 9",
651+
)
652+
@pytest.mark.sphinx(
653+
buildername="html",
654+
srcdir=os.path.join(SOURCE_DIR, "mathjax4"),
655+
freshenv=True,
656+
confoverrides={"myst_enable_extensions": ["dollarmath"]},
657+
)
658+
def test_mathjax4_warning(
659+
app,
660+
status,
661+
warning,
662+
):
663+
"""Test mathjax4_config override warning (Sphinx 9+)."""
664+
app.build()
665+
assert "build succeeded" in status.getvalue()
666+
warnings = warning.getvalue().strip()
667+
assert (
668+
"overridden by myst-parser: 'other' -> 'tex2jax_process|mathjax_process|math|output_area'"
669+
in warnings
670+
)
671+
672+
673+
@pytest.mark.skipif(
674+
int(__import__("sphinx").__version__.split(".")[0]) < 9,
675+
reason="mathjax4_config not available before Sphinx 9",
676+
)
677+
@pytest.mark.sphinx(
678+
buildername="html",
679+
srcdir=os.path.join(SOURCE_DIR, "mathjax"),
680+
freshenv=True,
681+
confoverrides={"myst_enable_extensions": ["dollarmath"]},
682+
)
683+
def test_mathjax3_config_on_sphinx9(
684+
app,
685+
status,
686+
warning,
687+
):
688+
"""Test that explicit mathjax3_config is still respected on Sphinx 9."""
689+
app.build()
690+
assert "build succeeded" in status.getvalue()
691+
warnings = warning.getvalue().strip()
692+
# mathjax3_config was explicitly set, so myst should modify it (not mathjax4_config)
693+
assert "mathjax3_config" in warnings
694+
assert app.config.mathjax3_config["options"]["processHtmlClass"] == (
695+
"tex2jax_process|mathjax_process|math|output_area"
696+
)
697+
698+
598699
@pytest.mark.skipif(
599700
sys.platform == "win32",
600701
reason="Unicode encoding issues on Windows",

0 commit comments

Comments
 (0)