Skip to content

Commit a9a4ca3

Browse files
authored
fix: Expand relative cross-references against the object the docstring was written on
With inherited members enabled, docstrings of base class members are rendered in the docs of the inheriting class, where the current object is a Griffe alias living under the inheriting class. Leading-dot relative cross-references found in such docstrings were therefore expanded against the consumer's tree instead of the dependency's tree, resolving to wrong targets or failing strict builds. Anchor the dot-walk on the docstring's parent (the defining object) when it is reachable, falling back to the current object otherwise. Issue-341: #341 PR-342: #342
1 parent 8125b0d commit a9a4ca3

2 files changed

Lines changed: 46 additions & 0 deletions

File tree

src/mkdocstrings_handlers/python/_internal/rendering.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -805,6 +805,11 @@ def expand_identifier(self, identifier: str) -> str:
805805
if self.config.relative_crossrefs and identifier.startswith("."): # type: ignore[attr-defined]
806806
identifier = identifier[1:]
807807
obj = self.current_object
808+
# Anchor on the docstring's parent: for inherited members the current
809+
# object is an alias living under the inheriting class, while the
810+
# docstring was written on the defining object in another tree.
811+
if self.current_object.docstring is not None and self.current_object.docstring.parent is not None:
812+
obj = self.current_object.docstring.parent
808813
while identifier and identifier[0] == ".":
809814
identifier = identifier[1:]
810815
if obj.parent is None:

tests/test_rendering.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@
44

55
import re
66
from dataclasses import dataclass
7+
from types import SimpleNamespace
78
from typing import TYPE_CHECKING, Any, Callable, cast
89

910
import pytest
1011
from griffe import Alias, ModulesCollection, Object, temporary_visited_module
1112

1213
from mkdocstrings_handlers.python._internal import rendering
14+
from mkdocstrings_handlers.python._internal.rendering import AutorefsHook
1315

1416
if TYPE_CHECKING:
1517
from markupsafe import Markup
@@ -174,3 +176,42 @@ def __init__(self, name: str, lineno: int | None = None, *, is_alias: bool = Fal
174176
members = [Obj("a", 10, is_alias=True), Obj("b", 9, is_alias=False), Obj("c", 8, is_alias=True)]
175177
ordered = rendering.do_order_members(members, order, members_list) # type: ignore[arg-type]
176178
assert [obj.name for obj in ordered] == expected_names
179+
180+
181+
def test_expand_identifier_relative_crossref_in_inherited_member() -> None:
182+
"""Relative cross-references in inherited members expand against the defining object.
183+
184+
The docstring of an inherited member is rendered in the docs of the inheriting
185+
class, where the current object is an alias living under that class. Dots in
186+
relative references must still climb the tree of the class the docstring was
187+
written on, not the consumer's tree.
188+
"""
189+
collection = ModulesCollection()
190+
with temporary_visited_module(
191+
'''
192+
class Base:
193+
MAPPING = {}
194+
"""The mapping to use for each [`Thing`][....Thing]."""
195+
''',
196+
module_name="pkga",
197+
modules_collection=collection,
198+
) as module:
199+
collection["pkga"] = module
200+
with temporary_visited_module(
201+
"""
202+
from pkga import Base
203+
204+
class Derived(Base): ...
205+
""",
206+
module_name="pkgb",
207+
modules_collection=collection,
208+
) as module_b:
209+
collection["pkgb"] = module_b
210+
config = SimpleNamespace(relative_crossrefs=True, scoped_crossrefs=False)
211+
# Control: a non-inherited object expands relative references in its own tree.
212+
direct = AutorefsHook(collection["pkga"]["Base"]["MAPPING"], config) # type: ignore[arg-type]
213+
assert direct.expand_identifier("....Thing") == "pkga.Thing"
214+
# Inherited member: the current object is an alias under pkgb.Derived,
215+
# but the docstring was written on pkga.Base.MAPPING.
216+
inherited = AutorefsHook(collection["pkgb"]["Derived"]["MAPPING"], config) # type: ignore[arg-type]
217+
assert inherited.expand_identifier("....Thing") == "pkga.Thing"

0 commit comments

Comments
 (0)