import * as fs from 'fs' ; import * as glob from 'glob' ; import * as mkdirp from 'mkdirp' ; import * as os from 'os' ; import * as pathUtil from 'path' ; import * as ts from 'typescript' ; export interface ResolveModuleIdParams { /** The identifier of the module being declared in the generated d.ts */ currentModuleId : string ; } export interface ResolveModuleImportParams { /** The identifier of the module currently being imported in the generated d.ts */ importedModuleId : string ; /** The identifier of the enclosing module currently being declared in the generated d.ts */ currentModuleId : string ; /** True if the imported module id is declared as a module in the input files. */ isDeclaredExternalModule : boolean ; } export interface Options { baseDir ? : string ; project ? : string ; files ? : string [ ] ; exclude ? : string [ ] ; externs ? : string [ ] ; types ? : string [ ] ; eol ? : string ; includes ? : string [ ] ; indent ? : string ; main ? : string ; moduleResolution ? : ts . ModuleResolutionKind ; name ? : string ; out : string ; outDir ? : string ; prefix ? : string ; rootDir ? : string ; target ? : ts . ScriptTarget ; sendMessage ? : ( message : any , ... optionalParams : any [ ] ) => void ; resolveModuleId ? : ( params : ResolveModuleIdParams ) => string ; resolveModuleImport ? : ( params : ResolveModuleImportParams ) => string ; verbose ? : boolean ; } const DTSLEN = '.d.ts' . length ; const filenameToMid = ( function ( ) { if ( pathUtil . sep === '/' ) { return function ( filename ) { return filename ; } ; } else { const separatorExpression = new RegExp ( pathUtil . sep . replace ( '\\' , '\\\\' ) , 'g' ) ; return function ( filename ) { return filename . replace ( separatorExpression , '/' ) ; } ; } } ) ( ) ; param diagnostics function getError ( diagnostics ) { let message = 'Declaration generation failed' ; diagnostics . forEach ( function ( diagnostic ) { if ( diagnostic . file ) { const position = diagnostic . file . getLineAndCharacterOfPosition ( diagnostic . start ) ; message += `\n${ diagnostic . file . fileName }(${ position . line + 1 },${ position . character + 1 }): ` + `error TS${ diagnostic . code }: ${ diagnostic . messageText }` ; } else { message += `\nerror TS${ diagnostic . code }: ${ diagnostic . messageText }` ; } } ) ; const error = new Error ( message ) ; error . name = 'EmitterError' ; return error ; } function getFilenames ( baseDir , files ) { return files . map ( function ( filename ) { const resolvedFilename = pathUtil . resolve ( filename ) ; if ( resolvedFilename . indexOf ( baseDir ) === 0 ) { return resolvedFilename ; } return pathUtil . resolve ( baseDir , filename ) ; } ) ; } function processTree ( sourceFile , replacer ) { let code = '' ; let cursorPosition = 0 ; function skip ( node ) { cursorPosition = node . end ; } function readThrough ( node ) { code += sourceFile . text . slice ( cursorPosition , node . pos ) ; cursorPosition = node . pos ; } function visit ( node ) { readThrough ( node ) ; const replacement = replacer ( node ) ; if ( replacement != null ) { code += replacement ; skip ( node ) ; } else { ts . forEachChild ( node , visit ) ; } } visit ( sourceFile ) ; code += sourceFile . text . slice ( cursorPosition ) ; return code ; } param options param fileName function getTSConfig ( fileName ) { const configText = fs . readFileSync ( fileName , { encoding : 'utf8' } ) ; const result = ts . parseConfigFileTextToJson ( fileName , configText ) ; if ( result . error ) { throw getError ( [ result . error ] ) ; } const configObject = result . config ; const configParseResult = ts . parseJsonConfigFileContent ( configObject , ts . sys , pathUtil . dirname ( fileName ) ) ; if ( configParseResult . errors && configParseResult . errors . length ) { throw getError ( configParseResult . errors ) ; } return [ configParseResult . fileNames , configParseResult . options ] ; } function isNodeKindImportDeclaration ( value ) { return value && value . kind === ts . SyntaxKind . ImportDeclaration ; } function isNodeKindExternalModuleReference ( value ) { return value && value . kind === ts . SyntaxKind . ExternalModuleReference ; } function isNodeKindStringLiteral ( value ) { return value && value . kind === ts . SyntaxKind . StringLiteral ; } function isNodeKindExportDeclaration ( value ) { return value && value . kind === ts . SyntaxKind . ExportDeclaration ; } function isNodeKindExportAssignment ( value ) { return value && value . kind === ts . SyntaxKind . ExportAssignment ; } function isNodeKindModuleDeclaration ( value ) { return value && value . kind === ts . SyntaxKind . ModuleDeclaration ; } export default function generate ( options ) { if ( Boolean ( options . main ) !== Boolean ( options . name ) ) { if ( Boolean ( options . name ) ) { throw new Error ( `name and main must be used together.  Perhaps you want prefix instead of
				name? In dts-generator version 2.1, name did double duty as the option to
				use to prefix module names with, but in >=2.2 the name option was split
				into two; prefix is what is now used to prefix imports and module names
				in the output.` ) ; } else { throw new Error ( 'name and main must be used together.' ) ; } } const noop = function ( ) { } ; const sendMessage = options . sendMessage || noop ; const verboseMessage = options . verbose ? sendMessage : noop ; let compilerOptions = { } ; let files = options . files ; if ( options . project || ! options . files || options . files . length === 0 ) { verboseMessage ( `project = "${ options . project || options . baseDir }"` ) ; let tsconfigFilename ; if ( Boolean ( options . project ) ) { if ( fs . lstatSync ( options . project ) . isDirectory ( ) ) { tsconfigFilename = pathUtil . join ( options . project , 'tsconfig.json' ) ; } else { tsconfigFilename = options . project ; } } else { tsconfigFilename = pathUtil . join ( options . baseDir , 'tsconfig.json' ) ; } if ( fs . existsSync ( tsconfigFilename ) ) { verboseMessage ( `  parsing "${ tsconfigFilename }"` ) ; [ files , compilerOptions ] = getTSConfig ( tsconfigFilename ) ; } else { sendMessage ( `No "tsconfig.json" found at "${ tsconfigFilename }"!` ) ; return new Promise < void > ( function ( { } , reject ) { reject ( new SyntaxError ( 'Unable to resolve configuration.' ) ) ; } ) ; } } const eol = options . eol || os . EOL ; const nonEmptyLineStart = new RegExp ( eol + '(?!' + eol + '|$)' , 'g' ) ; const indent = options . indent === undefined ? '\t' : options . indent ; compilerOptions . declaration = true ; compilerOptions . target = compilerOptions . target || ts . ScriptTarget . Latest ; compilerOptions . moduleResolution = compilerOptions . moduleResolution || options . moduleResolution ; compilerOptions . outDir = compilerOptions . outDir || options . outDir ; const baseDir = pathUtil . resolve ( compilerOptions . rootDir || options . project || options . baseDir ) ; const outDir = compilerOptions . outDir ; verboseMessage ( `baseDir = "${ baseDir }"` ) ; verboseMessage ( `target = ${ compilerOptions . target }` ) ; verboseMessage ( `outDir = ${ compilerOptions . outDir }` ) ; verboseMessage ( `rootDir = ${ compilerOptions . rootDir }` ) ; verboseMessage ( `moduleResolution = ${ compilerOptions . moduleResolution }` ) ; const filenames = getFilenames ( baseDir , files ) ; verboseMessage ( 'filenames:' ) ; filenames . forEach ( name => { verboseMessage ( '  ' + name ) ; } ) ; const excludesMap = { } ; options . exclude = options . exclude || [ 'node_modules/**/*.d.ts' ] ; options . exclude && options . exclude . forEach ( function ( filename ) { glob . sync ( filename , { cwd : baseDir } ) . forEach ( function ( globFileName ) { excludesMap [ filenameToMid ( pathUtil . resolve ( baseDir , globFileName ) ) ] = true ; } ) ; } ) ; if ( options . exclude ) { verboseMessage ( 'exclude:' ) ; options . exclude . forEach ( name => { verboseMessage ( '  ' + name ) ; } ) ; } mkdirp . sync ( pathUtil . dirname ( options . out ) ) ; const output = fs . createWriteStream ( options . out , < any > { mode : parseInt ( '644' , 8 ) } ) ; const host = ts . createCompilerHost ( compilerOptions ) ; const program = ts . createProgram ( filenames , compilerOptions , host ) ; function writeFile ( filename , data ) { if ( filename . slice ( - DTSLEN ) !== '.d.ts' ) { return ; } writeDeclaration ( ts . createSourceFile ( filename , data , compilerOptions . target , true ) , true ) ; } let declaredExternalModules = [ ] ; return new Promise < void > ( function ( resolve , reject ) { output . on ( 'close' , ( ) => { resolve ( undefined ) ; } ) ; output . on ( 'error' , reject ) ; if ( options . externs ) { options . externs . forEach ( function ( path ) { sendMessage ( `Writing external dependency ${ path }` ) ; output . write ( `/// <reference path="${ path }" />` + eol ) ; } ) ; } if ( options . types ) { options . types . forEach ( function ( type ) { sendMessage ( `Writing external @types package dependency ${ type }` ) ; output . write ( `/// <reference types="${ type }" />` + eol ) ; } ) ; } sendMessage ( 'processing:' ) ; let mainExportDeclaration = false ; let mainExportAssignment = false ; let foundMain = false ; program . getSourceFiles ( ) . forEach ( function ( sourceFile ) { processTree ( sourceFile , function ( node ) { if ( isNodeKindModuleDeclaration ( node ) ) { const name = node . name ; if ( isNodeKindStringLiteral ( name ) ) { declaredExternalModules . push ( name . text ) ; } } return null ; } ) ; } ) ; program . getSourceFiles ( ) . some ( function ( sourceFile ) { if ( pathUtil . normalize ( sourceFile . fileName ) . indexOf ( baseDir + pathUtil . sep ) !== 0 ) { return ; } if ( excludesMap [ filenameToMid ( pathUtil . normalize ( sourceFile . fileName ) ) ] ) { return ; } sendMessage ( `  ${ sourceFile . fileName }` ) ; if ( sourceFile . fileName . slice ( - DTSLEN ) === '.d.ts' ) { writeDeclaration ( sourceFile , false ) ; return ; } if ( options . main && options . main === ( options . prefix + filenameToMid ( sourceFile . fileName . slice ( baseDir . length , - 3 ) ) ) ) { foundMain = true ; ts . forEachChild ( sourceFile , function ( node ) { mainExportDeclaration = mainExportDeclaration || isNodeKindExportDeclaration ( node ) ; mainExportAssignment = mainExportAssignment || isNodeKindExportAssignment ( node ) ; } ) ; } const emitOutput = program . emit ( sourceFile , writeFile ) ; if ( emitOutput . emitSkipped || emitOutput . diagnostics . length > 0 ) { reject ( getError ( emitOutput . diagnostics . concat ( program . getSemanticDiagnostics ( sourceFile ) ) . concat ( program . getSyntacticDiagnostics ( sourceFile ) ) . concat ( program . getDeclarationDiagnostics ( sourceFile ) ) ) ) ; return true ; } } ) ; if ( options . main && ! foundMain ) { throw new Error ( `main module ${ options . main } was not found` ) ; } if ( options . main ) { output . write ( `declare module '${ options . name }' {` + eol + indent ) ; if ( compilerOptions . target >= ts . ScriptTarget . ES2015 ) { if ( mainExportAssignment ) { output . write ( `export {default} from '${ options . main }';` + eol + indent ) ; } if ( mainExportDeclaration ) { output . write ( `export * from '${ options . main }';` + eol ) ; } } else { output . write ( `import main = require('${ options . main }');` + eol + indent ) ; output . write ( 'export = main;' + eol ) ; } output . write ( '}' + eol ) ; sendMessage ( `Aliased main module ${ options . name } to ${ options . main }` ) ; } sendMessage ( `output to "${ options . out }"` ) ; output . end ( ) ; } ) ; function writeDeclaration ( declarationFile , isOutput ) { const filename = pathUtil . resolve ( declarationFile . fileName ) ; const outputDir = ( isOutput && Boolean ( outDir ) ) ? pathUtil . resolve ( outDir ) : baseDir ; const sourceModuleId = filenameToMid ( filename . slice ( outputDir . length + 1 , - DTSLEN ) ) ; const currentModuleId = filenameToMid ( filename . slice ( outputDir . length + 1 , - DTSLEN ) ) ; function resolveModuleImport ( moduleId ) { const isDeclaredExternalModule = declaredExternalModules . indexOf ( moduleId ) !== - 1 ; let resolved ; if ( options . resolveModuleImport ) { resolved = options . resolveModuleImport ( { importedModuleId : moduleId , currentModuleId : currentModuleId , isDeclaredExternalModule : isDeclaredExternalModule } ) ; } if ( ! resolved ) { if ( moduleId . charAt ( 0 ) === '.' ) { resolved = filenameToMid ( pathUtil . join ( pathUtil . dirname ( sourceModuleId ) , moduleId ) ) ; } else { resolved = moduleId ; } if ( Boolean ( options . prefix ) && ! isDeclaredExternalModule ) { resolved = `${ options . prefix }/${ resolved }` ; } } return resolved ; } if ( ( < any > declarationFile ) . externalModuleIndicator ) { let resolvedModuleId = sourceModuleId ; if ( options . resolveModuleId ) { const resolveModuleIdResult = options . resolveModuleId ( { currentModuleId : currentModuleId } ) ; if ( resolveModuleIdResult ) { resolvedModuleId = resolveModuleIdResult ; } else if ( options . prefix ) { resolvedModuleId = `${ options . prefix }/${ resolvedModuleId }` ; } } else if ( options . prefix ) { resolvedModuleId = `${ options . prefix }/${ resolvedModuleId }` ; } output . write ( 'declare module \'' + resolvedModuleId + '\' {' + eol + indent ) ; const content = processTree ( declarationFile , function ( node ) { if ( isNodeKindExternalModuleReference ( node ) ) { const expression = node . expression as ts . LiteralExpression ; const resolved = resolveModuleImport ( expression . text ) ; return ` require('${ resolved }')` ; } else if ( node . kind === ts . SyntaxKind . DeclareKeyword ) { return '' ; } else if ( isNodeKindStringLiteral ( node ) && node . parent && ( isNodeKindExportDeclaration ( node . parent ) || isNodeKindImportDeclaration ( node . parent ) ) ) { const text = node . text ; const resolved = resolveModuleImport ( text ) ; if ( resolved ) { return ` '${ resolved }'` ; } } } ) ; output . write ( content . replace ( nonEmptyLineStart , '$&' + indent ) ) ; output . write ( eol + '}' + eol ) ; } else { output . write ( declarationFile . text ) ; } } }