66 Node ,
77 PropertySignature ,
88 Symbol as TSMorphSymbol ,
9+ SourceFile ,
910} from 'ts-morph'
1011import { writeFile } from 'fs/promises'
1112import { join , dirname } from 'path'
@@ -14,7 +15,10 @@ import { fileURLToPath } from 'url'
1415const __filename = fileURLToPath ( import . meta. url )
1516const __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+ )
1822const DOCS_OUTPUT_DIR = join ( __dirname , '../docs/component-adapter' )
1923const DOCS_OUTPUT_FILE = join ( DOCS_OUTPUT_DIR , 'component-inventory.md' )
2024
@@ -64,7 +68,11 @@ function isBooleanUnion(types: Type[]): boolean {
6468function 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 ( / i m p o r t \( " [ ^ " ] + " \) \. / 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+
564662async 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