diff --git a/src/mkdocs_autorefs/plugin.py b/src/mkdocs_autorefs/plugin.py index 57b441a..ff8d3ae 100644 --- a/src/mkdocs_autorefs/plugin.py +++ b/src/mkdocs_autorefs/plugin.py @@ -302,7 +302,8 @@ def on_post_page(self, output: str, page: Page, **kwargs: Any) -> str: # noqa: fixed_output, unmapped = fix_refs(output, url_mapper, _legacy_refs=self.legacy_refs) if unmapped and log.isEnabledFor(logging.WARNING): - for ref in unmapped: - log.warning(f"{page.file.src_path}: Could not find cross-reference target '[{ref}]'") + for ref, context in unmapped: + message = f"from {context.filepath}:{context.lineno}: ({context.origin}) " if context else "" + log.warning(f"{page.file.src_path}: {message}Could not find cross-reference target '{ref}'") return fixed_output diff --git a/src/mkdocs_autorefs/references.py b/src/mkdocs_autorefs/references.py index f0f7723..f116310 100644 --- a/src/mkdocs_autorefs/references.py +++ b/src/mkdocs_autorefs/references.py @@ -230,7 +230,10 @@ def relative_url(url_a: str, url_b: str) -> str: # YORE: Bump 2: Remove block. -def _legacy_fix_ref(url_mapper: Callable[[str], str], unmapped: list[str]) -> Callable: +def _legacy_fix_ref( + url_mapper: Callable[[str], str], + unmapped: list[tuple[str, AutorefsHookInterface.Context | None]], +) -> Callable: """Return a `repl` function for [`re.sub`](https://docs.python.org/3/library/re.html#re.sub). In our context, we match Markdown references and replace them with HTML links. @@ -263,7 +266,7 @@ def inner(match: Match) -> str: return title if kind == "autorefs-optional-hover": return f'{title}' - unmapped.append(identifier) + unmapped.append((identifier, None)) if title == identifier: return f"[{identifier}][]" return f"[{title}][{identifier}]" @@ -286,7 +289,30 @@ def inner(match: Match) -> str: class _AutorefsAttrs(dict): - _handled_attrs: ClassVar[set[str]] = {"identifier", "optional", "hover", "class"} + _handled_attrs: ClassVar[set[str]] = { + "identifier", + "optional", + "hover", + "class", + "domain", + "role", + "origin", + "filepath", + "lineno", + } + + @property + def context(self) -> AutorefsHookInterface.Context | None: + try: + return AutorefsHookInterface.Context( + domain=self["domain"], + role=self["role"], + origin=self["origin"], + filepath=self["filepath"], + lineno=int(self["lineno"]), + ) + except KeyError: + return None @property def remaining(self) -> str: @@ -310,7 +336,10 @@ def handle_starttag(self, tag: str, attrs: list[tuple[str, str | None]]) -> None _html_attrs_parser = _HTMLAttrsParser() -def fix_ref(url_mapper: Callable[[str], str], unmapped: list[str]) -> Callable: +def fix_ref( + url_mapper: Callable[[str], str], + unmapped: list[tuple[str, AutorefsHookInterface.Context | None]], +) -> Callable: """Return a `repl` function for [`re.sub`](https://docs.python.org/3/library/re.html#re.sub). In our context, we match Markdown references and replace them with HTML links. @@ -343,7 +372,7 @@ def inner(match: Match) -> str: if hover: return f'{title}' return title - unmapped.append(identifier) + unmapped.append((identifier, attrs.context)) if title == identifier: return f"[{identifier}][]" return f"[{title}][{identifier}]" @@ -363,7 +392,12 @@ def inner(match: Match) -> str: # YORE: Bump 2: Replace `, *, _legacy_refs: bool = True` with `` within line. -def fix_refs(html: str, url_mapper: Callable[[str], str], *, _legacy_refs: bool = True) -> tuple[str, list[str]]: +def fix_refs( + html: str, + url_mapper: Callable[[str], str], + *, + _legacy_refs: bool = True, +) -> tuple[str, list[tuple[str, AutorefsHookInterface.Context | None]]]: """Fix all references in the given HTML text. Arguments: @@ -372,9 +406,9 @@ def fix_refs(html: str, url_mapper: Callable[[str], str], *, _legacy_refs: bool such as [mkdocs_autorefs.plugin.AutorefsPlugin.get_item_url][]. Returns: - The fixed HTML. + The fixed HTML, and a list of unmapped identifiers (string and optional context). """ - unmapped: list[str] = [] + unmapped: list[tuple[str, AutorefsHookInterface.Context | None]] = [] html = AUTOREF_RE.sub(fix_ref(url_mapper, unmapped), html) # YORE: Bump 2: Remove block. diff --git a/tests/test_references.py b/tests/test_references.py index 3eab1f0..6fdf24e 100644 --- a/tests/test_references.py +++ b/tests/test_references.py @@ -9,7 +9,7 @@ import pytest from mkdocs_autorefs.plugin import AutorefsPlugin -from mkdocs_autorefs.references import AutorefsExtension, fix_refs, relative_url +from mkdocs_autorefs.references import AutorefsExtension, AutorefsHookInterface, fix_refs, relative_url @pytest.mark.parametrize( @@ -46,7 +46,7 @@ def run_references_test( url_map: dict[str, str], source: str, output: str, - unmapped: list[str] | None = None, + unmapped: list[tuple[str, AutorefsHookInterface.Context | None]] | None = None, from_url: str = "page.html", extensions: Mapping = {}, ) -> None: @@ -169,7 +169,7 @@ def test_missing_reference() -> None: url_map={"NotFoo": "foo.html#NotFoo"}, source="[Foo][]", output="

[Foo][]

", - unmapped=["Foo"], + unmapped=[("Foo", None)], ) @@ -179,7 +179,7 @@ def test_missing_reference_with_markdown_text() -> None: url_map={"NotFoo": "foo.html#NotFoo"}, source="[`Foo`][Foo]", output="

[Foo][Foo]

", - unmapped=["Foo"], + unmapped=[("Foo", None)], ) @@ -189,7 +189,7 @@ def test_missing_reference_with_markdown_id() -> None: url_map={"Foo": "foo.html#Foo", "NotFoo": "foo.html#NotFoo"}, source="[Foo][*NotFoo*]", output="

[Foo][*NotFoo*]

", - unmapped=["*NotFoo*"], + unmapped=[("*NotFoo*", None)], ) @@ -199,7 +199,7 @@ def test_missing_reference_with_markdown_implicit() -> None: url_map={"Foo-bar": "foo.html#Foo-bar"}, source="[*Foo-bar*][] and [`Foo`-bar][]", output="

[Foo-bar][*Foo-bar*] and [Foo-bar][]

", - unmapped=["*Foo-bar*"], + unmapped=[("*Foo-bar*", None)], ) @@ -224,7 +224,7 @@ def test_legacy_custom_required_reference() -> None: with pytest.warns(DeprecationWarning, match="`span` elements are deprecated"): output, unmapped = fix_refs(source, url_map.__getitem__) assert output == '[foo][bar] ok' - assert unmapped == ["bar"] + assert unmapped == [("bar", None)] def test_custom_required_reference() -> None: @@ -233,7 +233,7 @@ def test_custom_required_reference() -> None: source = "foo ok" output, unmapped = fix_refs(source, url_map.__getitem__) assert output == '[foo][bar] ok' - assert unmapped == ["bar"] + assert unmapped == [("bar", None)] def test_legacy_custom_optional_reference() -> None: