From 05cc39d9b2a64b09504c06bfc2299aad96c85ccd Mon Sep 17 00:00:00 2001 From: Chris Sewell Date: Mon, 5 Aug 2024 10:28:13 +0200 Subject: [PATCH 1/2] [rst] Improve unreferenced footnote warnings (#12730) The previous `UnreferencedFootnotesDetector` transform was untested and missed warnings for a number of cases. This commit adds a test, to cover a reasonable range of scenarios, then changes how the detection works to pass this test. The transform now runs just after the docutils `Footnote` resolution transform (changing its priority from 200 to 622) then simply check for any footnotes without "back-references". --- CHANGES.rst | 6 +++ sphinx/transforms/__init__.py | 44 +++++++++++++------ .../test_unreferenced_footnotes.py | 39 ++++++++++++++++ 3 files changed, 76 insertions(+), 13 deletions(-) create mode 100644 tests/test_transforms/test_unreferenced_footnotes.py diff --git a/CHANGES.rst b/CHANGES.rst index 1d04bc27010..4b920ca3911 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -20,5 +20,11 @@ Bugs fixed :confval:`intersphinx_cache_limit`. Patch by Shengyu Zhang. +* #12730: The ``UnreferencedFootnotesDetector`` transform has been improved + to more consistently detect unreferenced footnotes. + Note, the priority of the transform has been changed from 200 to 622, + so that it now runs after the docutils ``Footnotes`` resolution transform. + Patch by Chris Sewell. + Testing ------- diff --git a/sphinx/transforms/__init__.py b/sphinx/transforms/__init__.py index 7ccef6aacaa..5fe133137fd 100644 --- a/sphinx/transforms/__init__.py +++ b/sphinx/transforms/__init__.py @@ -9,6 +9,7 @@ from docutils import nodes from docutils.transforms import Transform, Transformer from docutils.transforms.parts import ContentsFilter +from docutils.transforms.references import Footnotes from docutils.transforms.universal import SmartQuotes from docutils.utils import normalize_language_tag from docutils.utils.smartquotes import smartchars @@ -294,23 +295,40 @@ class UnreferencedFootnotesDetector(SphinxTransform): Detect unreferenced footnotes and emit warnings """ - default_priority = 200 + default_priority = Footnotes.default_priority + 2 def apply(self, **kwargs: Any) -> None: for node in self.document.footnotes: - if node['names'] == []: - # footnote having duplicated number. It is already warned at parser. - pass - elif node['names'][0] not in self.document.footnote_refs: - logger.warning(__('Footnote [%s] is not referenced.'), node['names'][0], - type='ref', subtype='footnote', - location=node) - + # note we do not warn on duplicate footnotes here + # (i.e. where the name has been moved to dupnames) + # since this is already reported by docutils + if not node['backrefs'] and node["names"]: + logger.warning( + __('Footnote [%s] is not referenced.'), + node['names'][0] if node['names'] else node['dupnames'][0], + type='ref', + subtype='footnote', + location=node + ) + for node in self.document.symbol_footnotes: + if not node['backrefs']: + logger.warning( + __('Footnote [*] is not referenced.'), + type='ref', + subtype='footnote', + location=node + ) for node in self.document.autofootnotes: - if not any(ref['auto'] == node['auto'] for ref in self.document.autofootnote_refs): - logger.warning(__('Footnote [#] is not referenced.'), - type='ref', subtype='footnote', - location=node) + # note we do not warn on duplicate footnotes here + # (i.e. where the name has been moved to dupnames) + # since this is already reported by docutils + if not node['backrefs'] and node["names"]: + logger.warning( + __('Footnote [#] is not referenced.'), + type='ref', + subtype='footnote', + location=node + ) class DoctestTransform(SphinxTransform): diff --git a/tests/test_transforms/test_unreferenced_footnotes.py b/tests/test_transforms/test_unreferenced_footnotes.py new file mode 100644 index 00000000000..938adcb7fc6 --- /dev/null +++ b/tests/test_transforms/test_unreferenced_footnotes.py @@ -0,0 +1,39 @@ +"""Test the ``UnreferencedFootnotesDetector`` transform.""" + +from pathlib import Path + +from sphinx.testing.util import SphinxTestApp +from sphinx.util.console import strip_colors + + +def test_warnings(make_app: type[SphinxTestApp], tmp_path: Path) -> None: + """Test that warnings are emitted for unreferenced footnotes.""" + tmp_path.joinpath("conf.py").touch() + tmp_path.joinpath("index.rst").write_text( + """ +Title +===== +[1]_ [#label2]_ + +.. [1] This is a normal footnote. +.. [2] This is a normal footnote. +.. [2] This is a normal footnote. +.. [3] This is a normal footnote. +.. [*] This is a symbol footnote. +.. [#] This is an auto-numbered footnote. +.. [#label1] This is an auto-numbered footnote with a label. +.. [#label1] This is an auto-numbered footnote with a label. +.. [#label2] This is an auto-numbered footnote with a label. + """, encoding="utf8" + ) + app = make_app(srcdir=tmp_path) + app.build() + warnings = strip_colors(app.warning.getvalue()).replace(str(tmp_path / "index.rst"), "source/index.rst") + print(warnings) + assert warnings.strip() == """ +source/index.rst:8: WARNING: Duplicate explicit target name: "2". [docutils] +source/index.rst:13: WARNING: Duplicate explicit target name: "label1". [docutils] +source/index.rst:9: WARNING: Footnote [3] is not referenced. [ref.footnote] +source/index.rst:10: WARNING: Footnote [*] is not referenced. [ref.footnote] +source/index.rst:11: WARNING: Footnote [#] is not referenced. [ref.footnote] +""".strip() From 586c0cd178f1d36692717dff0707078bb8f899ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20B=2E?= <2589111+jfbu@users.noreply.github.com> Date: Wed, 7 Aug 2024 17:04:07 +0200 Subject: [PATCH 2/2] Revert "Mark ``test_build_manpage`` as XFAIL following changes in Docutils master" (#12734) This reverts commit 1ed4ca7. Co-authored-by: Adam Turner <9087854+AA-Turner@users.noreply.github.com> --- tests/test_builders/test_build_manpage.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tests/test_builders/test_build_manpage.py b/tests/test_builders/test_build_manpage.py index aef507c6f80..544ddd4e261 100644 --- a/tests/test_builders/test_build_manpage.py +++ b/tests/test_builders/test_build_manpage.py @@ -7,8 +7,6 @@ from sphinx.config import Config -@pytest.mark.xfail(docutils.__version_info__[:2] > (0, 21), - reason='Docutils has removed the reference key in master') @pytest.mark.sphinx('man') def test_all(app): app.build(force_all=True) @@ -49,8 +47,6 @@ def test_man_make_section_directory(app): assert (app.outdir / 'man1' / 'projectnamenotset.1').exists() -@pytest.mark.xfail(docutils.__version_info__[:2] > (0, 21), - reason='Docutils has removed the reference key in master') @pytest.mark.sphinx('man', testroot='directive-code') def test_captioned_code_block(app): app.build(force_all=True)