Skip to content

[markdown] Generate header anchor ids from inline-parsed text - #2572

Open
hkarmoush wants to merge 1 commit into
dart-lang:mainfrom
hkarmoush:markdown-header-anchor-link-text-1339
Open

hkarmoush wants to merge 1 commit into
dart-lang:mainfrom
hkarmoush:markdown-header-anchor-link-text-1339

Conversation

@hkarmoush

Copy link
Copy Markdown
Contributor

Summary

  • HeaderWithIdSyntax/SetextHeaderWithIdSyntax generated a header's anchor id from the raw, un-inline-parsed markdown source. For a header containing a link (e.g. ### [Task](/packages/fpdart/lib/src/task.dart)), that folded the link target into the id (taskpackagesfpdartlibsrctaskdart) instead of using just the link text (task), breaking in-page anchor navigation wherever this pattern appears (e.g. on pub.dev-rendered READMEs).
  • Id generation is now deferred until after inline parsing, so it runs against the resolved element tree: link targets are excluded (only the link text is used), images contribute nothing (matching GitHub's behavior), and other inline formatting (**bold**, `code`, etc.) is flattened to plain text before the id is derived.
  • Added a needsGeneratedId flag on Element so header syntaxes can mark "this element wants a generated id," and Document computes the actual id right after resolving that element's inline children.

Fixes #1339

Test plan

  • Added unit-format test cases in test/extensions/headers_with_ids.unit and test/extensions/setext_headers_with_ids.unit reproducing the exact repro from the issue (a header containing a link, and a header containing a link + image) — confirmed these fail against the old code and pass with the fix.
  • dart test (full suite, 2766 tests) passes.
  • dart analyze — no issues.
  • dart format --output=none --set-exit-if-changed — no changes needed.

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request fixes header ID generation for headers containing inline elements (such as links and images) by delaying the ID generation until after inline parsing of the header's children has completed. This ensures that link text is used instead of the link target, and images are omitted entirely. The review feedback suggests decoupling the needsGeneratedId check from the node.children != null check in document.dart to make the implementation more robust for elements that might not have children but still require an ID.

Comment thread pkgs/markdown/lib/src/document.dart Outdated
Comment on lines 114 to 119
} else if (node is Element && node.children != null) {
_parseInlineContent(node.children!);
if (node.needsGeneratedId) {
node.generatedId = BlockSyntax.generateAnchorHash(node);
}
}

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.

medium

Decoupling the recursive inline parsing of children from the needsGeneratedId check makes the code more robust. If a future change or custom syntax creates an element with needsGeneratedId = true but children = null, the ID will still be generated correctly instead of being silently ignored.

      } else if (node is Element) {
        if (node.children != null) {
          _parseInlineContent(node.children!);
        }
        if (node.needsGeneratedId) {
          node.generatedId = BlockSyntax.generateAnchorHash(node);
        }
      }
References
  1. When building foundational components, prioritize robust, future-proof implementations over simpler ones that may introduce subtle bugs or rely on unintended behavior.

@hkarmoush

Copy link
Copy Markdown
Contributor Author

One follow-up thought on this fix: BlockSyntax.generateAnchorHash is still public and unguarded, so a future BlockSyntax subclass could still call it directly from its own parse() (the old pattern this PR moves away from) and silently reintroduce the same bug — link targets/image markup leaking into anchor IDs — since only a doc comment enforces the new precondition.

A few ways to harden this; curious which you'd prefer (or if you'd rather leave it as-is):

  1. Restrict generateAnchorHash so it can't be called from parse() directly (e.g. make it private/internal and expose ids only via the needsGeneratedId deferred mechanism) — safest, but could break external subclasses relying on the current API.
  2. Add a runtime guard/assertion against calling it on UnparsedContent children, so misuse fails loudly instead of silently producing bad ids.
  3. Leave it as a documented precondition only, since this is a future-proofing nit rather than a bug in this PR.

Happy to push whichever you'd like as a follow-up.

@srawlins srawlins left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thank you!

Just a merge conflict in the CHANGELOG and I think this is good to go.

@hkarmoush
hkarmoush force-pushed the markdown-header-anchor-link-text-1339 branch from 63d08b3 to d2143b0 Compare September 25, 2026 09:02
@hkarmoush
hkarmoush force-pushed the markdown-header-anchor-link-text-1339 branch 2 times, most recently from 9abccff to 6a8e35f Compare October 30, 2026 21:55
@hkarmoush
hkarmoush force-pushed the markdown-header-anchor-link-text-1339 branch 2 times, most recently from 8ce38bf to ed32851 Compare November 7, 2026 13:30
@hkarmoush
hkarmoush requested a review from a team as a code owner November 7, 2026 13:30
HeaderWithIdSyntax/SetextHeaderWithIdSyntax generated ids from the
header's raw, un-inline-parsed source, so a header containing a link
(e.g. `### [Task](/some/path)`) folded the link target into the id
instead of using only the link text.

Defer id generation until after inline parsing so it runs against the
resolved element tree: link targets are excluded, images contribute
nothing (matching GitHub's behavior), and other inline formatting is
flattened to plain text.

Fixes dart-lang#1339
@hkarmoush
hkarmoush force-pushed the markdown-header-anchor-link-text-1339 branch from ed32851 to dccca02 Compare November 13, 2026 07:29
@hkarmoush

Copy link
Copy Markdown
Contributor Author

@srawlins the CHANGELOG conflict is resolved and the branch is rebased on main, so this should be good to merge whenever you get a chance. Thanks!

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Anchor links for header with link are broken on pub.dev

2 participants