Skip to content

DOC: Polish MyST cross-references (bases, re-exports, page labels)#1824

Open
romanlutz wants to merge 5 commits into
microsoft:mainfrom
romanlutz:romanlutz/myst-polish-followups
Open

DOC: Polish MyST cross-references (bases, re-exports, page labels)#1824
romanlutz wants to merge 5 commits into
microsoft:mainfrom
romanlutz:romanlutz/myst-polish-followups

Conversation

@romanlutz
Copy link
Copy Markdown
Contributor

@romanlutz romanlutz commented May 28, 2026

Stacks on #1823.

Extends the docstring auto-linker added in the parent PR with three small follow-ups, plus a small cleanup that the parent PR's check-no-rest-roles hook turned up.

Before / after

Same section — pyrit.prompt_target.AzureBlobStorageTarget heading and Bases line — in the rendered API page. Before, the PromptTarget base is a plain pink code span; after, it's an underlined, clickable MyST link that jumps straight to the PromptTarget definition further down the page. External bases like str and Enum (not shown in this crop, but visible elsewhere on the page) correctly stay as plain code spans because they're not in the symbol index.

Before (origin/romanlutz/myst-cross-refs-audit):
before

After (this PR):
after

Auto-linker extensions

  1. Bases: line is linked. render_class now emits each base individually through a new _format_bases() helper that wraps it in a single backtick and runs it through _rewrite_symbol_refs. PyRIT bases become MyST links; external bases (str, Enum, etc.) stay as plain code spans because they're not in the symbol index.

    # Before:  Bases: `PromptTarget`
    # After:   Bases: [`PromptTarget`](#api-pyrit_prompt_target-PromptTarget)
    
  2. ## Re-exports section is linked on both sides. _format_reexport_alias() prefers the module-qualified path lookup (mod_name.alias_name) so an alias resolves to its OWN page rather than the canonical definition; falls back to the short-name rewriter when no FQN entry exists. _format_reexport_target() runs the target through _rewrite_symbol_refs so resolvable FQNs link and external paths stay plain.

    • Note: _resolve_aliases() rewrites every alias to a real class/function before render_module runs, so the ## Re-exports block is dead in current production builds. The linking is in place defensively for any future code path that emits aliases, and is fully exercised by the new unit tests.
  3. Page-level label as a frontmatter field. Each module page emits label: api-pyrit_<module_slug> in the frontmatter so cross-page references like [](#api-pyrit_prompt_target) resolve to the page itself.

    • The plan was to emit (api-pyrit_<slug>)= before the H1, but MyST consumes the H1 as the page title and discards any label placed in the body before it (verified by inspecting api.pyrit-prompt-target.json — no page-level label was registered). The frontmatter label: field is the only reliable way to bind a page-level anchor. Confirmed it registers as {"identifier":"api-pyrit_prompt_target","kind":"page","url":"/api/pyrit-prompt-target"} in myst.xref.json.

Cleanup

Replaces 4 stray :meth: reST roles in pyrit/models/conversation_reference.py and pyrit/models/retry_event.py with plain double-backticks. These landed via #1769 before the check-no-rest-roles hook from #1823 existed; they slipped past the hook because they were already in the tree. model_dump / model_validate are Pydantic API, not PyRIT, so the auto-linker leaves them as plain code spans (correct: nothing in our docs to link to).

Tests

16 new unit tests in tests/unit/build_scripts/test_gen_api_md.py (34 total now). Coverage:

  • bases: known PyRIT base, external bases stay plain, mixed list, empty/None
  • re-exports: alias prefers FQN, falls back to short name, unresolvable stays plain; target links FQN, unresolvable stays plain, empty handled
  • module label: emitted in frontmatter, uses module slug, nested packages

Full unit suite: 8165 passed, 119 skipped in 2:19 (unchanged).

Validation

  • make pre-commit — all hooks pass (including check-no-rest-roles)
  • make unit-test — 8165 passed, 119 skipped
  • make docs-build — zero new warnings vs the parent branch baseline (same 69 unique warnings, all pre-existing grid gutter / jupytext frontmatter notices)
  • Spot-checked the built api/pyrit-prompt-target/index.html: Bases: is a real <a href="#api-pyrit-prompt-target-prompttarget"> link for PyRIT bases and plain <code>str</code>, <code>Enum</code> for external ones

Known pre-existing issue not fixed here

uv run ty check tests/unit flags _rewrite_symbol_refs(None, {}) is None # type: ignore[arg-type] on tests/unit/build_scripts/test_gen_api_md.py:243. The # type: ignore comment is mypy-style and ty doesn't honor it yet. This already exists on the parent branch and isn't addressable here.

romanlutz and others added 5 commits May 27, 2026 10:09
When PR microsoft#1782 converted Sphinx reST roles to plain double-backticks under
jupyter-book 2's MyST renderer, all the symbol cross-references in our
docstrings stopped being clickable.  This restores them without forcing
contributors to learn MyST link syntax.

Changes:
* �uild_scripts/gen_api_md.py now emits an explicit, FQN-scoped MyST
  label before every class, function, and method heading (e.g.
  (api-pyrit_prompt_target-PromptTarget)=) and post-processes every
  docstring text, parameter description, return description, and raises
  description: backtick code spans whose contents unambiguously resolve
  to a PyRIT class, function, or method become MyST links to the right
  anchor.  Ambiguous short names and fenced code blocks are left alone.
  The API index page now links each preview symbol to its anchor too.
* Cleaned up 13 leftover Sphinx reST roles in pyrit/ that PR microsoft#1782
  missed (cli_helpers, scorer_metrics, pyrit_scan, tree_of_attacks).
* Added �uild_scripts/check_no_rest_roles.py plus a pre-commit hook so
  newly introduced :class: / :func: / :meth: / etc. roles are
  rejected before landing.
* Updated the style guide to describe the auto-linker behaviour and
  point at the new pre-commit guard.
* 21 new unit tests in 	ests/unit/build_scripts/ cover the rewriter
  (single/double backticks, Class.method, FQN, current-class context,
  ambiguous skip, fenced-block protection, existing-link idempotency,
  tilde/dot prefix) and the pre-commit guard.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Two extra unit tests on the auto-linker integration path:

* `test_process_docstring_text_protects_doctest_examples` pins the
  order I had to swap mid-implementation: `_escape_docstring_examples`
  must run before `_rewrite_symbol_refs` so a known PyRIT symbol
  appearing inside a `>>>` doctest example stays as raw text (otherwise
  the code sample would render as broken markdown).

* `test_render_function_emits_anchor_and_links_docstring_fields` plus
  `test_render_function_uses_method_anchor_when_class_name_given` are
  end-to-end smoke tests on `render_function`: they assert the
  `(api-...)=` label is emitted with the right scoping (module vs.
  method) and that every docstring field (text, params, returns, raises)
  goes through the rewriter so a regression in any of those four code
  paths fails the build.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Extends the docstring auto-linker added in the parent PR with three small
follow-ups so generated API pages cross-reference more aggressively:

1. `Bases:` lines run each base through the symbol rewriter, so PyRIT
   bases become MyST links while external bases (`str`, `Enum`) stay as
   plain code spans.

2. `## Re-exports` entries link both the alias name (preferring the
   alias's own module-qualified path) and the target FQN. Unresolvable
   targets fall back to plain code spans, matching the rest of the docs.

3. Each module page emits a `label: api-pyrit_<module_slug>` frontmatter
   field so cross-page references like `[](#api-pyrit_prompt_target)`
   target the page itself. Placing the label inline before the H1 doesn't
   work because MyST consumes the H1 as the page title, so the frontmatter
   field is the only reliable way to bind a page-level anchor.

16 new unit tests in tests/unit/build_scripts/test_gen_api_md.py cover
each path, including the external-base, unresolvable-target, and nested
module-slug edge cases.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…icks

The `check-no-rest-roles` pre-commit hook added in microsoft#1823
flags four `:meth:model_dump` / `:meth:model_validate` references in
`pyrit/models/conversation_reference.py` and `pyrit/models/retry_event.py`
that landed via PR microsoft#1769 before the hook existed. Replace them with plain
double-backticks so the hook passes cleanly on this stacked branch and the
deprecation notices render as readable code spans under MyST instead of
literal `:meth:
ame` text.

`model_dump` / `model_validate` are Pydantic methods, not PyRIT API, so
the auto-linker leaves them as plain code spans (correct: there is nothing
in our docs to link them to).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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.

1 participant