import * as ts from 'typescript' ; import { TsInterfaceInfo , TsInterfacePropertyInfo , } from '../../../../clientTypes' ; const defaultTsConfig = { moduleResolution : ts . ModuleResolutionKind . NodeJs , } ; export function collectInterfaceInfo ( fileName , exportName , options = defaultTsConfig ) { let program = ts . createProgram ( [ fileName ] , options ) ; let checker = program . getTypeChecker ( ) ; const sourceFile = program . getSourceFile ( fileName ) ! ; const fileSymbol = checker . getSymbolAtLocation ( sourceFile ) ; if ( ! fileSymbol || ! fileSymbol . exports ) { throw new Error ( `unexpected fileSymbol` ) ; } const escapedExportName = ts . escapeLeadingUnderscores ( exportName ) ; const exportSymbol = fileSymbol . exports . get ( escapedExportName ) ; if ( ! exportSymbol ) { throw new Error ( `Named export ${ exportName } is not found in file` ) ; } const sourceDeclareSymbol = getAliasedSymbolIfNecessary ( exportSymbol ) ; const sourceDeclare = sourceDeclareSymbol . declarations ?. [ 0 ] ; if ( ! sourceDeclare ) { throw new Error ( `Can't find sourceDeclare for ${ exportName }` ) ; } const interfaceInfo = collectInterfaceInfo ( sourceDeclare , sourceDeclareSymbol ) ; return interfaceInfo ; function getAliasedSymbolIfNecessary ( symbol ) { if ( ( symbol . flags & ts . SymbolFlags . Alias ) !== 0 ) return checker . getAliasedSymbol ( symbol ) ; return symbol ; } function collectInterfaceInfo ( node , symbol ) { if ( ! ts . isInterfaceDeclaration ( node ) ) throw new Error ( `target is not an InterfaceDeclaration` ) ; const type = checker . getTypeAtLocation ( node ) ; if ( ! symbol ) throw new Error ( `can't find symbol` ) ; const name = node . name . getText ( ) ; const commentText = getComment ( node , node . getSourceFile ( ) . getFullText ( ) ) ?? '' ; const description = ts . displayPartsToString ( symbol . getDocumentationComment ( checker ) ) ; const propertiesInfo = [ ] ; symbol . members ?. forEach ( ( symbol ) => { const name = symbol . name ; const declaration = symbol . valueDeclaration ; if ( ! ( declaration && ( ts . isPropertySignature ( declaration ) || ts . isMethodSignature ( declaration ) ) ) ) { if ( symbol . getFlags ( ) & ts . SymbolFlags . TypeParameter ) return ; console . warn ( `unexpected declaration type in interface. name: ${ name }, kind: ${ ts . SyntaxKind [ declaration ?. kind as any ] }` ) ; return ; } const commentText = getComment ( declaration , declaration . getSourceFile ( ) . getFullText ( ) ) ?? '' ; const typeText = declaration . type ?. getFullText ( ) ?? '' ; const description = ts . displayPartsToString ( symbol . getDocumentationComment ( checker ) ) ; const isOptional = ! ! ( symbol . getFlags ( ) & ts . SymbolFlags . Optional ) ; const jsDocTags = symbol . getJsDocTags ( ) ; const defaultValueTag = jsDocTags . find ( ( t ) => t . name === 'defaultValue' || 'default' ) ; const defaultValue = defaultValueTag ?. text ?. [ 0 ] . text ; propertiesInfo . push ( { name , type : typeText , description , defaultValue , optional : isOptional , } ) ; } ) ; const interfaceInfo = { name , description , properties : propertiesInfo , } ; return interfaceInfo ; } /** True if this is visible outside this file, false otherwise */ function isNodeExported ( node ) { return ( ( ts . getCombinedModifierFlags ( node as ts . Declaration ) & ts . ModifierFlags . Export ) !== 0 && ! ! node . parent && node . parent . kind === ts . SyntaxKind . SourceFile ) ; } } function getJSDocCommentRanges ( node , text ) { return ( ts as any ) . getJSDocCommentRanges . apply ( ts , arguments ) ; } function getComment ( declaration , sourceFileFullText ) { const ranges = getJSDocCommentRanges ( declaration , sourceFileFullText ) ; if ( ! ranges || ! ranges . length ) return ; const range = ranges [ ranges . length - 1 ] ; if ( ! range ) return ; const text = sourceFileFullText . slice ( range . pos , range . end ) ; return text ; } /**
 * ref:
 *
 * https://github.com/microsoft/TypeScript/wiki/Using-the-Compiler-API
 *
 * https://stackoverflow.com/questions/59838013/how-can-i-use-the-ts-compiler-api-to-find-where-a-variable-was-defined-in-anothe
 *
 * https://stackoverflow.com/questions/60249275/typescript-compiler-api-generate-the-full-properties-arborescence-of-a-type-ide
 *
 * https://stackoverflow.com/questions/47429792/is-it-possible-to-get-comments-as-nodes-in-the-ast-using-the-typescript-compiler
 *
 * Instructions of learning ts compiler:
 * https://stackoverflow.com/a/58885450
 *
 * https://learning-notes.mistermicheels.com/javascript/typescript/compiler-api/
 */