diff --git a/src/mkdocstrings_handlers/python/_internal/rendering.py b/src/mkdocstrings_handlers/python/_internal/rendering.py index d5ffc8e..56a1c39 100644 --- a/src/mkdocstrings_handlers/python/_internal/rendering.py +++ b/src/mkdocstrings_handlers/python/_internal/rendering.py @@ -45,6 +45,8 @@ _logger = get_logger(__name__) +_STASH_KEY_ALPHABET = string.ascii_letters + string.digits + def _sort_key_alphabetical(item: CollectorItem) -> str: # `chr(sys.maxunicode)` is a string that contains the final unicode character, @@ -107,11 +109,15 @@ class _StashCrossRefFilter: @staticmethod def _gen_key(length: int) -> str: - return "_" + "".join(random.choice(string.ascii_letters + string.digits) for _ in range(max(1, length - 1))) # noqa: S311 + return "_" + "".join(random.choice(_STASH_KEY_ALPHABET) for _ in range(max(1, length - 1))) # noqa: S311 def _gen_stash_key(self, length: int) -> str: key = self._gen_key(length) while key in self.stash: + key_length = len(key) + keyspace_size = len(_STASH_KEY_ALPHABET) ** (key_length - 1) + if sum(len(stash_key) == key_length for stash_key in self.stash) >= keyspace_size: + length = key_length + 1 key = self._gen_key(length) return key diff --git a/tests/test_rendering.py b/tests/test_rendering.py index 91f945a..6ec3ccc 100644 --- a/tests/test_rendering.py +++ b/tests/test_rendering.py @@ -54,6 +54,19 @@ def test_format_signature(name: Markup, signature: str) -> None: assert rendering._format_signature(name, signature, length) +def test_stash_crossref_filter_expands_exhausted_keyspace() -> None: + """Assert stashing more cross-references than fit at the requested length terminates.""" + stash_crossref = rendering._StashCrossRefFilter() + stash_crossref.stash.clear() + try: + keys = [stash_crossref(str(index), length=1) for index in range(63)] + finally: + stash_crossref.stash.clear() + + assert len(set(keys)) == 63 + assert len(keys[-1]) == 3 + + @dataclass class _FakeObject: name: str