Skip to content

gp-sphinx(feat[autodoc]): Component autodoc for docutils + Sphinx#53

Merged
tony merged 33 commits into
mainfrom
more-docutils-and-sphinx
Jun 7, 2026
Merged

gp-sphinx(feat[autodoc]): Component autodoc for docutils + Sphinx#53
tony merged 33 commits into
mainfrom
more-docutils-and-sphinx

Conversation

@tony
Copy link
Copy Markdown
Member

@tony tony commented Jun 7, 2026

Summary

  • Add registry-aware autodoc for every remaining docutils extension point in sphinx-autodoc-docutilsautotransform(s), autoreader(s), autoparser(s), autowriter(s), autonode(s), autotranslator(s) — extending the single + bulk-by-module pattern that autodirective/autorole established
  • Add autobuilder(s) and autodomain(s) to sphinx-autodoc-sphinx, documenting sphinx.builders.Builder and sphinx.domains.Domain subclasses beside the config values it already covers
  • Add two cross-reference domains — docutils (transform/reader/parser/writer/node/translator) and sphinxext (builder/domain) — so every documented component is linkable from prose via roles like {docutils:transform}`SanitizeTransform`, each with a grouped component index and dangling-ref warnings
  • Link the facts: component fact values that name a documented object (Python paths, translator classes, base classes, transform sets, handler overrides, config-value types, registering setup() entry points, option validators, Registered via app methods) render as cross-references that resolve locally or via intersphinx and degrade to plain chips otherwise; list-valued facts render as individual chips; container config defaults render as Pygments-highlighted blocks
  • Motivation: real consumers (django-docutils ships a writer, translator, three transforms, and a custom node) document these with plain automodule today; registry-aware autodoc gives them the same polished, cross-linked output directives and roles already get (sphinx-autodoc-docutils: autodoc for Transforms, Readers, Parsers, Writers, Nodes, and Sphinx builders #52)

Companion PR

Changes by area

sphinx-autodoc-docutils

  • domain.py: DocutilsDomain (six objtypes, XRefRole(warn_dangling=True), grouped index) + a generic DocutilsComponentDescription that owns anchors and domain notes
  • _components.py: the shared markup → parse → badges → facts pipeline every type renders through; safe_transform_classes, transform_chip_nodes, linked_paragraph
  • _transforms_doc.py_translators_doc.py: one module per type — discovery (recorder-replay with module-scan fallback), an *Info dataclass where registration metadata exists, fact rows, and the Auto*/Auto*s directive pair
  • _directives.py: directive/role Python paths and directive-option validators now link

sphinx-autodoc-sphinx

  • domain.py: SphinxExtDomain mirroring the lifecycle for builder/domain objtypes
  • _components.py + _builders_doc.py / _domains_doc.py: discovery, facts, directive pairs
  • _directives.py: confval Type routes through the shared annotation pipeline (builtins link to the python inventory); Registered by links to the extension setup(); container defaults render as Pygments blocks

Shared UX packages

  • sphinx-ux-autodoc-layout: build_linked_literal / build_chip_paragraph inline helpers (Tier-1 shared); (docutils, *) and (sphinxext, *) layout profiles
  • sphinx-ux-badges: per-type SAB.TYPE_* constants + outline modifiers, with light/dark palette tokens and a parity guard test

Docs

  • Per-package examples pages with single/bulk live demos and prose xref demos; new reference pages for sphinx-autodoc-docutils, sphinx-autodoc-typehints-gp, and sphinx-autodoc-pytest-fixtures; an "Extension entry point" autofunction section on every package reference page (so Registered by facts resolve); demo-module automodule sections so demo Python-path facts link

Design decisions

  • Real ObjectDescription directives, not py-domain retagging: generated markup targets the new domains, so desc nodes natively carry the right domain/objtype — anchors, TOC entries, xref targets, and layout profiles all come from Sphinx's own machinery
  • Graceful degradation as a contract: linked literals emit pending_xref with refwarn=False, so the same fact row is a live link where the inventory is mapped and a plain chip where it isn't — no per-site conditionals, no warning tax. docutils publishes no intersphinx inventory, so docutils-internal names degrade by design
  • Recorder-replay first, subclass-scan fallback: components with Sphinx registrations surface real metadata; directly-instantiated ones (django-docutils writers/readers, translator-handled nodes) are found by module scan. Discovery never raises at build time
  • sphinxext domain name: explicit and collision-free; avoids shadowing the sphinx.* namespace
  • Self-contained packages: sphinx-autodoc-sphinx mirrors the small rendering pipeline rather than depending on its docutils sibling

Test plan

  • Full gate on every commit: rm -rf docs/_build; uv run ruff check . --fix --show-fixes; uv run ruff format .; uv run mypy .; uv run py.test --reruns 0 -vvv; just build-docs
  • Per-type unit + doctree tests (discovery via NamedTuple matrices, fact rows, badge/facts injection, linked-fact structure assertions)
  • test_domain.py (both packages) — note/clear/merge/get_objects/lookup lifecycle and grouped index
  • test_domain_xref_integration.py (both packages) — resolvable refs build warning-free, dangling refs warn, resolved hrefs present; one scenario automodules the demo module and asserts a fact's Python path resolves to the autodoc anchor
  • Palette dark-block parity guard; pytest no longer collects docs/_build
  • Doctests on every new function; warning-free docs build
  • Built HTML inspected: per-type badges, transform priority badges, Pygments container defaults, and cross-linked facts (Python paths, validators, Registered by, confval types) all render

Closes #52

tony added 2 commits June 7, 2026 09:43
why: Issue #52 extends component autodoc to transforms, readers,
parsers, writers, nodes, and translators. Those classes have no
existing Sphinx domain objtype, so cross-references and anchors need
a home before the per-type autodoc directives can land.

what:
- Add DocutilsDomain (name="docutils") with six object types, an
  XRefRole per objtype, and a grouped-by-objtype component index,
  following the ArgparseDomain lifecycle shape (note/clear/merge/
  resolve/get_objects) for parallel-safe builds
- Add DocutilsComponentDescription, a generic ObjectDescription whose
  signature is a dotted Python path (module as desc_addname, class as
  desc_name); add_target_and_index owns the anchor and notes the
  component into the domain
- Lookup accepts fully-qualified paths and unambiguous bare class
  names
- Register the domain in setup(); stub add_domain in the shared-stack
  setup test app
why: Issue #52 adds builder and domain autodoc to this package. Like
the docutils component types, Builder and Domain subclasses have no
existing Sphinx objtype, so cross-references and anchors need a home
before autobuilder/autodomain can land.

what:
- Add SphinxExtDomain (name="sphinxext") with builder/domain object
  types, an XRefRole per objtype, and a grouped-by-objtype component
  index, mirroring the DocutilsDomain lifecycle shape
- Add SphinxExtComponentDescription rendering dotted Python paths as
  module desc_addname + class desc_name, owning anchors and domain
  notes
- Register the domain in setup(); add tests/ext/autodoc_sphinx
  package __init__ for parity with sibling test packages
@tony tony force-pushed the more-docutils-and-sphinx branch from d794d16 to c816bd6 Compare June 7, 2026 15:50
@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented Jun 7, 2026

Codecov Report

❌ Patch coverage is 96.58120% with 68 lines in your changes missing coverage. Please review.
✅ Project coverage is 92.38%. Comparing base (9715daf) to head (a568a22).

Files with missing lines Patch % Lines
docs/_ext/docutils_demo_components.py 63.07% 24 Missing ⚠️
...doc-docutils/src/sphinx_autodoc_docutils/domain.py 91.89% 9 Missing ⚠️
...autodoc-sphinx/src/sphinx_autodoc_sphinx/domain.py 91.58% 9 Missing ⚠️
...oc-sphinx/src/sphinx_autodoc_sphinx/_components.py 90.78% 7 Missing ⚠️
tests/ext/autodoc_sphinx/test_components.py 96.75% 6 Missing ⚠️
docs/_ext/sphinx_demo_builder.py 92.00% 2 Missing ⚠️
tests/ext/autodoc_docutils/test_components.py 99.33% 2 Missing ⚠️
...ocutils/src/sphinx_autodoc_docutils/_components.py 98.14% 1 Missing ⚠️
...ocutils/src/sphinx_autodoc_docutils/_directives.py 95.45% 1 Missing ⚠️
...docutils/src/sphinx_autodoc_docutils/_nodes_doc.py 98.52% 1 Missing ⚠️
... and 6 more
Additional details and impacted files
@@            Coverage Diff             @@
##             main      #53      +/-   ##
==========================================
+ Coverage   91.90%   92.38%   +0.48%     
==========================================
  Files         233      256      +23     
  Lines       18223    20188    +1965     
==========================================
+ Hits        16747    18651    +1904     
- Misses       1476     1537      +61     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@tony
Copy link
Copy Markdown
Member Author

tony commented Jun 7, 2026

Code review

Found 3 issues:

  1. Dark-mode tokens for parser, writer, node, and translator badges are missing from the @media (prefers-color-scheme: dark) block — the block jumps from the reader tokens straight to mod-priority, while the body[data-theme="dark"] block defines all four. The comment above that block says it holds "Identical values to the @media block above", so the two blocks have diverged: users whose OS prefers dark mode but who never clicked Furo's theme toggle will see light-mode colors for those four badge types.

--gp-sphinx-badge-transform-bg: #3b0764;
--gp-sphinx-badge-transform-fg: #d8b4fe;
--gp-sphinx-badge-transform-border: #c084fc;
--gp-sphinx-badge-reader-bg: #1e1b4b;
--gp-sphinx-badge-reader-fg: #a5b4fc;
--gp-sphinx-badge-reader-border: #818cf8;
--gp-sphinx-badge-mod-priority-fg: #9ca3af;
--gp-sphinx-badge-mod-priority-border: #4b5563;

(compare with the explicit-toggle block, which has them:

--gp-sphinx-badge-reader-bg: #1e1b4b;
--gp-sphinx-badge-reader-fg: #a5b4fc;
--gp-sphinx-badge-reader-border: #818cf8;
--gp-sphinx-badge-parser-bg: #082f49;
--gp-sphinx-badge-parser-fg: #7dd3fc;
--gp-sphinx-badge-parser-border: #38bdf8;
--gp-sphinx-badge-writer-bg: #042f2e;
--gp-sphinx-badge-writer-fg: #5eead4;
--gp-sphinx-badge-writer-border: #2dd4bf;
--gp-sphinx-badge-node-bg: #4a044e;
--gp-sphinx-badge-node-fg: #f0abfc;
--gp-sphinx-badge-node-border: #e879f9;
--gp-sphinx-badge-translator-bg: #172554;
--gp-sphinx-badge-translator-fg: #93c5fd;
--gp-sphinx-badge-translator-border: #60a5fa;
--gp-sphinx-badge-mod-priority-fg: #9ca3af;
)

  1. --gp-sphinx-badge-mod-domain-name-fg/-border are defined only in the light-mode :root block — neither dark block overrides them, unlike every other outlined modifier (mod-rebuild, mod-priority, mod-format all carry #9ca3af/#4b5563 dark overrides). The autodomain domain-name badge keeps its light-grey light-mode colors in dark mode.

--gp-sphinx-badge-mod-domain-name-fg: #6b7280;
--gp-sphinx-badge-mod-domain-name-border: #d1d5db;

  1. New functions inject_component_badges() and normalize_component_nodes() in both _components.py modules have docstrings but no doctests (CLAUDE.md says "All functions and methods MUST have working doctests"). Sibling functions in the same files (component_markup, component_classes, import_component, safe_transform_names) all carry working Examples, and these two are exercisable by constructing addnodes.desc nodes directly, as the unit tests already demonstrate.

def inject_component_badges(
node_list: list[nodes.Node],
*,
objtype: str,
badge_group: nodes.inline,
) -> None:
"""Attach shared badge-slot metadata to parsed ``docutils:*`` entries."""

def inject_component_badges(
node_list: list[nodes.Node],
*,
objtype: str,
badge_group: nodes.inline,
) -> None:
"""Attach shared badge-slot metadata to parsed ``sphinxext:*`` entries."""

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

tony added 10 commits June 7, 2026 11:22
why: Issue #52 — transforms are the first of six docutils component
types gaining registry-aware autodoc. django-docutils ships three
Transform subclasses documented today with plain automodule; this
gives them option-table-grade output with priority and phase facts.

what:
- Add autotransform/autotransforms directives discovering transforms
  from recorded app.add_transform()/app.add_post_transform() calls,
  with a module subclass-scan fallback for unregistered transforms
- Add _components.py: the shared markup -> parse -> badges -> facts
  pipeline all six component types render through, emitting
  docutils-domain object descriptions
- Badge group: filled "transform" kind badge plus outlined
  "priority N" badge from default_priority (SAB.TYPE_TRANSFORM,
  SAB.MOD_PRIORITY with light/dark palette tokens)
- Register the ("docutils", "transform") layout profile so entries
  get the shared card treatment
- Domains now construct XRefRole(warn_dangling=True) so dangling
  component refs warn like std:confval refs do
- Demo transform + examples page sections (single, bulk,
  cross-reference) and a two-page xref-resolution integration
  scenario asserting resolve-clean, dangling-warns, and href output
why: Issue #52 — readers have no Sphinx registration call and are
instantiated directly inside publishers (django-docutils), so they
were previously undocumentable except via automodule.

what:
- Add autoreader/autoreaders directives discovering Reader subclasses
  by module scan; facts surface supported formats, config section,
  and the get_transforms() set (guarded so framework-dependent
  readers degrade to a dash instead of breaking the build)
- Add safe_transform_names() to the shared component helpers for
  reader/writer transform-set facts
- SAB.TYPE_READER indigo palette + ("docutils", "reader") layout
  profile; demo reader extending standalone.Reader with the demo
  transform; examples page sections and reader xref demo
why: Issue #52 — parsers carry an alias tuple and may be registered
as Sphinx source parsers; neither fact was documentable before.

what:
- Add autoparser/autoparsers directives combining a module subclass
  scan with recorded app.add_source_parser() calls, so scanned
  parsers carry their Sphinx registration state and parsers
  registered from elsewhere still surface
- Facts: supported aliases, config section, and the
  add_source_parser registration when present
- SAB.TYPE_PARSER sky palette + ("docutils", "parser") layout
  profile; demo line parser registered via setup(); examples page
  sections; unit + integration coverage
why: Issue #52 — writers carry output formats and a translator class,
but the translator is often assigned in __init__ rather than as a
class attribute (django-docutils), making it invisible to naive
introspection.

what:
- Add autowriter/autowriters directives discovering Writer subclasses
  by module scan; facts surface supported formats, the resolved
  translator class, config section, and the get_transforms() set
- Add resolve_translator_class(): instantiate the writer to read the
  instance attribute, fall back to the class attribute when
  construction needs framework state
- SAB.TYPE_WRITER teal palette + ("docutils", "writer") layout
  profile; demo plain-text writer assigning its translator in
  __init__ to exercise the defensive path; examples page sections
why: Issue #52 — custom node classes carry the richest hidden
metadata: element category mixins and per-builder visit/depart
handlers registered through app.add_node(). django-docutils' icon
node is handled purely by a custom translator with no add_node call
at all, so discovery must work without registration too.

what:
- Add autonode/autonodes directives combining a module Element
  subclass scan with recorded app.add_node() calls; handler kwargs
  map builder names to (visit, depart) tuples, the override keyword
  is skipped, and repeated registrations follow override semantics
- Facts: base classes, docutils element categories (Root through
  Inline, via issubclass against the category mixins), and the
  builders handlers were registered for
- SAB.TYPE_NODE fuchsia palette + ("docutils", "node") layout
  profile; demo_marker inline node with html handlers; examples page
  sections; recorder-shape NamedTuple test matrix
why: Issue #52 — the interesting fact about a translator subclass is
which visit/depart handlers it overrides (django-docutils'
DjangoDocutilsHTMLTranslator overrides six), and naive dir()-based
introspection drowns that in hundreds of inherited handlers.

what:
- Add autotranslator/autotranslators directives combining a
  NodeVisitor subclass scan with recorded app.set_translator() calls
  (builder name + override flag)
- Overrides fact uses vars(cls) so only the class's own visit_/
  depart_ methods surface; SparseNodeVisitor-style generated handlers
  on intermediate bases stay out of subclass listings
- Badge group: filled "translator" kind badge plus the existing
  STATE_OVERRIDE outline badge when registered with override=True
- SAB.TYPE_TRANSLATOR blue palette + ("docutils", "translator")
  layout profile; demo setup() registers the demo text translator
  with override; examples page sections
why: Issue #52 — builders are pure Sphinx extension objects, so their
autodoc lives beside config values in this package rather than in the
docutils sibling. Their name/format/image-type surface was previously
only documentable as a plain py:class.

what:
- Add autobuilder/autobuilders directives discovering builders from
  recorded app.add_builder() calls with a Builder subclass-scan
  fallback; facts surface builder name, output format, supported
  image types, default translator (getattr — not on the base class),
  parallel-safety, and epilog
- Add the package's _components.py rendering pipeline targeting the
  sphinxext domain, with a cached replay_setup mirroring the docutils
  side; kept self-contained so the package installs standalone
- Badge group: filled amber "builder" kind badge plus an outlined
  output-format badge (SAB.TYPE_BUILDER, SAB.MOD_FORMAT)
- ("sphinxext", "builder") layout profile; demo archive builder;
  examples page sections with {sphinxext:builder} xref demo; unit +
  shared-scenario + two-page xref-resolution integration coverage
why: Issue #52 — domains are the final extension-point type. A
domain's interesting surface (object types, roles, directives,
indices, role prefix) lives in class-level dicts that plain autodoc
renders as opaque attributes.

what:
- Add autodomain/autodomains directives discovering domains from
  recorded app.add_domain() calls with a Domain subclass-scan
  fallback; facts surface the registered name, label (str() unwraps
  the lazy gettext proxy), object types, roles, directives, and the
  domain's own indices
- Badge group: filled lime "domain" kind badge plus an outlined
  domain-name badge (SAB.TYPE_DOMAIN, SAB.MOD_DOMAIN_NAME)
- ("sphinxext", "domain") layout profile; DemoTopicDomain demo;
  examples page documents the docutils domain that
  sphinx-autodoc-docutils itself registers — the bulk form replaying
  a sibling package's setup()
…todoc

why: The new component directives and cross-reference roles need a
documented home — domain roles never appear in autodirectives output,
so without a reference section they would be invisible API.

what:
- Add docs/packages/sphinx-autodoc-docutils/reference.md: the package
  documents its own twelve directives via autodirectives, plus a
  role table for the six {docutils:*} roles and the component index
- Extend the sphinx-autodoc-sphinx reference page with the
  {sphinxext:*} role table and index link
- Tutorial pages gain the component single/bulk pattern; how-to pages
  gain a cross-referencing recipe noting that :no-index: entries
  create no link target
- Package READMEs describe the broadened component surface
why: Issue #52 ships three user-visible deliverables in the
forthcoming release: the six docutils component autodoc pairs, the
builder/domain pairs, and the cross-reference domains.

what:
- Add three What's new deliverables to the unreleased entry with
  {ref} links to the live demo and reference pages
@tony tony force-pushed the more-docutils-and-sphinx branch from c816bd6 to 5d25e49 Compare June 7, 2026 16:24
why: All functions need working doctests; this one predates the
branch but its two new siblings in the same file are doctested, so
the gap stood out in review (PR #53).

what:
- Add Examples covering a mapped kind, a component kind, and the
  unknown-kind fallback to the directive colour class
tony added a commit that referenced this pull request Jun 7, 2026
why: Review on PR #53 caught four badge families whose dark tokens
landed in only one of the two dark-mode blocks, and one outlined
modifier with no dark overrides at all. The stylesheet documents
"Identical values" between its dark blocks but nothing enforced it,
so the drift was invisible until someone diffed the blocks by hand.

what:
- Add tests/ext/badges/test_palettes.py parsing sab_palettes.css as
  text and asserting three contracts: the @media and
  body[data-theme="dark"] blocks declare identical token/value
  pairs; every var() a colour class reads resolves to a :root
  declaration; every light-mode token family has dark-mode coverage
  (family-level, so state-deprecated's transparent light-only -bg
  stays legal)
@tony
Copy link
Copy Markdown
Member Author

tony commented Jun 7, 2026

All three review issues are addressed:

  1. & 2. The @media (prefers-color-scheme: dark) block now carries the parser/writer/node/translator token triples, and mod-domain-name has dark overrides in both dark blocks — fixed via fixup! commits autosquashed into the causal per-type commits, so each component commit ships correct CSS. The two dark blocks are byte-identical in declarations again.

  2. inject_component_badges() and normalize_component_nodes() now carry working doctests in both _components.py modules (autosquashed into the autotransform/autobuilder commits).

Follow-ups on top:

  • build_kind_badge_group() gained its missing doctest (pre-existing gap, forward commit).
  • New tests/ext/badges/test_palettes.py pins the stylesheet's own contracts — dark-block declaration parity, colour-class var() resolution, and family-level dark coverage — so this class of drift fails the suite instead of waiting for review.

🤖 Generated with Claude Code

tony added 2 commits June 7, 2026 11:41
why: Review on PR #53 caught four badge families whose dark tokens
landed in only one of the two dark-mode blocks, and one outlined
modifier with no dark overrides at all. The stylesheet documents
"Identical values" between its dark blocks but nothing enforced it,
so the drift was invisible until someone diffed the blocks by hand.

what:
- Add tests/ext/badges/test_palettes.py parsing sab_palettes.css as
  text and asserting three contracts: the @media and
  body[data-theme="dark"] blocks declare identical token/value
  pairs; every var() a colour class reads resolves to a :root
  declaration; every light-mode token family has dark-mode coverage
  (family-level, so state-deprecated's transparent light-only -bg
  stays legal)
why: All functions need working doctests; this one predates the
branch but its two new siblings in the same file are doctested, so
the gap stood out in the follow-up review of PR #53.

what:
- Add Examples covering the config kind badge, the rebuild-mode
  badge, and the empty-rebuild fallback to "none"
tony added 7 commits June 7, 2026 12:36
why: Fact rows render list values as one comma-joined literal blob,
and names that have documented py-domain targets (classes, methods)
render as dead text. Both autodoc packages need the same fix, so the
helpers live in the shared layout package.

what:
- Add build_linked_literal(): a literal chip wrapped in a py-domain
  "obj" pending_xref with refwarn off — resolves when a target exists
  (ReferencesResolver defaults refdoc, fully-qualified targets match
  in exact-match mode) and silently stays a literal when it does not,
  so externals without an intersphinx inventory degrade cleanly
- Add build_chip_paragraph(): one literal chip per value joined by
  ", " text nodes, em dash for empty value lists
why: Every fact value rendered as one comma-joined literal blob —
formats, transform sets, and translator overrides wrapped as a single
chip, and names with real py-domain targets (a consumer's automodule
pages document the same classes and methods) were dead text.

what:
- Render list-valued facts (supported formats/aliases, categories,
  handler builders) as individual literal chips
- Cross-link name-valued facts via the shared linked-literal helper:
  Python path on all six types, writer translator class, translator
  base class and per-method overrides (fully-qualified method
  targets), node base classes, and reader/writer transform sets
  (bare-name chips targeting qualified paths)
- Replace safe_transform_names() with safe_transform_classes() so
  chips can carry qualified targets; add transform_chip_nodes() and
  linked_paragraph() to the shared component helpers
- Integration: the xref scenario now automodules the demo module and
  asserts the Python-path fact resolves to the autodoc anchor;
  unresolvable chips stay literals with no warnings (covered by the
  existing resolve-clean assertion)
why: Same fact-presentation defects as the docutils package — image
types and domain surfaces (object types, roles, directives, indices)
clumped into single literals, and Python paths plus default
translators dead text despite frequently having automodule targets.

what:
- Python path and Default translator render as linked literals via
  the shared helpers; the translator chip targets the qualified
  class path
- Supported image types and the domain surface facts render as
  individual chips
- Add linked_paragraph() to the package's component helpers and
  structure assertions for the linked/chipped facts
why: The unreleased component-autodoc deliverable now also covers
fact presentation: per-value chips and py-domain cross-links.

what:
- Extend the deliverable prose with the chip rendering and
  graceful-degradation link behavior
why: The linked-facts pass covered the component modules but missed
the original autodirective/autorole fact builders, so role callables
and directive classes with real py-domain targets (consumers
automodule the same objects) still rendered dead text.

what:
- Wrap the directive and role Python path facts in the shared
  linked-literal helper; py:function role targets and py:class
  directive targets now resolve, degrading silently elsewhere
why: The Registered by fact names the extension's setup() callable
as dead text; sites that document extension surfaces in the py
domain (gp-sphinx's own docs do) have a real target for it.

what:
- Wrap the Registered by value in the shared linked-literal helper,
  targeting module.setup with the call-syntax display; degrades to
  the existing literal where setup() is undocumented
why: The Type fact rendered names like list, dict, and bool as dead
literals even though Python builtins carry py:class targets in the
python intersphinx inventory every consumer site already maps.

what:
- Route the Type fact through the shared annotation display pipeline
  (build_annotation_display_paragraph) with the directive's
  environment, so type names become pending_xrefs that resolve
  locally or via intersphinx and degrade silently otherwise
- Keep bare None and empty type text as plain literals: the display
  policy would collapse a lone None to the enum marker, and the
  workspace policy never links None
- _config_fact_rows grows an optional env parameter; without it the
  Type fact renders exactly as before
tony added 6 commits June 7, 2026 13:10
why: Confval "Registered by" facts cross-reference <pkg>.setup, but
only sphinx-ux-badges documented its setup function — every other
package's link degraded to dead text on our own site.

what:
- Add an "Extension entry point" autofunction section to the ten
  reference pages missing one, matching the sphinx-ux-badges
  precedent
- Add a minimal reference page for sphinx-autodoc-pytest-fixtures
  (directives + entry point; its confvals stay on the how-to page to
  avoid duplicate targets)
why: The linked-facts sentence in the unreleased deliverable now also
covers config value types and their registering entry points.

what:
- Extend the deliverable's linked-targets list
why: Small dict and list defaults rendered as inline literal blobs;
structured values read better as highlighted Python, and the
Pygments literal_block path already existed for long reprs.

what:
- _is_complex_default treats any non-empty container as complex, so
  dict/list/tuple/set defaults render through the existing
  Pygments-highlighted block regardless of repr length; empty
  containers and scalars stay inline
why: The package's public API had no rendered documentation, so its
how-to tables named build_resolved_annotation_paragraph and friends
as dead text with nothing to link to.

what:
- Add a reference page autodocumenting the public surface: the four
  build_* helpers, render_annotation_nodes, the normalize_* text
  helpers, classify_annotation_display, AnnotationDisplay, and the
  setup entry point
- Link the how-to tables and prose with {func}/{class} roles;
  typing.get_type_hints links to the python inventory, and
  sphinx_stringify_annotation mentions use the canonical
  sphinx.util.typing.stringify_annotation name (Sphinx publishes no
  target for it)
why: Demo modules under docs/_ext were never autodocumented, so
every demo Python-path fact (docutils_demo.DemoBadgeDirective,
sphinx_config_demo.setup(), ...) degraded to dead text with nothing
to link to.

what:
- Append a "Demo module reference" automodule section to the
  docutils, sphinx, and fastmcp examples pages, giving the demo
  objects py-domain anchors the entries' facts now resolve to
- pytest-fixtures excluded: its fixture entries already create
  py-domain descriptions on the same page, so an automodule block
  emits duplicate-object warnings
why: pytest's default norecursedirs covers "build" but not Sphinx's
"_build", so generated markdown under docs/_build collects as doctest
files and breaks the run whenever a build output exists — including
mid-session, since the objects-inv compatibility test's live docs
build leaks output there.

what:
- Set norecursedirs explicitly, adding _build and node_modules to the
  conventional exclusions
@tony tony force-pushed the more-docutils-and-sphinx branch from 5ec6048 to 5ee8fee Compare June 7, 2026 18:52
tony added 5 commits June 7, 2026 13:56
…lidators

why: A full sweep of every package's autodoc output found two more
name-valued facts rendered as dead text: the transform/parser
"Registered via app.add_*()" calls and directive option "Validator"
converters, all of which name real callables.

what:
- Registered via facts link to sphinx.application.Sphinx.add_* (the
  sphinx inventory resolves them where mapped; degrades to the
  literal call elsewhere)
- Directive option validators link to their converter callable —
  builtins target the python inventory, docutils converters their
  qualified path — degrading when no inventory is mapped
- Rebuild _option_field_list directly from the option_spec (the
  string round-trip through _option_rows could not carry converter
  identity)
why: This round's only new user-facing capability beyond the
already-documented linked facts is highlighting structured defaults.

what:
- Note container config-value defaults rendering as Pygments blocks
  in the unreleased deliverable
why: The role-option validator already linked its converter, but the
directive-option "Validator:" line — rendered through the generated
rst:directive:option markup, a different path — stayed dead text, an
inconsistency next to the now-linked role equivalent.

what:
- Emit the validator as a :py:obj: role with an explicit converter
  target in the generated markup: builtin converters link to the
  python inventory, docutils converters to their qualified path, and
  both render as plain text where no inventory is mapped (the build
  is not nitpicky)
- Build the option list directly from option_spec and drop the now
  unused _option_rows string-table helper
why: The What's new entries carried docstring-grade detail — per-type
fact enumerations, setup-replay/add_* discovery mechanics, and
rendering internals — that buried the upgrade-time takeaway.

what:
- Lead with a one-paragraph summary of the broadened component autodoc
- Trim the three deliverables to their user-visible surface: the new
  directives, what they document, and the cross-linking; mechanism
  detail moves to the autodoc output and the PR
why: The tightened release notes rendered the {docutils:transform}
example as a live cross-reference; SanitizeTransform is a
django-docutils class with no target on gp-sphinx's own site, so the
warn_dangling role failed the -W docs build in CI.

what:
- Wrap the role example in an inline code span so it shows the role
  syntax without resolving it
@tony tony merged commit 013a546 into main Jun 7, 2026
44 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

sphinx-autodoc-docutils: autodoc for Transforms, Readers, Parsers, Writers, Nodes, and Sphinx builders

2 participants