https://github.com/Microsoft/TypeScript
Tip revision: e5050bb405548a127b0a48a264bade6fd662ce06 authored by TypeScript Bot on 03 June 2022, 01:02:45 UTC
Bump version to 4.7.3 and LKG
Bump version to 4.7.3 and LKG
Tip revision: e5050bb
evaluatorImpl.ts
namespace evaluator {
declare let Symbol: SymbolConstructor;
const sourceFile = vpath.combine(vfs.srcFolder, "source.ts");
const sourceFileJs = vpath.combine(vfs.srcFolder, "source.js");
// Define a custom "Symbol" constructor to attach missing built-in symbols without
// modifying the global "Symbol" constructor
const FakeSymbol: SymbolConstructor = ((description?: string) => 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)!);
}
// Add "asyncIterator" if missing
if (!ts.hasProperty(FakeSymbol, "asyncIterator")) Object.defineProperty(FakeSymbol, "asyncIterator", { value: Symbol.for("Symbol.asyncIterator"), configurable: true });
export function evaluateTypeScript(source: string | { files: vfs.FileSet, rootFiles: string[], main: string }, options?: ts.CompilerOptions, globals?: Record<string, any>) {
if (typeof source === "string") source = { files: { [sourceFile]: source }, rootFiles: [sourceFile], main: sourceFile };
const fs = vfs.createFromFileSystem(Harness.IO, /*ignoreCase*/ false, { files: source.files });
const compilerOptions: ts.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(/*value*/ 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: string, globals?: Record<string, any>, sourceFile = sourceFileJs) {
globals = { Symbol: FakeSymbol, ...globals };
const fs = new vfs.FileSystem(/*ignoreCase*/ false, { files: { [sourceFile]: sourceText } });
return new CommonJsLoader(fs, globals).import(sourceFile);
}
function getLoader(compilerOptions: ts.CompilerOptions, fs: vfs.FileSystem, globals: Record<string, any>): Loader<unknown> {
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: vfs.FileSystem, globals: Record<string, any>) {
this.fs = fs;
this.globals = globals;
}
protected isFile(file: string) {
return this.fs.existsSync(file) && this.fs.statSync(file).isFile();
}
protected abstract evaluate(text: string, file: string, module: TModule): void;
protected abstract createModule(file: string): TModule;
protected abstract getExports(module: TModule): any;
protected load(file: string): TModule {
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: string, base: string) {
return vpath.resolve(base, id);
}
import(id: string, 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: string) {
if (this.isFile(file)) return file;
if (this.isFile(file + ".js")) return file + ".js";
return undefined;
}
private resolveIndex(dir: string) {
const indexFile = vpath.resolve(dir, "index.js");
if (this.isFile(indexFile)) return indexFile;
return undefined;
}
private resolveAsDirectory(dir: string) {
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: string, base: string) {
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(): CommonJSModule {
return { exports: {} };
}
protected getExports(module: CommonJSModule) {
return module.exports;
}
protected evaluate(text: string, file: string, module: CommonJSModule): void {
const globalNames: string[] = [];
const globalArgs: any[] = [];
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: string) => this.import(id, base);
const evaluateText = `(function (module, exports, require, __dirname, __filename, ${globalNames.join(", ")}) { ${text} })`;
// eslint-disable-next-line no-eval
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 {
// Instantiation phases:
Uninstantiated,
Instantiated,
// Linker phases:
AddingDependencies,
AllDependenciesAdded,
AllDependenciesInstantiated,
WiringSetters,
Linked,
// Evaluation phases:
Evaluating,
Ready,
}
interface SystemModuleExporter {
<T>(name: string, value: T): T;
<T extends object>(values: T): 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: string[], declare: SystemModuleRegisterCallback): void;
}
class SystemLoader extends Loader<SystemModule> {
protected createModule(file: string): SystemModule {
return {
file,
// eslint-disable-next-line no-null/no-null
exports: Object.create(/*o*/ null),
dependencies: [],
dependers: [],
setters: [],
hasExports: false,
state: SystemModuleState.Uninstantiated
};
}
protected getExports(module: SystemModule) {
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: SystemModule, error: any) {
if (!module.hasError) {
module.hasError = true;
module.error = error;
module.state = SystemModuleState.Ready;
}
}
protected evaluate(text: string, _file: string, module: SystemModule): void {
const globalNames: string[] = [];
const globalArgs: any[] = [];
for (const name in this.globals) {
if (ts.hasProperty(this.globals, name)) {
globalNames.push(name);
globalArgs.push(this.globals[name]);
}
}
const localSystem: SystemGlobal = {
register: (dependencies, declare) => this.instantiateModule(module, dependencies, declare)
};
const evaluateText = `(function (System, ${globalNames.join(", ")}) { ${text} })`;
try {
// eslint-disable-next-line no-eval
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: SystemModule, dependencies: string[], registration?: SystemModuleRegisterCallback) {
function exporter<T>(name: string, value: T): T;
function exporter<T>(value: T): T;
function exporter<T>(...args: [string, T] | [T]) {
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: SystemModuleContext = {
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: SystemModule) {
try {
for (;;) {
switch (module.state) {
case SystemModuleState.Uninstantiated: {
throw new Error("Module not yet instantiated");
}
case SystemModuleState.Instantiated: {
// Module has been instantiated, start requesting dependencies.
// Set state so that re-entry while adding dependencies does nothing.
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);
}
// All dependencies have been added, switch state
// to check whether all dependencies are instantiated
module.state = SystemModuleState.AllDependenciesAdded;
continue;
}
case SystemModuleState.AddingDependencies: {
// in the middle of adding dependencies for this module, do nothing
return;
}
case SystemModuleState.AllDependenciesAdded: {
// all dependencies have been added, advance state if all dependencies are instantiated.
for (const dependency of module.dependencies) {
if (dependency.state === SystemModuleState.Uninstantiated) {
return;
}
}
// indicate all dependencies are instantiated for this module.
module.state = SystemModuleState.AllDependenciesInstantiated;
// trigger links for dependers of this module.
for (const depender of module.dependers) {
this.linkModule(depender);
}
continue;
}
case SystemModuleState.AllDependenciesInstantiated: {
// all dependencies have been instantiated, start wiring setters
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) {
// wire hoisted exports or ready dependencies.
setter(dependency.exports);
}
}
}
module.state = SystemModuleState.Linked;
// ensure graph is fully linked
for (const depender of module.dependers) {
this.linkModule(depender);
}
continue;
}
case SystemModuleState.WiringSetters: // in the middle of wiring setters for this module, nothing to do
case SystemModuleState.Linked: // module has already been linked, nothing to do
case SystemModuleState.Evaluating: // module is currently evaluating, nothing to do
case SystemModuleState.Ready: // module is done evaluating, nothing to do
return;
}
}
}
catch (e) {
this.handleError(module, e);
throw e;
}
}
private resetDependers(module: SystemModule, stack: SystemModule[]) {
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: SystemModule, stack: SystemModule[]) {
if (module.state < SystemModuleState.Linked) throw new Error("Invalid state for evaluation.");
if (module.state !== SystemModuleState.Linked) return;
if (stack.lastIndexOf(module) !== -1) {
// we are already evaluating this module
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;
}
}
}
}