import ts from 'typescript' ; import { UnionType } from 'typescript' ; import path from 'path' ; import fs from 'fs' ; import * as util from './util' ; import * as dom from './dom' ; import { isEqual } from 'lodash' ; import * as envMod from './env' ; import { env } from './env' ; export { dom , util } ; export interface PlainObj < T = any > { [ key ] : T ; } export interface CreateOptions { dist ? : string ; flags ? : CreateDtsFlags ; } export enum ExportFlags { None = 0 , ExportEqual = 1 << 0 , Export = 1 << 1 } const NODE_MODULES = 'node_modules' ; const TYPE_ROOT = '@types/' ; const DTS_EXT = '.d.ts' ; const SyntaxKind = ts . SyntaxKind ; const nodeModulesRoots = [ util . formatUrl ( path . resolve ( process . cwd ( ) , `./${ NODE_MODULES }` ) ) , ] ; const cwdPkgInfo = tryGetPackageInfo ( process . cwd ( ) ) ; if ( cwdPkgInfo && cwdPkgInfo . dir ) { nodeModulesRoots . push ( util . formatUrl ( path . resolve ( cwdPkgInfo . dir , `./${ NODE_MODULES }` ) ) ) ; } const fromLibRE = /typescript\/lib\/lib(\.\w+)*\.d\.ts$/ ; export enum GetTypeDomFlags { None = 0 , TypeLiteralInline = 1 << 0 } export enum CreateDtsFlags { None = 0 , IgnorePrivate = 1 << 0 } export const defaultBuildFlags = ts . NodeBuilderFlags . AllowNodeModulesRelativePaths | ts . NodeBuilderFlags . GenerateNamesForShadowedTypeParams | ts . NodeBuilderFlags . NoTruncation | ts . NodeBuilderFlags . UseTypeOfFunction ; export interface ExportListObj { name : string ; node : ts . Node ; originalNode : ts . Node ; } export function getAnonymousName ( ) { return `T${ env . uniqId ++ }` ; } export function getTypeDom ( node ? , flags = GetTypeDomFlags . None ) { if ( ! node ) return ; switch ( node . kind ) { case SyntaxKind . StringKeyword : case SyntaxKind . StringLiteral : return dom . type . string ; case SyntaxKind . NumberKeyword : case SyntaxKind . NumericLiteral : return dom . type . number ; case SyntaxKind . LiteralType : return getLiteralTypeDom ( node as ts . LiteralTypeNode ) ; case SyntaxKind . BooleanKeyword : case SyntaxKind . TrueKeyword : case SyntaxKind . FalseKeyword : return dom . type . boolean ; case SyntaxKind . VoidKeyword : return dom . type . void ; case SyntaxKind . ObjectKeyword : return dom . type . object ; case SyntaxKind . ThisKeyword : return dom . type . this ; case SyntaxKind . TypeQuery : return getTypeQueryTypeDom ( node as ts . TypeQueryNode ) ; case SyntaxKind . ArrayType : return getArrayTypeDom ( node as ts . ArrayTypeNode ) ; case SyntaxKind . TypeLiteral : return getTypeLiteralTypeDom ( node as ts . TypeLiteralNode , flags ) ; case SyntaxKind . TypeReference : return getReferenceTypeDom ( node as ts . TypeReferenceNode ) ; case SyntaxKind . UnionType : return getUnionTypeDom ( node as ts . UnionTypeNode ) ; case SyntaxKind . IntersectionType : return getIntersectionTypeDom ( node as ts . IntersectionTypeNode ) ; case SyntaxKind . FunctionType : case SyntaxKind . ConstructorType : return getFunctionTypeDom ( node as ts . FunctionTypeNode ) ; case SyntaxKind . ImportType : return getImportTypeDom ( node as ts . ImportTypeNode ) ; case SyntaxKind . ParenthesizedType : return getTypeDom ( ( node as ts . ParenthesizedTypeNode ) . type ) ; case SyntaxKind . TypeOperator : return getOperatorTypeDom ( node as ts . TypeOperatorNode ) ; case SyntaxKind . AnyKeyword : case SyntaxKind . NullKeyword : case SyntaxKind . UndefinedKeyword : default : return dom . type . any ; } } export function getTypeDomFromType ( type ) { if ( type . flags & ts . TypeFlags . String ) { return dom . type . string ; } else if ( type . flags & ts . TypeFlags . Number ) { return dom . type . number ; } else if ( type . flags & ts . TypeFlags . Boolean ) { return dom . type . boolean ; } else if ( type . flags & ts . TypeFlags . NumberLiteral ) { return ( < ts . NumberLiteralType > type ) . value ; } else if ( type . flags & ts . TypeFlags . StringLiteral ) { return JSON . stringify ( ( < ts . StringLiteralType > type ) . value ) ; } else if ( type . flags & ts . TypeFlags . Any ) { return dom . type . any ; } else if ( type . flags & ts . TypeFlags . Null ) { return dom . type . null ; } else if ( type . flags & ts . TypeFlags . Undefined ) { return dom . type . undefined ; } else if ( type . flags & ts . TypeFlags . Void ) { return dom . type . void ; } else if ( type . flags & ts . TypeFlags . Union ) { return dom . create . union ( ( < UnionType > type ) . types . map ( t => getTypeDomFromType ( t ) ) ) ; } else if ( type . flags & ts . TypeFlags . Intersection ) { return dom . create . intersection ( ( < ts . IntersectionType > type ) . types . map ( t => getTypeDomFromType ( t ) ) ) ; } const typeNode = env . checker . typeToTypeNode ( type , undefined , defaultBuildFlags ) ; return getTypeDom ( typeNode ) || dom . type . any ; } export function getOperatorTypeDom ( node ) { if ( node . operator === SyntaxKind . KeyOfKeyword ) { const refer = getTypeDom ( node . type ) ; return refer === dom . type . any ? refer : dom . create . keyof ( refer ) ; } return dom . type . any ; } export function getCallSignatureDeclarationTypeDom ( node ) { return dom . create . callSignature ( getFunctionParametersTypeDom ( node . parameters ) , getTypeDom ( node . type ) || dom . type . void ) ; } export function getLiteralTypeDom ( typeNode ) { return getTypeDom ( typeNode . literal as ts . TypeNode ) ; } export function getFunctionTypeDom ( typeNode ) { return dom . create . functionType ( getFunctionParametersTypeDom ( typeNode . parameters ) , getTypeDom ( typeNode . type ) || dom . type . void ) ; } export function getTypeQueryTypeDom ( typeNode ) { const referTypeDom = getReferenceTypeDomFromEntity ( typeNode . exprName ) ; return referTypeDom ? dom . create . typeof ( referTypeDom ) : dom . type . any ; } export function getJSDocTypeLiteralTypeDom ( node ) { const decl = dom . create . objectType ( [ ] ) ; const tags = node . jsDocPropertyTags ; ( tags || [ ] ) . forEach ( tag => { if ( tag . typeExpression ) { const typeNode = tag . typeExpression . type ; const propName = util . getText ( tag . name ) ; let propTypeDom ; if ( ts . isJSDocTypeLiteral ( typeNode ) ) { propTypeDom = dom . create . property ( propName , getJSDocTypeLiteralTypeDom ( typeNode ) ) ; } else { const type = env . checker . getTypeFromTypeNode ( typeNode ) ; const typeDom = getTypeDomFromType ( type ) ; propTypeDom = dom . create . property ( propName , typeDom ) ; } if ( tag . isBracketed ) { propTypeDom . flags ! |= dom . DeclarationFlags . Optional ; } addMemberToObj ( decl , propTypeDom ) ; } } ) ; return decl ; } export function getTypeLiteralTypeDom ( typeNode , flags = GetTypeDomFlags . None ) { const members = typeNode . members ; const objectType = dom . create . objectType ( [ ] ) ; members . forEach ( member => { if ( ! member . name ) { if ( ts . isCallSignatureDeclaration ( member ) ) { const typeDom = getCallSignatureDeclarationTypeDom ( member ) ; addMemberToObj ( objectType , typeDom , { dropDuplicate : true } ) ; } return ; } const name = util . getText ( member . name ) ; if ( checkIsPrivate ( member . name , name ) ) { return ; } const typeDom = getPropTypeDomByNode ( name , member ) ; addJsDocToTypeDom ( typeDom , member . name ) ; addMemberToObj ( objectType , typeDom , { dropDuplicate : true } ) ; } ) ; if ( ! objectType . members . length ) { objectType . members . push ( dom . create . indexSignature ( 'key' , 'string' , dom . type . any ) ) ; } if ( flags & GetTypeDomFlags . TypeLiteralInline ) { return objectType ; } else { const interfaceDeclare = createInterfaceWithCache ( objectType . members ) ; return dom . create . namedTypeReference ( interfaceDeclare ) ; } } export function getIntersectionTypeDom ( typeNode ) { return dom . create . intersection ( typeNode . types . map ( node => getTypeDom ( node ) ) ) ; } export function getUnionTypeDom ( typeNode ) { return dom . create . union ( typeNode . types . map ( node => getTypeDom ( node ) ) ) ; } export function createDepsByFile ( file ) { const topEnv = envMod . getTopEnv ( ) ; const deps = topEnv . deps [ file ] ; if ( deps ) { return deps ; } const customEnv = create ( file ) ; const namespace = dom . create . namespace ( customEnv . exportDefaultName ! ) ; namespace . members . push ( dom . create . comment ( path . relative ( process . cwd ( ) , file ) ) ) ; customEnv . declaration . fragment . forEach ( decl => { decl . flags = dom . DeclarationFlags . Export ; ( decl as dom . DeclarationBase ) . namespace = namespace ; namespace . members . push ( decl ) ; } ) ; topEnv . declaration . fragment . push ( namespace ) ; return topEnv . deps [ file ] = { env : customEnv , namespace } ; } export function getImportTypeDom ( typeNode ) { if ( ts . isLiteralTypeNode ( typeNode . argument ) && ts . isStringLiteral ( typeNode . argument . literal ) ) { const importPath = typeNode . argument . literal . text ; const modName = getModNameByPath ( importPath ) ; let exportName ; if ( modName ) { exportName = collectImportModule ( modName , undefined , importPath ) ; } else { const filePath = util . resolveUrl ( importPath ) ; if ( filePath && path . extname ( filePath ) === '.js' ) { exportName = createDepsByFile ( filePath ) . namespace . name ; } } if ( exportName ) { const referenceType = dom . create . namedTypeReference ( exportName ) ; if ( typeNode . isTypeOf ) { return dom . create . typeof ( referenceType ) ; } return referenceType ; } } return dom . type . any ; } export function getReturnTypeFromDeclaration ( declaration ) { if ( declaration . type ) { return declaration . type ; } const signature = env . checker . getSignatureFromDeclaration ( declaration ) ; if ( ! signature ) { return ; } const type = env . checker . getReturnTypeOfSignature ( signature ! ) ; return env . checker . typeToTypeNode ( type , undefined , defaultBuildFlags ) ; } export function createClassDeclaration ( node , className ? ) { className = createNameByDecl ( node , className ) ; const classDeclaration = dom . create . class ( className ) ; addJsDocToTypeDom ( classDeclaration , node ) ; if ( node . heritageClauses ) { node . heritageClauses . forEach ( clause => { if ( clause . types . length && clause . token === ts . SyntaxKind . ExtendsKeyword ) { const expr = clause . types [ 0 ] . expression ; let typeNode ; if ( ts . isIdentifier ( expr ) ) { typeNode = getTypeNodeAtLocation ( expr ) ; } else if ( ts . isPropertyAccessExpression ( expr ) ) { typeNode = getTypeNodeAtLocation ( expr . name ) ; } if ( typeNode && ts . isTypeQueryNode ( typeNode ) ) { const reference = getReferenceTypeDomFromEntity ( typeNode . exprName ) ; if ( reference ) classDeclaration . baseType = dom . create . class ( reference . name ) ; } } } ) ; } eachPropertiesTypeDom < ts . ClassElement > ( node . members , ( name , member ) => { if ( ts . isConstructorDeclaration ( member ) ) { const constructorTypeDom = dom . create . constructor ( getFunctionParametersTypeDom ( member . parameters ) ) ; addJsDocToTypeDom ( constructorTypeDom , member ) ; if ( member . body && member . body . statements . length ) { findAssignToThis ( member . body . statements ) . forEach ( ( { name , node } ) => { addMemberToObj ( classDeclaration , getPropTypeDomByNode ( name , node ) , { dropDuplicate : true } ) ; } ) ; } addMemberToObj ( classDeclaration , constructorTypeDom ) ; return ; } if ( ! name ) { return ; } const typeDom = getPropTypeDomByNode ( name , member ) ; addJsDocToTypeDom ( typeDom , member ) ; addMemberToObj ( classDeclaration , typeDom , { dropDuplicate : true } ) ; } ) ; return classDeclaration ; } export function findAssignToThis ( statements ) { const assignList = [ ] ; util . findAssignByName ( statements , 'this' , ( { key , node } ) => { const propName = util . getText ( key ) ; if ( checkIsPrivate ( node , propName ) ) { return ; } assignList . push ( { name : propName , node : getTypeNodeAtLocation ( key ) , } ) ; } ) ; return assignList ; } export function addJsDocToTypeDom ( typeDom , originalNode ) { const jsDocs = util . getJSDocs ( originalNode ) ; typeDom . jsDocComment = jsDocs ? jsDocs . map ( doc => dom . create . jsDocComment ( doc . getText ( ) ) ) : undefined ; } export function eachPropertiesTypeDom < T extends ts . ClassElement | ts . ObjectLiteralElement > ( nodeList , callback ) { const propertyNameList = [ ] ; nodeList . forEach ( member => { const propertyName = util . getText ( member . name ) ; if ( propertyNameList . includes ( propertyName ) ) { return ; } if ( checkIsPrivate ( member , propertyName ) ) { return ; } propertyNameList . push ( propertyName ) ; callback ( propertyName , member ) ; } ) ; return propertyNameList ; } export function getPropTypeDomByNode ( name , node ? , flags = dom . DeclarationFlags . None ) { let typeDom ; const checkOptional = ( type ? , node ? ) => { if ( ( type && type . kind === SyntaxKind . UndefinedKeyword ) || ( node && util . hasQuestionToken ( node ) ) ) { flags |= dom . DeclarationFlags . Optional ; } } ; if ( name === '...' || ! node ) { return dom . create . property ( name , dom . type . any , flags ) ; } if ( util . modifierHas ( node , SyntaxKind . StaticKeyword ) ) { flags |= dom . DeclarationFlags . Static ; } if ( ts . isTypeNode ( node ) || ts . isToken ( node ) ) { checkOptional ( node ) ; const d = getTypeDom ( node ) || dom . type . any ; if ( dom . util . isFunctionType ( d ) ) { typeDom = dom . create . method ( name , d . parameters , d . returnType , flags ) ; } else { typeDom = dom . create . property ( name , d , flags ) ; } } else if ( ts . isMethodDeclaration ( node ) || ts . isMethodSignature ( node ) ) { const typeNode = getReturnTypeFromDeclaration ( node ) ; typeDom = dom . create . method ( name , getFunctionParametersTypeDom ( node . parameters ) , typeNode ? getTypeDom ( typeNode ) : dom . type . any , flags ) ; } else if ( ts . isGetAccessorDeclaration ( node ) || ts . isGetAccessor ( node ) ) { const typeNode = getTypeNodeAtLocation ( node ) ; checkOptional ( typeNode , node ) ; typeDom = dom . create . property ( name , getTypeDom ( typeNode ) || dom . type . any , flags ) ; } else if ( ts . isPropertyDeclaration ( node ) || ts . isPropertySignature ( node ) ) { checkOptional ( node . type , node ) ; typeDom = dom . create . property ( name , getTypeDom ( node . type ) || dom . type . any , flags ) ; } typeDom = typeDom || dom . create . property ( name , dom . type . any , flags ) ; return typeDom ; } export function getReferenceModule ( symbol ) { const symbolDeclaration = util . getDeclarationBySymbol ( symbol ) ; if ( ! symbolDeclaration ) return false ; const declarationFile = symbolDeclaration . getSourceFile ( ) . fileName ; const isFromLib = declarationFile . match ( fromLibRE ) ; const isFromNodeModule = nodeModulesRoots . find ( r => declarationFile . startsWith ( r ) ) ; if ( isFromLib ) { return ; } else if ( declarationFile === env . sourceFile . fileName ) { return symbolDeclaration ; } else if ( ! isFromNodeModule ) { return symbolDeclaration ; } let declaration = symbolDeclaration ; while ( declaration && ! ts . isSourceFile ( declaration ) ) { if ( util . isDeclareModule ( declaration ) ) { return declaration ; } declaration = declaration . parent ; } return symbolDeclaration . getSourceFile ( ) ; } export function getReferenceTypeDomFromEntity ( node ) { let referType ; let referenceModule ; const symbol = env . checker . getSymbolAtLocation ( node ) || util . getSymbol ( node ) ; if ( ! symbol ) { const decl = util . tryFindDeclarationByName ( node . getSourceFile ( ) , node ) ; if ( ! decl ) return ; referenceModule = decl ; } else { referenceModule = getReferenceModule ( symbol ) ; if ( referenceModule === false ) { return ; } } let interfaceName = util . getText ( node ) ; const checkAssignEqual = ( node , name ) => { const symbol = util . getSymbol ( node ) ! ; if ( ! symbol || ! symbol . exports ) return false ; const exportAssignment = symbol . exports . get ( ts . InternalSymbolName . ExportEquals ) ; return exportAssignment && util . getText ( ( exportAssignment . valueDeclaration as ts . ExportAssignment ) . expression ) === name ; } ; if ( referenceModule ) { if ( util . isDeclareModule ( referenceModule ) ) { referType = dom . ReferTypes . ambient ; const modName = util . getText ( referenceModule . name ) ; interfaceName = collectImportModule ( modName , checkAssignEqual ( referenceModule , interfaceName ) ? undefined : interfaceName ) ; } else if ( ts . isSourceFile ( referenceModule ) ) { referType = dom . ReferTypes . custom ; const modName = getModNameByPath ( referenceModule . fileName ) ; if ( modName ) { interfaceName = collectImportModule ( modName , checkAssignEqual ( referenceModule , interfaceName ) ? undefined : interfaceName , referenceModule . fileName ) ; } else { return ; } } else { referType = dom . ReferTypes . declaration ; const cache = env . declareCache . find ( ( { node } ) => node === referenceModule ) ; if ( ! cache ) { interfaceName = getDeclName ( interfaceName ) ; env . declareCache . push ( { node : referenceModule , name : interfaceName } ) ; const decl = createDeclarationTypeDom ( interfaceName , referenceModule ) ; if ( decl ) env . declaration . fragment . push ( decl ) ; } else { interfaceName = cache . name ; } } } else { referType = dom . ReferTypes . lib ; } return dom . create . namedTypeReference ( interfaceName , referType ) ; } export function createDeclarationTypeDom ( name , node ) { let decl ; if ( ts . isClassLike ( node ) ) { decl = createClassDeclaration ( node , name ) ; } else if ( ts . isFunctionLike ( node ) ) { decl = createFunctionDeclaration ( node , name ) ; } else if ( ts . isVariableDeclaration ( node ) ) { decl = createVariableDeclaration ( node , name ) ; } else if ( ts . isPropertyAccessExpression ( node ) ) { decl = createDeclByPropertyAccess ( node , name ) ; } else if ( ts . isJSDocTypedefTag ( node ) ) { decl = createDeclByTypedefTag ( node , name ) ; } else { return ; } return decl ; } export function createDeclByPropertyAccess ( node , name ? ) { name = createNameByDecl ( node , name ) ; return dom . create . const ( name , getPropertyAccessTypeDom ( node ) ) ; } export function createNameByDecl ( decl , name ? ) { return name || ( decl . name ? getDeclName ( util . getText ( decl . name ) ) : getAnonymousName ( ) ) ; } export function createDeclByTypedefTag ( node , name ? ) { if ( ! node . typeExpression ) { return ; } name = createNameByDecl ( node , name ) ; if ( ts . isJSDocTypeLiteral ( node . typeExpression ) ) { const { members } = getJSDocTypeLiteralTypeDom ( node . typeExpression ) ; createInterfaceWithCache ( members , name ) ; return ; } else { return dom . create . alias ( name , getTypeDom ( node . typeExpression . type ) ) ; } } export function getPropertyAccessTypeDom ( node ) { const symbol = env . checker . getSymbolAtLocation ( node . name ) ; if ( symbol && symbol . exports ) { const members = [ ] ; symbol . exports . forEach ( obj => { const declaration = util . getDeclarationBySymbol ( obj ) ; members . push ( dom . create . property ( obj . getName ( ) , getTypeDom ( declaration && getTypeNodeAtLocation ( declaration ) ) || dom . type . any ) ) ; } ) ; return createInterfaceWithCache ( members ) ; } else { const typeNode = getTypeNodeAtLocation ( ( node . parent as ts . BinaryExpression ) . right ) ; return getTypeDom ( typeNode ) || dom . type . any ; } } export function getReferenceTypeDom ( typeNode ) { const ref = getReferenceTypeDomFromEntity ( typeNode . typeName ) ; if ( ! ref ) return dom . type . any ; const typeArguments = util . getTypeArguments ( typeNode ) ; if ( typeArguments && typeArguments . length ) { ref . typeParameters = typeArguments . map ( type => getTypeDom ( type ) || dom . type . any ) ; } return transformBuildInRefer ( ref ) || ref ; } export function transformBuildInRefer ( ref ) { if ( ref . referType !== dom . ReferTypes . lib ) { return ; } switch ( ref . name ) { case 'String' : return dom . type . string ; case 'Number' : return dom . type . number ; case 'Boolean' : return dom . type . boolean ; case 'Function' : return dom . create . functionType ( [ dom . create . parameter ( 'args' , dom . type . array ( dom . type . any ) , dom . ParameterFlags . Rest ) , ] , dom . type . any ) ; case 'Array' : return dom . type . array ( ref . typeParameters [ 0 ] || dom . type . any ) ; default : break ; } } export function getArrayTypeDom ( typeNode ) { return dom . create . array ( getTypeDom ( typeNode . elementType ) ) ; } export function getFunctionParametersTypeDom ( parameters ) { const nameCache = { } ; const params = parameters . map ( param => { let type = param . type ; if ( ! type ) { type = getTypeNodeAtLocation ( param ) ; } let flags = ( param . initializer || util . hasQuestionToken ( param ) ) ? dom . ParameterFlags . Optional : dom . ParameterFlags . None ; if ( param . dotDotDotToken ) { flags |= dom . ParameterFlags . Rest ; } let name = util . getText ( param . name ) || getAnonymousName ( ) ; if ( nameCache [ name ] === undefined ) { nameCache [ name ] = 0 ; } else { nameCache [ name ] ++ ; name = `${ name }_${ nameCache [ name ] }` ; } return dom . create . parameter ( name , getTypeDom ( type ) || dom . type . any , flags ) ; } ) ; return params ; } export function checkIsPrivate ( obj , name ? ) { if ( env . flags & CreateDtsFlags . IgnorePrivate ) { return true ; } return ! ! util . findJsDocTag ( obj , 'private' ) || ( name && name . startsWith ( '_' ) ) ; } export function addMemberToObj ( obj , member , opt = { } ) { const declaration = obj as dom . ClassDeclaration ; if ( ( member as dom . PropertyDeclaration ) . name ) { const index = declaration . members . findIndex ( m => { return ( m as dom . PropertyDeclaration ) . name === ( member as dom . PropertyDeclaration ) . name ; } ) ; if ( index >= 0 ) { if ( opt . dropDuplicate ) { return ; } obj . members . splice ( index , 1 ) ; } } if ( opt . preInsert ) { declaration . members . unshift ( member as dom . ClassMember ) ; } else { declaration . members . push ( member as dom . ClassMember ) ; } } export function tryCreateFunctionAsClass ( fnName , node ) { const block = node . parent ; const isConstructor = util . findJsDocTag ( node , 'constructor' ) ; let isPrototypeClass = ! ! isConstructor ; if ( ts . isBlock ( block ) || ts . isSourceFile ( block ) ) { const classDeclare = dom . create . class ( fnName ) ; const originalName = util . getText ( node . name ) ; const prototypeExpression = `${ originalName }.prototype` ; util . findAssignByName ( block . statements , [ prototypeExpression , originalName ] , ( { name , key , value , node } ) => { const propName = util . getText ( key ) ; const keyIsPrototype = propName === 'prototype' ; const isStaticProp = name === originalName && ! keyIsPrototype ; const isPrototypeAssignment = name === originalName && keyIsPrototype ; if ( isPrototypeAssignment || name === prototypeExpression ) { isPrototypeClass = true ; } if ( isPrototypeAssignment ) { const typeNode = getTypeNodeAtLocation ( value ) ; const typeDom = getTypeDom ( typeNode , GetTypeDomFlags . TypeLiteralInline ) ; const cleanAssignment = ( ) => { classDeclare . members . length = 0 ; classDeclare . baseType = undefined ; } ; if ( dom . util . isObjectType ( typeDom ) ) { cleanAssignment ( ) ; typeDom . members . forEach ( member => addMemberToObj ( classDeclare , member ) ) ; } else if ( dom . util . isNamedTypeReference ( typeDom ) ) { cleanAssignment ( ) ; classDeclare . baseType = dom . create . class ( typeDom . name ) ; } } else { if ( checkIsPrivate ( node , propName ) ) { return ; } const typeNode = getTypeNodeAtLocation ( value ) ; const typeDom = getPropTypeDomByNode ( propName , typeNode ) ; if ( isStaticProp ) typeDom . flags = dom . DeclarationFlags . Static ; addJsDocToTypeDom ( typeDom , node ) ; addMemberToObj ( classDeclare , typeDom ) ; } } ) ; if ( isPrototypeClass ) { if ( node . body && node . body . statements . length ) { findAssignToThis ( node . body . statements ) . forEach ( ( { name , node } ) => { addMemberToObj ( classDeclare , getPropTypeDomByNode ( name , node ) ) ; } ) ; } addMemberToObj ( classDeclare , dom . create . constructor ( getFunctionParametersTypeDom ( node . parameters ) ) , { preInsert : true , } ) ; return classDeclare ; } } } export function createFunctionDeclaration ( node , fnName ? ) { let typeDom ; fnName = createNameByDecl ( node , fnName ) ; if ( ts . isFunctionDeclaration ( node ) ) { typeDom = tryCreateFunctionAsClass ( fnName , node ) ; } if ( ! typeDom ) { const signature = env . checker . getSignatureFromDeclaration ( node ) ! ; const returnType = env . checker . getReturnTypeOfSignature ( signature ) ; const returnTypeNode = env . checker . typeToTypeNode ( returnType , undefined , defaultBuildFlags ) ; typeDom = dom . create . function ( fnName , getFunctionParametersTypeDom ( node . parameters ) , getTypeDom ( returnTypeNode ) || dom . type . any ) ; } addJsDocToTypeDom ( typeDom , node ) ; return typeDom ; } function tryGetPackageInfo ( fileUrl ) { let pkgPath ; let currentDir = path . dirname ( fileUrl ) ; while ( ! fs . existsSync ( pkgPath = path . resolve ( currentDir , './package.json' ) ) ) { currentDir = path . dirname ( currentDir ) ; if ( currentDir === '/' || currentDir . endsWith ( NODE_MODULES ) ) { pkgPath = null ; break ; } } if ( pkgPath ) { const pkgInfo = JSON . parse ( fs . readFileSync ( pkgPath ) . toString ( ) ) ; return { dir : currentDir , pkgInfo , } ; } return ; } export function getModNameByPath ( fileName ) { if ( env . ambientModNames . includes ( fileName ) ) { return fileName ; } fileName = util . normalizeDtsUrl ( fileName ) ; if ( ! fileName . endsWith ( DTS_EXT ) || ! fs . existsSync ( fileName ) ) { return ; } const result = tryGetPackageInfo ( fileName ) ! || { } ; const basename = path . basename ( fileName , DTS_EXT ) ; const pkgName = result . pkgInfo && result . pkgInfo . name ; if ( pkgName && nodeModulesRoots . find ( r => fileName . startsWith ( r ) ) ) { const name = result . pkgInfo . name ; const modName = name . startsWith ( TYPE_ROOT ) ? name . substring ( TYPE_ROOT . length ) : name ; const modPath = fileName . substring ( result . dir . length + 1 ) ; if ( ( result . pkgInfo . main && path . basename ( result . pkgInfo . main , '.js' ) === path . basename ( modPath , '.d.ts' ) ) || ( ! result . pkgInfo . main && modPath === 'index.d.ts' ) || result . pkgInfo . types === modPath ) { return modName ; } return `${ modName }/${ modPath . substring ( 0 , modPath . length - DTS_EXT . length ) }` ; } else { const dir = path . dirname ( fileName ) ; const from = util . formatUrl ( path . relative ( path . dirname ( env . dist ) , path . join ( dir , basename ) ) ) ; return from . startsWith ( '.' ) ? from : `./${ from }` ; } } function collectImportModule ( name , exportName ? , realPath ? ) { realPath = realPath || name ; const importObj = env . importCache [ realPath ] = env . importCache [ realPath ] || { default : undefined , list : [ ] , realPath , from : name , } as envMod . ImportCacheElement ; if ( exportName ) { let existImportObj = importObj . list . find ( ( { name } ) => name === exportName ) ; if ( ! existImportObj ) { existImportObj = { name : exportName , as : getDeclName ( exportName ) } ; importObj . list . push ( existImportObj ) ; } return existImportObj . as ! ; } else { if ( ! importObj . default ) { importObj . default = getDeclName ( util . formatName ( name ) ) ; } return importObj . default ! ; } } export function getTypeNodeAtLocation ( node , flag ? ) { const type = env . checker . getTypeAtLocation ( node ) ; return env . checker . typeToTypeNode ( type , undefined , flag || defaultBuildFlags ) ; } export function createVariableDeclaration ( node , name ? ) { name = createNameByDecl ( node , name ) ; const typeNode = getTypeNodeAtLocation ( node . name ) ; const typeDom = getTypeDom ( typeNode ) ; return dom . create . const ( name , typeDom ) ; } export function getDeclName ( name ) { name = dom . reservedWords . includes ( name ) ? `_${ name }` : name ; if ( env . publicNames [ name ] === undefined ) { env . publicNames [ name ] = 0 ; return name ; } else { env . publicNames [ name ] ++ ; return getDeclName ( `${ name }_${ env . publicNames [ name ] }` ) ; } } export function createInterfaceWithCache ( members , name ? ) { const interfaceDeclare = env . interfaceList . find ( obj => isEqual ( obj . members , members ) ) ; if ( interfaceDeclare ) { return interfaceDeclare ; } const nsi = createExportInterface ( name || getAnonymousName ( ) ) ; nsi . members = members ; env . interfaceList . push ( nsi ) ; return nsi ; } export function createExportInterface ( name ) { const interfaceType = dom . create . interface ( name ) ; if ( env . exportFlags & ExportFlags . ExportEqual ) { interfaceType . namespace = env . exportNamespace ; env . exportNamespace ! . members . push ( interfaceType ) ; } else { env . declaration . fragment . push ( interfaceType ) ; } interfaceType . flags = dom . DeclarationFlags . Export ; return interfaceType ; } export function createExportNameByFile ( file ) { let name = path . basename ( file , path . extname ( file ) ) ; const pkgInfoPath = path . resolve ( path . dirname ( file ) , 'package.json' ) ; if ( fs . existsSync ( pkgInfoPath ) ) { const pkgInfo = JSON . parse ( fs . readFileSync ( pkgInfoPath ) . toString ( ) ) ; name = pkgInfo . name || name ; } if ( name === 'index' ) { name = path . basename ( path . dirname ( file ) ) ; } return getDeclName ( `_${ util . formatName ( name , true ) }` ) ; } export function create ( file , options ? ) { const cacheEnv = envMod . getEnv ( file ) ; if ( cacheEnv ) { return cacheEnv ; } envMod . createEnv ( file , options ) ; const declaration = env . declaration ; if ( ! env . sourceFile ) { declaration . fragment . push ( dom . create . comment ( 'source file create fail' ) ) ; return envMod . popEnv ( ) ; } const { exportEqual , exportList } = util . findExports ( env . sourceFile ) ; if ( ! exportEqual && ! exportList . size ) { declaration . fragment . push ( dom . create . comment ( 'cannot find export module' ) ) ; return envMod . popEnv ( ) ; } env . exportFlags = exportEqual ? ExportFlags . ExportEqual : ExportFlags . Export ; let exportInterface ; env . exportDefaultName = createExportNameByFile ( env . file ) ; env . exportNamespace = dom . create . namespace ( env . exportDefaultName ) ; if ( exportList . size ) { if ( ! exportEqual ) { exportList . forEach ( ( _ , name ) => getDeclName ( name ) ) ; } exportList . forEach ( ( { node , originalNode } , name ) => { if ( checkIsPrivate ( node , name ) ) { return ; } const typeNode = getTypeNodeAtLocation ( node ) ; let typeDom = getTypeDom ( typeNode ) || dom . type . any ; if ( env . exportFlags & ExportFlags . Export ) { if ( name === 'default' ) { const name = getDeclName ( util . getText ( node ) || getAnonymousName ( ) ) ; typeDom = dom . util . typeToDeclaration ( name , typeDom ) ; if ( dom . util . isCanBeExportDefault ( typeDom ) ) { typeDom . flags = dom . DeclarationFlags . ExportDefault ; } else { if ( dom . util . isConstDeclaration ( typeDom ) ) { const name = dom . util . isTypeofReference ( typeDom . type ) ? typeDom . type . type . name : ( dom . util . isNamedTypeReference ( typeDom . type ) ? typeDom . type . name : undefined ) ; const tds = name ? env . declaration . fragment . filter ( member => ( dom . util . isNamedDeclarationBase ( member ) && name === member . name ) ) : undefined ; if ( tds && tds . length === 1 && dom . util . isCanBeExportDefault ( tds [ 0 ] ) ) { tds [ 0 ] . flags ! |= dom . DeclarationFlags . ExportDefault ; return ; } } env . declaration . export . push ( dom . create . exportDefault ( name ) ) ; } } else { typeDom = dom . util . typeToDeclaration ( name , typeDom , dom . DeclarationFlags . Export ) ; } addJsDocToTypeDom ( typeDom , originalNode ) ; env . declaration . fragment . push ( typeDom ) ; } else { if ( ! exportInterface ) { exportInterface = dom . create . interface ( getAnonymousName ( ) ) ; env . declaration . fragment . push ( exportInterface ) ; } const memberDom = dom . create . property ( name , typeDom ) ; addJsDocToTypeDom ( memberDom , originalNode ) ; exportInterface . members . push ( memberDom ) ; } } ) ; } if ( exportEqual ) { const typeNode = getTypeNodeAtLocation ( exportEqual . node ) ; let typeDom = getTypeDom ( typeNode ) || dom . type . any ; typeDom = exportInterface ? dom . create . intersection ( [ typeDom , exportInterface ] ) : typeDom ; typeDom = dom . create . const ( env . exportDefaultName , typeDom ) ; declaration . fragment . push ( typeDom ) ; declaration . export . push ( dom . create . exportEquals ( env . exportDefaultName ) ) ; } Object . keys ( env . importCache ) . forEach ( k => { const obj = env . importCache [ k ] ; if ( obj . default ) { declaration . import . push ( dom . create . importAll ( obj . default , obj . from ) ) ; } if ( obj . list . length ) { declaration . import . push ( dom . create . importNamed ( obj . list , obj . from ) ) ; } } ) ; if ( env . exportNamespace && env . exportNamespace . members . length ) { declaration . fragment . push ( env . exportNamespace ) ; } return envMod . popEnv ( ) ; }