@mui/internal-docs-infra
Version:
MUI Infra - internal documentation creation tools.
1,050 lines (995 loc) • 36.9 kB
JavaScript
import * as React from 'react';
import { unified } from 'unified';
import { compressHast, decompressHast, hastToJsx as hastToJsxBase } from "../pipeline/hastUtils/index.mjs";
import { hastToFallback, fallbackToText } from "../pipeline/hastUtils/fallbackFormat.mjs";
import { stripHighlightingSpans } from "../pipeline/hastUtils/stripHighlightingSpans.mjs";
import { TypeCode } from "./TypeCode.mjs";
// Broad index signature to accept MDXComponents from `mdx/types`,
// which uses `{ [key: string]: NestedMDXComponents | Component<any> }`.
/**
* An enhanced property with HAST fields converted to React nodes.
* The components rendering each field are configured in `createTypes()`.
*/
/**
* An enhanced class property with HAST fields converted to React nodes.
* The components rendering each field are configured in `createTypes()`.
*/
/**
* An enhanced enum member (data attribute or CSS variable) with HAST fields converted to React nodes.
* The components rendering each field are configured in `createTypes()`.
*/
/**
* An enhanced function/hook parameter with HAST fields converted to React nodes.
* The components rendering each field are configured in `createTypes()`.
*/
/**
* Enhanced component type metadata with React nodes instead of HAST.
* The components rendering each field are configured in `createTypes()`.
*/
/** Discriminated union for hook return values. */
/**
* Enhanced hook type metadata with React nodes instead of HAST.
* The components rendering each field are configured in `createTypes()`.
*/
/** Discriminated union for function return values. */
/**
* Enhanced function type metadata with React nodes instead of HAST.
* The components rendering each field are configured in `createTypes()`.
*/
/**
* An enhanced class method with HAST fields converted to React nodes.
* The components rendering each field are configured in `createTypes()`.
*/
/**
* Enhanced class type metadata with React nodes instead of HAST.
* The components rendering each field are configured in `createTypes()`.
*/
/** An enhanced raw type enum member. */
/**
* Enhanced raw/alias type metadata with React nodes instead of HAST.
* The components rendering each field are configured in `createTypes()`.
*/
/**
* Discriminated union of all enhanced type kinds.
* The components rendering each field are configured in `createTypes()`.
*/
/**
* Enhanced export data with JSX nodes instead of HAST.
*/
/**
* Type guard to check if a value is a HastRoot node or a serialized HAST wrapper.
* Handles live `{ type: 'root', children: [...] }`, serialized `{ hastJson: string }`,
* and compressed `{ hastCompressed: string }`.
*/
function isHastRoot(value) {
if (typeof value !== 'object' || value === null) {
return false;
}
// Serialized HAST from loadPrecomputedTypes
if ('hastJson' in value || 'hastCompressed' in value) {
return true;
}
// Live HAST Root
return 'type' in value && value.type === 'root' && 'children' in value && Array.isArray(value.children);
}
/**
* Pre-resolved component maps for different field rendering contexts.
* Computed once from TypesJsxOptions to avoid re-creating maps per field.
*/
function resolveFieldMaps(options) {
const base = options.components ?? {};
const typeMap = {
...base,
pre: options.TypePre
};
const detailedTypeMap = options.DetailedTypePre ? {
...base,
pre: options.DetailedTypePre
} : typeMap;
return {
type: typeMap,
shortType: options.ShortTypeCode ? {
...typeMap,
code: options.ShortTypeCode
} : typeMap,
default: options.DefaultCode ? {
...typeMap,
code: options.DefaultCode
} : typeMap,
detailedType: detailedTypeMap,
rawType: options.RawTypePre ? {
...base,
pre: options.RawTypePre
} : detailedTypeMap,
highlightAt: options.highlightAt ?? 'visible'
};
}
/**
* Cache unified processors by enhancers reference. During SSG, each page renders
* hundreds of HAST fields through hastToJsx, all sharing the same enhancers array.
* Without caching, each call creates a new unified() processor (calling plugin
* attachers and allocating closures), causing significant GC pressure across
* 6 parallel SSG workers. With caching, we create at most 2 processors per page
* (one for enhancers, one for enhancersInline) instead of hundreds.
*
* WeakMap ensures processors are garbage collected when the enhancers array
* (created per abstractCreateTypes call) is no longer referenced.
*/
const processorCache = new WeakMap();
function getOrCreateProcessor(enhancers) {
let processor = processorCache.get(enhancers);
if (!processor) {
processor = unified().use(enhancers);
processorCache.set(enhancers, processor);
}
return processor;
}
/**
* Apply enhancers to HAST and convert to JSX.
* If no enhancers are provided or the array is empty, skips enhancement.
*
* Accepts either a live HAST tree, a serialized `{ hastJson: string }` wrapper,
* or a dictionary-compressed `{ hastCompressed: string }` wrapper produced by the
* loadPrecomputedTypes loader.
*/
/**
* Deserialize a HAST input that may be a live tree, JSON string, or dictionary-compressed base64.
* Returns the parsed tree and whether it's a fresh copy (no clone needed).
*
* This function decompresses using the **static dictionary only** (no textContent).
* It must only receive payloads that were compressed without a text dictionary.
* The deferred rendering path (`hastToJsxDeferred`) re-compresses with a text
* dictionary derived from the fallback HAST and passes the fallback as a prop
* so the client can reconstruct the same dictionary for decompression.
*/
function deserializeHast(input) {
if (typeof input === 'object' && input !== null) {
if ('hastCompressed' in input) {
return {
hast: JSON.parse(decompressHast(input.hastCompressed)),
freshCopy: true
};
}
if ('hastJson' in input) {
return {
hast: JSON.parse(input.hastJson),
freshCopy: true
};
}
}
return {
hast: input,
freshCopy: false
};
}
function hastToJsx(hastOrJson, components, enhancers) {
const {
hast: parsedHast,
freshCopy
} = deserializeHast(hastOrJson);
const hast = parsedHast;
if (!enhancers || enhancers.length === 0) {
return hastToJsxBase(hast, components);
}
// Deep clone only when the HAST tree is shared (not freshly parsed from JSON)
const input = freshCopy ? hast : structuredClone(hast);
// Reuse the unified processor for the same enhancers reference
const processor = getOrCreateProcessor(enhancers);
const enhanced = processor.runSync(input);
return hastToJsxBase(enhanced, components);
}
/**
* Find the first `<code>` element in a HAST tree (typically root > pre > code).
*/
function findCodeElement(node) {
if (node.type === 'element') {
const el = node;
if (el.tagName === 'code') {
return el;
}
for (const child of el.children) {
const found = findCodeElement(child);
if (found) {
return found;
}
}
}
if (node.type === 'root') {
for (const child of node.children) {
const found = findCodeElement(child);
if (found) {
return found;
}
}
}
return null;
}
/**
* Find the first `<pre>` element in a HAST tree (typically root > pre).
*/
function findPreElement(node) {
if (node.type === 'element') {
const el = node;
if (el.tagName === 'pre') {
return el;
}
}
if (node.type === 'root') {
for (const child of node.children) {
if (child.type === 'element' && child.tagName === 'pre') {
return child;
}
}
}
return null;
}
/**
* Convert HAST element properties to React-compatible props.
* Handles className array → string conversion.
*/
function hastPropsToReactProps(properties = {}) {
const result = {};
for (const [key, value] of Object.entries(properties)) {
if (key === 'className' && Array.isArray(value)) {
result[key] = value.join(' ');
} else {
result[key] = value;
}
}
return result;
}
/**
* Deferred HAST-to-JSX conversion for expensive fields (detailedType, formattedCode).
* Server-renders a links-only fallback inside an explicit pre > code wrapper
* and injects a TypeCode that replaces the inner
* code content with the fully-highlighted version on the client.
*
* Passes the original serialized HAST directly to the client component
* to avoid unnecessary re-serialization.
*/
function hastToJsxDeferred(hastOrJson, components, enhancers, highlightAt) {
// Deserialize and run enhancers to produce the enhanced HAST
const {
hast: parsedHast,
freshCopy
} = deserializeHast(hastOrJson);
let hast = parsedHast;
// Run enhancers (adds TypeRef links, inline code, etc.)
if (enhancers && enhancers.length > 0) {
const input = freshCopy ? hast : structuredClone(hast);
const processor = getOrCreateProcessor(enhancers);
hast = processor.runSync(input);
} else if (!freshCopy) {
hast = structuredClone(hast);
}
// Find the <code> element and extract its children
const codeElement = findCodeElement(hast);
if (!codeElement) {
// No code element — fall back to eager rendering
return hastToJsxBase(hast, components);
}
// Build links-only fallback from enhanced inner children.
// This is passed to the client component as a prop in compact format,
// serving two purposes:
// 1. Converted to HAST and rendered as the initial display until the full highlight is ready.
// 2. Its text is used as DEFLATE dictionary for decompression.
const linksOnlyRoot = stripHighlightingSpans({
type: 'root',
children: [...codeElement.children]
});
const fallback = hastToFallback(linksOnlyRoot);
// Derive dictionary text from the fallback, then compress the full
// highlighted HAST with that dictionary. On the client, TypeCode
// calls fallbackToText(fallback) to reconstruct the same dictionary.
// Serialize the enhanced HAST (post-enhancer) for the client. DEFLATE-compressing it
// (with the fallback text as the dictionary) only shrinks the server→client wire, so
// do it ONLY on the server. On the client there is no wire — the JSON is consumed
// in-process by TypeCode — so skip the synchronous, main-thread compress and hand it
// the JSON directly; TypeCode reads `hastJson` without decompressing (it only rebuilds
// the DEFLATE dictionary from `fallback` when it actually has a `hastCompressed`
// payload). `typeof window` is the isomorphic server check.
const enhancedJson = JSON.stringify(hast);
const hastPayload = typeof window === 'undefined' ? {
hastCompressed: compressHast(enhancedJson, fallbackToText(fallback))
} : {
hastJson: enhancedJson
};
// Find the <pre> element for wrapper props
const preElement = findPreElement(hast);
const PreComponent = components?.pre ?? 'pre';
// Build pre > TypeCode wrapper explicitly.
// fallback crosses the boundary as a serialized prop — no separate
// text dictionary string is needed.
return /*#__PURE__*/React.createElement(PreComponent, hastPropsToReactProps(preElement?.properties), /*#__PURE__*/React.createElement(TypeCode, {
...hastPayload,
highlightAt,
fallback,
codeProps: hastPropsToReactProps(codeElement.properties)
}));
}
/**
* Convert a type HAST field, using deferred rendering when highlightAt is set.
*/
function convertType(hast, fieldMaps, enhancers) {
if (fieldMaps.highlightAt !== 'init') {
return hastToJsxDeferred(hast, fieldMaps.type, enhancers, fieldMaps.highlightAt);
}
return hastToJsx(hast, fieldMaps.type, enhancers);
}
/**
* Convert a detailedType HAST field, using deferred rendering when highlightAt is set.
*/
function convertDetailedType(hast, fieldMaps, enhancers) {
if (fieldMaps.highlightAt !== 'init') {
return hastToJsxDeferred(hast, fieldMaps.detailedType, enhancers, fieldMaps.highlightAt);
}
return hastToJsx(hast, fieldMaps.detailedType, enhancers);
}
/**
* Convert a raw type formattedCode HAST field, using deferred rendering when highlightAt is set.
*/
function convertRawType(hast, fieldMaps, enhancers) {
if (fieldMaps.highlightAt !== 'init') {
return hastToJsxDeferred(hast, fieldMaps.rawType, enhancers, fieldMaps.highlightAt);
}
return hastToJsx(hast, fieldMaps.rawType, enhancers);
}
function enhanceComponentType(component, components, fieldMaps, enhancers, enhancersInline) {
return {
type: 'component',
name: component.name,
data: {
...component,
description: component.description && hastToJsx(component.description, components, enhancers),
props: Object.fromEntries(Object.entries(component.props).map(([key, prop]) => {
// Destructure to exclude HAST fields that need to be converted
const {
type,
shortType,
default: defaultValue,
description,
example,
detailedType,
see,
...rest
} = prop;
const enhanced = {
...rest,
type: convertType(prop.type, fieldMaps, enhancers)
};
if (prop.description) {
enhanced.description = hastToJsx(prop.description, components, enhancers);
}
if (prop.example) {
enhanced.example = hastToJsx(prop.example, components, enhancers);
}
if (prop.see) {
enhanced.see = hastToJsx(prop.see, components, enhancers);
}
if (prop.shortType) {
enhanced.shortType = hastToJsx(prop.shortType, fieldMaps.shortType, enhancersInline);
} else {
// Fallback to type without full enhancers
enhanced.shortType = hastToJsx(prop.type, fieldMaps.shortType, enhancersInline);
}
if (prop.default) {
enhanced.default = hastToJsx(prop.default, fieldMaps.default, enhancersInline);
}
if (prop.detailedType) {
enhanced.detailedType = convertDetailedType(prop.detailedType, fieldMaps, enhancers);
}
return [key, enhanced];
})),
dataAttributes: Object.fromEntries(Object.entries(component.dataAttributes).map(([key, attr]) => {
let enhancedType;
if (attr.type) {
enhancedType = typeof attr.type === 'string' ? attr.type : hastToJsx(attr.type, fieldMaps.type, enhancers);
}
return [key, {
type: enhancedType,
description: attr.description && hastToJsx(attr.description, fieldMaps.type, enhancers)
}];
})),
cssVariables: Object.fromEntries(Object.entries(component.cssVariables).map(([key, cssVar]) => {
let enhancedType;
if (cssVar.type) {
enhancedType = typeof cssVar.type === 'string' ? cssVar.type : hastToJsx(cssVar.type, fieldMaps.type, enhancers);
}
return [key, {
type: enhancedType,
description: cssVar.description && hastToJsx(cssVar.description, fieldMaps.type, enhancers)
}];
}))
}
};
}
/**
* Processes a record of HighlightedProperty values into EnhancedProperty values.
* Used for return value object properties and expanded options properties.
*/
function enhancePropertyRecord(properties, components, fieldMaps, enhancers, enhancersInline) {
const entries = Object.entries(properties).map(([key, prop]) => {
const enhancedType = prop.type && convertType(prop.type, fieldMaps, enhancers);
const enhancedShortType = prop.shortType && hastToJsx(prop.shortType, fieldMaps.shortType, enhancersInline);
const enhancedDefault = prop.default && hastToJsx(prop.default, fieldMaps.default, enhancersInline);
const enhancedDescription = prop.description && hastToJsx(prop.description, components, enhancers);
const enhancedExample = prop.example && hastToJsx(prop.example, components, enhancers);
const enhancedDetailedType = prop.detailedType && convertDetailedType(prop.detailedType, fieldMaps, enhancers);
const enhancedSee = prop.see && hastToJsx(prop.see, components, enhancers);
const {
type,
shortType,
default: defaultValue,
description,
example,
detailedType,
see,
...rest
} = prop;
const enhanced = {
...rest,
type: enhancedType
};
if (enhancedShortType) {
enhanced.shortType = enhancedShortType;
} else {
enhanced.shortType = hastToJsx(prop.type, fieldMaps.shortType, enhancersInline);
}
if (enhancedDefault) {
enhanced.default = enhancedDefault;
}
if (enhancedDescription) {
enhanced.description = enhancedDescription;
}
if (enhancedExample) {
enhanced.example = enhancedExample;
}
if (enhancedDetailedType) {
enhanced.detailedType = enhancedDetailedType;
}
if (enhancedSee) {
enhanced.see = enhancedSee;
}
return [key, enhanced];
});
return Object.fromEntries(entries);
}
function enhanceHookType(hook, components, fieldMaps, enhancers, enhancersInline) {
// Enhance parameters (array of named parameters)
let enhancedParameters;
if (hook.parameters) {
enhancedParameters = hook.parameters.map(param => {
const {
name,
type,
default: defaultValue,
description,
example,
detailedType,
shortType,
see,
...rest
} = param;
const enhanced = {
...rest,
name,
type: hastToJsx(param.type, fieldMaps.type, enhancers)
};
if (param.description) {
enhanced.description = hastToJsx(param.description, components, enhancers);
}
if (param.example) {
enhanced.example = hastToJsx(param.example, components, enhancers);
}
if (param.see) {
enhanced.see = hastToJsx(param.see, components, enhancers);
}
if (param.default) {
enhanced.default = hastToJsx(param.default, fieldMaps.default, enhancersInline);
}
if (detailedType) {
enhanced.detailedType = convertDetailedType(detailedType, fieldMaps, enhancers);
}
if (shortType) {
enhanced.shortType = hastToJsx(shortType, fieldMaps.shortType, enhancersInline);
} else {
// Fallback to type without full enhancers
enhanced.shortType = hastToJsx(param.type, fieldMaps.shortType, enhancersInline);
}
return enhanced;
});
}
// Process return value
let enhancedReturnValue;
// Check if it's a simple return value (HastRoot) vs object of properties
if (isHastRoot(hook.returnValue)) {
// It's a HastRoot - convert to simple discriminated union
enhancedReturnValue = {
kind: 'simple',
type: convertType(hook.returnValue, fieldMaps, enhancers)
};
if (hook.returnValueDetailedType) {
enhancedReturnValue.detailedType = convertDetailedType(hook.returnValueDetailedType, fieldMaps, enhancers);
}
} else {
const entries = Object.entries(hook.returnValue).map(([key, prop]) => {
// Type is always HastRoot for return value properties
const enhancedType = prop.type && convertType(prop.type, fieldMaps, enhancers);
// ShortType, default, description, example, and detailedType can be HastRoot or undefined
const enhancedShortType = prop.shortType && hastToJsx(prop.shortType, fieldMaps.shortType, enhancersInline);
const enhancedDefault = prop.default && hastToJsx(prop.default, fieldMaps.default, enhancersInline);
const enhancedDescription = prop.description && hastToJsx(prop.description, components, enhancers);
const enhancedExample = prop.example && hastToJsx(prop.example, components, enhancers);
const enhancedDetailedType = prop.detailedType && convertDetailedType(prop.detailedType, fieldMaps, enhancers);
const enhancedSee = prop.see && hastToJsx(prop.see, components, enhancers);
// Destructure to exclude HAST fields that need to be converted
const {
type,
shortType,
default: defaultValue,
description,
example,
detailedType,
see,
...rest
} = prop;
const enhanced = {
...rest,
type: enhancedType
};
if (enhancedShortType) {
enhanced.shortType = enhancedShortType;
} else {
// Fallback to type without full enhancers
enhanced.shortType = hastToJsx(prop.type, fieldMaps.shortType, enhancersInline);
}
if (enhancedDefault) {
enhanced.default = enhancedDefault;
}
if (enhancedDescription) {
enhanced.description = enhancedDescription;
}
if (enhancedExample) {
enhanced.example = enhancedExample;
}
if (enhancedDetailedType) {
enhanced.detailedType = enhancedDetailedType;
}
if (enhancedSee) {
enhanced.see = enhancedSee;
}
return [key, enhanced];
});
enhancedReturnValue = {
kind: 'object',
...(hook.returnValueTypeName ? {
typeName: hook.returnValueTypeName
} : {}),
properties: Object.fromEntries(entries)
};
}
// Process expandedProperties if present (expanded single object parameter)
let enhancedExpandedProperties;
if (hook.expandedProperties) {
enhancedExpandedProperties = enhancePropertyRecord(hook.expandedProperties, components, fieldMaps, enhancers, enhancersInline);
}
// Destructure fields that are replaced in the enhanced version
const {
parameters,
expandedProperties,
returnValue,
description,
returnValueDescription,
returnValueDetailedType,
...restHook
} = hook;
const hookData = {
...restHook,
description: hook.description && hastToJsx(hook.description, components, enhancers),
...(enhancedParameters && {
parameters: enhancedParameters
}),
...(enhancedExpandedProperties && {
expandedProperties: enhancedExpandedProperties
}),
returnValue: enhancedReturnValue,
returnValueDescription: hook.returnValueDescription && hastToJsx(hook.returnValueDescription, components, enhancers)
};
return {
type: 'hook',
name: hook.name,
data: hookData
};
}
function enhanceFunctionType(func, components, fieldMaps, enhancers, enhancersInline) {
// Enhance parameters (array of named parameters)
let enhancedParameters;
if (func.parameters) {
enhancedParameters = func.parameters.map(param => {
const {
name,
type,
default: defaultValue,
description,
example,
detailedType,
shortType,
see,
...rest
} = param;
const enhanced = {
...rest,
name,
type: hastToJsx(param.type, fieldMaps.type, enhancers)
};
if (param.description) {
enhanced.description = hastToJsx(param.description, components, enhancers);
}
if (param.example) {
enhanced.example = hastToJsx(param.example, components, enhancers);
}
if (param.see) {
enhanced.see = hastToJsx(param.see, components, enhancers);
}
if (param.default) {
enhanced.default = hastToJsx(param.default, fieldMaps.default, enhancersInline);
}
if (param.detailedType) {
enhanced.detailedType = convertDetailedType(param.detailedType, fieldMaps, enhancers);
}
if (shortType) {
enhanced.shortType = hastToJsx(shortType, fieldMaps.shortType, enhancersInline);
} else {
// Fallback to type without full enhancers
enhanced.shortType = hastToJsx(param.type, fieldMaps.shortType, enhancersInline);
}
return enhanced;
});
}
// Process return value - either simple HastRoot or object with properties
let enhancedReturnValue;
// Check if it's a simple return value (HastRoot) vs object of properties
if (isHastRoot(func.returnValue)) {
// It's a HastRoot - convert to simple discriminated union
enhancedReturnValue = {
kind: 'simple',
type: convertType(func.returnValue, fieldMaps, enhancers),
description: func.returnValueDescription && hastToJsx(func.returnValueDescription, components, enhancers)
};
if (func.returnValueDetailedType) {
enhancedReturnValue.detailedType = convertDetailedType(func.returnValueDetailedType, fieldMaps, enhancers);
}
} else {
const entries = Object.entries(func.returnValue).map(([key, prop]) => {
// Type is always HastRoot for return value properties
const enhancedType = prop.type && convertType(prop.type, fieldMaps, enhancers);
// ShortType, default, description, example, and detailedType can be HastRoot or undefined
const enhancedShortType = prop.shortType && hastToJsx(prop.shortType, fieldMaps.shortType, enhancersInline);
const enhancedDefault = prop.default && hastToJsx(prop.default, fieldMaps.default, enhancersInline);
const enhancedDescription = prop.description && hastToJsx(prop.description, components, enhancers);
const enhancedExample = prop.example && hastToJsx(prop.example, components, enhancers);
const enhancedDetailedType = prop.detailedType && convertDetailedType(prop.detailedType, fieldMaps, enhancers);
const enhancedSee = prop.see && hastToJsx(prop.see, components, enhancers);
// Destructure to exclude HAST fields that need to be converted
const {
type,
shortType,
default: defaultValue,
description,
example,
detailedType,
see,
...rest
} = prop;
const enhanced = {
...rest,
type: enhancedType
};
if (enhancedShortType) {
enhanced.shortType = enhancedShortType;
} else {
// Fallback to type without full enhancers
enhanced.shortType = hastToJsx(prop.type, fieldMaps.shortType, enhancersInline);
}
if (enhancedDefault) {
enhanced.default = enhancedDefault;
}
if (enhancedDescription) {
enhanced.description = enhancedDescription;
}
if (enhancedExample) {
enhanced.example = enhancedExample;
}
if (enhancedDetailedType) {
enhanced.detailedType = enhancedDetailedType;
}
if (enhancedSee) {
enhanced.see = enhancedSee;
}
return [key, enhanced];
});
enhancedReturnValue = {
kind: 'object',
...(func.returnValueTypeName ? {
typeName: func.returnValueTypeName
} : {}),
properties: Object.fromEntries(entries)
};
}
// Process expandedProperties if present (expanded single object parameter)
let enhancedExpandedProperties;
if (func.expandedProperties) {
enhancedExpandedProperties = enhancePropertyRecord(func.expandedProperties, components, fieldMaps, enhancers, enhancersInline);
}
// Destructure fields that are replaced in the enhanced version
const {
parameters,
expandedProperties,
returnValue,
description,
returnValueDescription,
returnValueDetailedType,
...restFunc
} = func;
return {
type: 'function',
name: func.name,
data: {
...restFunc,
description: func.description && hastToJsx(func.description, components, enhancers),
...(enhancedParameters && {
parameters: enhancedParameters
}),
...(enhancedExpandedProperties && {
expandedProperties: enhancedExpandedProperties
}),
returnValue: enhancedReturnValue
}
};
}
function enhanceClassType(classData, components, fieldMaps, enhancers, enhancersInline) {
// Process constructor parameters
const enhancedConstructorParameters = classData.constructorParameters.map(param => {
const {
type,
default: defaultValue,
description,
example,
detailedType,
shortType,
see,
...rest
} = param;
const enhanced = {
...rest,
type: hastToJsx(param.type, fieldMaps.type, enhancers)
};
if (param.description) {
enhanced.description = hastToJsx(param.description, components, enhancers);
}
if (param.example) {
enhanced.example = hastToJsx(param.example, components, enhancers);
}
if (param.see) {
enhanced.see = hastToJsx(param.see, components, enhancers);
}
if (param.default) {
enhanced.default = hastToJsx(param.default, fieldMaps.default, enhancersInline);
}
if (param.detailedType) {
enhanced.detailedType = convertDetailedType(param.detailedType, fieldMaps, enhancers);
}
if (shortType) {
enhanced.shortType = hastToJsx(shortType, fieldMaps.shortType, enhancersInline);
} else {
// Fallback to type without full enhancers
enhanced.shortType = hastToJsx(param.type, fieldMaps.shortType, enhancersInline);
}
return enhanced;
});
// Process methods
const methodEntries = Object.entries(classData.methods).map(([methodName, method]) => {
// Process method parameters
const enhancedMethodParams = method.parameters.map(param => {
const {
type,
default: defaultValue,
description,
example,
detailedType,
shortType,
see,
...rest
} = param;
const enhanced = {
...rest,
type: hastToJsx(param.type, fieldMaps.type, enhancers)
};
if (param.description) {
enhanced.description = hastToJsx(param.description, components, enhancers);
}
if (param.example) {
enhanced.example = hastToJsx(param.example, components, enhancers);
}
if (param.see) {
enhanced.see = hastToJsx(param.see, components, enhancers);
}
if (param.default) {
enhanced.default = hastToJsx(param.default, fieldMaps.default, enhancersInline);
}
if (param.detailedType) {
enhanced.detailedType = convertDetailedType(param.detailedType, fieldMaps, enhancers);
}
if (shortType) {
enhanced.shortType = hastToJsx(shortType, fieldMaps.shortType, enhancersInline);
}
return enhanced;
});
const enhancedMethod = {
...method,
description: method.description && hastToJsx(method.description, components, enhancers),
parameters: enhancedMethodParams,
returnValue: hastToJsx(method.returnValue, fieldMaps.type, enhancers),
returnValueDescription: method.returnValueDescription && hastToJsx(method.returnValueDescription, components, enhancers)
};
return [methodName, enhancedMethod];
});
const enhancedMethods = Object.fromEntries(methodEntries);
// Process properties
const propertyEntries = Object.entries(classData.properties).map(([propName, prop]) => {
const {
type,
default: defaultValue,
description,
shortType,
detailedType,
example,
see,
...rest
} = prop;
const enhanced = {
...rest,
type: convertType(prop.type, fieldMaps, enhancers)
};
if (prop.shortType) {
enhanced.shortType = hastToJsx(prop.shortType, fieldMaps.shortType, enhancersInline);
} else {
// Fallback to type without full enhancers
enhanced.shortType = hastToJsx(prop.type, fieldMaps.shortType, enhancersInline);
}
if (prop.detailedType) {
enhanced.detailedType = convertDetailedType(prop.detailedType, fieldMaps, enhancers);
}
if (prop.description) {
enhanced.description = hastToJsx(prop.description, components, enhancers);
}
if (prop.example) {
enhanced.example = hastToJsx(prop.example, components, enhancers);
}
if (prop.see) {
enhanced.see = hastToJsx(prop.see, components, enhancers);
}
if (prop.default) {
enhanced.default = hastToJsx(prop.default, fieldMaps.default, enhancersInline);
}
return [propName, enhanced];
});
const enhancedProperties = Object.fromEntries(propertyEntries);
return {
type: 'class',
name: classData.name,
data: {
...classData,
description: classData.description && hastToJsx(classData.description, components, enhancers),
constructorParameters: enhancedConstructorParameters,
properties: enhancedProperties,
methods: enhancedMethods
}
};
}
function enhanceRawType(raw, components, fieldMaps, enhancers, enhancersInline) {
// Process enum members if present
const enhancedEnumMembers = raw.enumMembers?.map(member => ({
...member,
description: member.description && hastToJsx(member.description, components, enhancers)
}));
return {
type: 'raw',
name: raw.name,
data: {
...raw,
description: raw.description && hastToJsx(raw.description, components, enhancers),
formattedCode: convertRawType(raw.formattedCode, fieldMaps, enhancers),
enumMembers: enhancedEnumMembers,
properties: raw.properties && enhancePropertyRecord(raw.properties, components, fieldMaps, enhancers, enhancersInline)
}
};
}
/**
* Helper to convert a single HighlightedTypesMeta to EnhancedTypesMeta.
*/
function enhanceTypeMeta(typeMeta, components, fieldMaps, enhancers, enhancersInline) {
let result;
if (typeMeta.type === 'component') {
result = enhanceComponentType(typeMeta.data, components, fieldMaps, enhancers, enhancersInline);
} else if (typeMeta.type === 'hook') {
result = enhanceHookType(typeMeta.data, components, fieldMaps, enhancers, enhancersInline);
} else if (typeMeta.type === 'function') {
result = enhanceFunctionType(typeMeta.data, components, fieldMaps, enhancers, enhancersInline);
} else if (typeMeta.type === 'class') {
result = enhanceClassType(typeMeta.data, components, fieldMaps, enhancers, enhancersInline);
} else if (typeMeta.type === 'raw') {
result = enhanceRawType(typeMeta.data, components, fieldMaps, enhancers, enhancersInline);
} else {
// This should never happen, but TypeScript needs exhaustive checking
return typeMeta;
}
// Add slug if present on the source type
if (typeMeta.slug) {
result.slug = typeMeta.slug;
}
// Propagate aliases so types can be looked up by alternative names
if (typeMeta.aliases) {
result.aliases = typeMeta.aliases;
}
return result;
}
/**
* Process a single export's type data to JSX.
* More efficient when you only need one export.
* @param exportData The export's type and namespaced additional types (undefined when only type exports exist)
* @param globalAdditionalTypes Top-level non-namespaced types (only included for single component mode)
* @param options JSX component options
* @param includeGlobalAdditionalTypes Whether to include global additional types (default: true for createTypes, false for createMultipleTypes)
*/
export function typeToJsx(exportData, globalAdditionalTypes, options, includeGlobalAdditionalTypes = true) {
const components = options.components;
const fieldMaps = resolveFieldMaps(options);
const enhancers = options.enhancers;
const enhancersInline = options.enhancersInline;
// Handle case where there's no main export (only type exports like loader-utils)
if (!exportData) {
// Only include global additional types if requested
if (includeGlobalAdditionalTypes) {
const enhancedGlobalAdditionalTypes = (globalAdditionalTypes ?? []).map(t => enhanceTypeMeta(t, components, fieldMaps, enhancers, enhancersInline));
return {
type: undefined,
additionalTypes: enhancedGlobalAdditionalTypes
};
}
return {
type: undefined,
additionalTypes: []
};
}
const enhancedExport = {
type: enhanceTypeMeta(exportData.type, components, fieldMaps, enhancers, enhancersInline),
additionalTypes: exportData.additionalTypes.map(t => enhanceTypeMeta(t, components, fieldMaps, enhancers, enhancersInline))
};
// Only include global additional types for single component mode (createTypes)
if (includeGlobalAdditionalTypes) {
const enhancedGlobalAdditionalTypes = (globalAdditionalTypes ?? []).map(t => enhanceTypeMeta(t, components, fieldMaps, enhancers, enhancersInline));
return {
type: enhancedExport.type,
additionalTypes: [...enhancedExport.additionalTypes, ...enhancedGlobalAdditionalTypes]
};
}
return {
type: enhancedExport.type,
additionalTypes: enhancedExport.additionalTypes
};
}
/**
* Process only additional types to JSX.
* Used for the AdditionalTypes component that only renders top-level non-namespaced types.
*/
export function additionalTypesToJsx(additionalTypes, options) {
const components = options.components;
const fieldMaps = resolveFieldMaps(options);
const enhancers = options.enhancers;
const enhancersInline = options.enhancersInline;
if (!additionalTypes || additionalTypes.length === 0) {
return [];
}
return additionalTypes.map(t => enhanceTypeMeta(t, components, fieldMaps, enhancers, enhancersInline));
}