UNPKG

@mui/internal-docs-infra

Version:

MUI Infra - internal documentation creation tools.

1,050 lines (995 loc) 36.9 kB
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)); }