• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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