Skip to content

Commit 1b087d2

Browse files
committed
api-style(feat[transforms]): prune empty desc_content after badge injection
why: Undocumented autodoc objects left empty desc_content, producing blank <dd> rows and extra vertical space in API cards. what: - Add _prune_empty_desc_content with doctest - Invoke it from on_doctree_resolved for handled py desc nodes - Add unit tests for direct prune, nonempty preserve, and pipeline
1 parent f793d8a commit 1b087d2

2 files changed

Lines changed: 77 additions & 0 deletions

File tree

packages/sphinx-autodoc-api-style/src/sphinx_autodoc_api_style/_transforms.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,32 @@ def _inject_badges(sig_node: addnodes.desc_signature, objtype: str) -> None:
188188
sig_node += toolbar
189189

190190

191+
def _prune_empty_desc_content(desc_node: addnodes.desc) -> None:
192+
"""Remove empty desc_content nodes from a desc tree.
193+
194+
Sphinx always appends a desc_content child even when the object has
195+
no docstring. An empty <dd></dd> wastes vertical space and creates
196+
layout noise. Remove it so the CSS card only shows the signature row.
197+
198+
Parameters
199+
----------
200+
desc_node : addnodes.desc
201+
The description node to inspect.
202+
203+
Examples
204+
--------
205+
>>> from sphinx import addnodes
206+
>>> desc = addnodes.desc()
207+
>>> desc += addnodes.desc_content() # empty
208+
>>> _prune_empty_desc_content(desc)
209+
>>> any(isinstance(c, addnodes.desc_content) for c in desc.children)
210+
False
211+
"""
212+
for child in list(desc_node.children):
213+
if isinstance(child, addnodes.desc_content) and not child.children:
214+
desc_node.remove(child)
215+
216+
191217
def on_doctree_resolved(
192218
app: Sphinx,
193219
doctree: nodes.document,
@@ -230,3 +256,4 @@ def on_doctree_resolved(
230256
for child in desc_node.children:
231257
if isinstance(child, addnodes.desc_signature):
232258
_inject_badges(child, objtype)
259+
_prune_empty_desc_content(desc_node)

tests/ext/api_style/test_api_style.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
_detect_deprecated,
2424
_detect_modifiers,
2525
_inject_badges,
26+
_prune_empty_desc_content,
2627
on_doctree_resolved,
2728
)
2829

@@ -413,6 +414,55 @@ def test_inject_badges_headerlink_not_in_toolbar() -> None:
413414
assert len(sig_direct_refs) == 1, "headerlink should remain a direct child of sig"
414415

415416

417+
# ---------------------------------------------------------------------------
418+
# _prune_empty_desc_content
419+
# ---------------------------------------------------------------------------
420+
421+
422+
def test_prune_empty_desc_content_removes_empty() -> None:
423+
"""Empty desc_content is removed from the desc node."""
424+
desc = addnodes.desc()
425+
desc += addnodes.desc_signature()
426+
desc += addnodes.desc_content() # empty — no children
427+
428+
_prune_empty_desc_content(desc)
429+
430+
assert not any(isinstance(c, addnodes.desc_content) for c in desc.children)
431+
432+
433+
def test_prune_empty_desc_content_keeps_nonempty() -> None:
434+
"""desc_content with children is not removed."""
435+
desc = addnodes.desc()
436+
content = addnodes.desc_content()
437+
content += nodes.paragraph("", "Has content.")
438+
desc += addnodes.desc_signature()
439+
desc += content
440+
441+
_prune_empty_desc_content(desc)
442+
443+
assert any(isinstance(c, addnodes.desc_content) for c in desc.children)
444+
445+
446+
def test_on_doctree_resolved_prunes_empty_desc_content() -> None:
447+
"""on_doctree_resolved removes empty desc_content via full pipeline."""
448+
from unittest.mock import MagicMock
449+
450+
app = MagicMock()
451+
doc = nodes.document(None, None) # type: ignore[arg-type]
452+
desc = addnodes.desc()
453+
desc["domain"] = "py"
454+
desc["objtype"] = "attribute"
455+
sig = addnodes.desc_signature()
456+
sig += addnodes.desc_name("", "session_id")
457+
desc += sig
458+
desc += addnodes.desc_content() # empty — simulates undocumented attribute
459+
460+
doc += desc
461+
on_doctree_resolved(app, doc, "index")
462+
463+
assert not any(isinstance(c, addnodes.desc_content) for c in desc.children)
464+
465+
416466
# ---------------------------------------------------------------------------
417467
# on_doctree_resolved
418468
# ---------------------------------------------------------------------------

0 commit comments

Comments
 (0)