import fs = require ( 'fs' ) ; import path = require ( 'path' ) ; import clone = require ( 'clone' ) ; export namespace dtsmake { const DTS_INTERFACE_STR = "!!!dtsinterface!!!" ; const DTS_NAMESPACE_STR = "!!!dtsnamespace!!!" ; export class DTSMake { /**
         * ternjs json format data
         */ ternjsData : JSON ; /**
         * currently nodejs module internal name
         * ex. "node_modules/path/to/module`js"
         */ nodeModuleName : string ; /**
         * user defined name when tern/condense
         */ userDefinedModuleName : string ; /**
         * Default Options
         */ option : Option = { isDebug : false , isOutVoidAsAny : false , isExportInterfaceSameNameVar : true , isAnnotateTypeInstance : true , isNodeJsModule : false , isOutExport : true , globalObject : Option . GlobalObject . WRAP , exportStyle : Option . ExportStyle . LEGACY , exportModuleName : "" } ; /**
         * main function
         * load json file
         */ main ( srcPathStr , distPathStr , options ? ) ; /**
         *
         */ main ( srcString , distPathStr , options ? ) ; main ( param1 , distPathStr , options ? ) { this . overrideDefaultOptions ( options ) ; if ( /\n/ . test ( param1 ) ) { let s = this . parseTernJson ( JSON . parse ( param1 ) , distPathStr ) ; this . saveTSDFile ( distPathStr , s ) ; } else if ( /\.json$/ . test ( param1 ) ) { this . loadTernJson ( param1 , ( data ) => { let s = this . parseTernJson ( data , distPathStr ) ; this . saveTSDFile ( distPathStr , s ) ; } ) ; } } private overrideDefaultOptions ( options ? ) { if ( options ) { for ( let i in options ) { if ( this . option [ i ] !== undefined || this . option [ i ] !== null ) { this . option [ i ] = options [ i ] ; } } } } param pathStr loadTernJson ( pathStr , cb ) { fs . readFile ( pathStr , "UTF-8" , ( err , data ) => { if ( err ) throw Error ( err . name + " " + err . code + " " + err . message + " " + err . path ) ; this . ternjsData = JSON . parse ( data . toString ( ) ) ; console . log ( "Load a JSON file complete." ) ; cb ( this . ternjsData ) ; } ) ; } param name param src saveTSDFile ( name , src ) { try { fs . writeFile ( `${ name }.d.ts` , src , { encoding : "UTF-8" } , ( err ) => { if ( err ) throw Error ( err . name + " " + err . code + " " + err . message + " " + err . path ) ; console . log ( `File saved. (${ name }.d.ts)` ) ; } ) ; } catch ( e ) { throw Error ( e ) ; } return true ; } saveJSON ( pathStr , data , complete ) { fs . writeFile ( pathStr , data , { encoding : "UTF-8" } , ( e ) => { if ( e ) throw Error ( e . name + " " + e . code + " " + e . message + " " + e . path ) ; complete ( ) ; } ) ; } parseTernJson ( data , distPathStr ? ) { this . depth = 0 ; let o = this . parseJsonNodeDTS ( data ) ; let d = this . preModifiedJson ( o ) ; if ( this . option . isOutExport && this . userDefinedModuleName && this . option . isNodeJsModule ) { d = this . replaceExportNamespace ( d ) ; } if ( this . option . isDebug ) { this . saveJSON ( distPathStr + ".json" , JSON . stringify ( d ) , ( ) => { } ) ; } let s = this . parseToDTS ( d ) ; if ( this . option . lib ) { let refs = this . option . lib . map ( ( v , i , a ) => { return `/// <reference path="${ v }" />` ; } ) . join ( "\n" ) ; s = refs + "\n" + s ; } s = `// Type definitions for ${ this . userDefinedModuleName }
// Project: [LIBRARY_URL_HERE] 
// Definitions by: [YOUR_NAME_HERE] <[YOUR_URL_HERE]> 
// Definitions: https://github.com/borisyankov/DefinitelyTyped
` + s ; if ( this . option . isOutExport ) { let n = this . option . exportModuleName ? this . option . exportModuleName : this . userDefinedModuleName ; const reg = /^['"]([^']+)['"]$/ ; if ( reg . test ( n ) ) { n = RegExp . $1 ; } s += `
declare module '${ n }' {
` ; if ( this . option . exportStyle === dtsmake . Option . ExportStyle . ES6 ) { s += `
	export default ${ this . userDefinedModuleName };    //es6 style module export 
` ; } else { s += `
	export = ${ this . userDefinedModuleName };    //legacy ts module export
` ; } s += "}" + "\n" ; } return s ; } private modJson = { } ; preModifiedJson ( data ) { let modJson = clone ( data ) ; const reg = /[.]/ ; for ( let i in modJson [ TernDef . DEFINE ] ) { let value = modJson [ TernDef . DEFINE ] [ i ] ; if ( typeof i === "string" && reg . test ( i ) ) { let p = i . split ( "." ) ; if ( p . length <= 1 ) continue ; let s = "" ; if ( value [ 0 ] && value [ 0 ] . class && value [ 0 ] . type === TSObjType . CLASS ) { let np = value [ 0 ] . class . split ( "." ) ; s = np [ np . length - 1 ] ; } else { s = this . resolvePathToDTSName ( p ) ; } this . searchAndReplaceDTS ( modJson , p , s ) ; if ( typeof value === "string" ) { let typeStr = value . toString ( ) ; modJson [ TernDef . DEFINE ] [ i ] = { } ; modJson [ TernDef . DEFINE ] [ i ] [ TernDef . TYPE ] = typeStr ; } else if ( value instanceof Array ) { if ( value [ 0 ] . class ) { } } modJson [ TernDef . DEFINE ] [ i ] [ DTS_INTERFACE_STR ] = s ; const ns = this . resolveNamespace ( p ) ; if ( ns !== "" ) { modJson [ TernDef . DEFINE ] [ i ] [ DTS_NAMESPACE_STR ] = ns ; } } } const GENDOC = "\n[dtsmake] this is generated by dtsmake.\n" ; for ( let j in modJson ) { if ( typeof j === "string" && this . isJSGlobalObject ( j ) ) { switch ( this . option . globalObject ) { case Option . GlobalObject . REMOVE : break ; case Option . GlobalObject . WRAP : let n = ( this . userDefinedModuleName ? this . userDefinedModuleName : "Lib" ) ; if ( ! modJson [ n ] ) modJson [ n ] = { } ; let c = j ; if ( modJson [ n ] [ c ] ) { c = c + ( Math . random ( ) * 1000 | 0 ) ; } modJson [ n ] [ c ] = clone ( modJson [ j ] ) ; modJson [ n ] [ c ] [ DTS_INTERFACE_STR ] = c ; break ; case Option . GlobalObject . RENAME : let n2 = ( this . userDefinedModuleName ? this . userDefinedModuleName : "Lib" ) + "$" + j ; if ( modJson [ n2 ] ) { n2 = n2 + ( Math . random ( ) * 1000 | 0 ) ; } modJson [ n2 ] = { } ; modJson [ n2 ] = clone ( modJson [ j ] ) ; break ; case Option . GlobalObject . RENAME_EXTEND : break ; } modJson [ j ] = null ; delete modJson [ j ] ; } } this . modJson = modJson ; return modJson ; } replaceExportNamespace ( data ) { let modJson = clone ( data ) ; if ( modJson [ TernDef . DEFINE ] [ TernDef . NODE ] ) { let nodejsNode = modJson [ TernDef . DEFINE ] [ TernDef . NODE ] ; for ( let i in nodejsNode ) { nodejsNode [ this . userDefinedModuleName ] = clone ( nodejsNode [ i ] ) ; delete nodejsNode [ i ] ; } } for ( let i in modJson [ TernDef . DEFINE ] ) { let cNode = modJson [ TernDef . DEFINE ] [ i ] ; if ( cNode [ DTS_NAMESPACE_STR ] && ! /\./ . test ( cNode [ DTS_NAMESPACE_STR ] ) ) { cNode [ DTS_NAMESPACE_STR ] = this . userDefinedModuleName ; } } if ( ( modJson [ this . userDefinedModuleName ] && modJson [ this . userDefinedModuleName ] instanceof Object && Object . keys ( modJson [ this . userDefinedModuleName ] ) . length === 0 ) || ( modJson [ this . userDefinedModuleName ] && modJson [ this . userDefinedModuleName ] instanceof Object && Object . keys ( modJson [ this . userDefinedModuleName ] ) . length === 1 && modJson [ this . userDefinedModuleName ] [ TernDef . SPAN ] ) ) { delete modJson [ this . userDefinedModuleName ] ; } return modJson ; } resolvePathToDTSName ( paths ) { let s = "" ; const reg2 = /^!.+/ ; const reg3 = /<.+>/ ; for ( let j = paths . length - 1 ; j > 0 ; j -- ) { if ( reg2 . test ( paths [ j ] ) ) { let tmp = paths [ j ] . replace ( "!" , "" ) . replace ( /^[a-z]/ , ( val ) => { return val . toUpperCase ( ) ; } ) ; s = tmp + s ; } else if ( reg3 . test ( paths [ j ] ) ) { let tmp = paths [ j ] . replace ( /^</ , "" ) . replace ( />$/ , "" ) . replace ( /^[a-z]/ , ( val ) => val . toUpperCase ( ) ) ; s = tmp + s ; } else if ( /\// . test ( paths [ j ] ) ) { if ( paths [ j ] === this . nodeModuleName ) { s = this . userDefinedModuleName . replace ( /^[a-z]/ , ( val ) => val . toUpperCase ( ) ) + s ; } break ; } else { let s2 = paths [ j ] . replace ( /^[a-z]/ , ( val ) => { return val . toUpperCase ( ) ; } ) ; s = s2 + s ; break ; } } return s ; } resolveNamespace ( p ) { let nsp = [ ] ; const len = p . length ; const reg2 = /^!.+/ ; const reg3 = /<.+>/ ; const isOutRepNS = this . option . isOutExport ; for ( let i = len - 1 ; i >= 0 ; i -- ) { if ( reg2 . test ( p [ i ] ) || reg3 . test ( p [ i ] ) ) { if ( i === len - 1 ) nsp = [ ] ; else if ( i >= 1 ) { let tmpPath = [ "!test" , p [ i - 1 ] , p [ i ] ] ; nsp . push ( this . resolvePathToDTSName ( tmpPath ) ) ; } i -- ; continue ; } if ( ( isOutRepNS && p [ i ] === this . nodeModuleName ) || ( isOutRepNS && /[`\/]/ . test ( p [ i ] ) ) ) { if ( this . userDefinedModuleName ) { nsp . push ( this . userDefinedModuleName ) ; } } else { nsp . push ( p [ i ] ) ; } } return nsp . reverse ( ) . join ( "." ) ; } param path param name param isCheckDefine searchAndReplaceDTS ( data , path , name , isCheckDefine = true ) { const len = path . length ; const t = path [ len - 1 ] ; const rt = this . checkReplaceType ( t ) ; let ref = this . searchRef ( data , path , isCheckDefine ) ; if ( ! ref || ! ref [ 0 ] ) return ; switch ( rt ) { case ReplaceType . RETURN : if ( ! ref || ! ref [ 0 ] ) { if ( this . option . isDebug ) console . warn ( "t:" + t + "," + JSON . stringify ( ref ) ) ; return ; } let ret = ref [ 0 ] [ "ret" ] ; let retLen = ret . length ; if ( this . option . isDebug ) console . warn ( "Ret:" , name , path , ret ) ; if ( retLen === 1 ) { const ns = this . resolveNamespace ( path ) ; if ( this . option . isDebug ) console . warn ( "ns" , ns ) ; ret [ 0 ] . class = ( ns != "" ) ? ns + "." + name : name ; ret [ 0 ] . type = TSObjType . CLASS ; } else { const ns = this . resolveNamespace ( path ) ; if ( this . option . isDebug ) console . warn ( "ns" , ns ) ; let o = < TSObj > { } ; o . class = ( ns != "" ) ? ns + "." + name : name ; o . type = TSObjType . CLASS ; ret . push ( o ) ; } break ; case ReplaceType . PARAM : if ( ! ref || ! ref [ 0 ] ) { return ; } let n = Number ( t . replace ( /^!/ , "" ) ) ; let param = ref [ 0 ] [ "params" ] [ n ] ; if ( param instanceof Array ) { let o = < TSObj > { } ; o . class = name ; o . type = TSObjType . CLASS ; const pStr = path . slice ( 0 , path . length - 2 ) . join ( "." ) + "." + name ; let hasSameClass = ( < Array < TSObj > > param ) . some ( ( v , i , a ) => { return v . class && this . resolvePathToDTSName ( v . class . split ( "." ) ) === name ; } ) ; if ( hasSameClass ) { ( < Array < TSObj > > param ) . filter ( ( v , i , a ) => { return v . class && this . resolvePathToDTSName ( v . class . split ( "." ) ) === name ; } ) . map ( ( v , i , a ) => { v . type = TSObjType . CLASS ; v . class = name ; } ) ; } else { ( < Array < TSObj > > param ) . push ( o ) ; } } else { const ns = this . resolveNamespace ( path ) ; param . class = ( ns != "" ) ? ns + "." + name : name ; param . type = TSObjType . CLASS ; } break ; case ReplaceType . CLASS : ref [ 0 ] [ "class" ] = "" ; ref [ 0 ] . class = name ; break ; case ReplaceType . ARRAY : let at = ref [ 0 ] [ "arrayType" ] ; let nspace = path . slice ( 0 , path . length - 2 ) . join ( "." ) + "." + name ; let nt = { type : TSObjType . CLASS , class : nspace } ; at [ at . length - 1 ] = nt ; break ; case ReplaceType . OTHER : break ; } } searchRef ( data , path , isCheckDefine ) { const len = path . length ; if ( data [ path [ 0 ] ] ) isCheckDefine = false ; let ref = isCheckDefine ? data [ TernDef . DEFINE ] : data ; const OBJECT_TO_STRING = "$toString" ; const OBJECT_VALUE_OF = "$valueOf" ; for ( let i = 0 ; i < len - 1 ; i ++ ) { let s = path [ i ] ; if ( s === "toString" ) s = OBJECT_TO_STRING ; if ( s === "valueOf" ) s = OBJECT_VALUE_OF ; if ( s === "prototype" && ! ref [ s ] && this . checkReplaceType ( path [ i - 1 ] ) === ReplaceType . CLASS ) { continue ; } else if ( ref [ s ] === undefined ) { if ( i > 0 ) { let pname = path . slice ( 0 , i ) . join ( "." ) + "." + s ; let iData = data [ TernDef . DEFINE ] ; if ( iData [ pname ] ) { ref = iData [ pname ] ; continue ; } if ( this . option . isDebug ) { console . warn ( "pname" , pname ) ; console . warn ( "iData[pname]" , JSON . stringify ( iData [ pname ] ) ) ; } } if ( this . option . isDebug ) { console . warn ( "current ref path:" + s ) ; console . warn ( "no path ref:" + path . join ( "." ) ) ; console . warn ( "data" , JSON . stringify ( ref ) ) ; } return undefined ; } else { ref = ref [ s ] ; } } if ( ref [ TernDef . TYPE ] ) { ref = ref [ TernDef . TYPE ] ; } return ref ; } checkReplaceType ( s ) { let rt ; if ( s === "!ret" ) { rt = ReplaceType . RETURN ; } else if ( /^![0-9]+/ . test ( s ) ) { rt = ReplaceType . PARAM ; } else if ( /^[A-Z].*/ . test ( s ) ) { rt = ReplaceType . CLASS ; } else if ( /^<.+>$/ . test ( s ) ) { rt = ReplaceType . ARRAY ; } else { rt = ReplaceType . OTHER ; } return rt ; } ternDefClassToDTSClass ( ternClassStr ) { let s = "" ; let regInstance = /^\+.*/ ; let regDefined = /^!.+/ ; if ( regInstance . test ( ternClassStr ) ) { let p = ternClassStr . split ( "." ) ; s = p [ p . length - 1 ] ; } else if ( regDefined . test ( ternClassStr ) ) { let p = ternClassStr . split ( "." ) ; s = this . resolvePathToDTSName ( p ) ; } return s ; } /**
         * ternjs type definition to TSObj
         */ parseJsonNodeDTS ( data ) { let o = JSON . parse ( "{}" ) ; for ( let i in data ) { let value ; value = data [ i ] ; switch ( i ) { case TernDef . DEFINE : o [ i ] = { } ; if ( typeof value === "string" ) continue ; else { for ( let j in value ) { o [ i ] = this . parseJsonNodeDTS ( value ) ; } } break ; case TernDef . NODE : if ( typeof i === "string" ) { if ( Object . keys ( value ) . length === 1 ) { this . nodeModuleName = Object . keys ( value ) [ 0 ] ; } } o [ i ] = this . parseJsonNodeDTS ( value ) ; break ; case TernDef . NAME : this . userDefinedModuleName = < string > value ; case TernDef . DOC : case TernDef . URL : o [ i ] = value ; break ; default : if ( typeof value === "string" ) { switch ( i ) { case "toString" : case "valueOf" : let newPropName = `$${ i }` ; o [ newPropName ] = { } ; o [ newPropName ] = this . parseTernDef ( value ) ; break ; default : o [ i ] = this . parseTernDef ( value ) ; } } else if ( value [ TernDef . TYPE ] && value [ "prototype" ] ) { o [ i ] = { } ; o [ i ] [ DTSDef . NEW ] = { } ; o [ i ] [ DTSDef . NEW ] = this . parseTernDef ( value [ TernDef . TYPE ] , i , true ) ; for ( let j in value [ "prototype" ] ) { if ( typeof value [ "prototype" ] [ j ] === "string" ) o [ i ] [ j ] = this . parseTernDef ( value [ "prototype" ] [ j ] ) ; else o [ i ] [ j ] = this . parseJsonNodeDTS ( value [ "prototype" ] [ j ] ) ; } for ( let j in value ) { if ( j === TernDef . TYPE ) continue ; if ( j === "prototype" ) continue ; if ( typeof value [ j ] === "string" ) { if ( j === TernDef . DOC || j === TernDef . URL ) { o [ i ] [ j ] = value [ j ] ; } else { o [ i ] [ j ] = this . parseTernDef ( value [ j ] ) ; } } else { o [ i ] [ j ] = this . parseJsonNodeDTS ( value [ j ] ) ; } } } else { o [ i ] = this . parseJsonNodeDTS ( value ) ; } break ; } } return o ; } private isInDefine = false ; private isNodeJsModule = false ; private isInClassOrInterface = false ; private isInObjectLiteral = false ; private isNeedDeclare = true ; parseToDTS ( data , parent ? ) { let s = "" ; for ( let i in data ) { let value ; value = data [ i ] ; switch ( i ) { case TernDef . NAME : break ; case TernDef . DEFINE : if ( value instanceof Array ) continue ; if ( typeof value === "string" ) continue ; this . isInDefine = true ; for ( let j in < Object > value ) { if ( j === TernDef . NODE ) { this . isNodeJsModule = true ; let dont = [ ] ; let wrap = [ ] ; for ( let k in value [ TernDef . NODE ] ) { const v = value [ TernDef . NODE ] [ k ] ; if ( v [ TernDef . TYPE ] ) { dont [ k ] = < TSObj > { } ; dont [ k ] = clone ( v ) ; } else { wrap [ k ] = < TSObj > { } ; wrap [ k ] = clone ( v ) ; } } s += this . parseToDTS ( dont ) ; s += this . wrapNamespace ( wrap , TernDef . NODE ) ; this . isNodeJsModule = false ; } else { let ns = "" ; let defName = "" ; if ( value [ j ] [ DTS_INTERFACE_STR ] ) { defName = value [ j ] [ DTS_INTERFACE_STR ] . toString ( ) ; delete value [ j ] [ DTS_INTERFACE_STR ] ; } else { defName = j ; } if ( value [ j ] [ DTS_NAMESPACE_STR ] ) { ns = value [ j ] [ DTS_NAMESPACE_STR ] ; delete value [ j ] [ DTS_NAMESPACE_STR ] ; } if ( value [ j ] instanceof Array && value [ j ] . length === 1 && value [ j ] [ 0 ] . type === TSObjType . CLASS ) { continue ; } if ( ns != "" ) { s += this . indent ( ) ; s += this . addDeclare ( true ) ; s += `namespace ${ ns }{\n` ; this . depth ++ ; } if ( value [ j ] instanceof Array ) { s += this . indent ( ) ; s += `// ${ j }\n` ; s += this . indent ( ) ; s += `type ${ defName } = ` ; s += this . tsObjsToUnionDTS ( value [ j ] , ( < TSObj [ ] > value [ j ] ) . some ( ( v , i , a ) => v . type === TSObjType . FUNCTION ) ) ; s += ";\n" ; } else if ( typeof value [ j ] === "string" ) { s += this . indent ( ) ; s += this . outJSDoc ( value [ j ] [ TernDef . DOC ] ? value [ j ] [ TernDef . DOC ] : undefined , value [ j ] [ TernDef . URL ] ? value [ j ] [ TernDef . URL ] : undefined ) ; s += `var ${ defName }: ${ value [ j ] }\n` ; } else { s += this . interfaceDTS ( defName , value [ j ] , j ) ; } if ( ns != "" ) { this . depth -- ; s += this . indent ( ) ; s += `}\n` ; } } } this . isInDefine = false ; break ; case TernDef . DOC : break ; case TernDef . SPAN : break ; case TernDef . TYPE : if ( this . isInClassOrInterface ) { s += this . indent ( ) ; s += this . convertTSObjToString ( "" , < TSObj [ ] > value , value [ TernDef . DOC ] , value [ TernDef . URL ] ) ; } break ; case TernDef . PROTO : break ; case "<i>" : break ; default : if ( /[\*\-]/ . test ( i ) ) { i = `"${ i }"` ; } if ( value instanceof Array ) { s += this . indent ( ) ; s += this . convertTSObjToString ( i , < TSObj [ ] > value , value [ TernDef . DOC ] , value [ TernDef . URL ] ) ; } else if ( typeof value === "string" ) { s += this . outJSDoc ( value [ TernDef . DOC ] , value [ TernDef . URL ] ) ; s += this . indent ( ) ; s += this . addDeclare ( ) ; s += i + " : " + value ; } else if ( value && ( value [ DTSDef . NEW ] || value [ TernDef . PROTO ] || value [ DTS_INTERFACE_STR ] ) && ! this . isInClassOrInterface ) { if ( this . option . globalObject === Option . GlobalObject . WRAP && value [ DTS_INTERFACE_STR ] ) { delete value [ DTS_INTERFACE_STR ] ; } s += this . interfaceDTS ( i , value ) ; } else if ( value instanceof Object && Object . keys ( value ) . length === 0 ) { s += this . indent ( ) ; s += this . convertTSObjToString ( i , [ < TSObj > { } ] , value [ TernDef . DOC ] , value [ TernDef . URL ] ) ; } else if ( value && value [ TernDef . TYPE ] ) { s += this . indent ( ) ; s += this . convertTSObjToString ( i , value [ TernDef . TYPE ] , value [ TernDef . DOC ] , value [ TernDef . URL ] ) ; if ( Object . keys ( value ) . length > 1 ) { let v = clone ( value ) ; delete v [ TernDef . TYPE ] ; delete v [ TernDef . DOC ] ; delete v [ TernDef . URL ] ; if ( this . option . isDebug ) { console . log ( "I:" + i , JSON . stringify ( v ) , Object . keys ( v ) . length ) ; } if ( Object . keys ( v ) . length > 0 && ! this . isInClassOrInterface ) { s += this . outNamespaceOrInterface ( v , i ) ; } } } else if ( this . isNamespace ( value ) ) { s += this . outNamespaceOrInterface ( value , i ) ; } else { s += this . outJSDoc ( value [ TernDef . DOC ] , value [ TernDef . URL ] ) ; s += this . indent ( ) ; if ( ! this . isInObjectLiteral && ! this . isInClassOrInterface ) { s += this . addDeclare ( ) ; s += "var " ; } s += `${ i } : {\n` ; this . isInObjectLiteral = true ; this . depth ++ ; s += this . parseToDTS ( value , parent ) ; this . depth -- ; this . isInObjectLiteral = false ; s += this . indent ( ) ; s += `}\n` ; } break ; } } return s ; } addDeclare ( flag ? ) { const DECLARE_STR = "declare " ; if ( flag && flag === true ) return DECLARE_STR ; else if ( this . depth === 0 ) return DECLARE_STR ; else return "" ; } outNamespaceOrInterface ( value , name , path ? ) { let s = "" ; let outI = { } ; let outN = { } ; for ( let i in value ) { if ( ! value [ i ] ) continue ; const v = value [ i ] ; if ( v [ 0 ] && v [ 0 ] . type === TSObjType . FUNCTION && /[\*\-]/ . test ( i ) ) { outI [ i ] = clone ( v ) ; } else { outN [ i ] = clone ( v ) ; } } if ( Object . keys ( outN ) . length > 0 ) s += this . outNamespaceDTS ( outN , name ) ; if ( Object . keys ( outI ) . length > 0 ) s += this . interfaceDTS ( name , outI ) ; return s ; } outNamespaceDTS ( value , name , parent ? ) { let s = "" ; s += this . outJSDoc ( value [ TernDef . DOC ] , value [ TernDef . URL ] ) ; s += this . indent ( ) ; s += this . addDeclare ( ) ; s += `namespace ${ name }{\n` ; this . depth ++ ; s += this . parseToDTS ( value , parent ) ; this . depth -- ; s += this . indent ( ) ; s += `}\n` ; return s ; } param defName interfaceDTS ( defName , value , path ? ) { let s = "" ; if ( path ) { s += this . indent ( ) ; s += `// ${ path }\n` ; } s += this . outJSDoc ( value [ TernDef . DOC ] ? value [ TernDef . DOC ] : null , value [ TernDef . URL ] ? value [ TernDef . URL ] : null ) ; s += this . indent ( ) ; s += this . addDeclare ( ) ; s += `interface ${ defName }` ; if ( value [ TernDef . PROTO ] ) { let proto = value [ TernDef . PROTO ] ; if ( proto instanceof Array && proto . length > 1 ) { const str = "Object extends 2 or more other objects, but TypeScript only extends SINGLE Class/Interface." ; console . warn ( str ) ; s += `/* ${ str } */` ; } else { let t = proto [ 0 ] ; if ( ! t . class ) { if ( this . option . isDebug ) console . log ( "t:" + JSON . stringify ( t ) ) ; } else { let p = t . class . split ( "." ) ; if ( p [ p . length - 1 ] === "prototype" ) { let ext = p . slice ( 0 , p . length - 1 ) . join ( "." ) ; s += ` extends ${ ext }` ; } value [ TernDef . PROTO ] = undefined ; delete value [ TernDef . PROTO ] ; } } } this . isInClassOrInterface = true ; this . isInObjectLiteral = true ; this . isNeedDeclare = false ; s += ` {\n` ; this . depth ++ ; s += this . parseToDTS ( value ) ; this . depth -- ; s += this . indent ( ) ; s += `}\n` ; this . isInObjectLiteral = false ; this . isInClassOrInterface = false ; this . isNeedDeclare = true ; if ( value [ dtsmake . DTSDef . NEW ] && this . option . isExportInterfaceSameNameVar ) { s += this . indent ( ) ; s += this . addDeclare ( ) ; s += `var ${ defName }: ${ defName };\n` ; } return s ; } isNamespace ( value ) { if ( ! value ) { return false ; } else if ( value instanceof Array ) return false ; else if ( value [ TernDef . TYPE ] ) return false ; else if ( ! ( value instanceof Object ) ) return false ; else if ( Object . keys ( value ) . length === 0 ) return false ; else if ( this . isInClassOrInterface ) { return false ; } else { for ( let i in value ) { if ( ! value [ i ] ) continue ; if ( value [ i ] instanceof Object && Object . keys ( value [ i ] ) . length > 1 ) { return true ; } else if ( value [ i ] [ 0 ] && value [ i ] [ 0 ] . type && ( value [ i ] [ 0 ] . type === TSObjType . FUNCTION || value [ i ] [ 0 ] . type === TSObjType . BOOLEAN || value [ i ] [ 0 ] . type === TSObjType . ARRAY ) ) { return true ; } } return false ; } } /**
         *
         */ outJSDoc ( comment ? , url ? , params ? , ret ? ) { let s = "" ; s += this . indent ( ) ; s += "\n" ; s += this . indent ( ) ; s += `/**\n` ; s += this . indent ( ) ; if ( comment ) { const aComment = comment . split ( "\n" ) ; const len = aComment . length ; for ( let i = 0 ; i < len ; i ++ ) { if ( i !== 0 ) s += this . indent ( ) ; s += ` * ${ aComment [ i ] }\n` ; } } else { s += ` * \n` ; } if ( params ) { for ( let i of params ) { s += this . indent ( ) ; s += ` * @param ${ i } \n` ; } } if ( ret ) { s += this . indent ( ) ; s += ` * @return ${ ret }\n` ; } if ( url ) { s += this . indent ( ) ; s += ` * @url ${ url }\n` ; } s += this . indent ( ) ; s += ` */\n` ; return s ; } outFuncJsDocs ( t , docData ? , urlData ? ) { let s = "" ; let ps = ( t . params ) ? t . params . map ( ( v , i , a ) => { if ( v instanceof Array ) { return v [ 0 ] . name ; } else { return v . name ; } } ) : null ; let rs = ( ! t . ret || t . ret . every ( ( v , i , a ) => v . type === TSObjType . VOID ) ) ? null : " " ; s += this . outJSDoc ( docData , urlData , ps , rs ) ; return s ; } convertTSObjToString ( symbolName , tsObjects , docData ? , urlData ? , spanData ? ) { let s = "" ; let keyword = "" ; let isFunc = false ; if ( tsObjects [ 0 ] . type === TSObjType . FUNCTION ) { isFunc = true ; } if ( /^\$(toString|valueOf)$/ . test ( symbolName ) ) { symbolName = symbolName . replace ( /^\$/ , "" ) ; } let isMaybeClass = false ; if ( /^[A-Z]/ . test ( symbolName ) && tsObjects [ 0 ] . type === TSObjType . FUNCTION ) { isMaybeClass = true ; } if ( this . isInClassOrInterface ) { } else if ( this . isInObjectLiteral ) { } else { if ( isFunc && ! isMaybeClass ) { keyword = "function " ; } else if ( isFunc && isMaybeClass ) { keyword = "class " ; } else { keyword = "var " ; } } if ( isFunc && ! isMaybeClass ) { let ol = tsObjects . length ; ol = tsObjects . length ; for ( let i = 0 ; i < ol ; i ++ ) { let t = tsObjects [ i ] ; s += this . outFuncJsDocs ( t , docData , urlData ) ; s += this . indent ( ) ; s += this . addDeclare ( ) ; s += keyword + symbolName + "(" + this . paramsToDTS ( t . params ) + ")" ; if ( t . ret && symbolName === DTSDef . NEW && ! this . option . isAnnotateTypeInstance && t . ret . every ( ( v ) => v . type === TSObjType . VOID ) ) { } else if ( t . ret ) { s += `: ${ this . tsObjsToUnionDTS ( t . ret , ( t . ret . length > 1 ) ? true : false , t , false ) }` ; } if ( i !== ol - 1 ) { s += ";" ; } } } else if ( isFunc && isMaybeClass ) { let nt = { [ DTSDef . NEW ] : [ ] , [ TernDef . DOC ] : docData ? docData : null } ; if ( urlData ) nt [ TernDef . URL ] = urlData ; for ( let i in tsObjects ) { if ( i === TSObj . Def . TYPE ) continue ; let tmp = tsObjects [ i ] ; tmp . ret . filter ( ( v , i , a ) => v . type === TSObjType . VOID ) . map ( ( v , i , a ) => { v . type = TSObjType . CLASS ; v . class = symbolName ; return v ; } ) ; nt [ DTSDef . NEW ] . push ( tmp ) ; } s += this . interfaceDTS ( symbolName , nt ) ; } else { s += this . outJSDoc ( docData , urlData ) ; s += this . indent ( ) ; if ( ! this . isInDefine && ! this . isInObjectLiteral && ! this . isInClassOrInterface ) s += "export " ; s += this . addDeclare ( ) ; s += keyword + symbolName + " : " + this . tsObjsToUnionDTS ( tsObjects ) ; } if ( isMaybeClass ) s += "\n" ; else s += ";\n" ; return s ; } paramsToDTS ( params ) { if ( params == null ) return "" ; else return params . map ( ( v , i , a ) => { if ( v instanceof Array ) { return this . tsObjsToUnionDTS ( < any > v , true ) ; } else { if ( ! v . name ) v . name = `param${ i + 1 }` ; return this . tsObjToDTS ( v ) ; } } ) . join ( ", " ) ; } param wrap tsObjsToUnionDTS ( t , wrap = false , parentTSObj ? , isOutName = true ) { if ( ! t ) { throw Error ( "union needs dts any tsObjs." ) ; } let isOnlyAny = t . every ( ( v , i , a ) => ( v . type === TSObjType . ANY ) ) ; if ( isOnlyAny ) { return this . tsObjToDTS ( t [ 0 ] , wrap , parentTSObj , isOutName ) ; } let hasAny = t . some ( ( v , i , a ) => ( v . type === TSObjType . ANY ) ) && ( t . length > 1 ) ; if ( hasAny ) { t = t . map ( ( v , i , a ) => { if ( v . type === TSObjType . ANY ) { v . type = TSObjType . CLASS ; v . class = "{}" ; } return v ; } ) ; } return t . map ( ( v , i , a ) => { let isOutParamName = isOutName ; if ( i !== 0 ) isOutParamName = false ; return this . tsObjToDTS ( v , wrap , parentTSObj , isOutParamName ) ; } ) . join ( " | " ) ; } tsObjToDTS ( t , wrap = false , parentTSObj ? , isOutName = true ) { if ( ! t ) return " /* error */ any" ; let s = "" ; if ( t . name && isOutName ) s += t . name + " : " ; wrap = wrap && ( t . type === TSObjType . FUNCTION ) ; if ( wrap ) s += "(" ; switch ( t . type ) { case TSObjType . ANY : s += "any" ; break ; case TSObjType . ARRAY : s += "Array<" + this . tsObjsToUnionDTS ( t . arrayType ) + ">" ; break ; case TSObjType . BOOLEAN : s += "boolean" ; break ; case TSObjType . CLASS : if ( /^\+.+/ . test ( t . class ) ) { s += t . class . replace ( /^\+/ , "" ) ; } else if ( /\./ . test ( t . class ) ) { const tp = t . class . split ( "." ) ; const last = tp [ tp . length - 1 ] ; if ( /^[A-Z].+([0-9A-Z]|Ret)$/ . test ( last ) ) { s += t . class ; } else { s += `/* ${ t . class } */ any` ; } } else { s += t . class ; } break ; case TSObjType . FUNCTION : s += "(" + this . paramsToDTS ( t . params ) + ")" ; if ( ! t . ret ) { t . ret = [ < TSObj > { type : TSObjType . VOID } ] ; } s += " => " + this . tsObjsToUnionDTS ( t . ret , false , null , false ) ; break ; case TSObjType . NUMBER : s += "number" ; break ; case TSObjType . OBJECT : if ( t . class ) { switch ( this . checkReplaceType ( t . class ) ) { case ReplaceType . PARAM : let n = Number ( t . class . replace ( /^!/ , "" ) ) ; if ( ! parentTSObj || ! parentTSObj . params ) { s += `/*${ t . class }*/ any` ; break ; } let rep = parentTSObj . params [ n ] ; if ( rep instanceof Array ) { const isWrap = rep . some ( ( v , i , a ) => v . type === TSObjType . FUNCTION ) ; s += this . tsObjsToUnionDTS ( rep , isWrap , null , false ) ; } else { s += this . tsObjToDTS ( < TSObj > rep , false , null , false ) ; if ( rep && ( < TSObj > rep ) . type && ( < TSObj > rep ) . type === TSObjType . ANY ) { s += ` /* same type param "${ ( < TSObj > rep ) . name }" */` ; } } break ; case ReplaceType . RETURN : s += `/* ${ t . class } */` ; break ; case ReplaceType . ARRAY : s += `/* Array<${ t . class }> */` ; break ; default : if ( this . isJSGlobalObject ( t . class ) ) { s += t . class ; } else { s += `/* ${ t . class } */ ` ; s += "any" ; } break ; } } else { s += "any" ; } break ; case TSObjType . STRING : s += "string" ; break ; case TSObjType . UNIONS : throw Error ( "unions? " + JSON . stringify ( t ) ) ; case TSObjType . VOID : if ( this . option . isOutVoidAsAny ) s += "/* void */ any" ; else s += "void" ; break ; default : s += "/*no type*/{}" ; break ; } if ( wrap ) s += ")" ; return s ; } isJSGlobalObject ( name ) { const g = [ "Object" , "Function" , "Boolean" , "Symbol" , "Error" , "EvalError" , "InternalError" , "RangeError" , "ReferenceError" , "SyntaxError" , "TypeError" , "URIError" , "Number" , "Math" , "Date" , "String" , "RegExp" , "Array" , "Int8Array" , "Uint8Array" , "Uint8ClampedArray" , "Int16Array" , "Uint16Array" , "Int32Array" , "Uint32Array" , "Float32Array" , "Float64Array" , "Map" , "Set" , "WeakMap" , "WeakSet" , "ArrayBuffer" , "DataView" , "JSON" , "Promise" , "Generator" , "GeneratorFunction" , "Reflect" , "Proxy" ] ; return g . some ( ( v , i , a ) => v === name ) ; } /**
         *
         */ wrapNamespace ( value , nodeName ) { let s = "" ; if ( typeof value === "string" ) { throw Error ( nodeName + " node value must not to be string." ) ; } else { for ( let j in value ) { s += "//nodejs module namespace\n" ; s += this . addDeclare ( ) ; s += `namespace ${ j }{\n` ; this . depth ++ ; s += this . parseToDTS ( value [ j ] ) ; this . depth -- ; s += `}\n` ; } } return s ; } private depth : number = 0 ; indent ( ) { let s = "" ; const INDENT_STR = "	" ; for ( let i = 0 | 0 ; ( i | 0 ) < ( this . depth | 0 ) ; i ++ ) { s += INDENT_STR ; } return s ; } convertTernJsToTs ( ternDef ) { let ts ; return ts ; } parseTernDef ( ternDef , parentName ? , isConstructor = false ) { if ( ! ternDef ) throw Error ( "need ternjs def string." ) ; ternDef = ternDef . replace ( /[\s\t\n]+/g , "" ) ; let reg = /^\((.+)\)$/ ; if ( reg . test ( ternDef ) ) { ternDef = ternDef . match ( reg ) [ 1 ] ; } let sa = this . splitUnions ( ternDef ) ; let ret = [ ] ; for ( let i of sa ) { let ts = < TSObj > { } ; ts . type = this . checkType ( i ) ; if ( parentName ) ts . name = parentName ; if ( ts . type === TSObjType . CLASS || ts . type === TSObjType . OBJECT ) { ts . class = i ; } switch ( ts . type ) { case TSObjType . UNIONS : ret . push ( this . parseTernDef ( i ) ) ; continue ; case TSObjType . ARRAY : ts . arrayType = this . parseTernDef ( i . replace ( /^\[/ , "" ) . replace ( /\]$/ , "" ) ) ; break ; case TSObjType . FUNCTION : ts . ret = this . parseFnReturn ( i , parentName , isConstructor ) ; ts . params = this . parseParams ( i ) ; break ; case TSObjType . CLASS : case TSObjType . OBJECT : ts . class = i ; break ; case TSObjType . ANY : case TSObjType . BOOLEAN : case TSObjType . NUMBER : default : break ; } ret . push ( ts ) ; } return ret ; } private parseFnReturn ( fnStr , parentName ? , isConstructor = false ) { let sa = this . splitReturn ( fnStr ) ; if ( isConstructor && this . option . isAnnotateTypeInstance && parentName && sa . length === 1 ) { return [ < TSObj > { type : TSObjType . CLASS , class : parentName } ] ; } else if ( sa . length === 1 ) { return [ < TSObj > { type : TSObjType . VOID } ] ; } let ret = this . parseTernDef ( sa [ 1 ] ) ; if ( isConstructor && this . option . isAnnotateTypeInstance && parentName ) { ret . filter ( ( v , i , a ) => v . type === TSObjType . VOID ) . map ( ( v , i , a ) => { return < TSObj > { type : TSObjType . CLASS , class : parentName } ; } ) ; } return ret ; } parseParams ( fnStr ) { let fns = this . splitReturn ( fnStr ) [ 0 ] ; let reg1 = /^fn\(/ ; let reg2 = /\)$/ ; let fna = reg1 . test ( fns ) && reg2 . test ( fns ) ? fns . replace ( /^fn\(/ , "" ) . replace ( /\)$/ , "" ) : fns ; if ( ! fna || fna . length === 1 ) { return null ; } else { let paramStr = fna ; let sa = this . splitParams ( paramStr ) ; let ret = [ ] ; for ( let i of sa ) { let n = i . search ( /:/ ) ; let name = ( n === - 1 ) ? null : i . substring ( 0 , n ) ; let sType = i . substring ( n + 1 ) ; let checked = this . checkType ( sType ) ; if ( TSObjType . UNIONS === checked ) { let pa = this . parseTernDef ( sType , name ) ; ret . push ( pa ) ; } else if ( TSObjType . ARRAY === checked ) { let a = this . parseTernDef ( sType , name ) ; if ( a . length === 1 ) { ret . push ( a [ 0 ] ) ; } else { ret . push ( a ) ; } } else { let ts = < TSObj > { } ; if ( n !== - 1 ) ts . name = name ; ts . type = checked ; if ( ts . type === TSObjType . OBJECT || ts . type === TSObjType . CLASS ) { ts . class = sType ; } ret . push ( ts ) ; } } return ret ; } } return checkType ( ternDef ) { if ( this . splitUnions ( ternDef ) . length > 1 ) { return TSObjType . UNIONS ; } else if ( /^fn\(/ . test ( ternDef ) ) { return TSObjType . FUNCTION ; } else if ( ternDef === "?" ) { return TSObjType . ANY ; } else if ( ternDef === "bool" ) { return TSObjType . BOOLEAN ; } else if ( ternDef === "number" ) { return TSObjType . NUMBER ; } else if ( ternDef === "string" ) { return TSObjType . STRING ; } else if ( /^\[.+\]$/ . test ( ternDef ) ) { return TSObjType . ARRAY ; } else if ( /^\+.+$/ . test ( ternDef ) ) { return TSObjType . CLASS ; } else if ( ternDef != "" ) { return TSObjType . OBJECT ; } else { throw Error ( "\u001b[31mcan not check type. : \u001b[0m" + ternDef ) ; } } private splitUnions ( ternDef ) { return this . splits ( ternDef , "(" , ")" , "|" ) ; } splitParams ( paramStr ) { return this . splits ( paramStr , "(" , ")" , "," ) ; } private splitReturn ( fnStr ) { return this . splits ( fnStr , "(" , ")" , "->" ) ; } private splits ( str , depthUpStr , depthDownStr , splitter ) { let delimIdxs = [ ] ; let retStr = [ ] ; const len = str . length | 0 ; let depth = 0 ; const dUpIni = depthUpStr . charAt ( 0 ) ; const dDwIni = depthDownStr . charAt ( 0 ) ; const splIni = splitter . charAt ( 0 ) ; for ( let i = 0 | 0 ; ( i | 0 ) < ( len | 0 ) ; i = ( i + 1 ) | 0 ) { let cs = str . charAt ( i ) ; switch ( cs ) { case dUpIni : depth ++ ; break ; case dDwIni : depth -- ; break ; case splIni : if ( str . substr ( i , splitter . length ) != splitter ) break ; if ( depth === 0 ) { delimIdxs . push ( i ) ; i += splitter . length ; } break ; default : break ; } } const delimLen = delimIdxs . length ; if ( delimLen === 0 ) { retStr . push ( str ) ; } else { let start = 0 ; let end = 0 ; for ( let j = 0 ; j < delimLen ; j ++ ) { end = delimIdxs [ j ] ; let s = str . slice ( start , end ) ; start = delimIdxs [ j ] + splitter . length ; retStr . push ( s ) ; if ( j == delimLen - 1 ) { retStr . push ( str . slice ( start ) ) ; } } } return retStr ; } } /**
     * config option interface
     */ export interface Option { isDebug ? : boolean ; isOutVoidAsAny ? : boolean ; isExportInterfaceSameNameVar ? : boolean ; isAnnotateTypeInstance ? : boolean ; isNodeJsModule ? : boolean ; isOutExport ? : boolean ; globalObject ? : string ; exportStyle ? : string ; exportModuleName ? : string ; lib ? : string [ ] ; } /**
     * d.ts file data
     */ interface DTS { name : string ; isNodeModule : boolean ; defines : { } ; globals : Object [ ] ; } interface DTSNode { path ? : string ; } export const enum EnumTernDef { NAME , DEFINE , TYPE , DOC , URL , EFFECTS , THIS , RET , SPAN , PROTO , STR_PROTO , NODE } export enum ReplaceType { RETURN , PARAM , CLASS , ARRAY , OTHER } /**
     *
     * TODO:compat to TypeScript AST
     */ export interface TSObj { type : TSObjType ; name ? : string ; params ? : TSObj [ ] ; ret ? : TSObj [ ] ; arrayType ? : TSObj [ ] ; class ? : string ; } export enum TSObjType { ANY , VOID , BOOLEAN , NUMBER , STRING , FUNCTION , ARRAY , CLASS , UNIONS , OBJECT } } export namespace dtsmake . DTSDef { export const NEW = "new " ; } export namespace dtsmake . TSObj . Def { export const TYPE = "type" ; export const NAME = "name" ; export const PARAMS = "params" ; export const RET = "ret" ; export const ARRAYTYPE = "arrayType" ; export const CLASS = "class" ; } export namespace dtsmake . Option . ExportStyle { /**
     * es6 style `export default MODULE;`
     */ export const ES6 = "es6" ; /**
     * legacy ts style `export = MODULE;`
     */ export const LEGACY = "legacy" ; } export namespace dtsmake . Option . GlobalObject { /**
     * remove extending global objects, no output
     */ export const REMOVE = "remove" ; example export const WRAP = "wrap" ; example export const RENAME = "rename" ; example export const RENAME_EXTEND = "renameExtend" ; } export namespace dtsmake . TernDef { export const NAME = "!name" ; export const DEFINE = "!define" ; export const TYPE = "!type" ; export const DOC = "!doc" ; export const URL = "!url" ; export const EFFECTS = "!effects" ; export const THIS = "!this" ; export const RET = "!ret" ; export const SPAN = "!span" ; export const PROTO = "!proto" ; export const STR_PROTO = "!stdProto" ; export const NODE = "!modules" ; param s return export function strToEnum ( s ) { switch ( s ) { case this . NAME : return EnumTernDef . NAME ; case this . DEFINE : return EnumTernDef . DEFINE ; case this . TYPE : return EnumTernDef . TYPE ; case this . DOC : return EnumTernDef . DOC ; case this . URL : return EnumTernDef . URL ; case this . EFFECTS : return EnumTernDef . EFFECTS ; case this . THIS : return EnumTernDef . THIS ; case this . RET : return EnumTernDef . RET ; case this . SPAN : return EnumTernDef . SPAN ; case this . PROTO : return EnumTernDef . PROTO ; case this . STD_PROTO : return EnumTernDef . STR_PROTO ; case this . NODE : return EnumTernDef . NODE ; default : throw Error ( "no match enum strings:" + s ) ; } } }