Skip to content

Commit fe52ac4

Browse files
authored
chore: fix tree shaking and add pagination to adapter docs (#880)
1 parent 40f4e2a commit fe52ac4

File tree

6 files changed

+208
-59
lines changed

6 files changed

+208
-59
lines changed

build/generateAdapterPropDocs.ts

Lines changed: 150 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
Node,
77
PropertySignature,
88
Symbol as TSMorphSymbol,
9+
SourceFile,
910
} from 'ts-morph'
1011
import { writeFile } from 'fs/promises'
1112
import { join, dirname } from 'path'
@@ -14,7 +15,10 @@ import { fileURLToPath } from 'url'
1415
const __filename = fileURLToPath(import.meta.url)
1516
const __dirname = dirname(__filename)
1617

17-
const UI_COMPONENTS_DIR = join(__dirname, '../src/components/Common/UI')
18+
const COMPONENT_ADAPTER_TYPES_FILE = join(
19+
__dirname,
20+
'../src/contexts/ComponentAdapter/componentAdapterTypes.ts',
21+
)
1822
const DOCS_OUTPUT_DIR = join(__dirname, '../docs/component-adapter')
1923
const DOCS_OUTPUT_FILE = join(DOCS_OUTPUT_DIR, 'component-inventory.md')
2024

@@ -64,7 +68,11 @@ function isBooleanUnion(types: Type[]): boolean {
6468
function formatType(type: Type | undefined): string {
6569
if (!type) return '-'
6670

67-
const typeText = type.getText()
71+
let typeText = type.getText()
72+
73+
// Clean up absolute import paths to just show type names
74+
// e.g., import("/absolute/path/to/file").TypeName → TypeName
75+
typeText = typeText.replace(/import\("[^"]+"\)\./g, '')
6876

6977
// Handle React types - check text representation first before expanding unions
7078
if (typeText === 'React.ReactNode' || typeText === 'ReactNode') {
@@ -201,6 +209,28 @@ function findReferencedTypes(type: Type, knownTypes: Set<string>): string[] {
201209
return type.getIntersectionTypes().flatMap(t => findReferencedTypes(t, knownTypes))
202210
}
203211

212+
// Handle function types - check parameter types and return type
213+
const callSignatures = type.getCallSignatures()
214+
if (callSignatures.length > 0) {
215+
const referencedTypes: string[] = []
216+
for (const sig of callSignatures) {
217+
// Check parameter types
218+
for (const param of sig.getParameters()) {
219+
const paramType = param.getTypeAtLocation(param.getDeclarations()[0])
220+
referencedTypes.push(...findReferencedTypes(paramType, knownTypes))
221+
}
222+
// Check return type
223+
referencedTypes.push(...findReferencedTypes(sig.getReturnType(), knownTypes))
224+
}
225+
return referencedTypes
226+
}
227+
228+
// Check alias symbol first (for type aliases like PaginationItemsPerPage)
229+
const aliasSymbol = type.getAliasSymbol()
230+
if (aliasSymbol && knownTypes.has(aliasSymbol.getName())) {
231+
return [aliasSymbol.getName()]
232+
}
233+
204234
const symbol = type.getSymbol()
205235
if (symbol && knownTypes.has(symbol.getName())) {
206236
return [symbol.getName()]
@@ -324,6 +354,7 @@ function generateComponentSection(
324354
type: ComponentType,
325355
componentTypeMap: Map<string, ComponentType>,
326356
parentToChildren: Map<string, string[]>,
357+
knownTypeNames: Set<string>,
327358
level = 2,
328359
documented = new Set<string>(),
329360
): string {
@@ -341,6 +372,22 @@ function generateComponentSection(
341372
documented.add(typeName)
342373

343374
const heading = `${'#'.repeat(level)} ${typeName}`
375+
376+
// Handle literal union types (e.g., 5 | 10 | 50)
377+
const resolvedType = type.getType()
378+
if (resolvedType.isUnion()) {
379+
const unionTypes = resolvedType.getUnionTypes()
380+
const allLiterals = unionTypes.every(
381+
t => t.isLiteral() || t.isStringLiteral() || t.isNumberLiteral() || t.isBooleanLiteral(),
382+
)
383+
if (allLiterals) {
384+
const values = unionTypes.map(t => t.getText()).join(' | ')
385+
// Use ### level for type definitions (consistent with child types)
386+
// Use TypeScript code block for clear type presentation
387+
return `### ${typeName}\n\n\`\`\`typescript\ntype ${typeName} = ${values}\n\`\`\``
388+
}
389+
}
390+
344391
const props = getComponentProps(type, componentTypeMap)
345392

346393
// Handle type aliases
@@ -350,15 +397,22 @@ function generateComponentSection(
350397
}
351398

352399
// Generate prop table
353-
const table = generatePropTable(type, props)
400+
const table = generatePropTable(type, props, knownTypeNames)
354401
let section = `${heading}\n\n${table}`
355402

356403
// Add child components
357404
const childSections = (parentToChildren.get(typeName) || [])
358405
.map(childName => componentTypeMap.get(childName))
359406
.filter((child): child is ComponentType => child !== undefined)
360407
.map(child =>
361-
generateComponentSection(child, componentTypeMap, parentToChildren, level + 1, documented),
408+
generateComponentSection(
409+
child,
410+
componentTypeMap,
411+
parentToChildren,
412+
knownTypeNames,
413+
level + 1,
414+
documented,
415+
),
362416
)
363417
.filter(Boolean)
364418
.join('\n\n')
@@ -403,9 +457,14 @@ function handleTypeAlias(
403457
*
404458
* @param type - The component type containing the props
405459
* @param props - Array of property symbols to document
460+
* @param knownTypeNames - Set of known type names for linkification
406461
* @returns Markdown table string with prop documentation
407462
*/
408-
function generatePropTable(type: ComponentType, props: TSMorphSymbol[]): string {
463+
function generatePropTable(
464+
type: ComponentType,
465+
props: TSMorphSymbol[],
466+
knownTypeNames: Set<string>,
467+
): string {
409468
const TABLE_HEADER =
410469
'| Prop | Type | Required | Description |\n|------|------|----------|-------------|'
411470

@@ -418,7 +477,7 @@ function generatePropTable(type: ComponentType, props: TSMorphSymbol[]): string
418477
return true
419478
})
420479

421-
const rows = uniqueProps.map(prop => generatePropRow(type, prop))
480+
const rows = uniqueProps.map(prop => generatePropRow(type, prop, knownTypeNames))
422481
return `${TABLE_HEADER}\n${rows.join('\n')}`
423482
}
424483

@@ -435,7 +494,11 @@ function generatePropTable(type: ComponentType, props: TSMorphSymbol[]): string
435494
* @param prop - The property symbol to document
436495
* @returns Markdown table row string
437496
*/
438-
function generatePropRow(type: ComponentType, prop: TSMorphSymbol): string {
497+
function generatePropRow(
498+
type: ComponentType,
499+
prop: TSMorphSymbol,
500+
knownTypeNames: Set<string>,
501+
): string {
439502
// Get basic prop info
440503
const name = prop.getName()
441504
const nodeArg = type.getType().getSymbol()?.getDeclarations()?.[0]
@@ -447,7 +510,14 @@ function generatePropRow(type: ComponentType, prop: TSMorphSymbol): string {
447510
propType = propDecl ? prop.getTypeAtLocation(propDecl) : undefined
448511
}
449512

450-
const typeText = formatType(propType).replace(/[\n\r]/g, ' ')
513+
let typeText = formatType(propType).replace(/[\n\r]/g, ' ')
514+
515+
// Linkify known type names that appear in function signatures or other contexts
516+
for (const typeName of knownTypeNames) {
517+
// Match type name as a whole word (not part of another word)
518+
const regex = new RegExp(`\\b${typeName}\\b(?![^\\[]*\\])`, 'g')
519+
typeText = typeText.replace(regex, `[${typeName}](#${typeName.toLowerCase()})`)
520+
}
451521

452522
// Get prop declaration and metadata
453523
const decl = prop.getDeclarations()[0]
@@ -561,20 +631,77 @@ function buildComponentHierarchy(
561631
return { parentToChildren, getTopLevelComponents }
562632
}
563633

634+
function getTypeSourceFilesFromAdapterTypes(
635+
project: Project,
636+
adapterTypesFile: SourceFile,
637+
): SourceFile[] {
638+
const exportDeclarations = adapterTypesFile.getExportDeclarations()
639+
const sourceFilePaths = new Set<string>()
640+
641+
for (const exportDecl of exportDeclarations) {
642+
const moduleSpecifier = exportDecl.getModuleSpecifierValue()
643+
if (moduleSpecifier) {
644+
const resolvedPath = moduleSpecifier.replace('@/', join(__dirname, '../src/'))
645+
const fullPath = resolvedPath.endsWith('.ts') ? resolvedPath : `${resolvedPath}.ts`
646+
sourceFilePaths.add(fullPath)
647+
}
648+
}
649+
650+
const sourceFiles: SourceFile[] = []
651+
for (const filePath of sourceFilePaths) {
652+
project.addSourceFilesAtPaths(filePath)
653+
const sourceFile = project.getSourceFile(filePath)
654+
if (sourceFile) {
655+
sourceFiles.push(sourceFile)
656+
}
657+
}
658+
659+
return sourceFiles
660+
}
661+
564662
async function generateAdapterPropDocs() {
565663
const project = new Project({
566664
tsConfigFilePath: join(__dirname, '../tsconfig.json'),
567665
skipAddingFilesFromTsConfig: false,
568666
})
569667

570-
// Add source files
571-
project.addSourceFilesAtPaths(join(UI_COMPONENTS_DIR, '**/*Types.ts'))
668+
project.addSourceFilesAtPaths(COMPONENT_ADAPTER_TYPES_FILE)
669+
const adapterTypesFile = project.getSourceFile(COMPONENT_ADAPTER_TYPES_FILE)
670+
671+
if (!adapterTypesFile) {
672+
throw new Error(`Could not find componentAdapterTypes.ts at ${COMPONENT_ADAPTER_TYPES_FILE}`)
673+
}
572674

573-
// Get all interfaces and type aliases
574-
const sourceFiles = project.getSourceFiles(join(UI_COMPONENTS_DIR, '**/*Types.ts'))
675+
const sourceFiles = getTypeSourceFilesFromAdapterTypes(project, adapterTypesFile)
575676
const interfaces = sourceFiles.flatMap(sourceFile => sourceFile.getInterfaces())
576677
const typeAliases = sourceFiles.flatMap(sourceFile => sourceFile.getTypeAliases())
577678

679+
const isLiteralUnionType = (alias: TypeAliasDeclaration): boolean => {
680+
const type = alias.getType()
681+
if (type.isUnion()) {
682+
const unionTypes = type.getUnionTypes()
683+
return unionTypes.every(
684+
t => t.isLiteral() || t.isStringLiteral() || t.isNumberLiteral() || t.isBooleanLiteral(),
685+
)
686+
}
687+
return false
688+
}
689+
690+
const isDocumentableTypeAlias = (alias: TypeAliasDeclaration): boolean => {
691+
const type = alias.getType()
692+
if (type.isObject() && type.getProperties().length > 0) {
693+
return true
694+
}
695+
if (type.isUnion()) {
696+
const unionTypes = type.getUnionTypes()
697+
const hasObjectType = unionTypes.some(t => t.isObject() && t.getProperties().length > 0)
698+
if (hasObjectType) return true
699+
// Include literal unions - they'll be documented differently
700+
if (isLiteralUnionType(alias)) return true
701+
}
702+
return true
703+
}
704+
578705
// Create component type entries
579706
const componentEntries = [
580707
...interfaces.map(intf => {
@@ -588,7 +715,7 @@ async function generateAdapterPropDocs() {
588715
]
589716
return entry
590717
}),
591-
...typeAliases.map(alias => {
718+
...typeAliases.filter(isDocumentableTypeAlias).map(alias => {
592719
const entry: [string, ComponentType] = [
593720
alias.getName(),
594721
{
@@ -622,7 +749,16 @@ async function generateAdapterPropDocs() {
622749
// Create a shared set to track documented components across all sections
623750
const documented = new Set<string>()
624751
const sections = topLevelComponents
625-
.map(type => generateComponentSection(type, componentTypeMap, parentToChildren, 2, documented))
752+
.map(type =>
753+
generateComponentSection(
754+
type,
755+
componentTypeMap,
756+
parentToChildren,
757+
knownTypeNames,
758+
2,
759+
documented,
760+
),
761+
)
626762
.filter(Boolean)
627763
.join('\n\n')
628764

0 commit comments

Comments
 (0)