import { Node , Project , ts } from "ts-morph" ; import * as fs from "fs" ; import { getPackageDetails , PackageDetails } from "./packageJson" ; export interface PackageAndTypeData { packageDetails : PackageDetails ; typeData : TypeData [ ] ; project : Project ; } export interface TypeData { readonly name : string ; readonly kind : string ; readonly node : Node ; } export function getFullTypeName ( typeData ) { return `${ typeData . kind }_${ typeData . name }` ; } export function hasDocTag ( data , tagName ) { if ( Node . isJSDocableNode ( data . node ) ) { for ( const doc of data . node . getJsDocs ( ) ) { for ( const tag of doc . getTags ( ) ) { if ( tag . getTagName ( ) === tagName ) { return true ; } } } } return false ; } function getNodeTypeData ( node , namespacePrefix ? ) { if ( Node . isNamespaceDeclaration ( node ) ) { const typeData = [ ] ; for ( const s of node . getStatements ( ) ) { typeData . push ( ... getNodeTypeData ( s , node . getName ( ) ) ) ; } return typeData ; } if ( Node . isVariableStatement ( node ) ) { const typeData = [ ] ; for ( const dec of node . getDeclarations ( ) ) { typeData . push ( ... getNodeTypeData ( dec , namespacePrefix ) ) ; } return typeData ; } if ( Node . isClassDeclaration ( node ) || Node . isEnumDeclaration ( node ) || Node . isInterfaceDeclaration ( node ) || Node . isTypeAliasDeclaration ( node ) || Node . isVariableDeclaration ( node ) || Node . isFunctionDeclaration ( node ) ) { const name = namespacePrefix !== undefined ? `${ namespacePrefix }.${ node . getName ( ) }` : node . getName ( ) ! ; const typeData = [ { name , kind : node . getKindName ( ) , node , } ] ; return typeData ; } throw new Error ( `Unknown Export Kind: ${ node . getKindName ( ) }` ) ; } export function toTypeString ( prefix , typeData ) { const node = typeData . node ; let typeParams ; if ( Node . isInterfaceDeclaration ( node ) || Node . isTypeAliasDeclaration ( node ) || Node . isClassDeclaration ( node ) ) { if ( node . getTypeParameters ( ) . length > 0 && node . getTypeParameters ( ) . some ( ( tp ) => tp . getDefault ( ) === undefined ) ) { typeParams = `<${ node . getTypeParameters ( ) . map ( ( ) => "any" ) . join ( "," ) }>` ; } } const typeStringBase = `${ prefix }.${ typeData . name }${ typeParams ?? "" }` ; switch ( node . getKind ( ) ) { case ts . SyntaxKind . ClassDeclaration : return `Omit<${ typeStringBase },"">` ; case ts . SyntaxKind . VariableDeclaration : case ts . SyntaxKind . FunctionDeclaration : return `typeof ${ typeStringBase }` ; default : return typeStringBase ; } } function tryFindDependencyPath ( packageDir , dependencyName ) { let testPath = packageDir ; while ( ! fs . existsSync ( `${ testPath }/node_modules/${ dependencyName }/package.json` ) && ! fs . existsSync ( `${ testPath }/lerna.json` ) ) { testPath += "/.." ; } return `${ testPath }/node_modules/${ dependencyName }` ; } export function generateTypeDataForProject ( packageDir , dependencyName ) { const basePath = dependencyName === undefined ? packageDir : tryFindDependencyPath ( packageDir , dependencyName ) ; const tsConfigPath = `${ basePath }/tsconfig.json` ; if ( ! fs . existsSync ( tsConfigPath ) ) { throw new Error ( `Tsconfig json does not exist: ${ tsConfigPath }.\nYou may need to install the package via npm install in the package dir.` ) ; } const packageDetails = getPackageDetails ( basePath ) ; const project = new Project ( { skipFileDependencyResolution : true , tsConfigFilePath : tsConfigPath , } ) ; const file = project . getSourceFile ( "index.ts" ) ; if ( file == undefined ) { throw new Error ( "index.ts does not exist in package source.\nYou may need to install the package via npm install in the package dir." ) ; } const typeData = [ ] ; const exportedDeclarations = file . getExportedDeclarations ( ) ; for ( const declarations of exportedDeclarations . values ( ) ) { for ( const dec of declarations ) { typeData . push ( ... getNodeTypeData ( dec ) ) ; } } return { packageDetails , typeData : typeData . sort ( ( a , b ) => a . name . localeCompare ( b . name ) ) , project , } ; }