diff --git a/docs/src/reference.rst b/docs/src/reference.rst index 01177a2..507a545 100644 --- a/docs/src/reference.rst +++ b/docs/src/reference.rst @@ -65,6 +65,13 @@ Configuration Type: ``bool``. Issue warning when failing to resolve the canonical location of an object that a code element references. Defaults to :code:`False`. +.. confval:: codeautolink_warn_on_no_backreference + + Type: ``bool``. Issue warning when no backreference could be found + from reference documentation using the :rst:dir:`autolink-examples` table. + This highlights objects for which no tutorial, example or how-to exists. + Defaults to :code:`False`. + Directives ---------- .. rst:directive:: .. autolink-examples:: object diff --git a/docs/src/release_notes.rst b/docs/src/release_notes.rst index fba8213..54a342e 100644 --- a/docs/src/release_notes.rst +++ b/docs/src/release_notes.rst @@ -8,8 +8,11 @@ These release notes are based on sphinx-codeautolink adheres to `Semantic Versioning `_. -0.16.3 (unreleased) +0.17.0 (unreleased) ------------------- +- Introduce :confval:`codeautolink_warn_on_no_backreference` to highlight + where reference documentation does not appear to have + a corresponding tutorial or how-to (:issue:`161`) - Add more Pygments lexer aliases in code blocks (:issue:`160`) 0.16.2 (2025-01-16) diff --git a/pyproject.toml b/pyproject.toml index eeeb668..d66715c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -75,6 +75,7 @@ extend-ignore = [ "COM812", # recommended by ruff format "ISC001", # recommended by ruff format "PLR0913", # many arguments is fine + "PLR0915", # many statements is fine ] extend-unsafe-fixes = ["F401"] isort.split-on-trailing-comma = false diff --git a/src/sphinx_codeautolink/__init__.py b/src/sphinx_codeautolink/__init__.py index cccae04..0f34af7 100644 --- a/src/sphinx_codeautolink/__init__.py +++ b/src/sphinx_codeautolink/__init__.py @@ -43,6 +43,12 @@ def setup(app: Sphinx): rebuild="html", types=[bool], ) + app.add_config_value( + "codeautolink_warn_on_no_backreference", + default=False, + rebuild="html", + types=[bool], + ) app.add_directive("autolink-concat", directive.Concat) app.add_directive("autolink-examples", directive.Examples) diff --git a/src/sphinx_codeautolink/extension/__init__.py b/src/sphinx_codeautolink/extension/__init__.py index bd1f1c4..1ccf33f 100644 --- a/src/sphinx_codeautolink/extension/__init__.py +++ b/src/sphinx_codeautolink/extension/__init__.py @@ -74,6 +74,7 @@ def __init__(self) -> None: self.inventory_map: dict[str, str] = {} self.warn_missing_inventory = None self.warn_failed_resolve = None + self.warn_no_backreference = None # Populated once self.outdated_docs: set[str] = set() @@ -103,6 +104,7 @@ def build_inited(self, app) -> None: self.inventory_map = app.config.codeautolink_inventory_map self.warn_missing_inventory = app.config.codeautolink_warn_on_missing_inventory self.warn_failed_resolve = app.config.codeautolink_warn_on_failed_resolve + self.warn_no_backreference = app.config.codeautolink_warn_on_no_backreference # Append static resources path so references in setup() are valid app.config.html_static_path.append( @@ -255,7 +257,11 @@ def generate_backref_tables(self, app, doctree, docname): rm_vis = RemoveExtensionVisitor(doctree) return doctree.walkabout(rm_vis) - visitor = CodeRefsVisitor(doctree, code_refs=self.code_refs) + visitor = CodeRefsVisitor( + doctree, + code_refs=self.code_refs, + warn_no_backreference=self.warn_no_backreference, + ) doctree.walk(visitor) return None diff --git a/src/sphinx_codeautolink/extension/backref.py b/src/sphinx_codeautolink/extension/backref.py index 270a7a8..abcff27 100644 --- a/src/sphinx_codeautolink/extension/backref.py +++ b/src/sphinx_codeautolink/extension/backref.py @@ -4,6 +4,8 @@ from docutils import nodes +from sphinx_codeautolink.warn import logger, warn_type + from .directive import DeferredExamples @@ -56,10 +58,15 @@ class CodeRefsVisitor(nodes.SparseNodeVisitor): """Replace :class:`DeferredCodeReferences` with table of concrete references.""" def __init__( - self, *args, code_refs: dict[str, list[CodeExample]], **kwargs + self, + *args, + code_refs: dict[str, list[CodeExample]], + warn_no_backreference: bool = False, + **kwargs, ) -> None: super().__init__(*args, **kwargs) self.code_refs = code_refs + self.warn_no_backreference = warn_no_backreference def unknown_departure(self, node) -> None: """Ignore unknown nodes.""" @@ -79,6 +86,11 @@ def unknown_visit(self, node) -> None: items = sorted(set(items)) if not items: + if self.warn_no_backreference: + msg = f"No backreference for: '{node.ref}'" + logger.warning( + msg, type=warn_type, subtype="no_backreference", location=node + ) # Remove surrounding paragraph too node.parent.parent.remove(node.parent) return diff --git a/tests/extension/__init__.py b/tests/extension/__init__.py index bf3613c..f03bf59 100644 --- a/tests/extension/__init__.py +++ b/tests/extension/__init__.py @@ -26,6 +26,7 @@ } codeautolink_warn_on_missing_inventory = True codeautolink_warn_on_failed_resolve = True +codeautolink_warn_on_no_backreference = False """ any_whitespace = re.compile(r"\s*") diff --git a/tests/extension/fail/no_backreference.txt b/tests/extension/fail/no_backreference.txt new file mode 100644 index 0000000..93d3129 --- /dev/null +++ b/tests/extension/fail/no_backreference.txt @@ -0,0 +1,9 @@ +codeautolink_warn_on_no_backreference = True +# split +Test project +============ + +.. code:: python + + import test_project + test_project.bar()