Files
v10/site/scripts/api-docs-builder/src/parts-handler.ts
T

71 lines
2.4 KiB
TypeScript

import * as ts from 'typescript';
import * as tae from 'typescript-api-extractor';
export interface PartExport {
/** PascalCase export name (e.g., "Value", "Group", "Separator"). */
name: string;
/** Source path (e.g., "./time-value", "./time-group"). */
source: string;
}
/**
* Extract part definitions from a React `index.parts.ts` file.
*
* Discovery algorithm:
* 1. Parses named exports from `index.parts.ts` (filters out type-only exports)
* 2. Each value export becomes a part: `export { Group } from './time-group'` -> part "Group"
* 3. Source path is preserved for HTML element matching in the main builder
*
* If a part isn't appearing in the output:
* - Ensure it's exported as a value export (not `type`-only) in `index.parts.ts`
* - Ensure the source path follows `'./{component}-{part}'` naming
*/
export function extractParts(filePath: string, program: ts.Program): PartExport[] {
const sourceFile = program.getSourceFile(filePath);
if (!sourceFile) return [];
const parts: PartExport[] = [];
function visit(node: ts.Node) {
// Match: export { Name } from './source' or export { Name, type NameProps } from './source'
if (ts.isExportDeclaration(node) && node.moduleSpecifier && ts.isStringLiteral(node.moduleSpecifier)) {
const source = node.moduleSpecifier.text;
// Skip type-only export declarations (export type { ... } from '...')
if (node.isTypeOnly) return;
if (node.exportClause && ts.isNamedExports(node.exportClause)) {
for (const element of node.exportClause.elements) {
// Skip type-only specifiers (e.g., `type GroupProps`)
if (element.isTypeOnly) continue;
parts.push({
name: element.name.text,
source,
});
}
}
}
ts.forEachChild(node, visit);
}
visit(sourceFile);
return parts;
}
/**
* Extract the JSDoc description from a React component export.
*
* Parses the file with `typescript-api-extractor`, finds the export matching
* `partName`, and returns its description (stripping `@example` blocks).
*/
export function extractPartDescription(filePath: string, program: ts.Program, partName: string): string | undefined {
const ast = tae.parseFromProgram(filePath, program);
const component = ast.exports.find((exp) => exp.name === partName);
let desc = component?.documentation?.description;
if (desc) desc = desc.replace(/\n*@example[\s\S]*$/, '').trim();
return desc || undefined;
}