feat(cli): render Doxygen group pages for C++ library docs - #17420
feat(cli): render Doxygen group pages for C++ library docs#17420Ryan-Amirthan wants to merge 2 commits into
Conversation
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
| const docstring = group.docstring; | ||
|
|
||
| const title = group.title || group.name; | ||
| const description = docstring ? renderSegmentsPlainText(docstring.summary) : `Members of the ${title} group.`; |
There was a problem hiding this comment.
🟡 Group pages can end up with a blank search/SEO description
The page summary text for a documentation group is taken from its comment even when that comment has no summary sentence (renderSegmentsPlainText(docstring.summary) at packages/cli/library-docs-generator/cpp/src/renderers/GroupPageRenderer.ts:114), so the intended "Members of the … group." wording is skipped and the page ships an empty description.
Impact: Affected group pages show an empty description in search results and previews instead of a meaningful one.
Why the fallback never fires for groups documented with only a long description
renderGroupPage branches on the mere presence of docstring, not on docstring.summary having content. A Doxygen group written with only a detailed description (or whose summary segments all render to empty text) yields description === "", which renderFrontmatter (packages/cli/library-docs-generator/cpp/src/renderers/shared.ts:94-97) emits as description: "". The very next lines in the same function already guard on docstring?.summary && docstring.summary.length > 0 (packages/cli/library-docs-generator/cpp/src/renderers/GroupPageRenderer.ts:117), showing the intended condition.
| const description = docstring ? renderSegmentsPlainText(docstring.summary) : `Members of the ${title} group.`; | |
| const summaryText = docstring ? renderSegmentsPlainText(docstring.summary) : ""; | |
| const description = summaryText.length > 0 ? summaryText : `Members of the ${title} group.`; |
Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
Good catch — applied your suggestion in 8448683. A group documented with only a detailed description now falls back to Members of the … group. instead of shipping description: "".
| for (const subgroup of subgroups) { | ||
| writeGroupPage(subgroup, `${dir}/${groupFolderName(subgroup)}`, writer); | ||
| } |
There was a problem hiding this comment.
🟡 Group traversal has no depth limit, so a self-referencing group tree never terminates
Documentation groups are walked recursively with no depth or visited-node limit (writeGroupPage recursion at packages/cli/library-docs-generator/src/CppDocsGenerator.ts:655-657), so a group tree that references an ancestor makes generation loop forever writing pages.
Impact: Docs generation can hang and fill the disk instead of failing cleanly.
Repository rule and the three unbounded traversals
REVIEW.md (Performance) requires flagging "any unbounded loops or recursive traversals over IR nodes without depth limits". Stage 5 adds three such traversals over untrusted IR that is cast without validation in packages/cli/library-docs-generator/src/orchestrate.ts:299: groupHasContent (packages/cli/library-docs-generator/cpp/src/renderers/GroupPageRenderer.ts:85), writeGroupPage (packages/cli/library-docs-generator/src/CppDocsGenerator.ts:655-657), and the subgroup filtering that calls groupHasContent per level (packages/cli/library-docs-generator/src/CppDocsGenerator.ts:646). A cycle or pathological nesting depth in subgroups causes non-termination or stack exhaustion.
Prompt for agents
Stage 5 of CppDocsGenerator walks ir.groups recursively (generateGroupPages -> writeGroupPage, plus groupHasContent in GroupPageRenderer) with no depth cap or visited set, while the IR is an unvalidated cast of remote/parser output (orchestrate.ts:299). REVIEW.md's Performance section requires recursive IR traversals to be depth-limited. Consider threading a depth counter (or a Set of already-visited group ids) through writeGroupPage/groupHasContent and bailing out (or raising a CliError) when a cycle or an unreasonable nesting depth is detected, so a malformed group tree cannot cause an infinite loop of page writes or stack exhaustion.
Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
Fixed in 8448683. Both writeGroupPage and groupHasContent now carry a visited set of group ids and bail on a repeat, so a subgroup that points back at an ancestor terminates instead of writing pages forever. Added a test with a deliberately cyclic IR (terminates on a group tree whose subgroup references an ancestor) that asserts exactly 3 group pages are written.
Went with a visited set rather than a depth cap since the cycle is the real failure mode here — depth is bounded by the group tree once each id is written at most once.
…ptions Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Description
generateCpponly ever walkedir.rootNamespace;ir.groups— the library author's own organization, written in the source as@defgroup/@ingroup/@{…@}— was parsed and then dropped on the floor. This adds a Stage 5 that renders it.Group pages are link indexes, not content owners: a group lists its members with links to the symbol pages written in Stage 3, so nothing renders twice and search / Ask Fern don't see duplicate bodies. Layout mirrors the existing namespace/category index pages:
Real output, from the parser's IR for a header with two
@defgroups and one nested subgroup:Changes Made
cpp/src/renderers/GroupPageRenderer.ts— new: group page + groups index, member entries resolved through the existingbuildLinkPathentity registry (a member with no page is listed unlinked rather than dropped), overloads deduped by stripped path.CppDocsGenerator.ts— Stage 5 walksir.groupsrecursively; groups with no members anywhere in their subtree are skipped so nothing empty lands in the nav.CppLibraryDocsIr.ts—CppGroupIrgains the inlined member arrays, marked optional so this release keeps working against an IR produced before fern-api/fern-platform#13971 deploys.Nav needs no changes: the output folder is already rendered as folder navigation, so
groups/shows up as a sibling of the kind folders.Depends on fern-api/fern-platform#13971, which puts the members on the group in the first place — previously a group only had Doxygen refids and our member types carry no id, so there was nothing to link to.
Testing
CppDocsGenerator.test.ts(nested group tree with class/function/subgroup links asserted on the actual MDX; empty groups produce nogroups/folder). Full package suite: 412 passed.@defgroup/@ingroup/@{/@}with one nested subgroup, then this generator over that IR; output is the snippet above, and every member link resolves to a page the same run wrote.tscclean viapnpm turbo run compile --filter @fern-api/library-docs-generator; biome clean.Not in scope, and DOCA still renders 0 pages until it lands: C file-scope symbols (a C library has no namespace, so there is nothing for the parser to walk today). Also unchanged: the C++ index-page gate that skips landing pages when the docs.yml library name differs from the root namespace, and
#definemacros, which have no IR representation at all.Link to Devin session: https://app.devin.ai/sessions/4002454f8a644c61b0844b02054779aa
Requested by: @Ryan-Amirthan