1namespace evaluator { 2 declare let Symbol: SymbolConstructor; 3 4 const sourceFile = vpath.combine(vfs.srcFolder, "source.ts"); 5 const sourceFileJs = vpath.combine(vfs.srcFolder, "source.js"); 6 7 function compile(sourceText: string, options?: ts.CompilerOptions) { 8 const fs = vfs.createFromFileSystem(Harness.IO, /*ignoreCase*/ false); 9 fs.writeFileSync(sourceFile, sourceText); 10 const compilerOptions: ts.CompilerOptions = { 11 target: ts.ScriptTarget.ES5, 12 module: ts.ModuleKind.CommonJS, 13 lib: ["lib.esnext.d.ts", "lib.dom.d.ts"], 14 ...options 15 }; 16 const host = new fakes.CompilerHost(fs, compilerOptions); 17 return compiler.compileFiles(host, [sourceFile], compilerOptions); 18 } 19 20 function noRequire(id: string) { 21 throw new Error(`Module '${id}' could not be found.`); 22 } 23 24 // Define a custom "Symbol" constructor to attach missing built-in symbols without 25 // modifying the global "Symbol" constructor 26 const FakeSymbol: SymbolConstructor = ((description?: string) => Symbol(description)) as any; 27 (<any>FakeSymbol).prototype = Symbol.prototype; 28 for (const key of Object.getOwnPropertyNames(Symbol)) { 29 Object.defineProperty(FakeSymbol, key, Object.getOwnPropertyDescriptor(Symbol, key)!); 30 } 31 32 // Add "asyncIterator" if missing 33 if (!ts.hasProperty(FakeSymbol, "asyncIterator")) Object.defineProperty(FakeSymbol, "asyncIterator", { value: Symbol.for("Symbol.asyncIterator"), configurable: true }); 34 35 export function evaluateTypeScript(sourceText: string, options?: ts.CompilerOptions, globals?: Record<string, any>) { 36 const result = compile(sourceText, options); 37 if (ts.some(result.diagnostics)) { 38 assert.ok(/*value*/ false, "Syntax error in evaluation source text:\n" + ts.formatDiagnostics(result.diagnostics, { 39 getCanonicalFileName: file => file, 40 getCurrentDirectory: () => "", 41 getNewLine: () => "\n" 42 })); 43 } 44 45 const output = result.getOutput(sourceFile, "js")!; 46 assert.isDefined(output); 47 48 return evaluateJavaScript(output.text, globals, output.file); 49 } 50 51 export function evaluateJavaScript(sourceText: string, globals?: Record<string, any>, sourceFile = sourceFileJs) { 52 globals = { Symbol: FakeSymbol, ...globals }; 53 54 const globalNames: string[] = []; 55 const globalArgs: any[] = []; 56 for (const name in globals) { 57 if (ts.hasProperty(globals, name)) { 58 globalNames.push(name); 59 globalArgs.push(globals[name]); 60 } 61 } 62 63 const evaluateText = `(function (module, exports, require, __dirname, __filename, ${globalNames.join(", ")}) { ${sourceText} })`; 64 // eslint-disable-next-line no-eval 65 const evaluateThunk = (void 0, eval)(evaluateText) as (module: any, exports: any, require: (id: string) => any, dirname: string, filename: string, ...globalArgs: any[]) => void; 66 const module: { exports: any; } = { exports: {} }; 67 evaluateThunk.call(globals, module, module.exports, noRequire, vpath.dirname(sourceFile), sourceFile, FakeSymbol, ...globalArgs); 68 return module.exports; 69 } 70} 71