Skip to content

[rst] Improve unreferenced footnote warnings #12730

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
-------
44 changes: 31 additions & 13 deletions sphinx/transforms/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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):
Expand Down
39 changes: 39 additions & 0 deletions tests/test_transforms/test_unreferenced_footnotes.py
Original file line number Diff line number Diff line change
@@ -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()
Loading