Skip to content

feat(cli): render Doxygen group pages for C++ library docs - #17420

Open
Ryan-Amirthan wants to merge 2 commits into
mainfrom
devin/1786721871-cpp-group-pages
Open

feat(cli): render Doxygen group pages for C++ library docs#17420
Ryan-Amirthan wants to merge 2 commits into
mainfrom
devin/1786721871-cpp-group-pages

Conversation

@Ryan-Amirthan

@Ryan-Amirthan Ryan-Amirthan commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Description

generateCpp only ever walked ir.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:

groups/index.mdx                              # top-level groups
groups/DOCA_FLOW/index.mdx                    # docs + Classes/Functions/… links + Subgroups
groups/DOCA_FLOW/DOCA_FLOW_ADVANCED/index.mdx # nested <innergroup>, recursively

Real output, from the parser's IR for a header with two @defgroups and one nested subgroup:

---
title: DOCA Flow
description: "Packet flow pipeline API."
---

Packet flow pipeline API.

## Functions

- [`doca::doca_flow_pipe_create`](../doca/functions/docaflowpipecreate)

## Enumerations

- [`doca::doca_flow_dir`](../doca/enums/docaflowdir)

## Subgroups

- [Advanced flow](docaflow/docaflowadvanced)

Changes Made

  • cpp/src/renderers/GroupPageRenderer.ts — new: group page + groups index, member entries resolved through the existing buildLinkPath entity registry (a member with no page is listed unlinked rather than dropped), overloads deduped by stripped path.
  • CppDocsGenerator.ts — Stage 5 walks ir.groups recursively; groups with no members anywhere in their subtree are skipped so nothing empty lands in the nav.
  • CppLibraryDocsIr.tsCppGroupIr gains 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

  • Unit tests added/updated — two cases in CppDocsGenerator.test.ts (nested group tree with class/function/subgroup links asserted on the actual MDX; empty groups produce no groups/ folder). Full package suite: 412 passed.
  • Manual testing completed — ran the real parser (Doxygen 1.15.0) over a C++ header using @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.
  • tsc clean via pnpm 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 #define macros, which have no IR representation at all.

Link to Devin session: https://app.devin.ai/sessions/4002454f8a644c61b0844b02054779aa
Requested by: @Ryan-Amirthan


Open in Devin Review

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@Ryan-Amirthan Ryan-Amirthan self-assigned this Aug 14, 2026
@devin-ai-integration

Copy link
Copy Markdown
Contributor

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

@nitpickybot nitpickybot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the changes — everything looks good. No issues found.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 2 potential issues.

View 1 additional finding in Devin Review.

Open in Devin Review

const docstring = group.docstring;

const title = group.title || group.name;
const description = docstring ? renderSegmentsPlainText(docstring.summary) : `Members of the ${title} group.`;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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.

Suggested change
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.`;
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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: "".

Comment on lines +655 to +657
for (const subgroup of subgroups) {
writeGroupPage(subgroup, `${dir}/${groupFolderName(subgroup)}`, writer);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
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