@mui/internal-docs-infra
Version:
MUI Infra - internal documentation creation tools.
143 lines (132 loc) • 5.86 kB
JavaScript
import { formatParameters, formatProperties, parseMarkdownToHast, applyDescriptionReplacements } from "./format.mjs";
import { formatType } from "./formatType.mjs";
import { isAnonymousObjectType, isFunctionType, isObjectType } from "./typeGuards.mjs";
import { rewriteTypeStringsDeep } from "./rewriteTypes.mjs";
/**
* Formatted function metadata with plain text types and parsed markdown descriptions.
*
* Type highlighting (type → HAST, shortType, detailedType) is deferred to
* the loadServerTypes stage via highlightTypesMeta() after highlightTypes().
*/
/**
* Formats function export data into a structured metadata object.
*
* @param func - The function export node from typescript-api-extractor
* @param typeNameMap - Map for transforming type names
* @param rewriteContext - Context for type string rewriting including type compatibility map
* @param options - Formatting options
* @returns Formatted function metadata with parameters and return value
*/
export async function formatFunctionData(func, typeNameMap, rewriteContext, options = {}) {
const {
descriptionReplacements,
formatting,
externalTypes
} = options;
const {
exportNames
} = rewriteContext;
const descriptionText = func.documentation?.description ? applyDescriptionReplacements(func.documentation.description, descriptionReplacements) : undefined;
const description = descriptionText ? await parseMarkdownToHast(descriptionText) : undefined;
// Handle function overloads: pick the signature with the most parameters,
// then mark parameters as optional if they don't appear in all signatures.
const callSignatures = func.type.callSignatures;
const signature = callSignatures.reduce((longest, current) => current.parameters.length > longest.parameters.length ? current : longest);
const parameters = signature.parameters;
// Determine which parameters are optional by checking if they exist in all overloads
const minParamCount = Math.min(...callSignatures.map(sig => sig.parameters.length));
const optionalFromIndex = minParamCount;
const formattedParameters = await formatParameters(parameters, {
exportNames,
typeNameMap,
formatting,
externalTypes,
descriptionReplacements
});
// Mark parameters as optional if they don't appear in all overloads
parameters.forEach((param, index) => {
if (index >= optionalFromIndex) {
const entry = formattedParameters.find(p => p.name === param.name);
if (entry) {
entry.optional = true;
}
}
});
// Check if this is a single anonymous object parameter — if so,
// expand it into `expandedProperties` instead of `parameters` (same as hooks).
let resultParameters;
let resultProperties;
if (parameters.length === 1 && isObjectType(parameters[0].type) && isAnonymousObjectType(parameters[0].type)) {
resultProperties = await formatProperties(parameters[0].type.properties, {
exportNames,
typeNameMap,
formatting,
externalTypes,
descriptionReplacements
});
} else {
resultParameters = formattedParameters;
}
// Format return value - either as object with properties or plain text string
// Only expand anonymous object types into a property table.
// Named types (like class instances `DialogHandle<Payload>`) are kept as type references.
let formattedReturnValue;
let returnValueText;
const returnType = signature.returnValueType;
const shouldExpandReturnType = isObjectType(returnType) && isAnonymousObjectType(returnType) && returnType.properties && returnType.properties.length > 0;
if (shouldExpandReturnType) {
formattedReturnValue = await formatProperties(returnType.properties, {
exportNames,
typeNameMap,
formatting,
externalTypes,
descriptionReplacements
});
} else {
// Format type as plain text - highlighting is deferred to loadServerTypes
// Only expand anonymous objects (no type name) — named types like
// `DialogHandle<Payload>` should be shown as type references.
const shouldExpand = isObjectType(returnType) && isAnonymousObjectType(returnType);
returnValueText = formatType(signature.returnValueType, {
expandObjects: shouldExpand,
exportNames,
typeNameMap,
externalTypesCollector: externalTypes
});
formattedReturnValue = returnValueText;
}
// Get return value description from @returns tag
const returnsTag = func.documentation?.tags?.find(tag => tag.name === 'returns');
const returnValueDescriptionText = returnsTag?.value;
const returnValueDescription = returnValueDescriptionText ? await parseMarkdownToHast(returnValueDescriptionText) : undefined;
const raw = {
name: func.name,
description,
descriptionText,
...(resultParameters && {
parameters: resultParameters
}),
...(resultProperties && {
expandedProperties: resultProperties
}),
returnValue: formattedReturnValue,
returnValueText,
returnValueDescription,
returnValueDescriptionText
};
// Post-process type strings to align naming across re-exports
return rewriteTypeStringsDeep(raw, rewriteContext);
}
/**
* Type guard to check if an export node is a public function (not a hook).
*
* @param exportNode - The export node to check
* @returns true if the export is a public function that should be documented
*/
export function isPublicFunction(exportNode) {
const isPublic = exportNode.documentation?.visibility !== 'private' && exportNode.documentation?.visibility !== 'internal';
const hasIgnoreTag = exportNode.documentation?.tags?.some(tag => tag.name === 'ignore');
// Functions that start with 'use' are hooks, not regular functions
const isHook = exportNode.name.startsWith('use');
return isFunctionType(exportNode.type) && !isHook && !hasIgnoreTag && isPublic;
}