namespace evaluator { declare let Symbol ; const sourceFile = vpath . combine ( vfs . srcFolder , "source.ts" ) ; const sourceFileJs = vpath . combine ( vfs . srcFolder , "source.js" ) ; const FakeSymbol = ( ( description ? ) => Symbol ( description ) ) as any ; ( FakeSymbol as any ) . prototype = Symbol . prototype ; for ( const key of Object . getOwnPropertyNames ( Symbol ) ) { Object . defineProperty ( FakeSymbol , key , Object . getOwnPropertyDescriptor ( Symbol , key ) ! ) ; } if ( ! ts . hasProperty ( FakeSymbol , "asyncIterator" ) ) Object . defineProperty ( FakeSymbol , "asyncIterator" , { value : Symbol . for ( "Symbol.asyncIterator" ) , configurable : true } ) ; export function evaluateTypeScript ( source , options ? , globals ? ) { if ( typeof source === "string" ) source = { files : { [ sourceFile ] : source } , rootFiles : [ sourceFile ] , main : sourceFile } ; const fs = vfs . createFromFileSystem ( Harness . IO , false , { files : source . files } ) ; const compilerOptions = { target : ts . ScriptTarget . ES5 , module : ts . ModuleKind . CommonJS , lib : [ "lib.esnext.d.ts" , "lib.dom.d.ts" ] , ... options } ; const host = new fakes . CompilerHost ( fs , compilerOptions ) ; const result = compiler . compileFiles ( host , source . rootFiles , compilerOptions ) ; if ( ts . some ( result . diagnostics ) ) { assert . ok ( false , "Syntax error in evaluation source text:\n" + ts . formatDiagnostics ( result . diagnostics , { getCanonicalFileName : file => file , getCurrentDirectory : ( ) => "" , getNewLine : ( ) => "\n" } ) ) ; } const output = result . getOutput ( source . main , "js" ) ! ; assert . isDefined ( output ) ; globals = { Symbol : FakeSymbol , ... globals } ; const loader = getLoader ( compilerOptions , fs , globals ) ; return loader . import ( output . file ) ; } export function evaluateJavaScript ( sourceText , globals ? , sourceFile = sourceFileJs ) { globals = { Symbol : FakeSymbol , ... globals } ; const fs = new vfs . FileSystem ( false , { files : { [ sourceFile ] : sourceText } } ) ; return new CommonJsLoader ( fs , globals ) . import ( sourceFile ) ; } function getLoader ( compilerOptions , fs , globals ) { const moduleKind = ts . getEmitModuleKind ( compilerOptions ) ; switch ( moduleKind ) { case ts . ModuleKind . UMD : case ts . ModuleKind . CommonJS : return new CommonJsLoader ( fs , globals ) ; case ts . ModuleKind . System : return new SystemLoader ( fs , globals ) ; case ts . ModuleKind . AMD : case ts . ModuleKind . None : default : throw new Error ( `ModuleKind '${ ts . ModuleKind [ moduleKind ] }' not supported by evaluator.` ) ; } } abstract class Loader < TModule > { protected readonly fs : vfs . FileSystem ; protected readonly globals : Record < string , any > ; private moduleCache = new ts . Map < string , TModule > ( ) ; constructor ( fs , globals ) { this . fs = fs ; this . globals = globals ; } protected isFile ( file ) { return this . fs . existsSync ( file ) && this . fs . statSync ( file ) . isFile ( ) ; } protected abstract evaluate ( text , file , module ) ; protected abstract createModule ( file ) ; protected abstract getExports ( module ) ; protected load ( file ) { if ( ! ts . isExternalModuleNameRelative ( file ) ) throw new Error ( `Module '${ file }' could not be found.` ) ; let module = this . moduleCache . get ( file ) ; if ( module ) return module ; this . moduleCache . set ( file , module = this . createModule ( file ) ) ; try { const sourceText = this . fs . readFileSync ( file , "utf8" ) ; this . evaluate ( sourceText , file , module ) ; return module ; } catch ( e ) { this . moduleCache . delete ( file ) ; throw e ; } } protected resolve ( id , base ) { return vpath . resolve ( base , id ) ; } import ( id , base = this . fs . cwd ( ) ) { if ( ! ts . isExternalModuleNameRelative ( id ) ) throw new Error ( `Module '${ id }' could not be found.` ) ; const file = this . resolve ( id , base ) ; const module = this . load ( file ) ; if ( ! module ) throw new Error ( `Module '${ id }' could not be found.` ) ; return this . getExports ( module ) ; } } interface CommonJSModule { exports : any ; } class CommonJsLoader extends Loader < CommonJSModule > { private resolveAsFile ( file ) { if ( this . isFile ( file ) ) return file ; if ( this . isFile ( file + ".js" ) ) return file + ".js" ; return undefined ; } private resolveIndex ( dir ) { const indexFile = vpath . resolve ( dir , "index.js" ) ; if ( this . isFile ( indexFile ) ) return indexFile ; return undefined ; } private resolveAsDirectory ( dir ) { const packageFile = vpath . resolve ( dir , "package.json" ) ; if ( this . isFile ( packageFile ) ) { const text = this . fs . readFileSync ( packageFile , "utf8" ) ; const json = JSON . parse ( text ) ; if ( json . main ) { const main = vpath . resolve ( dir , json . main ) ; const result = this . resolveAsFile ( main ) || this . resolveIndex ( main ) ; if ( result === undefined ) throw new Error ( "Module not found" ) ; } } return this . resolveIndex ( dir ) ; } protected resolve ( id , base ) { const file = vpath . resolve ( base , id ) ; const resolved = this . resolveAsFile ( file ) || this . resolveAsDirectory ( file ) ; if ( ! resolved ) throw new Error ( `Module '${ id }' could not be found.` ) ; return resolved ; } protected createModule ( ) { return { exports : { } } ; } protected getExports ( module ) { return module . exports ; } protected evaluate ( text , file , module ) { const globalNames = [ ] ; const globalArgs = [ ] ; for ( const name in this . globals ) { if ( ts . hasProperty ( this . globals , name ) ) { globalNames . push ( name ) ; globalArgs . push ( this . globals [ name ] ) ; } } const base = vpath . dirname ( file ) ; const localRequire = ( id ) => this . import ( id , base ) ; const evaluateText = `(function (module, exports, require, __dirname, __filename, ${ globalNames . join ( ", " ) }) { ${ text } })` ; const evaluateThunk = ( void 0 , eval ) ( evaluateText ) as ( module : any , exports : any , require : ( id : string ) => any , dirname : string , filename : string , ... globalArgs : any [ ] ) => void ; evaluateThunk . call ( this . globals , module , module . exports , localRequire , vpath . dirname ( file ) , file , ... globalArgs ) ; } } interface SystemModule { file : string ; exports : any ; hasExports : boolean ; state : SystemModuleState ; dependencies : SystemModule [ ] ; dependers : SystemModule [ ] ; setters : SystemModuleDependencySetter [ ] ; requestedDependencies ? : string [ ] ; declaration ? : SystemModuleDeclaration ; hasError ? : boolean ; error ? : any ; } const enum SystemModuleState { Uninstantiated , Instantiated , AddingDependencies , AllDependenciesAdded , AllDependenciesInstantiated , WiringSetters , Linked , Evaluating , Ready } interface SystemModuleExporter { < T > ( name , value ) : T ; < T extends object > ( values ) : T ; } interface SystemModuleContext { import : ( id : string ) => Promise < any > ; meta : any ; } type SystemModuleRegisterCallback = ( exporter : SystemModuleExporter , context : SystemModuleContext ) => SystemModuleDeclaration ; type SystemModuleDependencySetter = ( dependency : any ) => void ; interface SystemModuleDeclaration { setters : SystemModuleDependencySetter [ ] ; execute : ( ) => void ; } interface SystemGlobal { register ( dependencies , declare ) : void ; } class SystemLoader extends Loader < SystemModule > { protected createModule ( file ) { return { file , exports : Object . create ( null ) , dependencies : [ ] , dependers : [ ] , setters : [ ] , hasExports : false , state : SystemModuleState . Uninstantiated } ; } protected getExports ( module ) { if ( module . state < SystemModuleState . Ready ) { this . resetDependers ( module , [ ] ) ; this . evaluateModule ( module , [ ] ) ; if ( module . state < SystemModuleState . Ready ) { const error = new Error ( "Module graph could not be loaded" ) ; this . handleError ( module , error ) ; throw error ; } } if ( module . hasError ) { throw module . error ; } return module . exports ; } private handleError ( module , error ) { if ( ! module . hasError ) { module . hasError = true ; module . error = error ; module . state = SystemModuleState . Ready ; } } protected evaluate ( text , _file , module ) { const globalNames = [ ] ; const globalArgs = [ ] ; for ( const name in this . globals ) { if ( ts . hasProperty ( this . globals , name ) ) { globalNames . push ( name ) ; globalArgs . push ( this . globals [ name ] ) ; } } const localSystem = { register : ( dependencies , declare ) => this . instantiateModule ( module , dependencies , declare ) } ; const evaluateText = `(function (System, ${ globalNames . join ( ", " ) }) { ${ text } })` ; try { const evaluateThunk = ( void 0 , eval ) ( evaluateText ) as ( System : any , ... globalArgs : any [ ] ) => void ; evaluateThunk . call ( this . globals , localSystem , ... globalArgs ) ; } catch ( e ) { this . handleError ( module , e ) ; throw e ; } } private instantiateModule ( module , dependencies , registration ? ) { function exporter < T > ( name , value ) ; function exporter < T > ( value ) ; function exporter < T > ( ... args ) { module . hasExports = true ; const name = args . length === 1 ? undefined : args [ 0 ] ; const value = args . length === 1 ? args [ 0 ] : args [ 1 ] ; if ( name !== undefined ) { module . exports [ name ] = value ; } else { for ( const name in value ) { module . exports [ name ] = value [ name ] ; } } for ( const setter of module . setters ) { setter ( module . exports ) ; } return value ; } const context = { import : ( _id ) => { throw new Error ( "Dynamic import not implemented." ) ; } , meta : { url : ts . isUrl ( module . file ) ? module . file : `file:///${ ts . normalizeSlashes ( module . file ) . replace ( /^\// , "" ) . split ( "/" ) . map ( encodeURIComponent ) . join ( "/" ) }` } } ; module . requestedDependencies = dependencies ; try { module . declaration = registration ?. ( exporter , context ) ; module . state = SystemModuleState . Instantiated ; for ( const depender of module . dependers ) { this . linkModule ( depender ) ; } this . linkModule ( module ) ; } catch ( e ) { this . handleError ( module , e ) ; throw e ; } } private linkModule ( module ) { try { for ( ; ; ) { switch ( module . state ) { case SystemModuleState . Uninstantiated : { throw new Error ( "Module not yet instantiated" ) ; } case SystemModuleState . Instantiated : { module . state = SystemModuleState . AddingDependencies ; const base = vpath . dirname ( module . file ) ; const dependencies = module . requestedDependencies || [ ] ; for ( const dependencyId of dependencies ) { const dependency = this . load ( this . resolve ( dependencyId , base ) ) ; module . dependencies . push ( dependency ) ; dependency . dependers . push ( module ) ; } module . state = SystemModuleState . AllDependenciesAdded ; continue ; } case SystemModuleState . AddingDependencies : { return ; } case SystemModuleState . AllDependenciesAdded : { for ( const dependency of module . dependencies ) { if ( dependency . state === SystemModuleState . Uninstantiated ) { return ; } } module . state = SystemModuleState . AllDependenciesInstantiated ; for ( const depender of module . dependers ) { this . linkModule ( depender ) ; } continue ; } case SystemModuleState . AllDependenciesInstantiated : { module . state = SystemModuleState . WiringSetters ; for ( let i = 0 ; i < module . dependencies . length ; i ++ ) { const dependency = module . dependencies [ i ] ; const setter = module . declaration ?. setters [ i ] ; if ( setter ) { dependency . setters . push ( setter ) ; if ( dependency . hasExports || dependency . state === SystemModuleState . Ready ) { setter ( dependency . exports ) ; } } } module . state = SystemModuleState . Linked ; for ( const depender of module . dependers ) { this . linkModule ( depender ) ; } continue ; } case SystemModuleState . WiringSetters : case SystemModuleState . Linked : case SystemModuleState . Evaluating : case SystemModuleState . Ready : return ; } } } catch ( e ) { this . handleError ( module , e ) ; throw e ; } } private resetDependers ( module , stack ) { if ( stack . lastIndexOf ( module ) !== - 1 ) { return ; } stack . push ( module ) ; module . dependers . length = 0 ; for ( const dependency of module . dependencies ) { this . resetDependers ( dependency , stack ) ; } stack . pop ( ) ; } private evaluateModule ( module , stack ) { if ( module . state < SystemModuleState . Linked ) throw new Error ( "Invalid state for evaluation." ) ; if ( module . state !== SystemModuleState . Linked ) return ; if ( stack . lastIndexOf ( module ) !== - 1 ) { return ; } stack . push ( module ) ; module . state = SystemModuleState . Evaluating ; try { for ( const dependency of module . dependencies ) { this . evaluateModule ( dependency , stack ) ; } module . declaration ?. execute ?. ( ) ; module . state = SystemModuleState . Ready ; } catch ( e ) { this . handleError ( module , e ) ; throw e ; } } } }