Skip to content

feat(markdown): render inline {@link Target} references as links#181

Merged
adrianschmidt merged 3 commits into
mainfrom
feat/inline-link-references
Jun 19, 2026
Merged

feat(markdown): render inline {@link Target} references as links#181
adrianschmidt merged 3 commits into
mainfrom
feat/inline-link-references

Conversation

@Kiarokh

@Kiarokh Kiarokh commented May 18, 2026

Copy link
Copy Markdown
Collaborator

Summary

JSDoc/TSDoc {@link Target} references inside component descriptions
previously surfaced in the generated docs as literal text — readers saw
{@link Rule} verbatim with nothing clickable. This PR teaches the
markdown pipeline to rewrite those references into proper links, the
same way IDEs interpret them for cmd-click navigation.

The change is split into three atomic commits:

  1. fix(markdown): skip type-linking inside anchor ancestors — A
    prerequisite bug fix in typeLinks. Without it, a <code> element
    inside an existing <a> would get its type token wrapped in a
    second <a>, producing invalid nested anchors. Generalises the
    existing <pre><code> skip-set into a broader "skippable code" set
    that also captures code inside any anchor ancestor.

  2. feat(markdown): render inline {@link Target} references as links — Adds a remark plugin that walks mdast text nodes for
    {@link …} syntax and emits link nodes. Supports the three TSDoc
    forms ({@link Target}, {@link Target Display},
    {@link Target | Display}) plus absolute URLs. Targets are resolved
    against the project's known types and component tags via a new
    component registry that mirrors the existing type registry. Bare
    identifiers are wrapped in <code> so they match how type names
    render elsewhere; unresolved targets still render as inline code
    instead of bare prose; references inside inline and fenced code are
    intentionally left untouched.

  3. docs(markdown): add example demonstrating inline @link references — Adds kompendium-example-inline-links so the
    rendered kompendium-markdown page shows each syntactic form
    alongside its source. An integration test pins the rendered HTML
    against the actual built kompendium.json registry to keep the
    showcase honest.

Behaviour matrix

Input Output
{@link MenuItem} (known type) <a href="#/type/MenuItem"><code>MenuItem</code></a>
{@link kompendium-markdown} (known component) <a href="#/component/kompendium-markdown/"><code>kompendium-markdown</code></a>
{@link MenuItem | the menu item} (free-form label) <a href="#/type/MenuItem">the menu item</a>
{@link https://example.com | docs} (absolute URL) <a href="https://example.com">docs</a>
{@link Unknown} (unresolved bare identifier) <code>Unknown</code>
`{@link Foo}` (inside inline code) unchanged
{@link Foo} inside ``` fenced block unchanged

Test plan

  • Unit tests for the plugin against minimal mdast trees
    (markdown-inline-links.spec.ts)
  • End-to-end tests through the full pipeline covering all syntactic
    forms, inline-code passthrough, fenced-code passthrough, and URL
    targets (markdown.spec.ts)
  • Integration test asserting the example renders correctly against
    Kompendium's own built registry (markdown-example-integration.spec.ts)
  • npm run build clean
  • npm run lint:src clean
  • npm test — 115 passing, 5 pre-existing skips
  • Manual: open npm start → navigate to kompendium-markdown
    visually verify the new "Inline @link references" example

Notes

  • The feature is purely additive in the markdown pipeline. Pages that
    contain no {@link …} references are unaffected.
  • The typeLinks fix in commit 1 is a sensible cleanup on its own merits
    (nested <a> is invalid HTML) and is a hard prerequisite for the
    feature: without it, {@link KnownType} would produce nested anchors.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added {@link ...} syntax support for clickable documentation links to components, types, and external URLs.
    • Component references now work alongside type references in markdown.
    • Added an inline-links documentation example showcasing supported label formats and fallbacks.
  • Bug Fixes
    • Improved inline link processing so links resolve correctly while avoiding unintended transformation inside existing links/code spans and blocks.
  • Tests
    • Expanded comprehensive unit and integration test coverage for inline link behavior and URL normalization.

@coderabbitai

coderabbitai Bot commented May 18, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds TSDoc/JSDoc {@link ...} inline reference transformation to the markdown pipeline. It extracts component metadata from app data, implements a configurable link resolver that routes known types and components to fragment URLs, integrates the transformation into remark/rehype preprocessing, and strengthens type-link safety to prevent double-linking when types appear inside resolved links.

Changes

Inline @link Reference Handling

Layer / File(s) Summary
Component registry storage and propagation
src/components/markdown/markdown-types.ts, src/components/app/app.tsx, src/components/markdown/markdown.tsx
Module-scoped components array added alongside types, populated from app's this.data.docs.components, and threaded through markdownToHtml calls.
Inline links AST transformation
src/kompendium/markdown-inline-links.ts
New utilities: normalizeInlineLinkUrls() preprocesses URL-bearing {@link ...} into markdown links; inlineLinks() transformer walks mdast text nodes, replaces {@link ...} with link/code nodes via resolver callback, and skips processing inside existing links.
Markdown pipeline integration and link resolution
src/kompendium/markdown.ts
markdownToHtml accepts components parameter, creates resolver mapping types/components to #/type/... and #/component/.../ routes, registers normalizeInlineLinkUrls preprocessing step, and adds inlineLinks transformer to pipeline.
Type-link safety for inline links
src/kompendium/markdown-typelinks.ts
Replaces pre-only code-skipping logic with collectSkippableCodeElements() that also gathers <code> inside <a> anchors, preventing type-link wrapping inside resolved inline links.
Unit tests for inline links transformer
src/kompendium/test/markdown-inline-links.spec.ts
Jest suite covering no-op behavior, bare identifier and labeled transformations with resolver, space/pipe-separated display text, unknown target fallbacks, absolute URL handling, and nested-link prevention.
Integration tests for markdown pipeline
src/kompendium/test/markdown.spec.ts
New inline @link references suite validating resolved type/component links, display-text formats, fallback rendering in edge cases, and behavior inside code blocks.
End-to-end registry test
src/kompendium/test/markdown-example-integration.spec.ts
Conditional integration spec loading real registry from www/kompendium.json, verifying inlineLinksExample renders with correct links and no syntax leakage.
Example component and reference documentation
src/components/markdown/examples/inline-links-example.ts, src/components/markdown/examples/inline-links.tsx
inlineLinksExample template string documents {@link ...} behavior; InlineLinksExample Stencil component renders it via kompendium-markdown.

Sequence Diagram

sequenceDiagram
  participant App as App.fetchData()
  participant Registry as markdown-types
  participant MarkdownTsx as Markdown.renderMarkdown()
  participant Pipeline as markdownToHtml()
  participant Normalize as normalizeInlineLinkUrls()
  participant Resolver as createLinkResolver()
  participant InlineLinks as inlineLinks()
  participant TypeLinks as typeLinks()
  participant HTML as HTML Output
  
  App->>Registry: setTypes(typeNames)
  App->>Registry: setComponents(componentTags)
  
  MarkdownTsx->>MarkdownTsx: types = getTypes()
  MarkdownTsx->>MarkdownTsx: components = getComponents()
  MarkdownTsx->>Pipeline: markdownToHtml(text, types, components)
  
  Pipeline->>Normalize: normalizeInlineLinkUrls(text)
  Normalize->>Pipeline: markdown with [label](url) links
  
  Pipeline->>Resolver: createLinkResolver(types, components)
  Resolver->>Pipeline: resolver: (target) => url | null
  
  Pipeline->>InlineLinks: inlineLinks({resolve})
  InlineLinks->>InlineLinks: walk mdast text nodes
  InlineLinks->>Resolver: resolve(target)
  Resolver->>InlineLinks: `#/type/X` or `#/component/Y/`
  InlineLinks->>Pipeline: mdast link nodes
  
  Pipeline->>TypeLinks: typeLinks() transformer
  TypeLinks->>TypeLinks: collect skippable code (inside pre or anchor)
  TypeLinks->>Pipeline: avoid wrapping types in links
  
  Pipeline->>HTML: final HTML with resolved links
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • jgroth/kompendium#178: Introduces normalizeLegacyAdmonitions preprocessing step in markdownToHtml pipeline; this PR builds on that foundation by adding normalizeInlineLinkUrls preprocessing and the inline-links transformer.

Suggested labels

released

Suggested reviewers

  • jgroth
  • adrianschmidt

Poem

A rabbit hops through markdown prose,
Linking types and components with {@link} flows,
Components now resolve with care,
Double-links prevented everywhere. 🐰✨
Docs bloom bright with reference flair!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 13.64% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main feature: implementing inline {@link Target} reference rendering as links in the markdown pipeline.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/inline-link-references

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Kiarokh and others added 3 commits June 17, 2026 15:09
When an inline `<code>` element already sits inside an `<a>` (for example,
from a markdown link like `[label `Foo`](url)`), the typeLinks pass would
wrap the type token in another anchor and produce a nested `<a>`, which is
invalid HTML and confuses navigation.

Generalises the existing `<pre><code>` skip-set into a more general
"skippable code" set that also captures `<code>` inside any anchor
ancestor, and leaves those code elements untouched.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
JSDoc/TSDoc `{@link Target}` references in component descriptions
previously surfaced in the generated docs as literal text. Adds a remark
plugin that rewrites them into mdast link nodes during the markdown
pipeline, supporting the standard syntactic forms:

  {@link Target}
  {@link Target Display text}
  {@link Target | Display text}
  {@link https://example.com | Display text}

Targets are resolved against the project's known types and component
tags (a new component registry mirrors the existing type registry).
Bare-identifier references — those without an explicit display label —
are wrapped in `<code>` so they visually match how type names render
elsewhere in the docs. Unresolved targets still render as inline code
rather than dangling syntax, and absolute URLs link directly.

References inside inline code (``{@link Foo}``) and fenced code blocks
are intentionally left untouched so the syntax itself can be documented.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a dedicated `kompendium-example-inline-links` example component
attached to `kompendium-markdown` so the rendered docs page shows each
inline `{@link …}` syntactic form alongside its source. The integration
test renders the example against the actual built `kompendium.json`
registry to keep the showcase honest as the docs build evolves.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@adrianschmidt adrianschmidt force-pushed the feat/inline-link-references branch from 4e5284e to 8d14c39 Compare June 17, 2026 13:16

@coderabbitai coderabbitai 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.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/kompendium/markdown-inline-links.ts`:
- Around line 65-70: The normalizeInlineLinkUrls() function applies URL pattern
matching across the entire text without distinguishing between prose and code
content, which breaks inline code and fenced code blocks. Refactor the function
to first parse and identify code spans (delimited by backticks) and fenced code
blocks (delimited by triple backticks), then apply the URL replacement only to
non-code prose segments while preserving all code content as literal text.
- Around line 44-52: The splitLabel() function incorrectly identifies the first
occurrence of a pipe character anywhere in the rest string as a label separator,
which can truncate valid labels containing pipes. Instead of using indexOf() to
find any pipe, modify the logic to only treat a pipe as a separator when it
appears at the start of the label section. Check if the rest string begins with
a pipe character or restructure the parsing logic to ensure pipes that appear
later in the content are not mistaken for the separator.
- Around line 101-111: The guard condition in the visit callback for text nodes
only checks if the immediate parent is a link type, but it fails when text nodes
are nested deeper within links through intermediate formatting elements like
strong or emphasis. Modify the logic to pre-collect all text node descendants of
link nodes before processing, then check if the current text node belongs to
that collection instead of only checking parent.type === 'link'. This ensures
all text nodes under any link in the tree hierarchy are skipped, regardless of
nesting depth through formatting nodes.

In `@src/kompendium/test/markdown-inline-links.spec.ts`:
- Around line 152-170: The performance assertions in the two test cases
('returns promptly without rewriting (mdast plugin)' and 'returns promptly
without rewriting (URL pre-pass)') use hardcoded 100ms thresholds with
Date.now() comparisons, which are brittle and prone to flaking in noisy CI
environments. Replace these tight fixed-time assertions with either
significantly relaxed thresholds (e.g., 500ms or 1000ms) to reduce false
failures while still catching true complexity regressions, or implement a
relative-growth assertion pattern that compares performance against a baseline
rather than an absolute time value.

In `@src/kompendium/test/markdown.spec.ts`:
- Around line 565-586: The two test functions "rewrites a URL reference even
inside inline code (known gap)" and "rewrites a URL reference even inside fenced
code (known gap)" have assertions that expect `{`@link`}` URL references to be
rewritten into markdown links even when inside code blocks. This conflicts with
the stated contract that references inside code should remain unchanged. Either
update both test assertions to expect the original `{`@link`}` syntax to be
preserved literally in the code blocks (and fix the underlying pipeline to
achieve this), or update the contract documentation to explicitly state that URL
rewriting inside code blocks is intentional behavior. Choose one consistent
approach and align the tests, implementation, and documentation accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: b5c033fa-9951-4180-aa33-3db802417a65

📥 Commits

Reviewing files that changed from the base of the PR and between 4e5284e and 8d14c39.

📒 Files selected for processing (11)
  • src/components/app/app.tsx
  • src/components/markdown/examples/inline-links-example.ts
  • src/components/markdown/examples/inline-links.tsx
  • src/components/markdown/markdown-types.ts
  • src/components/markdown/markdown.tsx
  • src/kompendium/markdown-inline-links.ts
  • src/kompendium/markdown-typelinks.ts
  • src/kompendium/markdown.ts
  • src/kompendium/test/markdown-example-integration.spec.ts
  • src/kompendium/test/markdown-inline-links.spec.ts
  • src/kompendium/test/markdown.spec.ts
🚧 Files skipped from review as they are similar to previous changes (8)
  • src/components/app/app.tsx
  • src/kompendium/markdown.ts
  • src/components/markdown/examples/inline-links-example.ts
  • src/components/markdown/examples/inline-links.tsx
  • src/components/markdown/markdown-types.ts
  • src/kompendium/markdown-typelinks.ts
  • src/components/markdown/markdown.tsx
  • src/kompendium/test/markdown-example-integration.spec.ts

Comment on lines +44 to +52
const pipeIndex = rest.indexOf('|');
if (pipeIndex !== -1) {
const pipeLabel = rest.slice(pipeIndex + 1).trim();
if (pipeLabel.length > 0) {
return { label: pipeLabel, explicit: true };
}

return { label: null, explicit: false };
}

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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Only treat | as a separator when it starts the label section.

splitLabel() currently uses the first | anywhere in rest, which can truncate valid space-form labels containing pipes.

Suggested fix
 function splitLabel(rest: string): SplitLabel {
-    const pipeIndex = rest.indexOf('|');
-    if (pipeIndex !== -1) {
-        const pipeLabel = rest.slice(pipeIndex + 1).trim();
+    const trimmedStart = rest.trimStart();
+    if (trimmedStart.startsWith('|')) {
+        const pipeLabel = trimmedStart.slice(1).trim();
         if (pipeLabel.length > 0) {
             return { label: pipeLabel, explicit: true };
         }
 
         return { label: null, explicit: false };
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const pipeIndex = rest.indexOf('|');
if (pipeIndex !== -1) {
const pipeLabel = rest.slice(pipeIndex + 1).trim();
if (pipeLabel.length > 0) {
return { label: pipeLabel, explicit: true };
}
return { label: null, explicit: false };
}
const trimmedStart = rest.trimStart();
if (trimmedStart.startsWith('|')) {
const pipeLabel = trimmedStart.slice(1).trim();
if (pipeLabel.length > 0) {
return { label: pipeLabel, explicit: true };
}
return { label: null, explicit: false };
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/kompendium/markdown-inline-links.ts` around lines 44 - 52, The
splitLabel() function incorrectly identifies the first occurrence of a pipe
character anywhere in the rest string as a label separator, which can truncate
valid labels containing pipes. Instead of using indexOf() to find any pipe,
modify the logic to only treat a pipe as a separator when it appears at the
start of the label section. Check if the rest string begins with a pipe
character or restructure the parsing logic to ensure pipes that appear later in
the content are not mistaken for the separator.

Comment on lines +65 to +70
export function normalizeInlineLinkUrls(text: string): string {
return text.replace(URL_LINK_PATTERN, (_match, url, rest) => {
const { label } = splitLabel(rest);

return `[${(label ?? url).trim()}](${url})`;
});

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.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Prevent URL {@link ...} normalization inside code spans/fenced blocks.

normalizeInlineLinkUrls() is a raw string replace, so it also rewrites code content. This breaks the contract that inline/fenced code should remain literal.

Suggested fix (apply URL replacement only to prose segments)
 export function normalizeInlineLinkUrls(text: string): string {
-    return text.replace(URL_LINK_PATTERN, (_match, url, rest) => {
-        const { label } = splitLabel(rest);
-
-        return `[${(label ?? url).trim()}](${url})`;
-    });
+    return splitMarkdownIntoCodeAndProse(text)
+        .map((segment) => {
+            if (segment.kind === 'code') {
+                return segment.value;
+            }
+
+            return segment.value.replace(URL_LINK_PATTERN, (_match, url, rest) => {
+                const { label } = splitLabel(rest);
+                return `[${(label ?? url).trim()}](${url})`;
+            });
+        })
+        .join('');
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/kompendium/markdown-inline-links.ts` around lines 65 - 70, The
normalizeInlineLinkUrls() function applies URL pattern matching across the
entire text without distinguishing between prose and code content, which breaks
inline code and fenced code blocks. Refactor the function to first parse and
identify code spans (delimited by backticks) and fenced code blocks (delimited
by triple backticks), then apply the URL replacement only to non-code prose
segments while preserving all code content as literal text.

Comment on lines +101 to +111
visit(
tree,
'text',
(node: MdastText, index: number | null, parent: Parent | null) => {
if (!parent || index === null) {
return;
}

if (parent.type === 'link') {
return;
}

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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Skip all text nodes under existing links, not only direct link children.

Current guard only checks parent.type === 'link'. If text is nested inside formatting within a link (for example, link -> strong -> text), it will still be transformed and can create nested anchors.

Suggested fix (pre-collect text descendants of existing links)
     return (tree: Node) => {
+        const textUnderExistingLinks = new Set<Node>();
+        visit(tree, 'link', (linkNode: Parent) => {
+            visit(linkNode, 'text', (textNode: Node) => {
+                textUnderExistingLinks.add(textNode);
+            });
+        });
+
         visit(
             tree,
             'text',
             (node: MdastText, index: number | null, parent: Parent | null) => {
                 if (!parent || index === null) {
                     return;
                 }
 
-                if (parent.type === 'link') {
+                if (textUnderExistingLinks.has(node)) {
                     return;
                 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/kompendium/markdown-inline-links.ts` around lines 101 - 111, The guard
condition in the visit callback for text nodes only checks if the immediate
parent is a link type, but it fails when text nodes are nested deeper within
links through intermediate formatting elements like strong or emphasis. Modify
the logic to pre-collect all text node descendants of link nodes before
processing, then check if the current text node belongs to that collection
instead of only checking parent.type === 'link'. This ensures all text nodes
under any link in the tree hierarchy are skipped, regardless of nesting depth
through formatting nodes.

Comment on lines +152 to +170
it('returns promptly without rewriting (mdast plugin)', () => {
const resolve: LinkResolver = () => '#/type/X';
const start = Date.now();
const result = run(tree(paragraph(text(unterminated))), resolve);
expect(Date.now() - start).toBeLessThan(100);
// No `}`, so nothing matches: the text node is left as-is.
expect(result.children[0]).toEqual(paragraph(text(unterminated)));
});

it('returns promptly without rewriting (URL pre-pass)', () => {
const start = Date.now();
const result = normalizeInlineLinkUrls(
`{@link https://example.com${' '.repeat(50000)}`,
);
expect(Date.now() - start).toBeLessThan(100);
expect(result).toEqual(
`{@link https://example.com${' '.repeat(50000)}`,
);
});

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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Avoid brittle fixed-time assertions for the short perf checks.

The Date.now() + toBeLessThan(100) guards are likely to flake under noisy CI load even without regressions. Keep the regression intent, but relax these short thresholds (or use a relative-growth assertion) so failures better indicate true complexity regressions.

Also applies to: 179-198

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/kompendium/test/markdown-inline-links.spec.ts` around lines 152 - 170,
The performance assertions in the two test cases ('returns promptly without
rewriting (mdast plugin)' and 'returns promptly without rewriting (URL
pre-pass)') use hardcoded 100ms thresholds with Date.now() comparisons, which
are brittle and prone to flaking in noisy CI environments. Replace these tight
fixed-time assertions with either significantly relaxed thresholds (e.g., 500ms
or 1000ms) to reduce false failures while still catching true complexity
regressions, or implement a relative-growth assertion pattern that compares
performance against a baseline rather than an absolute time value.

Comment on lines +565 to +586
// Known limitation: the URL pre-pass (`normalizeInlineLinkUrls`) runs over
// raw text before parsing, so unlike the non-URL mdast path it cannot tell
// a code span from prose. A URL `{@link}` inside inline or fenced code is
// therefore rewritten to markdown link source rather than passed through
// verbatim. Documented here as intended (if imperfect) behaviour; non-URL
// references in code are correctly passed through (see the tests above).
it('rewrites a URL reference even inside inline code (known gap)', async () => {
const md = 'Use `{@link https://example.com}` literally.';
const result = await markdownToHtml(md);
const html = result.toString();
expect(html).toContain('<code>');
expect(html).toContain('[https://example.com](https://example.com)');
expect(html).not.toContain('{@link');
});

it('rewrites a URL reference even inside fenced code (known gap)', async () => {
const md = '```\n{@link https://example.com}\n```';
const result = await markdownToHtml(md);
const html = result.toString();
expect(html).toContain('[https://example.com](https://example.com)');
expect(html).not.toContain('{@link');
});

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.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

These assertions lock in behavior that conflicts with the feature contract.

This block treats URL {@link ...} rewriting inside inline/fenced code as expected, but the stated contract for this PR is that references inside code should stay unchanged. Please align one source of truth: either flip these tests to preserve literal code content and fix the pipeline, or explicitly revise the contract/docs to make this behavior official.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/kompendium/test/markdown.spec.ts` around lines 565 - 586, The two test
functions "rewrites a URL reference even inside inline code (known gap)" and
"rewrites a URL reference even inside fenced code (known gap)" have assertions
that expect `{`@link`}` URL references to be rewritten into markdown links even
when inside code blocks. This conflicts with the stated contract that references
inside code should remain unchanged. Either update both test assertions to
expect the original `{`@link`}` syntax to be preserved literally in the code
blocks (and fix the underlying pipeline to achieve this), or update the contract
documentation to explicitly state that URL rewriting inside code blocks is
intentional behavior. Choose one consistent approach and align the tests,
implementation, and documentation accordingly.

@adrianschmidt adrianschmidt merged commit f626620 into main Jun 19, 2026
5 checks passed
@adrianschmidt adrianschmidt deleted the feat/inline-link-references branch June 19, 2026 09:17
@jgroth

jgroth commented Jun 19, 2026

Copy link
Copy Markdown
Owner

🎉 This PR is included in version 1.2.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants