import * as ts from 'typescript' ; import { getCompilerOptions } from './getCompilerOptions' ; export interface DocEntry { name ? : string ; fileName ? : string ; documentation ? : string ; type ? : string ; calls ? : DocEntry [ ] ; parameters ? : DocEntry [ ] ; returnType ? : string ; checkerReturnType ? : string ; retTypeObjProps ? : { name : string ; type : string ; } [ ] ; retTypeIsArray ? : boolean ; retTypeIsNullable ? : boolean ; retTypeIsOptional ? : boolean ; param ? : { typeName : string ; objectProps : Record < string , string > ; } ; } /** returns DocEntry objects to be stored as a type information dump */ export function processFile ( fileName ) { const program = ts . createProgram ( [ fileName ] , getCompilerOptions ( ) ) ; const checker = program . getTypeChecker ( ) ; const output = { } ; const sourceFile = program . getSourceFile ( fileName ) ; if ( ! sourceFile ) { throw new Error ( `Missing source file for ${ fileName }` ) ; } if ( sourceFile . isDeclarationFile ) { throw new Error ( `Provided source file ${ fileName } is a declaration and missing implementation` ) ; } const exportedSymbols = checker . getExportsOfModule ( checker . getSymbolAtLocation ( sourceFile ) ! ) ; exportedSymbols . forEach ( ( exportedSymbol ) => { const exportedSymbolName = exportedSymbol . getName ( ) ; const exportedSymbolFlags = exportedSymbol . getFlags ( ) ; if ( exportedSymbolFlags === ts . SymbolFlags . Function ) { return ; } let valueDeclaration = undefined ; if ( exportedSymbolFlags === ts . SymbolFlags . Alias ) { valueDeclaration = exportedSymbol . getDeclarations ( ) ! [ 0 ] ; } else if ( exportedSymbolFlags !== ts . SymbolFlags . BlockScopedVariable ) { return ; } if ( ! valueDeclaration ) { ( { valueDeclaration } = exportedSymbol ) ; } if ( ! valueDeclaration ) { throw new Error ( `Exported symbol ${ exportedSymbolName } is missing value declaration` ) ; } const symbolType = checker . getTypeOfSymbolAtLocation ( exportedSymbol , valueDeclaration ) ; if ( symbolType . getFlags ( ) !== ts . TypeFlags . Object ) { return output ; } const objectType = symbolType as ts . ObjectType ; output [ exportedSymbol . name ] = objectType . getProperties ( ) . map ( ( symbol ) => serializeFunction ( symbol ) ) ; } ) ; return output ; function symbolDocumentationToString ( symbol ) { return ts . displayPartsToString ( symbol . getDocumentationComment ( checker ) ) ; } /** Serialize a class symbol information */ function serializeFunction ( symbol ) { const details = { name : symbol . getName ( ) , documentation : symbolDocumentationToString ( symbol ) , type : checker . typeToString ( checker . getTypeOfSymbolAtLocation ( symbol , symbol . valueDeclaration ! ) ) , } ; const functionType = checker . getTypeOfSymbolAtLocation ( symbol , symbol . valueDeclaration ! ) ; details . calls = functionType . getCallSignatures ( ) . map ( serializeCallSignature ) ; return details ; } function serializeCallSignature ( signature ) { let retTypeObjProps = undefined ; let retTypeIsArray = undefined ; let returnType = signature . getReturnType ( ) ; let retTypeIsNullable = false ; let retTypeIsOptional = false ; if ( returnType . isUnion ( ) ) { const otherReturnTypes = returnType . types . filter ( ( type ) => { switch ( type . getFlags ( ) ) { case ts . TypeFlags . Null : retTypeIsNullable = true ; return false ; case ts . TypeFlags . Undefined : retTypeIsOptional = true ; return false ; default : return true ; } } ) ; if ( otherReturnTypes . length !== 1 ) { throw new Error ( `Union return type of ${ checker . typeToString ( returnType ) } is not supported` ) ; } [ returnType ] = otherReturnTypes ; } if ( returnType . getFlags ( ) === ts . TypeFlags . Object ) { let objectReturnType = returnType as ts . ObjectType ; if ( objectReturnType . getSymbol ( ) ?. name === 'Array' ) { const refReturnType = objectReturnType as ts . TypeReference ; retTypeIsArray = true ; returnType = checker . getTypeArguments ( refReturnType ) ! [ 0 ] as ts . ObjectType ; objectReturnType = returnType as ts . ObjectType ; } retTypeObjProps = objectReturnType . getProperties ( ) . map ( ( symbol ) => ( { name : symbol . getName ( ) , type : symbol . valueDeclaration . getChildAt ( 2 ) . getText ( ) , } ) ) ; } return { parameters : signature . parameters . map ( serializeParameterSymbol ) , returnType : signature . declaration ! . type ?. getText ( ) ?? checker . typeToString ( returnType ) , checkerReturnType : checker . typeToString ( returnType ) , retTypeObjProps , retTypeIsArray , retTypeIsNullable , retTypeIsOptional , documentation : ts . displayPartsToString ( signature . getDocumentationComment ( checker ) ) , } ; } /** Serialize a symbol into a json object */ function serializeParameterSymbol ( symbol ) { const parameterDeclaration = symbol . valueDeclaration ! ; if ( ! ts . isParameter ( parameterDeclaration ) ) { throw new Error ( 'Expecting parameter declaration' ) ; } const type = checker . getTypeOfSymbolAtLocation ( symbol , parameterDeclaration ) ; return { name : symbol . getName ( ) , documentation : ts . displayPartsToString ( symbol . getDocumentationComment ( checker ) ) , type : checker . typeToString ( type ) , param : serializeParameterDeclaration ( parameterDeclaration ) , } ; } function serializeParameterDeclaration ( node ) { const objectProps = { } ; if ( ts . isTypeLiteralNode ( node . type ! ) ) { node . type ! . members . forEach ( ( member ) => { if ( ! ts . isPropertySignature ( member ) || ! member . type ) { return ; } objectProps [ member . name . getText ( ) ] = member . type ! . getText ( ) ; } ) ; } return { typeName : node . type ! . getText ( ) , objectProps , } ; } }