UNPKG

@mui/internal-docs-infra

Version:

MUI Infra - internal documentation creation tools.

838 lines (780 loc) 36 kB
/** * Highlighted code types processing for converting plain text types to syntax-highlighted HAST. * * This module runs after highlightTypes() in the loadServerTypes pipeline and: * 1. Converts typeText strings to syntax-highlighted HAST (type field) * 2. Derives shortType HAST from the highlighted type structure * 3. Generates detailedType HAST with expanded type references * 4. Converts defaultText to syntax-highlighted HAST (default field) */ import { unified } from 'unified'; import { transformHtmlCodeBlock } from "../transformHtmlCodeBlock/transformHtmlCodeBlock.mjs"; import transformHtmlCodeInline from "../transformHtmlCodeInline/index.mjs"; import { prettyFormat, parseMarkdownToHast } from "../loadServerTypesMeta/format.mjs"; import { formatInlineTypeAsHast, formatDetailedTypeAsHast, wrapInlineTypeInPre, DEFAULT_UNION_PRINT_WIDTH, DEFAULT_TYPE_PRINT_WIDTH } from "./typeHighlighting.mjs"; import { getShortTypeFromHast, shouldShowDetailedTypeFromHast, replaceTypeReferences, collectTypeReferences, getHastTextContent, resolveSerializer } from "./hastTypeUtils.mjs"; import { extractTypeProps as extractTypePropsFromCode } from "./extractTypeProps.mjs"; /** A HAST root or its serialized/compressed wrapper. */ /** * Strips generic type arguments from a type string. * e.g., `useRender.Parameters<Record<string, unknown>, Element>` → `useRender.Parameters` */ function stripGenericArgs(typeText) { const idx = typeText.indexOf('<'); return idx === -1 ? typeText : typeText.slice(0, idx); } /** * Looks up a type name in rawTypeProperties, falling back to stripping * generic arguments when an exact match isn't found. */ function lookupRawTypeProperties(typeText, rawTypeProperties) { const exact = rawTypeProperties[typeText]; if (exact && Object.keys(exact).length > 0) { return { name: typeText, properties: exact }; } const stripped = stripGenericArgs(typeText); if (stripped !== typeText) { const fallback = rawTypeProperties[stripped]; if (fallback && Object.keys(fallback).length > 0) { return { name: stripped, properties: fallback }; } } return undefined; } /** A FormattedProperty where description/example may have been pre-serialized. */ /** * Processes raw type properties' `description` and `example` HAST through * `transformHtmlCodeBlock` and `transformHtmlCodeInline`. * * Raw type properties skip `highlightTypes` (which only handles component/hook/function types), * so their HAST fields need processing when they're expanded into a props table. * * Returns new property objects — does not mutate the originals (they may be * reused across multiple expansion sites). */ async function highlightRawProperties(properties, output, codeBlockEmphasisOptions) { const s = resolveSerializer(output); const processor = unified().use(transformHtmlCodeInline).use(transformHtmlCodeBlock, codeBlockEmphasisOptions); const entries = await Promise.all(Object.entries(properties).map(async ([name, prop]) => { const [description, example] = await Promise.all([prop.description ? processor.run(prop.description).then(h => s(h)) : undefined, prop.example ? processor.run(prop.example).then(h => s(h)) : undefined]); return [name, { ...prop, ...(description !== undefined && { description }), ...(example !== undefined && { example }) }]; })); return Object.fromEntries(entries); } /** * Highlighted property with syntax-highlighted HAST fields. */ /** * Highlighted class property with syntax-highlighted HAST fields. * Extends HighlightedProperty with class-specific modifiers. */ /** * Highlighted parameter with syntax-highlighted HAST fields. */ /** * Highlighted component type metadata with highlighted types. */ /** * Highlighted hook type metadata with highlighted types. */ /** * Highlighted function type metadata with highlighted types. */ /** * Highlighted method with syntax-highlighted HAST fields. */ /** * Highlighted class type metadata with highlighted types. */ /** * Highlighted enum member with syntax-highlighted HAST fields. */ /** * Highlighted raw type metadata with syntax-highlighted HAST fields. */ /** * Highlighted TypesMeta with highlighted type fields. */ /** * Options for highlightTypesMeta. */ /** * Highlights TypesMeta by converting plain text type strings to syntax-highlighted HAST. * * This function processes all TypesMeta objects and: * - Converts typeText → type (HAST) * - Derives shortType from the highlighted HAST structure * - Generates detailedType with expanded references * - Converts defaultText → default (HAST) * * @param types - Types array with plain text type fields * @param options - Options including highlightedExports map for type expansion * @returns Highlighted types array with HAST type fields */ export async function highlightTypesMeta(types, options = {}) { const { highlightedExports = {}, rawTypeProperties = {}, formatting, codeBlockEmphasisOptions } = options; const shortTypeUnionPrintWidth = formatting?.shortTypeUnionPrintWidth ?? DEFAULT_UNION_PRINT_WIDTH; const defaultValueUnionPrintWidth = formatting?.defaultValueUnionPrintWidth ?? DEFAULT_UNION_PRINT_WIDTH; const typePrintWidth = formatting?.typePrintWidth ?? DEFAULT_TYPE_PRINT_WIDTH; const topLevelTypePrintWidth = formatting?.topLevelTypePrintWidth; const output = options.output ?? 'hast'; const highlightedTypes = await Promise.all(types.map(async typeMeta => { if (typeMeta.type === 'component') { return { ...typeMeta, data: await highlightComponentTypeMeta(typeMeta.data, highlightedExports, shortTypeUnionPrintWidth, defaultValueUnionPrintWidth, typePrintWidth, output) }; } if (typeMeta.type === 'hook') { return { ...typeMeta, data: await highlightHookTypeMeta(typeMeta.data, highlightedExports, rawTypeProperties, shortTypeUnionPrintWidth, defaultValueUnionPrintWidth, typePrintWidth, topLevelTypePrintWidth, output, codeBlockEmphasisOptions) }; } if (typeMeta.type === 'function') { return { ...typeMeta, data: await highlightFunctionTypeMeta(typeMeta.data, highlightedExports, rawTypeProperties, shortTypeUnionPrintWidth, defaultValueUnionPrintWidth, typePrintWidth, topLevelTypePrintWidth, output, codeBlockEmphasisOptions) }; } if (typeMeta.type === 'class') { return { ...typeMeta, data: await highlightClassTypeMeta(typeMeta.data, highlightedExports, shortTypeUnionPrintWidth, defaultValueUnionPrintWidth, typePrintWidth, topLevelTypePrintWidth, output) }; } if (typeMeta.type === 'raw') { return { ...typeMeta, data: await highlightRawTypeMeta(typeMeta.data, highlightedExports, typePrintWidth, topLevelTypePrintWidth, shortTypeUnionPrintWidth, defaultValueUnionPrintWidth, output) }; } // This should never happen, but TypeScript needs exhaustive checking return typeMeta; })); return highlightedTypes; } /** * Highlights a component's type metadata with syntax-highlighted HAST. */ async function highlightComponentTypeMeta(data, highlightedExports, shortTypeUnionPrintWidth, defaultValueUnionPrintWidth, typePrintWidth, output) { const highlightedPropsEntries = await Promise.all(Object.entries(data.props).map(async ([propName, prop]) => { const highlighted = await highlightPropertyMeta(propName, prop, highlightedExports, shortTypeUnionPrintWidth, defaultValueUnionPrintWidth, typePrintWidth, output); return [propName, highlighted]; })); return { ...data, props: Object.fromEntries(highlightedPropsEntries) }; } /** * Highlights a hook's type metadata with syntax-highlighted HAST. */ async function highlightHookTypeMeta(data, highlightedExports, rawTypeProperties, shortTypeUnionPrintWidth, defaultValueUnionPrintWidth, typePrintWidth, topLevelTypePrintWidth, output, codeBlockEmphasisOptions) { const s = resolveSerializer(output); // Highlight parameters or expanded properties let highlightedParameters; let expandedProperties; let expandedTypeName; if (data.expandedProperties) { // Anonymous object parameter was expanded at format time const expandedEntries = await Promise.all(Object.entries(data.expandedProperties).map(async ([propName, prop]) => { const highlighted = await highlightPropertyMeta(propName, prop, highlightedExports, shortTypeUnionPrintWidth, defaultValueUnionPrintWidth, typePrintWidth, output); return [propName, highlighted]; })); expandedProperties = Object.fromEntries(expandedEntries); } else if (data.parameters) { // Highlight each parameter highlightedParameters = await Promise.all(data.parameters.map(async param => { const highlighted = await highlightPropertyMeta(param.name, param, highlightedExports, shortTypeUnionPrintWidth, defaultValueUnionPrintWidth, typePrintWidth, output); return { name: param.name, ...highlighted }; })); // Check if there's a single parameter whose type matches a raw type with properties. // If so, expand it into a property table (like component props). if (data.parameters.length === 1) { const param = data.parameters[0]; // Strip '| undefined' suffix from optional parameters before matching const paramTypeText = param.typeText.replace(/\s*\|\s*undefined$/, ''); const paramMatch = lookupRawTypeProperties(paramTypeText, rawTypeProperties); if (paramMatch) { expandedTypeName = paramMatch.name; const highlightedProps = await highlightRawProperties(paramMatch.properties, output, codeBlockEmphasisOptions); const propEntries = await Promise.all(Object.entries(highlightedProps).map(async ([propName, prop]) => { const highlighted = await highlightPropertyMeta(propName, prop, highlightedExports, shortTypeUnionPrintWidth, defaultValueUnionPrintWidth, typePrintWidth, output); return [propName, highlighted]; })); expandedProperties = Object.fromEntries(propEntries); } } } // Enhance returnValue let highlightedReturnValue; let returnValueDetailedType; let returnValueTypeName; if (typeof data.returnValue === 'string') { // Check if the return type name matches a raw type with structured properties. // If so, expand it into a property table instead of a plain code reference. const returnMatch = lookupRawTypeProperties(data.returnValue, rawTypeProperties); if (returnMatch) { returnValueTypeName = returnMatch.name; const highlightedProps = await highlightRawProperties(returnMatch.properties, output, codeBlockEmphasisOptions); const returnValueEntries = await Promise.all(Object.entries(highlightedProps).map(async ([propName, prop]) => { const highlighted = await highlightPropertyMeta(propName, prop, highlightedExports, shortTypeUnionPrintWidth, defaultValueUnionPrintWidth, typePrintWidth, output); return [propName, highlighted]; })); highlightedReturnValue = Object.fromEntries(returnValueEntries); } else { // It's a plain text type string - format with prettier and convert to HAST let formattedReturnValue = data.returnValue; if (topLevelTypePrintWidth !== undefined) { formattedReturnValue = await prettyFormat(data.returnValue, undefined, topLevelTypePrintWidth); } if (formattedReturnValue.endsWith(';')) { formattedReturnValue = formattedReturnValue.slice(0, -1); } highlightedReturnValue = s(wrapInlineTypeInPre(await formatInlineTypeAsHast(formattedReturnValue))); // Check if the return type references types that can be expanded const expanded = await expandReturnValueType(data.returnValue, highlightedExports, typePrintWidth); if (expanded) { returnValueDetailedType = s(expanded); } } } else { // It's an object with FormattedProperty values const returnValueEntries = await Promise.all(Object.entries(data.returnValue).map(async ([propName, prop]) => { const highlighted = await highlightPropertyMeta(propName, prop, highlightedExports, shortTypeUnionPrintWidth, defaultValueUnionPrintWidth, typePrintWidth, output); return [propName, highlighted]; })); highlightedReturnValue = Object.fromEntries(returnValueEntries); } // Destructure fields that are replaced in the highlighted version const { parameters, expandedProperties: ep, returnValue: rv, ...restData } = data; const result = { ...restData, ...(restData.returnValueDescription && { returnValueDescription: s(restData.returnValueDescription) }), ...(highlightedParameters && { parameters: highlightedParameters }), ...(expandedProperties && { expandedProperties }), ...(expandedTypeName && { expandedTypeName }), returnValue: highlightedReturnValue }; if (returnValueDetailedType) { result.returnValueDetailedType = returnValueDetailedType; } if (returnValueTypeName) { result.returnValueTypeName = returnValueTypeName; } return result; } /** * Highlights a function's type metadata with syntax-highlighted HAST. */ async function highlightFunctionTypeMeta(data, highlightedExports, rawTypeProperties, shortTypeUnionPrintWidth, defaultValueUnionPrintWidth, typePrintWidth, topLevelTypePrintWidth, output, codeBlockEmphasisOptions) { const s = resolveSerializer(output); // Highlight parameters or expanded properties let highlightedParameters; let expandedProperties; let expandedTypeName; if (data.expandedProperties) { // Anonymous object parameter was expanded at format time const expandedEntries = await Promise.all(Object.entries(data.expandedProperties).map(async ([propName, prop]) => { const highlighted = await highlightPropertyMeta(propName, prop, highlightedExports, shortTypeUnionPrintWidth, defaultValueUnionPrintWidth, typePrintWidth, output); return [propName, highlighted]; })); expandedProperties = Object.fromEntries(expandedEntries); } else if (data.parameters) { // Highlight each parameter highlightedParameters = await Promise.all(data.parameters.map(async param => { const highlighted = await highlightPropertyMeta(param.name, param, highlightedExports, shortTypeUnionPrintWidth, defaultValueUnionPrintWidth, typePrintWidth, output); return { name: param.name, ...highlighted }; })); // Check if there's a single parameter whose type matches a raw type with properties. // If so, expand it into a property table (like component props). if (data.parameters.length === 1) { const param = data.parameters[0]; // Strip '| undefined' suffix from optional parameters before matching const paramTypeText = param.typeText.replace(/\s*\|\s*undefined$/, ''); const funcParamMatch = lookupRawTypeProperties(paramTypeText, rawTypeProperties); if (funcParamMatch) { expandedTypeName = funcParamMatch.name; const highlightedProps = await highlightRawProperties(funcParamMatch.properties, output, codeBlockEmphasisOptions); const propEntries = await Promise.all(Object.entries(highlightedProps).map(async ([propName, prop]) => { const highlighted = await highlightPropertyMeta(propName, prop, highlightedExports, shortTypeUnionPrintWidth, defaultValueUnionPrintWidth, typePrintWidth, output); return [propName, highlighted]; })); expandedProperties = Object.fromEntries(propEntries); } } } // Enhance returnValue - either object with properties or plain text string let highlightedReturnValue; let returnValueDetailedType; let returnValueTypeName; if (typeof data.returnValue === 'string') { // Check if the return type name matches a raw type with structured properties. const funcReturnMatch = lookupRawTypeProperties(data.returnValue, rawTypeProperties); if (funcReturnMatch) { returnValueTypeName = funcReturnMatch.name; const highlightedProps = await highlightRawProperties(funcReturnMatch.properties, output, codeBlockEmphasisOptions); const returnValueEntries = await Promise.all(Object.entries(highlightedProps).map(async ([propName, prop]) => { const highlighted = await highlightPropertyMeta(propName, prop, highlightedExports, shortTypeUnionPrintWidth, defaultValueUnionPrintWidth, typePrintWidth, output); return [propName, highlighted]; })); highlightedReturnValue = Object.fromEntries(returnValueEntries); } else { // It's a plain text type string - format with prettier and convert to HAST let formattedReturnValue = data.returnValue; if (topLevelTypePrintWidth !== undefined) { formattedReturnValue = await prettyFormat(data.returnValue, undefined, topLevelTypePrintWidth); } if (formattedReturnValue.endsWith(';')) { formattedReturnValue = formattedReturnValue.slice(0, -1); } highlightedReturnValue = s(wrapInlineTypeInPre(await formatInlineTypeAsHast(formattedReturnValue))); // Check if the return type references types that can be expanded const expanded = await expandReturnValueType(data.returnValue, highlightedExports, typePrintWidth); if (expanded) { returnValueDetailedType = s(expanded); } } } else { // It's an object with FormattedProperty values const returnValueEntries = await Promise.all(Object.entries(data.returnValue).map(async ([propName, prop]) => { const highlighted = await highlightPropertyMeta(propName, prop, highlightedExports, shortTypeUnionPrintWidth, defaultValueUnionPrintWidth, typePrintWidth, output); return [propName, highlighted]; })); highlightedReturnValue = Object.fromEntries(returnValueEntries); } // Destructure fields that are replaced in the highlighted version const { parameters, expandedProperties: ep, returnValue: rv, ...restData } = data; const result = { ...restData, // description is already serialized by highlightTypes // returnValueDescription bypasses highlightTypes — serialize here ...(restData.returnValueDescription && { returnValueDescription: s(restData.returnValueDescription) }), ...(highlightedParameters && { parameters: highlightedParameters }), ...(expandedProperties && { expandedProperties }), ...(expandedTypeName && { expandedTypeName }), returnValue: highlightedReturnValue }; if (returnValueDetailedType) { result.returnValueDetailedType = returnValueDetailedType; } if (returnValueTypeName) { result.returnValueTypeName = returnValueTypeName; } return result; } /** * Highlights a class's type metadata with syntax-highlighted HAST. */ async function highlightClassTypeMeta(data, highlightedExports, shortTypeUnionPrintWidth, defaultValueUnionPrintWidth, typePrintWidth, topLevelTypePrintWidth, output) { const s = resolveSerializer(output); // Enhance constructor parameters const highlightedConstructorParams = await Promise.all(data.constructorParameters.map(async param => { const highlighted = await highlightPropertyMeta(param.name, param, highlightedExports, shortTypeUnionPrintWidth, defaultValueUnionPrintWidth, typePrintWidth, output); return { ...highlighted, name: param.name }; })); // Enhance properties const highlightedPropertiesEntries = await Promise.all(Object.entries(data.properties).map(async ([propName, prop]) => { const highlighted = await highlightClassPropertyMeta(propName, prop, highlightedExports, shortTypeUnionPrintWidth, typePrintWidth, output); return [propName, highlighted]; })); // Enhance methods const highlightedMethodsEntries = await Promise.all(Object.entries(data.methods).map(async ([methodName, method]) => { // Enhance method parameters const highlightedMethodParams = await Promise.all(method.parameters.map(async param => { const highlighted = await highlightPropertyMeta(param.name, param, highlightedExports, shortTypeUnionPrintWidth, defaultValueUnionPrintWidth, typePrintWidth, output); return { ...highlighted, name: param.name }; })); // Enhance return value - format with prettier before highlighting let formattedReturnValue = method.returnValue; if (topLevelTypePrintWidth !== undefined) { formattedReturnValue = await prettyFormat(method.returnValue, undefined, topLevelTypePrintWidth); } if (formattedReturnValue.endsWith(';')) { formattedReturnValue = formattedReturnValue.slice(0, -1); } const highlightedReturnValue = s(wrapInlineTypeInPre(await formatInlineTypeAsHast(formattedReturnValue))); const highlightedMethod = { ...method, // Class types bypass highlightTypes — serialize description/returnValueDescription here ...(method.description && { description: s(method.description) }), ...(method.returnValueDescription && { returnValueDescription: s(method.returnValueDescription) }), parameters: highlightedMethodParams, returnValue: highlightedReturnValue }; return [methodName, highlightedMethod]; })); return { ...data, // Class types bypass highlightTypes — serialize description here ...(data.description && { description: s(data.description) }), constructorParameters: highlightedConstructorParams, properties: Object.fromEntries(highlightedPropertiesEntries), methods: Object.fromEntries(highlightedMethodsEntries) }; } /** * Expands a return value type string by resolving type references from highlightedExports. * * When a hook/function returns a named type like `AutocompleteFilter`, this function * checks if that type reference can be expanded from the highlightedExports map. * If so, it produces a detailed HAST representation (similar to detailedType for properties). * * @param returnValueText - The plain text return type string * @param highlightedExports - Map of type names to their highlighted HAST definitions * @param typePrintWidth - Print width for formatting the expanded type * @returns Expanded HAST if references were resolved, undefined otherwise */ async function expandReturnValueType(returnValueText, highlightedExports, typePrintWidth) { const typeHast = await formatInlineTypeAsHast(returnValueText); const typeRefs = collectTypeReferences(typeHast); const hasExpandableRefs = typeRefs.some(ref => highlightedExports[ref.name] !== undefined); if (!hasExpandableRefs) { return undefined; } const expanded = replaceTypeReferences(typeHast, highlightedExports); const expandedText = getHastTextContent(expanded); const originalText = getHastTextContent(typeHast); if (expandedText === originalText) { return undefined; } let formattedExpandedText = await prettyFormat(expandedText, undefined, typePrintWidth); if (formattedExpandedText.endsWith(';')) { formattedExpandedText = formattedExpandedText.slice(0, -1); } return formatDetailedTypeAsHast(formattedExpandedText); } /** * Highlights a single property with syntax-highlighted HAST. */ async function highlightPropertyMeta(name, prop, highlightedExports, shortTypeUnionPrintWidth, defaultValueUnionPrintWidth, typePrintWidth, output) { const s = resolveSerializer(output); // For shortType derivation, strip trailing `| undefined` from optional props // since required/optional status is shown separately (required props have *) const isOptional = !('required' in prop && prop.required); const strippedUndefined = isOptional && prop.typeText.endsWith(' | undefined'); const shortTypeInputText = strippedUndefined ? prop.typeText.slice(0, -' | undefined'.length) : prop.typeText; const shortTypeInput = await formatInlineTypeAsHast(shortTypeInputText); // Derive shortType from the highlighted HAST structure (without | undefined for optional) // If we stripped | undefined, we need a shortType so the UI shows the clean version const derivedShortType = getShortTypeFromHast(name, shortTypeInput); const shortTypeText = derivedShortType ?? (strippedUndefined ? shortTypeInputText : undefined); const shortType = shortTypeText ? await formatInlineTypeAsHast(shortTypeText, shortTypeUnionPrintWidth) : undefined; // Generate detailedType if needed // Two cases: // 1. The prop name/type triggers detailed display (e.g., event handlers, className) // 2. There are type references that can be expanded from highlightedExports (e.g., external types) let detailedType; // First, check if any type references can be expanded // Reuse shortTypeInput if we didn't strip | undefined, otherwise format the full typeText const typeForExpansion = strippedUndefined ? await formatInlineTypeAsHast(prop.typeText) : shortTypeInput; const typeRefs = collectTypeReferences(typeForExpansion); const hasExpandableRefs = typeRefs.some(ref => highlightedExports[ref.name] !== undefined); if (shouldShowDetailedTypeFromHast(name, shortTypeInput) || hasExpandableRefs) { // Create a detailed type with expanded references const expanded = replaceTypeReferences(typeForExpansion, highlightedExports); // Only include detailedType if it differs from the basic type // (i.e., if any references were actually expanded to different text) const expandedText = getHastTextContent(expanded); const originalText = getHastTextContent(typeForExpansion); if (expandedText !== originalText) { // Format expanded type with prettier before highlighting let formattedExpandedText = await prettyFormat(expandedText, undefined, typePrintWidth); // Strip trailing semicolon added by prettier if (formattedExpandedText.endsWith(';')) { formattedExpandedText = formattedExpandedText.slice(0, -1); } // Use the detailed format (pre > code with line numbers) detailedType = await formatDetailedTypeAsHast(formattedExpandedText); } } // Convert typeText to highlighted HAST // If no detailedType exists but the type needs detailed display, format with prettier // and use block-level format (pre > code) since prettier output is multiline let type; if (!detailedType && shouldShowDetailedTypeFromHast(name, shortTypeInput)) { let formattedTypeText = await prettyFormat(prop.typeText, undefined, typePrintWidth); // Strip trailing semicolon added by prettier if (formattedTypeText.endsWith(';')) { formattedTypeText = formattedTypeText.slice(0, -1); } type = await formatDetailedTypeAsHast(formattedTypeText); } else { // Format with prettier before highlighting to ensure consistent output let formattedTypeText = await prettyFormat(prop.typeText, undefined, typePrintWidth); if (formattedTypeText.endsWith(';')) { formattedTypeText = formattedTypeText.slice(0, -1); } type = wrapInlineTypeInPre(await formatInlineTypeAsHast(formattedTypeText)); } // Convert defaultText to highlighted HAST const defaultValue = prop.defaultText ? await formatInlineTypeAsHast(prop.defaultText, defaultValueUnionPrintWidth) : undefined; const { typeText: removedTypeText, ...propWithoutTypeText } = prop; const highlighted = { ...propWithoutTypeText, // description and example are already serialized by highlightTypes (or highlightRawProperties) // see bypasses highlightTypes — serialize here ...('see' in prop && prop.see !== undefined ? { see: s(prop.see) } : {}), type: s(type) }; if (shortType) { highlighted.shortType = s(shortType); } if (defaultValue) { highlighted.default = s(defaultValue); } if (detailedType) { highlighted.detailedType = s(detailedType); } return highlighted; } /** * Highlights a class property with syntax-highlighted HAST. * Class properties have a different structure than component props. */ async function highlightClassPropertyMeta(name, prop, highlightedExports, shortTypeUnionPrintWidth, typePrintWidth, output) { const s = resolveSerializer(output); // For shortType derivation, strip trailing `| undefined` from optional props const strippedUndefined = prop.optional && prop.typeText.endsWith(' | undefined'); const shortTypeInputText = strippedUndefined ? prop.typeText.slice(0, -' | undefined'.length) : prop.typeText; const shortTypeInput = await formatInlineTypeAsHast(shortTypeInputText); // Derive shortType from the highlighted HAST structure const derivedShortType = getShortTypeFromHast(name, shortTypeInput); const shortTypeText = derivedShortType ?? (strippedUndefined ? shortTypeInputText : undefined); const shortType = shortTypeText ? await formatInlineTypeAsHast(shortTypeText, shortTypeUnionPrintWidth) : undefined; // Generate detailedType if needed let detailedType; const typeForExpansion = strippedUndefined ? await formatInlineTypeAsHast(prop.typeText) : shortTypeInput; const typeRefs = collectTypeReferences(typeForExpansion); const hasExpandableRefs = typeRefs.some(ref => highlightedExports[ref.name] !== undefined); if (shouldShowDetailedTypeFromHast(name, shortTypeInput) || hasExpandableRefs) { const expanded = replaceTypeReferences(typeForExpansion, highlightedExports); const expandedText = getHastTextContent(expanded); const originalText = getHastTextContent(typeForExpansion); if (expandedText !== originalText) { let formattedExpandedText = await prettyFormat(expandedText, `detailed_${name}`, typePrintWidth); if (formattedExpandedText.endsWith(';')) { formattedExpandedText = formattedExpandedText.slice(0, -1); } detailedType = await formatDetailedTypeAsHast(formattedExpandedText); } } // Format the base type with prettier before highlighting let formattedTypeText = await prettyFormat(prop.typeText, undefined, typePrintWidth); if (formattedTypeText.endsWith(';')) { formattedTypeText = formattedTypeText.slice(0, -1); } const type = wrapInlineTypeInPre(await formatInlineTypeAsHast(formattedTypeText)); const highlighted = { type: s(type) }; if (!prop.optional) { highlighted.required = true; } if (prop.descriptionText) { highlighted.descriptionText = prop.descriptionText; } // Class types bypass highlightTypes — serialize description here if (prop.description) { highlighted.description = s(prop.description); } if (shortType) { highlighted.shortType = s(shortType); } if (detailedType) { highlighted.detailedType = s(detailedType); } // Propagate class-specific fields if (prop.isStatic) { highlighted.isStatic = prop.isStatic; } if (prop.readonly) { highlighted.readonly = prop.readonly; } return highlighted; } /** * Highlights a raw type's metadata with syntax-highlighted HAST. * Converts the formattedCode string to highlighted HAST and expands type references. * * JSDoc comments are extracted from the highlighted HAST, wrapped in * `span[data-comment]` elements for CSS hiding, and returned as highlighted * property records on the result's `properties` field. */ async function highlightRawTypeMeta(data, highlightedExports, typePrintWidth, topLevelTypePrintWidth, shortTypeUnionPrintWidth, defaultValueUnionPrintWidth, output) { const s = resolveSerializer(output); // Re-format the raw code with prettier at the configured width let formattedCode = data.formattedCode; if (topLevelTypePrintWidth !== undefined) { formattedCode = await prettyFormat(data.formattedCode, null, topLevelTypePrintWidth); } if (formattedCode.endsWith(';')) { formattedCode = formattedCode.slice(0, -1); } // Highlight the formatted code const initialHast = await formatDetailedTypeAsHast(formattedCode); // Check if any type references can be expanded const typeRefs = collectTypeReferences(initialHast); const hasExpandableRefs = typeRefs.some(ref => highlightedExports[ref.name] !== undefined); let formattedCodeHast; if (hasExpandableRefs) { // Expand type references and re-format with prettier const expanded = replaceTypeReferences(initialHast, highlightedExports); let formattedText = getHastTextContent(expanded); if (topLevelTypePrintWidth !== undefined) { formattedText = await prettyFormat(formattedText, null, topLevelTypePrintWidth); } // Strip trailing semicolon added by prettier if (formattedText.endsWith(';')) { formattedText = formattedText.slice(0, -1); } formattedCodeHast = await formatDetailedTypeAsHast(formattedText); } else { // No expansion needed - use already-formatted code directly formattedCodeHast = initialHast; } // Extract JSDoc comments from the highlighted HAST // Comments are wrapped in span[data-comment] for CSS hiding let extractedProperties; { const { hast: annotatedHast, properties: extractedComments } = extractTypePropsFromCode(formattedCodeHast); formattedCodeHast = annotatedHast; // Convert extracted comments to highlighted properties if (Object.keys(extractedComments).length > 0) { const highlightedEntries = await Promise.all(Object.entries(extractedComments).map(async ([path, comment]) => { // Build a FormattedProperty from the extracted comment // Descriptions/examples are created here via parseMarkdownToHast — serialize immediately const description = comment.description ? s(await parseMarkdownToHast(comment.description)) : undefined; const example = comment.example !== undefined ? s(await parseMarkdownToHast(comment.example)) : undefined; // see is NOT serialized here — enhanceProperty handles it const see = comment.see && comment.see.length > 0 ? await parseMarkdownToHast(comment.see.join('\n')) : undefined; const formattedProp = { typeText: comment.typeText, ...(description && { description }), ...(comment.description && { descriptionText: comment.description }), ...(!comment.optional && { required: true }), ...(comment.defaultValue !== undefined && { defaultText: comment.defaultValue }), ...(example && { example, exampleText: comment.example }), ...(see && { see, seeText: comment.see.join('\n') }) }; const highlighted = await highlightPropertyMeta(path, formattedProp, highlightedExports, shortTypeUnionPrintWidth, defaultValueUnionPrintWidth, typePrintWidth, output); return [path, highlighted]; })); extractedProperties = Object.fromEntries(highlightedEntries); } } // Enhance enum members if present const highlightedEnumMembers = data.enumMembers ? data.enumMembers.map(member => ({ ...member, ...(member.description && { description: s(member.description) }) })) : undefined; // Destructure `properties` out of data to avoid spreading FormattedProperty // into a field that expects HighlightedProperty — raw properties are replaced by // the structured `extractedProperties` produced from `extractTypeProps` above. // eslint-disable-next-line @typescript-eslint/naming-convention const { properties: _rawProperties, ...restData } = data; const result = { ...restData, // Raw types bypass highlightTypes — serialize description here ...(restData.description && { description: s(restData.description) }), formattedCode: s(formattedCodeHast), enumMembers: highlightedEnumMembers }; if (extractedProperties) { result.properties = extractedProperties; } return result; }