Warning
This is an internal project, and is not intended for public use. No support or stability guarantees are provided.
A rehype plugin that transforms code identifiers into clickable links, allowing users to navigate to type documentation by clicking on type references in code snippets.
The enhanceCodeTypes plugin scans syntax-highlighted code for type names and converts matching spans into links. It supports both inline <code> elements and code blocks within <pre> elements, and handles dotted chains like Accordion.Trigger.State.
class="language-*" on <code> elements and gates features accordinglyjs and css anchor maps, resolving per code element based on languageComponent.Root.Props as a single anchorTypeRef) instead of <a> tags for interactive popoverspl-c1 (constants) and pl-en (entity names/types)const values (strings, numbers, booleans, objects, arrays, scalar expressions, and simple template literals) with dot-access resolution and variable compositionThis plugin is part of @mui/internal-docs-infra and doesn't need separate installation.
import { unified } from 'unified';
import rehypeParse from 'rehype-parse';
import rehypeStringify from 'rehype-stringify';
import enhanceCodeTypes from '@mui/internal-docs-infra/pipeline/enhanceCodeTypes';
const linkMap = {
js: {
Trigger: '#trigger',
'Accordion.Trigger': '#trigger',
'Accordion.Trigger.Props': '#trigger.props',
},
};
const processor = unified()
.use(rehypeParse, { fragment: true })
.use(enhanceCodeTypes, { linkMap })
.use(rehypeStringify);
const result = await processor.process(
'<code class="language-tsx"><span class="pl-en">Trigger</span></code>',
);
console.log(String(result));
// Output: <code class="language-tsx"><a href="#trigger" class="pl-en">Trigger</a></code>
This plugin is typically used with abstractCreateTypes to link type references to their documentation:
import enhanceCodeTypes from '@mui/internal-docs-infra/pipeline/enhanceCodeTypes';
// The linkMap is automatically generated from your type exports,
// scoped by platform (js or css)
const linkMap = {
js: {
Root: '#root',
'Accordion.Root': '#root',
'Accordion.Root.Props': '#root.props',
'Accordion.Trigger': '#trigger',
AccordionTrigger: '#trigger', // Flat name mapping
},
};
// Used as a rehype enhancer in type processing
const enhancers = [[enhanceCodeTypes, { linkMap }]];
By default, the plugin wraps matched identifiers in <a> tags. When the typeRefComponent option is set, it emits a custom element instead:
const processor = unified()
.use(rehypeParse, { fragment: true })
.use(enhanceCodeTypes, {
linkMap,
typeRefComponent: 'TypeRef',
})
.use(rehypeStringify);
This produces HAST elements with the custom tag name:
<!-- Without typeRefComponent (default) -->
<a href="#trigger" class="pl-en">Trigger</a>
<!-- With typeRefComponent: 'TypeRef' -->
<TypeRef href="#trigger" name="Trigger" class="pl-en">Trigger</TypeRef>
The custom element receives three properties:
href: The link from the linkMapname: The matched identifier text (e.g., "Trigger" or "Accordion.Trigger")className: The original syntax highlighting class (e.g., "pl-en")To resolve these custom HAST elements to React components, you must include the component in the components map passed to hast-util-to-jsx-runtime. This is typically done via the components option in abstractCreateTypes.
See useType for how to build an interactive TypeRef component.
<!-- Input (after syntax highlighting) -->
<code><span class="pl-en">InputType</span></code>
<!-- Output -->
<code><a href="#inputtype" class="pl-en">InputType</a></code>
<!-- Input -->
<code><span class="pl-c1">Trigger</span></code>
<!-- Output -->
<code><a href="#trigger" class="pl-c1">Trigger</a></code>
<!-- Input -->
<code><span class="pl-en">Accordion</span>.<span class="pl-en">Trigger</span></code>
<!-- Output -->
<code
><a href="#trigger"
><span class="pl-en">Accordion</span>.<span class="pl-en">Trigger</span></a
></code
>
<!-- Input -->
<code
><span class="pl-en">Component</span>.<span class="pl-en">Root</span>.<span class="pl-en"
>ChangeEventDetails</span
></code
>
<!-- Output -->
<code
><a href="#root.changeeventdetails"
><span class="pl-en">Component</span>.<span class="pl-en">Root</span>.<span class="pl-en"
>ChangeEventDetails</span
></a
></code
>
Code blocks with frame/line spans are processed recursively:
<!-- Input -->
<code class="language-ts">
<span class="frame">
<span class="line"> <span class="pl-en">Component</span>.<span class="pl-en">Root</span> </span>
</span>
</code>
<!-- Output -->
<code class="language-ts">
<span class="frame">
<span class="line">
<a href="#root"><span class="pl-en">Component</span>.<span class="pl-en">Root</span></a>
</span>
</span>
</code>
<!-- Input -->
<code><span class="pl-en">TypeA</span> and <span class="pl-en">TypeB</span></code>
<!-- Output -->
<code><a href="#typea" class="pl-en">TypeA</a> and <a href="#typeb" class="pl-en">TypeB</a></code>
The plugin looks for these patterns in code elements:
<span> with class pl-c1 or pl-en containing a type name. text nodesSingle span:
span.pl-en("Trigger") → a.pl-en[href="#trigger"]("Trigger")
Dotted chain:
span.pl-en("Accordion") + text(".") + span.pl-en("Trigger")
↓
a[href="#trigger"](span.pl-en("Accordion") + "." + span.pl-en("Trigger"))
The linkMap is a platform-scoped object with optional js and css keys. Each key maps to a Record<string, string> of type names to anchor hrefs:
const linkMap = {
js: {
// Direct type names
Root: '#root',
Trigger: '#trigger',
// Dotted names (full namespace)
'Accordion.Root': '#root',
'Accordion.Trigger': '#trigger',
'Accordion.Trigger.Props': '#trigger.props',
// Flat names (for backward compatibility)
AccordionRoot: '#root',
AccordionTrigger: '#trigger',
},
css: {
// CSS custom properties, selectors, etc.
'--accordion-gap': '#accordion-gap',
},
};
Each code element resolves its anchor map based on the detected language:
js, jsx, ts, tsx) use linkMap.jscss, scss, less, sass) use linkMap.css<code> elements without a language-* class get an empty map (no linking)The plugin recursively processes nested elements (like frame and line spans) to find linkable spans at any depth, making it work with both simple inline code and complex code block structures.
Types not in the anchor map are left unchanged:
<!-- Input -->
<code class="language-tsx"><span class="pl-en">UnknownType</span></code>
<!-- linkMap: { js: { Trigger: '#trigger' } } -->
<!-- Output: unchanged -->
<code class="language-tsx"><span class="pl-en">UnknownType</span></code>
Matching is case-sensitive:
<!-- Input -->
<code class="language-tsx"><span class="pl-en">trigger</span></code>
<!-- linkMap: { js: { Trigger: '#trigger' } } -->
<!-- Output: unchanged (case doesn't match) -->
<code class="language-tsx"><span class="pl-en">trigger</span></code>
Only exact chain matches are linked:
<!-- Input -->
<code class="language-tsx"
><span class="pl-en">Accordion</span>.<span class="pl-en">Unknown</span></code
>
<!-- linkMap: { js: { 'Accordion.Trigger': '#trigger' } } -->
<!-- Output: unchanged (chain doesn't match) -->
<code class="language-tsx"
><span class="pl-en">Accordion</span>.<span class="pl-en">Unknown</span></code
>
Spans separated by text other than . are treated as separate matches:
<!-- Input -->
<code class="language-tsx"
><span class="pl-en">Accordion</span>: <span class="pl-en">Trigger</span></code
>
<!-- linkMap: { js: { Accordion: '#accordion', Trigger: '#trigger' } } -->
<!-- Output: linked separately -->
<code class="language-tsx"
><a href="#accordion" class="pl-en">Accordion</a>:
<a href="#trigger" class="pl-en">Trigger</a></code
>
This plugin should run after syntax highlighting plugins:
transformHtmlCodeInline - Applies syntax highlightingenhanceCodeTypes - Links type references (this plugin)enhanceCodeInline - Consolidates tag bracketsRunning it earlier would prevent the pattern from matching since the highlighting spans wouldn't exist yet.
The plugin detects the language of each <code> element by looking for a class="language-*" class (following standard markdown fenced-code conventions). Different languages enable different capabilities:
| Language | Types | JSX | Semantics |
|---|---|---|---|
ts / typescript | ✓ | ✗ | js |
tsx | ✓ | ✓ | js |
js / javascript | ✗ | ✗ | js |
jsx | ✗ | ✓ | js |
css / scss / less / sass | ✗ | ✗ | css |
| Unknown / no class | ✗ | ✗ | — |
supportsTypes): Recognition of type Name = definitions and : Name = type annotations. Without this, type keywords and colon-based annotations are ignored.supportsJsx): Recognition of <Component prop="value" /> syntax. Without this, < / > / / characters don't trigger JSX component detection.semantics): Determines the platform. 'js' enables recognition of function calls like func({ key: value }) and resolves the js anchor map. 'css' enables CSS property linking and resolves the css anchor map. When absent, no platform-specific behavior or anchor map is applied.Code elements without a recognized language class (including bare inline <code> without a class) get no capabilities enabled and no anchor map resolved, so no linking occurs.
The plugin can also link property names inside type definitions, object literals, function calls, and JSX components. This is opt-in via the linkProps option.
The plugin distinguishes between definition sites and reference sites:
type Props = { label: string }) are canonical — properties are wrapped with an id attribute (anchor targets) so other code can link to them.href attribute (clickable links) pointing to the definition.const linkMap = {
js: {
'Accordion.Root.Props': '#root.props',
makeItem: '#make-item',
Component: '#component',
},
};
const processor = unified()
.use(rehypeParse, { fragment: true })
.use(enhanceCodeTypes, {
linkMap,
linkProps: 'shallow',
})
.use(rehypeStringify);
The linkProps option accepts:
'shallow': Handles only top-level properties of known owners'deep': Handles nested properties with dotted paths (e.g., address.street-name)undefined (default): No property handling (backward compatible)The plugin detects "owners" — named types or functions whose properties can be enhanced:
| Context | Trigger | Example | Output |
|---|---|---|---|
| Type definition | type keyword + = + { | type Props = { label: string } | <span id> (definition) |
| Type annotation | Linkable name + : + { | const x: Props = { ... } | <a href> (reference) |
| Function call | Linkable name + ( + { | makeItem({ label: 'hello' }) | <a href> (reference) |
| JSX | < + Linkable name | <Component label="hi" /> | <a href> (reference) |
| CSS property | Linkable name + : | justify-content: space-between | <a href> (reference) |
With anchor map { js: { 'Accordion.Root.Props': '#root.props' } }:
<!-- Input (syntax-highlighted TypeScript: type Props = { label: string }) -->
<span class="pl-en">Accordion</span>.<span class="pl-en">Root</span>.<span class="pl-en"
>Props</span
>
= {
<span class="pl-v">label</span>: ... }
<!-- Output: property gets id (definition target), not href -->
<a href="#root.props"
><span class="pl-en">Accordion</span>.<span class="pl-en">Root</span>.<span class="pl-en"
>Props</span
></a
>
= {
<span id="root.props:label" class="pl-v">label</span>: ... }
With anchor map { js: { makeItem: '#make-item' } }:
<!-- Input (syntax-highlighted: makeItem({ label: 'hello' })) -->
<span class="pl-en">makeItem</span>({ label: ... })
<!-- Output (plain text "label" is extracted and linked) -->
<a href="#make-item" class="pl-en">makeItem</a>({ <a href="#make-item::label">label</a>: ... })
Property names inside object literal arguments are plain text nodes (not wrapped in <span> elements). The plugin splits text nodes to extract and wrap individual properties.
With anchor map { js: { Component: '#component' } }:
<!-- Input (syntax-highlighted JSX: <Component label="hi" />) -->
<<span class="pl-en">Component</span> <span class="pl-e">label</span>=<span class="pl-s">"hi"</span>
/>
<!-- Output -->
<<a href="#component" class="pl-en">Component</a>
<a href="#component::label"><span class="pl-e">label</span></a
>=<span class="pl-s">"hi"</span> />
For CSS code blocks, a linked property name becomes an owner and its subsequent values are linked as properties. With anchor map { css: { 'justify-content': '#justify-content' } }:
<!-- Input (syntax-highlighted CSS: justify-content: space-between;) -->
<span class="pl-c1">justify-content</span>: <span class="pl-c1">space-between</span>;
<!-- Output: property name linked, value linked as property ref -->
<a href="#justify-content" class="pl-c1">justify-content</a>:
<a
href="#justify-content:space-between"
data-name="justify-content"
data-prop="space-between"
class="pl-c1"
>space-between</a
>;
Numeric values (e.g., 8, 1.5) and CSS function calls (e.g., var(), calc()) are excluded from value linking. The owner context ends at ;, {, or }, so consecutive declarations each resolve independently.
With linkProps: 'deep', nested properties produce dotted paths:
<!-- Input (syntax-highlighted TypeScript: type Props = { address: { streetName: string } }) -->
<span class="pl-v">address</span>: { <span class="pl-v">streetName</span>: ... }
<!-- Output (type definition, so id attributes) -->
<span id="root.props:address" class="pl-v">address</span>: {
<span id="root.props:address.street-name" class="pl-v">streetName</span>: ... }
Property names are converted to kebab-case in anchors (e.g., streetName → street-name, onValueChange → on-value-change).
Property anchors (whether used as id or href) are computed as:
| Owner kind | Anchor format | Example | Attribute |
|---|---|---|---|
| Type definition | {ownerAnchor}:{prop} | root.props:label | id (without leading #) |
| Type annotation | {ownerAnchor}:{prop} | #root.props:label | href |
| Function call (param 0) | {ownerAnchor}::{prop} | #make-item::label | href |
| Function call (param N) | {ownerAnchor}:{N}:{prop} | #make-item:1:active | href |
| Function call (named anchor) | {namedAnchor}:{prop} | #make-item:props:label | href |
| JSX (param 0) | {ownerAnchor}::{prop} | #component::label | href |
| CSS property | {ownerAnchor}:{value} | #justify-content:space-between | href |
For function calls and JSX, the parameter index appears between the owner anchor and the property name. Parameter 0 uses :: (zero omitted), while other parameters use :{N}:.
You can provide type-system-derived names for function parameters in the linkMap using the name[N] format:
const linkMap = {
js: {
makeItem: '#make-item',
'makeItem[0]': '#make-item:props', // Named anchor for param 0
'makeItem[1]': '#make-item:state', // Named anchor for param 1
},
};
// With this linkMap, property linking produces:
// - param 0, prop "label" → href="#make-item:props:label"
// - param 1, prop "active" → href="#make-item:state:active"
Without a named entry, the fallback index-based format is used:
// Without makeItem[0] in linkMap.js:
// - param 0, prop "label" → href="#make-item::label"
// - param 1, prop "active" → href="#make-item:1:active"
Similar to typeRefComponent, the typePropRefComponent option emits a custom element for property handling:
const processor = unified()
.use(rehypeParse, { fragment: true })
.use(enhanceCodeTypes, {
linkMap,
linkProps: 'shallow',
typePropRefComponent: 'TypePropRef',
})
.use(rehypeStringify);
The custom element receives:
id, name, prop, and optionally classNamehref, name, prop, and optionally classNameWhere:
id / href: The computed property anchor (e.g., id="root.props:label", href="#root.props:label")name: The owner identifier (e.g., "Accordion.Root.Props")prop: The kebab-case property path (e.g., "label" or "on-value-change")The plugin uses a single-pass traversal with a stack-based state machine:
type, const, :, = keywords to identify contexts{ / } and ( / ) to know when owners start and end, at the top paren level to increment the parameter indexState is threaded across line boundaries within code blocks, so multi-line code snippets are handled correctly.
The plugin can also link function parameter names inside arrow function types, type annotations, function declarations, and callback properties. This is opt-in via the linkParams option.
Like property linking, the plugin distinguishes between definition sites and reference sites:
type Callback = (details: EventDetails) => void) are canonical — parameter names are wrapped with an id attribute (anchor targets).const cb: Callback = (d) => {}), function declarations (function test(a, b) {}), and callback properties in object literals and JSX are references — parameter names are wrapped with an href attribute (clickable links) pointing to the definition.const linkMap = {
js: {
Callback: '#callback',
'Callback[0]': '#callback:details', // Named anchor for param 0
'Callback[1]': '#callback:options', // Named anchor for param 1
},
};
const processor = unified()
.use(rehypeParse, { fragment: true })
.use(enhanceCodeTypes, {
linkMap,
linkParams: true,
})
.use(rehypeStringify);
The plugin only activates parameter linking when parentheses are followed by => or {, confirming a function context. Plain parenthesized expressions like (value) without an arrow are left unchanged.
With anchor map { js: { Callback: '#callback' } }:
<!-- Input (syntax-highlighted: type Callback = (details: EventDetails) => void) -->
<span class="pl-k">type</span> <span class="pl-en">Callback</span> = (<span class="pl-v"
>details</span
>: <span class="pl-en">EventDetails</span>) <span class="pl-k">=></span>
<span class="pl-k">void</span>
<!-- Output: param gets id (definition target) using positional format -->
<span class="pl-k">type</span>
<a href="#callback" class="pl-en">Callback</a> = (<span
id="callback[0]"
data-param="details"
class="pl-v"
>details</span
>: <a href="#eventdetails" class="pl-en">EventDetails</a>) <span class="pl-k">=></span>
<span class="pl-k">void</span>
When a named anchor is provided (e.g., 'Callback[0]': '#callback:details'), the named value is used as the id instead.
With anchor map { js: { Callback: '#callback', 'Callback[0]': '#callback:details' } }:
<!-- Input (syntax-highlighted: const cb: Callback = (d) => {}) -->
<span class="pl-k">const</span> cb: <span class="pl-en">Callback</span> = (<span class="pl-v"
>d</span
>) <span class="pl-k">=></span> {}
<!-- Output: param gets href (link) using the named anchor -->
<span class="pl-k">const</span> cb: <a href="#callback" class="pl-en">Callback</a> = (<a
href="#callback:details"
data-param="d"
class="pl-v"
>d</a
>) <span class="pl-k">=></span> {}
At reference sites, the parameter's position determines its anchor. The actual parameter name at the call site (here d) doesn't affect the lookup — only its index matters.
Without a named Callback[0] entry in the anchor map, the plugin falls back to an index-based anchor:
<!-- Without Callback[0]: -->
<a href="#callback[0]" data-param="d" class="pl-v">d</a>
<!-- With Callback[0] = '#callback:details': -->
<a href="#callback:details" data-param="d" class="pl-v">d</a>
With anchor map { js: { test: '#test', 'test[0]': '#test:first', 'test[1]': '#test:second' } }:
<!-- Input (syntax-highlighted: function test(one: TypeA, two: TypeB) {}) -->
<span class="pl-k">function</span> <span class="pl-en">test</span>(<span class="pl-v">one</span>:
<span class="pl-en">TypeA</span>, <span class="pl-v">two</span>: <span class="pl-en">TypeB</span>)
{}
<!-- Output -->
<span class="pl-k">function</span>
<a href="#test" class="pl-en">test</a>(<a href="#test:first" data-param="one" class="pl-v">one</a>:
<a href="#typea" class="pl-en">TypeA</a>,
<a href="#test:second" data-param="two" class="pl-v">two</a>:
<a href="#typeb" class="pl-en">TypeB</a>) {}
When combined with linkProps: 'deep', callback properties inside type definitions get both property anchors and parameter anchors:
<!-- Input (syntax-highlighted: type Opts = { callback: (details: X) => void }) -->
<span class="pl-v">callback</span>: (<span class="pl-v">details</span>:
<span class="pl-en">X</span>) <span class="pl-k">=></span>
<span class="pl-k">void</span>
<!-- Output -->
<span id="opts:callback" data-prop="callback" class="pl-v">callback</span>: (<span
id="opts:callback[0]"
data-param="details"
class="pl-v"
>details</span
>: <a href="#x" class="pl-en">X</a>) <span class="pl-k">=></span> <span class="pl-k">void</span>
The parameter anchor (opts:callback[0]) chains the owner, the property path, and the positional index. When a named anchor is provided (e.g., 'Opts:callback[0]': '#opts:callback:details'), the named value is used instead.
| Context | Anchor format | Example | Attribute |
|---|---|---|---|
| Type definition (positional) | {ownerAnchor}[{N}] | callback[0] | id |
| Type definition (named) | {namedAnchor} | callback:details | id |
| Type annotation (named) | {namedAnchor} | #callback:details | href |
| Type annotation (positional) | {ownerAnchor}[{N}] | #callback[0] | href |
| Function decl (named) | {namedAnchor} | #test:first | href |
| Function decl (positional) | {ownerAnchor}[{N}] | #test[0] | href |
| Deep callback (named) | {propAnchor}:{namedParam} | #type:callback:first | href |
| Deep callback (positional) | {propAnchor}[{N}] | #type:callback[0] | href |
The plugin correctly handles destructured parameters. Commas inside { } or [ ] destructuring patterns are not counted as parameter separators:
// Input: const cb: Callback = ({ a, b }, second) => {}
// second is parameter index 1, not 2
Similar to typePropRefComponent, the typeParamRefComponent option emits a custom element for parameter references:
const processor = unified()
.use(rehypeParse, { fragment: true })
.use(enhanceCodeTypes, {
linkMap,
linkParams: true,
typeParamRefComponent: 'TypeParamRef',
})
.use(rehypeStringify);
The custom element receives:
id, name, param, and optionally classNamehref, name, param, and optionally classNameWhere:
id / href: The computed parameter anchor (e.g., id="callback[0]", href="#callback:details")name: The owner identifier (e.g., "Callback")param: The parameter name as it appears in the code (e.g., "details")linkParams works independently of linkProps — you can enable one without the otherts, tsx, js, jsx), not CSSpl-v spans (variable-class) inside function parentheses are treated as parametersThe plugin can link variable references (pl-smi spans) back to the type from their declaration, establishing a scope-aware chain from usage to documentation. This is opt-in via the linkScope option.
When linkScope is enabled, the plugin performs a single-pass scope analysis:
x: TypeA), the plugin records a scope binding for x in the current scope.{ pushes a new scope, } pops it. Function bodies push kind: 'function' scopes; bare blocks push kind: 'block' scopes.pl-smi span (variable reference) is encountered, the plugin walks up the scope stack looking for a matching binding. If found, the span is replaced with a link.The analysis is conservative and single-pass — only syntactically explicit bindings are tracked. Uncertain cases stay unlinked.
function test(x) does not link x.const x: TypeB inside a block overrides param x: TypeA from the outer function.:, nested destructuring, and renamed destructured params are conservatively excluded.const linkMap = {
js: {
TypeA: '#type-a',
TypeB: '#type-b',
},
};
const processor = unified()
.use(rehypeParse, { fragment: true })
.use(enhanceCodeTypes, {
linkMap,
linkScope: true,
})
.use(rehypeStringify);
The plugin creates scope bindings from these declaration patterns:
| Declaration pattern | Example | Binding kind | Anchor resolution |
|---|---|---|---|
| Typed function parameter | function f(x: TypeA) | 'type' | href from TypeA |
| Typed const / let | const x: TypeA = ... | 'type' | href from TypeA |
| Typed var | var x: TypeA = ... | 'type' | href from TypeA |
| Destructured parameter | function f({ a }: TypeA) | 'prop' | href from TypeA:a |
| Unannotated callback param | call((x) => { ... }) | 'param' | href from call[0][0] |
| Typed callback param | call((x: TypeA) => { ... }) | 'type' | href from TypeA |
const and let are block-scoped — they are visible only within the innermost { } block where they are declared. var and function parameters are function-scoped — they are visible throughout the entire function body, including nested blocks.
function outer(x: TypeA) {
// x is visible here (function-scoped param)
{
const y: TypeB = ...;
// Both x and y are visible here
}
// x is still visible, y is not (const is block-scoped)
{
var z: TypeC = ...;
}
// z is still visible (var is function-scoped)
}
With anchor map { js: { TypeA: '#type-a' } }:
<!-- Input (syntax-highlighted: function test(one: TypeA) { console.log(one) }) -->
<span class="pl-k">function</span> <span class="pl-en">test</span>( <span class="pl-v">one</span
><span class="pl-k">:</span> <span class="pl-en">TypeA</span>) {
<span class="pl-smi">one</span>
}
<!-- Output: the pl-smi reference is linked to TypeA's href -->
<span class="pl-k">function</span> <span class="pl-en">test</span>( <span class="pl-v">one</span
><span class="pl-k">:</span> <a href="#type-a" class="pl-en">TypeA</a>) {
<a href="#type-a">one</a>
}
<!-- Input (syntax-highlighted: { const x: TypeB = val; use(x) }) -->
{ <span class="pl-k">const</span> <span class="pl-c1">x</span><span class="pl-k">:</span>
<span class="pl-en">TypeB</span> <span class="pl-k">=</span> val; <span class="pl-smi">x</span> }
<!-- Output -->
{ <span class="pl-k">const</span> <span class="pl-c1">x</span><span class="pl-k">:</span>
<a href="#type-b" class="pl-en">TypeB</a> <span class="pl-k">=</span> val; <a href="#type-b">x</a> }
When a function parameter is destructured, each destructured property gets a 'prop' binding with property path resolution:
<!-- Input (syntax-highlighted: function test({ a }: TypeA) { use(a) }) -->
<span class="pl-k">function</span> <span class="pl-en">test</span>({
<span class="pl-v">a</span>
}<span class="pl-k">:</span> <span class="pl-en">TypeA</span>) {
<span class="pl-smi">a</span>
}
<!-- Output: a links to TypeA's property anchor -->
...
<a href="#type-a:a">a</a>
...
// function outer(x: TypeA) { function inner() { use(x) } }
// x in inner() resolves to TypeA from the outer scope
The scope stack allows inner functions to see bindings from outer scopes.
<!-- Input (syntax-highlighted: const cb = (x: TypeA) => { use(x) }) -->
...
<span class="pl-v">x</span><span class="pl-k">:</span> <span class="pl-en">TypeA</span>)
<span class="pl-k">=></span> {
<span class="pl-smi">x</span>
}
<!-- Output: x in arrow body resolves via scope -->
...
<a href="#type-a">x</a>
...
Arrow functions with block bodies (=> { ... }) create function scopes. Expression-bodied arrows (=> expr) do not create scopes, so their parameters cannot be resolved.
When an unannotated callback is passed to a known function, the plugin infers parameter bindings from positional anchor map entries:
const linkMap = {
js: {
callFunction: '#call-function',
'callFunction[0][0]': '#call-function-first-param',
'callFunction[0][1]': '#call-function-second-param',
},
};
The key format is {functionName}[{argIndex}][{paramIndex}]:
callFunction[0][0] — first parameter of the first callback argumentcallFunction[0][1] — second parameter of the first callback argumentcallFunction[1][0] — first parameter of the second callback argument// Input: callFunction((a, b) => { use(a); use(b) })
// a resolves to callFunction[0][0], b resolves to callFunction[0][1]
Positional inference only creates bindings when the corresponding anchor map entry exists. If callFunction[0][0] is not in the anchor map, the parameter stays unlinked.
The plugin conservatively excludes several patterns:
| Pattern | Example | Reason |
|---|---|---|
| Use-before-declare | use(x); const x: Type = ... | No hoisting — binding doesn't exist yet |
| Untyped parameter | function f(x) { use(x) } | No type provenance to link to |
| Expression arrow body | (x: Type) => x | No block body, so no function scope is created |
| Nested destructuring | function f({ a: { b } }: T) | Deep destructuring has uncertain provenance |
| Destructured rename | function f({ a: renamed }: T) | Renamed property has uncertain provenance |
| Ternary colon | const x = cond ? a : Type | : is a ternary operator, not a type annotation |
| Unknown variable | use(unknown) | No matching binding in any scope |
linkScope works independently of linkParams — scope-derived variable references link even when linkParams is false (parameter definitions stay unlinked, but their body references still resolve).linkScope and linkParams are enabled, parameters are linked at their definition site (via linkParams) and their body references are also linked (via linkScope).linkScope works with linkProps — destructured parameter properties can produce 'prop' scope bindings that link to owner property anchors.ts, tsx, js, jsx), not CSS.The plugin can track the literal values of const declarations and annotate later variable references with those values. This enables downstream components to display resolved values (e.g., default values, configuration constants) directly in the documentation.
When linkValues is enabled, the plugin extends the scope analysis to capture literal values:
const declaration is followed by a literal value ('hello', 42, true), the plugin records a value binding for that variable name in the current scope.const declaration is followed by { ... }, the plugin collects all top-level key: value pairs into a value-object binding. Only properties with literal values (strings, numbers, booleans) are tracked.linkArrays): When a const declaration is followed by [ ... ], the plugin collects all element values — literals and resolved variable references — into a single array value binding.${identifier} interpolations are converted into expression tokens and evaluated when possible.pl-smi span (variable reference) resolves to a value binding, the plugin annotates it with data-value and data-name attributes.const processor = unified()
.use(rehypeParse, { fragment: true })
.use(enhanceCodeTypes, {
linkMap,
linkScope: true, // Required — value tracking builds on scope analysis
linkValues: true,
})
.use(rehypeStringify);
| Value type | Example | Tracked value | Option required |
|---|---|---|---|
| String | const x = 'hello' | 'hello' | linkValues |
| Number | const n = 42 | 42 | linkValues |
| Boolean | const b = true | true | linkValues |
| Object | const o = { a: 'one' } | { a: 'one' } | linkValues |
| Array | const a = ['x', 'y'] | ['x', 'y'] | linkArrays |
| Expression | const n = 1 + 2 * 3 | 7 | linkValues |
| Template | const x = `hi ${name}` | 'hi world' or partial expression | linkValues |
<!-- Input (syntax-highlighted: const x = 'hello'; use(x)) -->
<span class="pl-k">const</span> <span class="pl-c1">x</span> <span class="pl-k">=</span>
<span class="pl-s"><span class="pl-pds">'</span>hello<span class="pl-pds">'</span></span
>;
<span class="pl-smi">x</span>
<!-- Output: pl-smi reference is annotated with the tracked value -->
<span class="pl-k">const</span> <span class="pl-c1">x</span> <span class="pl-k">=</span>
<span class="pl-s"><span class="pl-pds">'</span>hello<span class="pl-pds">'</span></span
>; <span data-value="'hello'" data-name="x">x</span>
When a const is initialized with an object literal, dot-access expressions resolve to individual property values:
<!-- Input (syntax-highlighted: const obj = { key: 'val' }; use(obj.key)) -->
<span class="pl-k">const</span> <span class="pl-c1">obj</span> <span class="pl-k">=</span> { key:
<span class="pl-s"><span class="pl-pds">'</span>val<span class="pl-pds">'</span></span> };
<span class="pl-smi">obj</span>.<span class="pl-smi">key</span>
<!-- Output: dot-access resolves to property value -->
...
<span data-value="'val'" data-name="obj.key">key</span>
When the object is referenced without dot access, the full shape is used:
<!-- Input (syntax-highlighted: const obj = { a: 'one', b: 'two' }; use(obj)) -->
...
<span class="pl-smi">obj</span>
<!-- Output: full object shape as the value -->
<span data-value="{ a: 'one', b: 'two' }" data-name="obj">obj</span>
With linkArrays: true:
<!-- Input (syntax-highlighted: const arr = ['one', 'two']; use(arr)) -->
<span class="pl-k">const</span> <span class="pl-c1">arr</span> <span class="pl-k">=</span> [<span
class="pl-s"
>'one'</span
>, <span class="pl-s">'two'</span>];
<span class="pl-smi">arr</span>
<!-- Output -->
<span data-value="['one', 'two']" data-name="arr">arr</span>
Array elements can reference previously tracked const variables. Their values are resolved at the point of array construction:
const a = 'x';
const b = 'y';
const arr = [a, b]; // Tracked as ['x', 'y']
use(arr); // data-value="['x', 'y']"
This requires both linkValues: true (to track a and b) and linkArrays: true (to track arr).
Simple scalar expressions are evaluated and annotated at both the definition site and later references:
const n = 1 + 2 * 3;
use(n); // data-value="7"
Supported forms include:
+, -, *, and /+'item-' + 3Object-valued and array-valued bindings are excluded from expression evaluation. For example, const x = arr + 'b' is not tracked even when arr itself is tracked.
Simple template literals are tracked as string values:
const greeting = `hello`;
use(greeting); // data-value="'hello'"
Simple identifier interpolation is also supported:
const name = 'world';
const greeting = `hello ${name}`;
use(greeting); // data-value="'hello world'"
Interpolation parsing is whitespace-tolerant for simple identifier forms such as ${ name }.
When the interpolated variable is not tracked, the plugin keeps a partial expression instead of bailing out:
const label = `prefix-${name}-suffix`;
// data-value="'prefix-' + name + '-suffix'"
When a variable has both a type annotation and a literal value, the type annotation takes priority:
const x: TypeA = 'hello';
use(x); // Links to TypeA, not annotated with 'hello'
Type annotations provide stronger provenance — the value is a runtime detail, while the type is the semantic contract.
const Is TrackedOnly const declarations create value bindings. let and var are mutable, so their values cannot be reliably tracked:
const a = 'stable'; // Tracked ✓
let b = 'mutable'; // Not tracked ✗
var c = 'hoisted'; // Not tracked ✗
The plugin uses a deferred binding strategy to avoid false captures while still supporting complete scalar expressions:
const x = 42; // Tracked — simple literal
const y = 42 + 1; // Tracked — evaluates to 43
const z = fn('hi'); // NOT tracked — function call result
The literal value is stored as a candidate and only committed at the next statement boundary (;, newline, or end of code). If any operator, function call, or additional expression token appears after the literal, the candidate is invalidated.
For definition-site wrapping, line comments terminate the annotated expression region, while inline block comments remain part of the wrapped source when they occur inside a valid expression:
const a = 1 + 2; // wrapper covers `1 + 2`
const b = 1 + /* comment */ 2; // wrapper covers `1 + /* comment */ 2`
When typeValueRefComponent is set, the plugin emits a custom element instead of a plain <span> for value references:
const processor = unified()
.use(rehypeParse, { fragment: true })
.use(enhanceCodeTypes, {
linkMap,
linkScope: true,
linkValues: true,
typeValueRefComponent: 'TypeValueRef',
})
.use(rehypeStringify);
The custom element receives:
value: The tracked literal value (e.g., "'hello'", "42", "{ a: 'one' }")name: The variable or expression name (e.g., "x", "obj.key")<!-- Output with typeValueRefComponent: 'TypeValueRef' -->
<TypeValueRef value="'hello'" name="x">x</TypeValueRef>
| Pattern | Example | Reason |
|---|---|---|
| Mutable variable | let x = 'hello' | Value may change — unreliable |
| Function call result | const x = fn('hello') | Return value is unknown |
| Unsupported interpolation | const x = `${a + b}` | Complex interpolation expression |
| Member access interpolation | const x = `${obj.prop}` | Interpolation is not a simple identifier |
| Object/array operand expression | const x = arr + 'b' | Non-scalar operands are excluded |
| Unresolved dot/bracket access | const x = obj.key | Property access result is unknown unless resolved from a tracked object |
| Without linkScope | linkScope: false | Value tracking requires scope analysis |
linkValues requires linkScope to be enabled — without scope analysis, variable references cannot be resolved.linkArrays works independently of linkValues — you can track arrays without tracking scalar values, and vice versa. However, array element variable resolution requires linkValues to be enabled for the referenced variables.ts, tsx, js, jsx), not CSS.The plugin can link import specifiers (the module path string) to documentation pages and automatically register imported identifiers for downstream linking. This is opt-in via the moduleLinkMap option.
const processor = unified()
.use(rehypeParse, { fragment: true })
.use(enhanceCodeTypes, {
linkMap: {},
moduleLinkMap: {
js: {
'@base-ui/react': {
href: '/react',
defaultSlug: '#api',
exports: {
Accordion: { slug: '#accordion' },
useDialogRoot: { slug: '#use-dialog-root', title: 'useDialogRoot' },
},
},
},
css: {
'./theme.css': { href: '/theme' },
},
},
})
.use(rehypeStringify);
When the plugin encounters an import statement, it:
'@base-ui/react' part of import { Accordion } from '@base-ui/react' becomes a clickable link to the module's href.exports map and added to both the linkMap and scope stack, so subsequent usages of the imported identifiers are linked automatically.moduleLinkMap, a data-import attribute is added to the string span, making unresolved imports discoverable in the DOM.All standard JS/TS import forms and CSS @import are supported:
import { Accordion, useDialogRoot } from '@base-ui/react';
// ^^^^^^^^^ registered via exports ^^^^^^^^^^^^^^^^^^ linked to href
Each imported name is looked up in exports. If found, the identifier is registered so later usages like <Accordion /> or useDialogRoot() are linked.
import { Accordion as MyAccordion } from '@base-ui/react';
// ^^^^^^^^^^^ registered under the local alias
The local alias (MyAccordion) is what gets registered. The exports map is consulted using the original name (Accordion).
{ default as X } Importsimport { default as Base } from '@base-ui/react';
// ^^^^ registered using defaultSlug
import { default as X } is treated equivalently to import X from '...' — the local name is resolved using defaultSlug.
import React from '@base-ui/react';
// ^^^^^ registered using defaultSlug
The default import name is linked using the module's defaultSlug (or the global defaultImportSlug fallback).
import * as BaseUI from '@base-ui/react';
// ^^^^^^ registered as a module binding
BaseUI.Accordion; // resolves via dot-access against exports
Namespace imports create a module binding. Dot-access like BaseUI.Accordion is resolved against the module's exports map.
const mod = import('@base-ui/react');
// ^^^^^^^^^^^^^^^^^^ linked to href
Simple dynamic imports with a single string literal are linked. Computed dynamic imports like import('@pkg/' + name) are correctly excluded.
import '@base-ui/react';
// ^^^^^^^^^^^^^^^^^^ linked to href (no identifiers registered)
@import@import './theme.css';
/* ^^^^^^^^^^^^^^ linked to href */
@import url('./theme.css');
/* ^^^^^^^^^^^^^^ also supported */
CSS imports are resolved against the css key of moduleLinkMap.
Each entry in the module link map has the following shape:
interface ModuleLinkMapEntry {
/** The page URL for this module. */
href: string;
/** Anchor slug for default and namespace imports (_per-module override_). */
defaultSlug?: string;
/** Maps exported names to their slug and optional display title. */
exports?: Record<string, { slug: string; title?: string }>;
}
href — The base URL. All slugs are appended to this.defaultSlug — Used for import X from '...', import { default as X } from '...', and import * as X from '...' (the namespace identifier itself). Falls back to the global defaultImportSlug option.exports — Maps each named export to a slug (appended to href) and an optional title used as the type name for scope resolution.When moduleLinkMap is provided but a module specifier doesn't match any entry, the plugin adds data-import="<specifier>" to the string span:
<!-- import { x } from '@unknown-pkg' with @unknown-pkg not in moduleLinkMap -->
<span class="pl-s" data-import="@unknown-pkg">
<span class="pl-pds">'</span>@unknown-pkg<span class="pl-pds">'</span>
</span>
This makes it easy for downstream tooling to discover which import specifiers lack documentation links. The annotation is only added for statically resolvable imports — computed dynamic imports like import('@pkg/' + name) are never annotated.
In addition to per-span data-import annotations, the plugin sets two summary attributes on the <code> element itself:
data-imports — a JSON object of all resolved imports, keyed by module specifier:
{
"@base-ui/react": {
"link": "/base",
"exports": [
{ "slug": "#button-api", "title": "Button" },
{ "slug": "#switch-api", "title": "Switch" }
]
}
}
Only actually-imported exports appear in the exports array (not all available exports from the module entry). Entries are deduplicated by slug, so multiple local aliases of the same export (e.g. import Foo from 'pkg'; import { default as Bar } from 'pkg') produce a single entry — the first alias encountered wins. Default imports use the local name as title, and side-effect or dynamic imports have an empty exports array.
data-imports-missing — a JSON array of module specifiers that were not found in moduleLinkMap:
["unknown-module", "@other/pkg"]
Both attributes are omitted when empty. Together they give downstream tooling a complete picture of the code block's import dependencies without re-parsing.
Like linkMap, the moduleLinkMap is platform-scoped:
ts, tsx, js, jsx) resolve against moduleLinkMap.jscss, scss, less, sass) resolve against moduleLinkMap.css@import is only recognized in CSS-family code elements; JS import is only recognized in JS-family code elementslinkScope — identifiers are registered in both the linkMap and scope stack regardless of scoping options.linkScope is enabled, imported identifiers participate in scope resolution just like manually declared type bindings.moduleLinkMap does not modify the original linkMap object — registered identifiers are written to a runtime copy.For JS-family code blocks (ts, tsx, js, jsx), the plugin automatically parses export statements and records metadata about each export.
id AnchorsEvery export receives an id attribute for anchor linking. For single-name exports (declarations, defaults), the id is set on the export keyword span:
<!-- export function Button() {} -->
<span class="pl-k" id="Button">export</span>
<!-- export default function App() {} -->
<span class="pl-k" id="default">export</span>
For export lists (export { a, b }), each identifier span inside the braces receives its own id:
<!-- export { alpha, beta }; -->
<span class="pl-k">export</span> { <span class="pl-smi" id="alpha">alpha</span>,
<span class="pl-smi" id="beta">beta</span> };
<!-- export { foo as bar }; — id goes on the alias (exported name) -->
<span class="pl-k">export</span> { <span class="pl-smi">foo</span> <span class="pl-k">as</span>
<span class="pl-smi" id="bar">bar</span> };
This makes it easy for downstream tooling to scroll to or highlight a specific export by anchor.
The plugin recognizes the following export patterns:
// Named declarations
export function Button() {} // kind: 'function'
export const TIMEOUT = 1000; // kind: 'const'
export let count = 0; // kind: 'let'
export var legacy = true; // kind: 'var'
export type Props = { x: number }; // kind: 'type'
export interface Config {} // kind: 'interface'
export class Widget {} // kind: 'class'
export enum Status {} // kind: 'enum'
// Default exports
export default function App() {} // kind: 'function'
export default value; // kind: 'unknown'
// Named export lists
export { foo, bar }; // kind: 'unknown'
export { foo as renamed }; // kind: 'unknown' (name: 'renamed')
export { foo as default }; // kind: 'unknown' (name: 'default')
export type { Foo, Bar }; // kind: 'type'
// Re-exports
export { a, b } from './module'; // kind: 'unknown'
export * from './module'; // kind: 'unknown' (name: '*')
The plugin sets a summary attribute on the <code> element:
data-exports — a JSON array of all exports found in the code block:
[
{ "name": "Button", "kind": "function" },
{ "name": "TIMEOUT", "kind": "const", "type": "1000" },
{ "name": "label", "kind": "const", "type": "'hello'" },
{ "name": "count", "kind": "const", "type": "Counter", "typeHref": "#counter" },
{ "name": "default", "kind": "function" }
]
Each entry contains:
name — the exported name (or "default" for default exports, "*" for star re-exports). For CSS class exports, the plugin emits a single "default" export that models the generated CSS module object.kind — one of 'function', 'const', 'let', 'var', 'type', 'interface', 'class', 'enum', 'object', or 'unknown'. Arrow function exports (export const fn = () => {}) are recorded as 'function'. 'unknown' is used when the declaration type cannot be determined (e.g., default expression exports, named export lists, re-exports).type — (optional) the type annotation or inferred literal type for non-object const/let/var exports. When a type annotation is present (e.g., export const x: MyType = ...), the annotation name is used. Otherwise, simple literal values (strings, numbers, booleans) are captured. Omitted when the type cannot be determined (e.g., function call results).properties — (optional) a structured key/value map for kind: 'object' exports. CSS class exports use this to store selector literals, for example { button: '.button' }.typeHref — (optional) the resolved href for the type annotation, when found in the linkMap. Omitted for literal types or when the type is not in the link map.For CSS-family code blocks, bare class selectors are aggregated into a single default export object. A selector like .button {} is recorded as { name: 'default', kind: 'object', properties: { button: '.button' } }, while .test-one {} contributes testOne: '.test-one' to the same properties object. The bare definition receives an id matching the normalized property name. Compound selectors such as .button:hover, .button[data-state="open"], or .button span do not create additional exports; instead, when the bare definition appears earlier in the same block, the .button occurrence links back to the bare definition anchor.
Selectors wrapped in :global(...) are excluded from both exports and back-linking. This applies to all forms: simple (:global(.button)), multi-selector (:global(.a, .b)), compound (:global(.button:hover)), nested functional selectors (:global(:where(.button)), :global(:is(.a, .b))), and the shorthand space-separated syntax (:global .button).
Selectors wrapped in :local(...) are treated identically to bare selectors — :local(.button) {} produces an export just like .button {} does. In CSS Modules, all selectors are implicitly local by default, so :local(...) simply makes the default behavior explicit. Compound selectors like :local(.button):hover are not treated as bare definitions, consistent with how plain .button:hover is handled.
The attribute is omitted when no exports are found. This gives downstream tooling a complete picture of the code block's exports without re-parsing.
When used with loadPrecomputedTypes, the anchor map is automatically generated from your type exports and returned as a platform-scoped object:
// Generated automatically from your types — scoped under `js`
const linkMap = loadPrecomputedTypes(...);
// linkMap === {
// js: {
// 'Root': '#root',
// 'Accordion.Root': '#root',
// 'Accordion.Root.Props': '#root.props',
// 'AccordionRoot': '#root', // from typeNameMap
// }
// }
Each type in the processed output includes a slug property that matches the anchor:
{
type: 'component',
name: 'Accordion.Trigger',
slug: 'trigger', // Used for <details id="trigger">
data: { /* ... */ }
}
A rehype plugin that links code identifiers and their properties to corresponding type documentation anchors.
Type/export linking (existing behavior):
Transforms <span class="pl-en">Trigger</span> → <a href="#trigger">Trigger</a>
and chains like Accordion.Trigger into single anchors.
Property linking (new, opt-in via linkProps):
Inside type definitions, object literals, function calls, and JSX components,
wraps property names with prop ref elements linked to #anchor:prop-name.
| Property | Type | Description |
|---|---|---|
| linkMap | | Platform-scoped anchor maps. Each code element resolves its anchor map based
on its language class: JS-family languages use Each map maps export names (both flat and dotted) to their anchor hrefs.
Examples (within
|
| typeRefComponent | | When set, the plugin emits a custom component element instead of an |
| typePropRefComponent | | When set, the plugin emits a custom component element instead of a plain HTML element for property references within type definitions, object literals, function calls, and JSX. For definition sites (type definitions), the element receives |
| linkProps | | Opt-in property linking mode.
|
| linkParams | | Opt-in function parameter linking.
When At definition sites (type definitions), params produce positional |
| typeParamRefComponent | | When set, the plugin emits a custom component element instead of a plain HTML element for function parameter references. For definition sites, the element receives |
| linkScope | | Links later uses of identifiers whose type provenance was proven during parse.
Conservative and single-pass: only syntactically explicit bindings ( Variable references ( |
| linkValues | | Opt-in literal value tracking for For object shapes, dot-access resolution is supported:
Requires |
| linkArrays | | Opt-in array literal tracking for Array elements can reference previously tracked variables:
Requires |
| typeValueRefComponent | | When set, the plugin emits a custom component element instead of a plain HTML element
for literal value references (tracked The custom element receives |
| moduleLinkMap | | Platform-scoped module link maps. Each code element resolves its module link
map based on its language class, mirroring the Maps module specifier strings to documentation page links and export metadata. When an import statement references a module in this map, the module specifier string is linked and imported identifiers are registered for downstream linking. Example: |
| defaultImportSlug | | Global fallback anchor slug for default and namespace imports.
Used when the module entry in |
((tree: Root) => void)A unified transformer function
Options for the enhanceCodeTypes plugin.
type EnhanceCodeTypesOptions = {
/**
* Platform-scoped anchor maps. Each code element resolves its anchor map based
* on its language class: JS-family languages use `js`, CSS-family use `css`.
*
* Each map maps export names (both flat and dotted) to their anchor hrefs.
* Examples (within `js`):
* - `"AccordionTrigger"` → `"#trigger"`
* - `"Accordion.Trigger"` → `"#trigger"`
*/
linkMap: {
/** Anchors for JS-family languages (js, jsx, ts, tsx). */
js?: Record<string, string>;
/** Anchors for CSS-family languages (css, scss, less, sass). */
css?: Record<string, string>;
};
/**
* When set, the plugin emits a custom component element instead of an `<a>` tag
* for type/export name references.
* The custom element receives `href` and `name` (the matched identifier) as properties.
* This is used to render interactive type popovers via a `TypeRef` component.
*/
typeRefComponent?: string;
/**
* When set, the plugin emits a custom component element instead of a plain HTML element
* for property references within type definitions, object literals, function calls, and JSX.
*
* For definition sites (type definitions), the element receives `id` (anchor target).
* For reference sites (annotations, function calls, JSX), the element receives `href` (link).
* Both also receive `name` (the owner identifier) and `prop` (kebab-case property path).
*/
typePropRefComponent?: string;
/**
* Opt-in property linking mode.
* - `'shallow'`: Link only top-level properties of known owners.
* - `'deep'`: Link nested properties with dotted paths (e.g., `address.street-name`).
* - `undefined` (default): No property linking (backward compatible).
*/
linkProps?: 'shallow' | 'deep';
/**
* Opt-in function parameter linking.
* When `true`, links function parameter names (`pl-v` spans inside parentheses)
* to documentation anchors.
*
* At definition sites (type definitions), params produce positional `id` anchors
* (e.g., `id="callback[0]"`). Named anchors can be provided via `linkMap`
* (e.g., `linkMap["Callback[0]"]`) to override the positional id.
* At reference sites (annotations, function calls), params produce positional
* `href` anchors resolved through `linkMap["Owner[N]"]` named anchors.
*/
linkParams?: boolean;
/**
* When set, the plugin emits a custom component element instead of a plain HTML element
* for function parameter references.
*
* For definition sites, the element receives `id` (anchor target).
* For reference sites, the element receives `href` (link).
* Both also receive `name` (the owner identifier) and `param` (parameter name).
*/
typeParamRefComponent?: string;
/**
* Links later uses of identifiers whose type provenance was proven during parse.
* Conservative and single-pass: only syntactically explicit bindings (`param: Type`,
* `const x: Type`, `{ a }: Type`) are tracked. Uncertain cases stay unlinked.
*
* Variable references (`pl-smi` spans) are resolved against a scope stack and linked
* to the appropriate type, property, or parameter anchor depending on how the variable
* was declared. `let`/`const` are block-scoped; `var` and function params are
* function-scoped (no hoisting — linked only after their declaration).
*/
linkScope?: boolean;
/**
* Opt-in literal value tracking for `const` declarations.
* When `true`, tracks the literal value of `const x = 'hello'` or
* `const obj = { key: 'val' }` and annotates later `pl-smi` references
* with the tracked value.
*
* For object shapes, dot-access resolution is supported:
* `const obj = { a: 'one' }; use(obj.a)` annotates `obj.a` with `'one'`.
*
* Requires `linkScope` to be enabled.
*/
linkValues?: boolean;
/**
* Opt-in array literal tracking for `const` declarations.
* When `true`, tracks the elements of `const arr = ['a', 'b']` and annotates
* later `pl-smi` references with the tracked array value.
*
* Array elements can reference previously tracked variables:
* `const a = 'x'; const arr = [a, 'y']` annotates `arr` as `['x', 'y']`.
*
* Requires `linkScope` to be enabled.
*/
linkArrays?: boolean;
/**
* When set, the plugin emits a custom component element instead of a plain HTML element
* for literal value references (tracked `const` values).
*
* The custom element receives `value` (the literal value string) and `name`
* (the variable or expression name) as properties.
*/
typeValueRefComponent?: string;
/**
* Platform-scoped module link maps. Each code element resolves its module link
* map based on its language class, mirroring the `linkMap` scoping.
*
* Maps module specifier strings to documentation page links and export metadata.
* When an import statement references a module in this map, the module specifier
* string is linked and imported identifiers are registered for downstream linking.
*
* Example:
* ```ts
* moduleLinkMap: {
* js: {
* '@mui/internal-docs-infra/pipeline/enhanceCodeTypes': {
* href: '/docs-infra/pipeline/enhanceCodeTypes',
* exports: {
* enhanceCodeTypes: { slug: '#enhance-code-types' },
* },
* },
* },
* }
* ```
*/
moduleLinkMap?: {
js?: Record<string, ModuleLinkMapEntry>;
css?: Record<string, ModuleLinkMapEntry>;
};
/**
* Global fallback anchor slug for default and namespace imports.
* Used when the module entry in `moduleLinkMap` does not specify a `defaultSlug`.
* Example: `'#api-reference'`
*/
defaultImportSlug?: string;
}