UNPKG

ts-evaluator

Version:

An interpreter for Typescript that can evaluate an arbitrary Node within a Typescript AST

1,581 lines (1,518 loc) 158 kB
// src/interpreter/evaluate.ts import * as TSModule from "typescript"; // src/interpreter/lexical-environment/lexical-environment.ts import objectPath from "object-path"; // src/interpreter/environment/ecma/ecma-globals.ts var ECMA_GLOBALS = () => { const base = { Infinity: Infinity, NaN: NaN, undefined: void 0, isNaN, parseFloat, parseInt, decodeURI, decodeURIComponent, encodeURI, encodeURIComponent, Array, Boolean, Date, Error, EvalError, Number, Object, RangeError, ReferenceError, RegExp, String, SyntaxError, TypeError, URIError, JSON, Math, escape, unescape, // eslint-disable-next-line no-eval eval, Function /* eslint-enable @typescript-eslint/naming-convention */ }; try { base.AggregateError = AggregateError; } catch { } try { base.FinalizationRegistry = FinalizationRegistry; } catch { } try { base.WeakRef = WeakRef; } catch { } try { base.BigInt = BigInt; } catch { } try { base.Reflect = Reflect; } catch { } try { base.WeakMap = WeakMap; } catch { } try { base.WeakSet = WeakSet; } catch { } try { base.Set = Set; } catch { } try { base.Map = Map; } catch { } try { base.Uint8Array = Uint8Array; } catch { } try { base.BigUint64Array = BigUint64Array; } catch { } try { base.BigInt64Array = BigInt64Array; } catch { } try { base.Atomics = Atomics; } catch { } try { base.SharedArrayBuffer = SharedArrayBuffer; } catch { } try { base.WebAssembly = WebAssembly; } catch { } try { base.Uint8ClampedArray = Uint8ClampedArray; } catch { } try { base.Uint16Array = Uint16Array; } catch { } try { base.Uint32Array = Uint32Array; } catch { } try { base.Intl = Intl; } catch { } try { base.Int8Array = Int8Array; } catch { } try { base.Int16Array = Int16Array; } catch { } try { base.Int32Array = Int32Array; } catch { } try { base.Float32Array = Float32Array; } catch { } try { base.Float64Array = Float64Array; } catch { } try { base.ArrayBuffer = ArrayBuffer; } catch { } try { base.DataView = DataView; } catch { } try { base.isFinite = isFinite; } catch { } try { base.Promise = Promise; } catch { } try { base.Proxy = Proxy; } catch { } try { base.Symbol = Symbol; } catch { } return base; }; // src/interpreter/util/descriptor/merge-descriptors.ts function mergeDescriptors(a, b, c) { const newObj = {}; const normalizedB = b ?? {}; const normalizedC = c ?? {}; [a, normalizedB, normalizedC].forEach((item) => Object.defineProperties(newObj, Object.getOwnPropertyDescriptors(item))); return newObj; } // src/interpreter/util/object/subtract.ts function subtract(a, b) { const newA = {}; Object.getOwnPropertyNames(a).forEach((name) => { if (!(name in b)) { Object.defineProperty(newA, name, Object.getOwnPropertyDescriptor(a, name)); } }); return newA; } // src/interpreter/environment/node/node-cjs-globals.ts import path from "crosspath"; // src/interpreter/util/loader/require-module.ts import { createRequire } from "module"; var requireModule = createRequire(import.meta.url); // src/interpreter/environment/node/node-cjs-globals.ts var NODE_CJS_GLOBALS = () => { const ecmaGlobals = ECMA_GLOBALS(); const merged = mergeDescriptors(subtract(global, ecmaGlobals), ecmaGlobals, { require: requireModule, process, __dirname: (fileName) => path.native.normalize(path.native.dirname(fileName)), __filename: (fileName) => path.native.normalize(fileName) }); Object.defineProperties(merged, { global: { get() { return merged; } }, globalThis: { get() { return merged; } } }); return merged; }; // src/interpreter/environment/browser/lib/raf.ts function rafImplementation(global2) { let lastTime = 0; const _requestAnimationFrame = function requestAnimationFrame(callback) { const currTime = (/* @__PURE__ */ new Date()).getTime(); const timeToCall = Math.max(0, 16 - (currTime - lastTime)); const id = global2.setTimeout(function() { callback(currTime + timeToCall); }, timeToCall); lastTime = currTime + timeToCall; return id; }; const _cancelAnimationFrame = function cancelAnimationFrame(id) { clearTimeout(id); }; return { requestAnimationFrame: _requestAnimationFrame, cancelAnimationFrame: _cancelAnimationFrame }; } // src/interpreter/util/loader/optional-peer-dependency-loader.ts var jsdomModule; function loadJsdom(required = false) { return jsdomModule ??= loadModules("evaluate against a browser environment", required, "jsdom"); } function loadModules(description, required, moduleSpecifier = description) { try { return requireModule(moduleSpecifier); } catch { if (required) { throw new ReferenceError(`You must install the peer dependency '${moduleSpecifier}' in order to ${description} with ts-evaluator`); } return void 0; } } // src/interpreter/environment/browser/browser-globals.ts var BROWSER_GLOBALS = () => { const { JSDOM } = loadJsdom(true); const { window } = new JSDOM("", { url: "https://example.com" }); const ecmaGlobals = ECMA_GLOBALS(); if (window.requestAnimationFrame == null) { const raf = rafImplementation(window); Object.defineProperties(window, Object.getOwnPropertyDescriptors(raf)); } const missingEcmaGlobals = subtract(ecmaGlobals, window); if (Object.keys(missingEcmaGlobals).length > 0) { Object.defineProperties(window, Object.getOwnPropertyDescriptors(ecmaGlobals)); } return window; }; // src/interpreter/util/return/return-symbol.ts var RETURN_SYMBOL = "[return]"; // src/interpreter/util/break/break-symbol.ts var BREAK_SYMBOL = "[break]"; // src/interpreter/util/continue/continue-symbol.ts var CONTINUE_SYMBOL = "[continue]"; // src/interpreter/util/this/this-symbol.ts var THIS_SYMBOL = "this"; // src/interpreter/util/super/super-symbol.ts var SUPER_SYMBOL = "super"; // src/interpreter/environment/node/node-esm-globals.ts import path2 from "crosspath"; var NODE_ESM_GLOBALS = () => { const ecmaGlobals = ECMA_GLOBALS(); const merged = mergeDescriptors(subtract(global, ecmaGlobals), ecmaGlobals, { import: { meta: { url: (fileName) => { const normalized = path2.normalize(fileName); return `file:///${normalized.startsWith(`/`) ? normalized.slice(1) : normalized}`; } } }, process }); Object.defineProperties(merged, { global: { get() { return merged; } }, globalThis: { get() { return merged; } } }); return merged; }; // src/interpreter/util/declaration/is-declaration.ts function isDeclaration(node, typescript) { return typescript.isDeclaration(node); } function isNamedDeclaration(node, typescript) { if (typescript.isPropertyAccessExpression(node)) return false; return "name" in node && node.name != null; } // src/interpreter/util/flags/is-var-declaration.ts function isVarDeclaration(declarationList, typescript) { return declarationList.flags !== typescript.NodeFlags.Const && declarationList.flags !== typescript.NodeFlags.Let; } // src/interpreter/error/evaluation-error/evaluation-error.ts var EvaluationError = class extends Error { /** * The node that caused or thew the error */ node; environment; constructor({ node, environment, message }) { super(message); Error.captureStackTrace(this, this.constructor); this.node = node; this.environment = environment; } }; function isEvaluationError(item) { return typeof item === "object" && item != null && item instanceof EvaluationError; } // src/interpreter/error/module-not-found-error/module-not-found-error.ts var ModuleNotFoundError = class extends EvaluationError { /** * The path/moduleName that could not be resolved */ path; constructor({ path: path4, node, environment, message = `Module '${path4}' could not be resolved'` }) { super({ message, environment, node }); this.path = path4; } }; // src/interpreter/error/unexpected-node-error/unexpected-node-error.ts var UnexpectedNodeError = class extends EvaluationError { constructor({ node, environment, typescript, message = `Unexpected Node: '${typescript.SyntaxKind[node.kind]}'` }) { super({ message, node, environment }); } }; // src/interpreter/util/declaration/get-declaration-name.ts function getDeclarationName(options) { const { node, evaluate: evaluate2, environment, typescript, throwError } = options; const name = typescript.getNameOfDeclaration(node); if (name == null) return void 0; if (typescript.isIdentifier(name)) { return name.text; } else if (typescript.isPrivateIdentifier?.(name)) { return name.text; } else if (typescript.isStringLiteralLike(name)) { return name.text; } else if (typescript.isNumericLiteral(name)) { return Number(name.text); } else if (typescript.isComputedPropertyName(name)) { return evaluate2.expression(name.expression, options); } else { return throwError(new UnexpectedNodeError({ node: name, environment, typescript })); } } // src/interpreter/util/module/get-resolved-module-name.ts import path3 from "crosspath"; function getResolvedModuleName(moduleSpecifier, options) { const { node, typescript } = options; if (!typescript.isExternalModuleNameRelative(moduleSpecifier)) { return moduleSpecifier; } const parentPath = node.getSourceFile().fileName; return path3.join(path3.dirname(parentPath), moduleSpecifier); } // src/interpreter/util/module/get-implementation-for-declaration-within-declaration-file.ts function getImplementationForDeclarationWithinDeclarationFile(options) { const { node, typescript, throwError, environment } = options; const name = getDeclarationName(options); if (isEvaluationError(name)) { return name; } if (name == null) { return throwError(new UnexpectedNodeError({ node, environment, typescript })); } const matchInLexicalEnvironment = getFromLexicalEnvironment(node, options.environment, name); if (matchInLexicalEnvironment?.literal != null) { return matchInLexicalEnvironment.literal; } const require2 = getFromLexicalEnvironment(node, options.environment, "require").literal; const moduleDeclaration = typescript.isModuleDeclaration(node) ? node : findNearestParentNodeOfKind(node, typescript.SyntaxKind.ModuleDeclaration, typescript); if (moduleDeclaration == null) { return throwError(new UnexpectedNodeError({ node, environment, typescript })); } const moduleSpecifier = moduleDeclaration.name.text; const resolvedModuleSpecifier = getResolvedModuleName(moduleSpecifier, options); try { const module = options.moduleOverrides?.[moduleSpecifier] ?? options.moduleOverrides?.[resolvedModuleSpecifier] ?? require2(resolvedModuleSpecifier); return typescript.isModuleDeclaration(node) ? module : module[name] ?? module; } catch (ex) { if (isEvaluationError(ex)) return ex; else return throwError(new ModuleNotFoundError({ node: moduleDeclaration, environment, path: resolvedModuleSpecifier })); } } function getImplementationFromExternalFile(name, moduleSpecifier, options) { const { node, throwError, environment } = options; const require2 = getFromLexicalEnvironment(node, options.environment, "require").literal; const resolvedModuleSpecifier = getResolvedModuleName(moduleSpecifier, options); try { const module = options.moduleOverrides?.[moduleSpecifier] ?? options.moduleOverrides?.[resolvedModuleSpecifier] ?? require2(resolvedModuleSpecifier); return module[name] ?? module.default ?? module; } catch (ex) { if (isEvaluationError(ex)) return ex; else return throwError(new ModuleNotFoundError({ node, environment, path: resolvedModuleSpecifier })); } } // src/interpreter/util/node/find-nearest-parent-node-of-kind.ts function findNearestParentNodeOfKind(from, kind, typescript) { let currentParent = from; while (true) { currentParent = currentParent.parent; if (currentParent == null) return void 0; if (currentParent.kind === kind) { const combinedNodeFlags = typescript.getCombinedNodeFlags(currentParent); const isNamespace = (combinedNodeFlags & typescript.NodeFlags.Namespace) !== 0 || (combinedNodeFlags & typescript.NodeFlags.NestedNamespace) !== 0; if (!isNamespace) return currentParent; } if (typescript.isSourceFile(currentParent)) return void 0; } } function findNearestParentNodeWithName(from, name, options, visitedRoots = /* @__PURE__ */ new WeakSet()) { const { typescript } = options; let result; function visit(nextNode, nestingLayer = 0) { if (visitedRoots.has(nextNode)) return false; visitedRoots.add(nextNode); if (typescript.isIdentifier(nextNode)) { if (nextNode.text === name) { result = nextNode; return true; } } else if (typescript.isShorthandPropertyAssignment(nextNode)) { return false; } else if (typescript.isPropertyAssignment(nextNode)) { return false; } else if (typescript.isImportDeclaration(nextNode)) { if (nextNode.importClause != null) { if (nextNode.importClause.name != null && visit(nextNode.importClause.name)) { const moduleSpecifier = nextNode.moduleSpecifier; if (moduleSpecifier != null && typescript.isStringLiteralLike(moduleSpecifier)) { result = getImplementationFromExternalFile(name, moduleSpecifier.text, options); return true; } } else if (nextNode.importClause.namedBindings != null && visit(nextNode.importClause.namedBindings)) { return true; } } return false; } else if (typescript.isImportEqualsDeclaration(nextNode)) { if (nextNode.name != null && visit(nextNode.name)) { if (typescript.isIdentifier(nextNode.moduleReference)) { result = findNearestParentNodeWithName(nextNode.parent, nextNode.moduleReference.text, options, visitedRoots); return result != null; } else if (typescript.isQualifiedName(nextNode.moduleReference)) { return false; } else { const moduleSpecifier = nextNode.moduleReference.expression; if (moduleSpecifier != null && typescript.isStringLiteralLike(moduleSpecifier)) { result = getImplementationFromExternalFile(name, moduleSpecifier.text, options); return true; } } } return false; } else if (typescript.isNamespaceImport(nextNode)) { if (visit(nextNode.name)) { const moduleSpecifier = nextNode.parent?.parent?.moduleSpecifier; if (moduleSpecifier == null || !typescript.isStringLiteralLike(moduleSpecifier)) { return false; } result = getImplementationFromExternalFile(name, moduleSpecifier.text, options); return true; } } else if (typescript.isNamedImports(nextNode)) { for (const importSpecifier of nextNode.elements) { if (visit(importSpecifier)) { return true; } } } else if (typescript.isImportSpecifier(nextNode)) { if (visit(nextNode.name)) { const moduleSpecifier = nextNode.parent?.parent?.parent?.moduleSpecifier; if (moduleSpecifier == null || !typescript.isStringLiteralLike(moduleSpecifier)) { return false; } result = getImplementationFromExternalFile(name, moduleSpecifier.text, options); return true; } } else if (typescript.isSourceFile(nextNode)) { for (const statement of nextNode.statements) { if (visit(statement)) { return true; } } } else if (typescript.isVariableStatement(nextNode)) { for (const declaration of nextNode.declarationList.declarations) { if (visit(declaration) && (isVarDeclaration(nextNode.declarationList, typescript) || nestingLayer < 1)) { return true; } } } else if (typescript.isBlock(nextNode)) { for (const statement of nextNode.statements) { if (visit(statement, nestingLayer + 1)) { return true; } } } else if (isNamedDeclaration(nextNode, typescript)) { if (nextNode.name != null && visit(nextNode.name)) { result = nextNode; return true; } } return false; } const suceeded = typescript.findAncestor(from, (nextNode) => visit(nextNode)); return !suceeded ? void 0 : result; } function getStatementContext(from, typescript) { let currentParent = from; while (true) { currentParent = currentParent.parent; if (currentParent == null) return void 0; if (isDeclaration(currentParent, typescript) || typescript.isSourceFile(currentParent)) { return currentParent; } } } // src/interpreter/util/proxy/can-be-observed.ts function isObjectLike(value) { return value != null && (typeof value === "function" || typeof value === "object"); } function canBeObserved(value) { return isObjectLike(value); } // src/interpreter/util/function/is-bind-call-apply.ts function isBindCallApply(func, environment) { switch (func) { case Function.prototype.bind: case Function.prototype.call: case Function.prototype.apply: return true; } if (environment != null) { const _Function = getFromLexicalEnvironment(void 0, environment, "Function").literal; switch (func) { case _Function.prototype.bind: case _Function.prototype.call: case _Function.prototype.apply: return true; } } return false; } // src/interpreter/policy/policy-trap-kind.ts function stringifyPolicyTrapKindOnPath(kind, path4) { switch (kind) { case "__$$_PROXY_GET" /* GET */: return `get ${path4}`; case "__$$_PROXY_APPLY" /* APPLY */: return `${path4}(...)`; case "__$$_PROXY_CONSTRUCT" /* CONSTRUCT */: return `new ${path4}(...)`; } } // src/interpreter/error/evaluation-error/evaluation-error-intent.ts var EvaluationErrorIntent = class { constructor(intent) { this.intent = intent; } construct(node, options) { return this.intent(node, options); } }; function isEvaluationErrorIntent(item) { return typeof item === "object" && item != null && item instanceof EvaluationErrorIntent; } function maybeThrow(node, options, value) { return isEvaluationErrorIntent(value) ? options.throwError(value.construct(node, options)) : value; } // src/interpreter/proxy/create-policy-proxy.ts function stringifyPath(path4) { return path4.map((part) => typeof part === "symbol" ? part.description : part).join("."); } function createPolicyProxy({ hook, item, scope, policy }) { function createAccessTrap(inputPath, currentItem) { const handleHookResult = (result, successCallback) => { if (result === false) return; if (isEvaluationErrorIntent(result) || isEvaluationError(result)) return result; return successCallback(); }; return !canBeObserved(currentItem) || isBindCallApply(currentItem) ? currentItem : new Proxy(currentItem, { /** * Constructs a new instance of the given target */ construct(target, argArray, newTarget) { return handleHookResult( hook({ kind: "__$$_PROXY_CONSTRUCT" /* CONSTRUCT */, policy, newTarget, argArray, target, path: stringifyPath(inputPath) }), () => Reflect.construct(target, argArray, newTarget) ); }, /** * A trap for a function call. Used to create new proxies for methods on the retrieved module objects */ apply(target, thisArg, argArray = []) { return handleHookResult( hook({ kind: "__$$_PROXY_APPLY" /* APPLY */, policy, thisArg, argArray, target, path: stringifyPath(inputPath) }), () => Reflect.apply(target, thisArg, argArray) ); }, /** * Gets a trap for 'get' accesses */ get(target, property, receiver) { const newPath = [...inputPath, property]; return handleHookResult( hook({ kind: "__$$_PROXY_GET" /* GET */, policy, path: stringifyPath(newPath), target }), () => { const match = Reflect.get(target, property, receiver); const config = Reflect.getOwnPropertyDescriptor(currentItem, property); if (config != null && config.configurable === false && config.writable === false) { return currentItem[property]; } return createAccessTrap(newPath, match); } ); } }); } return !canBeObserved(item) ? item : createAccessTrap([scope], item); } // src/interpreter/policy/network/network-map.ts var NETWORK_MAP = { "node:http2": "http2", http2: { connect: { ["__$$_PROXY_APPLY" /* APPLY */]: true }, createSecureServer: { ["__$$_PROXY_APPLY" /* APPLY */]: true }, createServer: { ["__$$_PROXY_APPLY" /* APPLY */]: true } }, "node:https": "https", https: { createServer: { ["__$$_PROXY_APPLY" /* APPLY */]: true }, request: { ["__$$_PROXY_APPLY" /* APPLY */]: true }, get: { ["__$$_PROXY_APPLY" /* APPLY */]: true }, Server: { ["__$$_PROXY_CONSTRUCT" /* CONSTRUCT */]: true }, globalAgent: { destroy: { ["__$$_PROXY_APPLY" /* APPLY */]: true } }, Agent: { ["__$$_PROXY_CONSTRUCT" /* CONSTRUCT */]: true } }, "node:http": "http", http: { createServer: { ["__$$_PROXY_APPLY" /* APPLY */]: true }, request: { ["__$$_PROXY_APPLY" /* APPLY */]: true }, get: { ["__$$_PROXY_APPLY" /* APPLY */]: true }, Server: { ["__$$_PROXY_CONSTRUCT" /* CONSTRUCT */]: true }, ClientRequest: { ["__$$_PROXY_CONSTRUCT" /* CONSTRUCT */]: true }, globalAgent: { destroy: { ["__$$_PROXY_APPLY" /* APPLY */]: true } }, Agent: { ["__$$_PROXY_CONSTRUCT" /* CONSTRUCT */]: true } }, "node:dgram": "dgram", dgram: { createSocket: { ["__$$_PROXY_APPLY" /* APPLY */]: true } }, "node:dns": "dns", dns: { lookup: { ["__$$_PROXY_APPLY" /* APPLY */]: true }, lookupService: { ["__$$_PROXY_APPLY" /* APPLY */]: true }, resolve: { ["__$$_PROXY_APPLY" /* APPLY */]: true }, resolve4: { ["__$$_PROXY_APPLY" /* APPLY */]: true }, resolve6: { ["__$$_PROXY_APPLY" /* APPLY */]: true }, resolveAny: { ["__$$_PROXY_APPLY" /* APPLY */]: true }, resolveCname: { ["__$$_PROXY_APPLY" /* APPLY */]: true }, resolveMx: { ["__$$_PROXY_APPLY" /* APPLY */]: true }, resolveNaptr: { ["__$$_PROXY_APPLY" /* APPLY */]: true }, resolveNs: { ["__$$_PROXY_APPLY" /* APPLY */]: true }, resolvePtr: { ["__$$_PROXY_APPLY" /* APPLY */]: true }, resolveSoa: { ["__$$_PROXY_APPLY" /* APPLY */]: true }, resolveSrv: { ["__$$_PROXY_APPLY" /* APPLY */]: true }, resolveTxt: { ["__$$_PROXY_APPLY" /* APPLY */]: true }, reverse: { ["__$$_PROXY_APPLY" /* APPLY */]: true }, Resolver: { ["__$$_PROXY_CONSTRUCT" /* CONSTRUCT */]: true } }, "node:net": "net", net: { createServer: { ["__$$_PROXY_APPLY" /* APPLY */]: true }, createConnection: { ["__$$_PROXY_APPLY" /* APPLY */]: true }, connect: { ["__$$_PROXY_APPLY" /* APPLY */]: true }, Server: { ["__$$_PROXY_CONSTRUCT" /* CONSTRUCT */]: true } }, "node:tls": "tls", tls: { createServer: { ["__$$_PROXY_APPLY" /* APPLY */]: true }, createSecureContext: { ["__$$_PROXY_APPLY" /* APPLY */]: true }, connect: { ["__$$_PROXY_APPLY" /* APPLY */]: true }, Server: { ["__$$_PROXY_CONSTRUCT" /* CONSTRUCT */]: true }, TLSSocket: { ["__$$_PROXY_CONSTRUCT" /* CONSTRUCT */]: true } } }; // src/interpreter/policy/nondeterministic/nondeterministic-map.ts var NONDETERMINISTIC_MAP = { // Any network operation will always be non-deterministic ...NETWORK_MAP, Math: { random: { ["__$$_PROXY_APPLY" /* APPLY */]: true } }, Date: { now: { ["__$$_PROXY_APPLY" /* APPLY */]: true }, // Dates that receive no arguments are nondeterministic since they care about "now" and will evaluate to a new value for each invocation ["__$$_PROXY_CONSTRUCT" /* CONSTRUCT */]: (...args) => args.length === 0 && !(args[0] instanceof Date) } }; // src/interpreter/policy/trap-condition-map.ts function isTrapCondition(item, condition) { return typeof item === typeof condition || typeof item === "function"; } function isTrapConditionFunction(item) { return typeof item === "function"; } // src/interpreter/policy/is-trap-condition-met.ts function isTrapConditionMet(map, condition, item) { const atoms = item.path.split("."); return walkAtoms(map, condition, item, atoms); } function walkAtoms(map, matchCondition, item, atoms) { const [head, ...tail] = atoms; if (head == null) return false; const mapEntry = map[head]; if (mapEntry == null) return false; if (typeof mapEntry === "string") { return walkAtoms(map, matchCondition, item, [mapEntry, ...tail]); } if (isTrapCondition(mapEntry, matchCondition)) { return handleTrapCondition(mapEntry, matchCondition, item); } else { const trapMapMatch = mapEntry[item.kind]; if (trapMapMatch != null) { return handleTrapCondition(trapMapMatch, matchCondition, item); } else { return walkAtoms(mapEntry, matchCondition, item, tail); } } } function handleTrapCondition(trapCondition, matchCondition, item) { if (isTrapConditionFunction(trapCondition)) { const castItem = item; return castItem.argArray != null && trapCondition(...castItem.argArray) === matchCondition; } else { return trapCondition === matchCondition; } } // src/interpreter/policy/nondeterministic/is-nondeterministic.ts function isNonDeterministic(item) { return isTrapConditionMet(NONDETERMINISTIC_MAP, true, item); } // src/interpreter/error/policy-error/policy-error.ts var PolicyError = class extends EvaluationError { /** * The kind of policy violation encountered */ violation; constructor({ violation, node, environment, message }) { super({ node, environment, message: `[${violation}]: ${message}` }); this.violation = violation; } }; // src/interpreter/error/policy-error/non-deterministic-error/non-deterministic-error.ts var NonDeterministicError = class extends PolicyError { /** * The kind of operation that was attempted to be performed but was in violation of the policy */ operation; constructor({ operation, node, environment, message = `The operation: '${operation}' is nondeterministic. That is in violation of the policy` }) { super({ violation: "deterministic", message, node, environment }); this.operation = operation; } }; // src/interpreter/policy/io/io-map.ts var IO_MAP = { "node:fs": "fs", fs: { readFile: { ["__$$_PROXY_APPLY" /* APPLY */]: "read" }, readFileSync: { ["__$$_PROXY_APPLY" /* APPLY */]: "read" }, readdir: { ["__$$_PROXY_APPLY" /* APPLY */]: "read" }, readdirSync: { ["__$$_PROXY_APPLY" /* APPLY */]: "read" }, read: { ["__$$_PROXY_APPLY" /* APPLY */]: "read" }, readSync: { ["__$$_PROXY_APPLY" /* APPLY */]: "read" }, exists: { ["__$$_PROXY_APPLY" /* APPLY */]: "read" }, existsSync: { ["__$$_PROXY_APPLY" /* APPLY */]: "read" }, access: { ["__$$_PROXY_APPLY" /* APPLY */]: "read" }, accessSync: { ["__$$_PROXY_APPLY" /* APPLY */]: "read" }, close: { ["__$$_PROXY_APPLY" /* APPLY */]: "read" }, closeSync: { ["__$$_PROXY_APPLY" /* APPLY */]: "read" }, createReadStream: { ["__$$_PROXY_APPLY" /* APPLY */]: "read" }, stat: { ["__$$_PROXY_APPLY" /* APPLY */]: "read" }, statSync: { ["__$$_PROXY_APPLY" /* APPLY */]: "read" }, watch: { ["__$$_PROXY_APPLY" /* APPLY */]: "read" }, watchFile: { ["__$$_PROXY_APPLY" /* APPLY */]: "read" }, unwatchFile: { ["__$$_PROXY_APPLY" /* APPLY */]: "read" }, realpath: { ["__$$_PROXY_APPLY" /* APPLY */]: "read" }, realpathSync: { ["__$$_PROXY_APPLY" /* APPLY */]: "read" }, fstat: { ["__$$_PROXY_APPLY" /* APPLY */]: "read" }, fstatSync: { ["__$$_PROXY_APPLY" /* APPLY */]: "read" }, createWriteStream: { ["__$$_PROXY_APPLY" /* APPLY */]: "write" }, copyFile: { ["__$$_PROXY_APPLY" /* APPLY */]: "write" }, copyFileSync: { ["__$$_PROXY_APPLY" /* APPLY */]: "write" }, unlink: { ["__$$_PROXY_APPLY" /* APPLY */]: "write" }, unlinkSync: { ["__$$_PROXY_APPLY" /* APPLY */]: "write" }, rmdir: { ["__$$_PROXY_APPLY" /* APPLY */]: "write" }, rmdirSync: { ["__$$_PROXY_APPLY" /* APPLY */]: "write" }, symlink: { ["__$$_PROXY_APPLY" /* APPLY */]: "write" }, symlinkSync: { ["__$$_PROXY_APPLY" /* APPLY */]: "write" }, truncate: { ["__$$_PROXY_APPLY" /* APPLY */]: "write" }, truncateSync: { ["__$$_PROXY_APPLY" /* APPLY */]: "write" }, utimes: { ["__$$_PROXY_APPLY" /* APPLY */]: "write" }, utimesSync: { ["__$$_PROXY_APPLY" /* APPLY */]: "write" }, appendFile: { ["__$$_PROXY_APPLY" /* APPLY */]: "write" }, appendFileSync: { ["__$$_PROXY_APPLY" /* APPLY */]: "write" }, write: { ["__$$_PROXY_APPLY" /* APPLY */]: "write" }, writeSync: { ["__$$_PROXY_APPLY" /* APPLY */]: "write" }, writeFile: { ["__$$_PROXY_APPLY" /* APPLY */]: "write" }, writeFileSync: { ["__$$_PROXY_APPLY" /* APPLY */]: "write" }, chmod: { ["__$$_PROXY_APPLY" /* APPLY */]: "write" }, chmodSync: { ["__$$_PROXY_APPLY" /* APPLY */]: "write" }, chown: { ["__$$_PROXY_APPLY" /* APPLY */]: "write" }, chownSync: { ["__$$_PROXY_APPLY" /* APPLY */]: "write" }, mkdir: { ["__$$_PROXY_APPLY" /* APPLY */]: "write" }, mkdirSync: { ["__$$_PROXY_APPLY" /* APPLY */]: "write" }, rename: { ["__$$_PROXY_APPLY" /* APPLY */]: "write" }, renameSync: { ["__$$_PROXY_APPLY" /* APPLY */]: "write" }, futimes: { ["__$$_PROXY_APPLY" /* APPLY */]: "write" }, futimesSync: { ["__$$_PROXY_APPLY" /* APPLY */]: "write" }, link: { ["__$$_PROXY_APPLY" /* APPLY */]: "write" }, linkSync: { ["__$$_PROXY_APPLY" /* APPLY */]: "write" }, mkdtemp: { ["__$$_PROXY_APPLY" /* APPLY */]: "write" }, open: { ["__$$_PROXY_APPLY" /* APPLY */]: "write" }, openSync: { ["__$$_PROXY_APPLY" /* APPLY */]: "write" }, fchmod: { ["__$$_PROXY_APPLY" /* APPLY */]: "write" }, fchmodSync: { ["__$$_PROXY_APPLY" /* APPLY */]: "write" }, fchown: { ["__$$_PROXY_APPLY" /* APPLY */]: "write" }, fchownSync: { ["__$$_PROXY_APPLY" /* APPLY */]: "write" }, ftruncate: { ["__$$_PROXY_APPLY" /* APPLY */]: "write" }, ftruncateSync: { ["__$$_PROXY_APPLY" /* APPLY */]: "write" }, fsync: { ["__$$_PROXY_APPLY" /* APPLY */]: "write" }, fsyncSync: { ["__$$_PROXY_APPLY" /* APPLY */]: "write" }, fdatasync: { ["__$$_PROXY_APPLY" /* APPLY */]: "write" }, fdatasyncSync: { ["__$$_PROXY_APPLY" /* APPLY */]: "write" }, lchmod: { ["__$$_PROXY_APPLY" /* APPLY */]: "write" }, lchmodSync: { ["__$$_PROXY_APPLY" /* APPLY */]: "write" } } }; // src/interpreter/policy/io/is-io-read.ts function isIoRead(item) { return isTrapConditionMet(IO_MAP, "read", item); } // src/interpreter/error/policy-error/io-error/io-error.ts var IoError = class extends PolicyError { /** * The kind of IO operation that was violated */ kind; constructor({ node, environment, kind, message = `${kind} operations are in violation of the policy` }) { super({ violation: "io", message, environment, node }); this.kind = kind; } }; // src/interpreter/policy/io/is-io-write.ts function isIoWrite(item) { return isTrapConditionMet(IO_MAP, "write", item); } // src/interpreter/policy/network/is-network-operation.ts function isNetworkOperation(item) { return isTrapConditionMet(NETWORK_MAP, true, item); } // src/interpreter/error/policy-error/network-error/network-error.ts var NetworkError = class extends PolicyError { /** * The kind of operation that was attempted to be performed but was in violation of the policy */ operation; constructor({ operation, node, environment, message = `The operation: '${operation}' is performing network activity. That is in violation of the policy` }) { super({ violation: "deterministic", message, node, environment }); this.operation = operation; } }; // src/interpreter/policy/process/process-map.ts var PROCESS_MAP = { "node:process": "process", process: { exit: { ["__$$_PROXY_APPLY" /* APPLY */]: "exit" } }, // Everything inside child_process is just one big violation of this policy "node:child_process": "child_process", child_process: { ["__$$_PROXY_APPLY" /* APPLY */]: "spawnChild" }, "node:cluster": "cluster", cluster: { Worker: { ["__$$_PROXY_CONSTRUCT" /* CONSTRUCT */]: "spawnChild" } } }; // src/interpreter/policy/process/is-process-exit-operation.ts function isProcessExitOperation(item) { return isTrapConditionMet(PROCESS_MAP, "exit", item); } // src/interpreter/error/policy-error/process-error/process-error.ts var ProcessError = class extends PolicyError { /** * The kind of process operation that was violated */ kind; constructor({ kind, node, environment, message = `${kind} operations are in violation of the policy` }) { super({ violation: "process", message, node, environment }); this.kind = kind; } }; // src/interpreter/policy/process/is-process-spawn-child-operation.ts function isProcessSpawnChildOperation(item) { return isTrapConditionMet(PROCESS_MAP, "spawnChild", item); } // src/interpreter/policy/console/console-map.ts var CONSOLE_MAP = { "node:console": "console", console: { ["__$$_PROXY_APPLY" /* APPLY */]: true } }; // src/interpreter/policy/console/is-console-operation.ts function isConsoleOperation(item) { return isTrapConditionMet(CONSOLE_MAP, true, item); } // src/interpreter/environment/create-sanitized-environment.ts function createSanitizedEnvironment({ policy, env }) { const hook = (item) => { if (!policy.console && isConsoleOperation(item)) { return false; } if (!policy.io.read && isIoRead(item)) { return new EvaluationErrorIntent((node, options) => new IoError({ ...options, node, kind: "read" })); } if (!policy.io.write && isIoWrite(item)) { return new EvaluationErrorIntent((node, options) => new IoError({ ...options, node, kind: "write" })); } if (!policy.process.exit && isProcessExitOperation(item)) { return new EvaluationErrorIntent((node, options) => new ProcessError({ ...options, node, kind: "exit" })); } if (!policy.process.exit && isProcessSpawnChildOperation(item)) { return new EvaluationErrorIntent((node, options) => new ProcessError({ ...options, node, kind: "spawnChild" })); } if (!policy.network && isNetworkOperation(item)) { return new EvaluationErrorIntent((node, options) => new NetworkError({ ...options, node, operation: stringifyPolicyTrapKindOnPath(item.kind, item.path) })); } if (policy.deterministic && isNonDeterministic(item)) { return new EvaluationErrorIntent((node, options) => new NonDeterministicError({ ...options, node, operation: stringifyPolicyTrapKindOnPath(item.kind, item.path) })); } return true; }; const descriptors = Object.entries(Object.getOwnPropertyDescriptors(env)); const gettersAndSetters = Object.assign({}, ...descriptors.filter(([, descriptor]) => !("value" in descriptor)).map(([name, descriptor]) => ({ [name]: descriptor }))); const values = Object.assign( {}, ...descriptors.filter(([, descriptor]) => "value" in descriptor).map(([name, descriptor]) => ({ [name]: name === "require" ? new Proxy(descriptor.value, { /** * A trap for a function call. Used to create new proxies for methods on the retrieved module objects */ apply(target, thisArg, argArray = []) { const [moduleName] = argArray; if (moduleName != null) { return createPolicyProxy({ policy, item: Reflect.apply(target, thisArg, argArray), scope: moduleName, hook }); } else { return Reflect.apply(target, thisArg, argArray); } } }) : createPolicyProxy({ policy, item: descriptor.value, scope: name, hook }) })) ); return Object.defineProperties(values, { ...gettersAndSetters }); } // src/interpreter/lexical-environment/lexical-environment.ts function getRelevantDictFromLexicalEnvironment(env, path4) { const [firstBinding] = path4.split("."); if (firstBinding == null) return void 0; if (objectPath.has(env.env, firstBinding)) return env.env; if (env.parentEnv != null) return getRelevantDictFromLexicalEnvironment(env.parentEnv, path4); return void 0; } function getPresetForLexicalEnvironment(env) { if (env.preset != null) return env.preset; else if (env.parentEnv != null) return getPresetForLexicalEnvironment(env.parentEnv); else return "NONE"; } function findLexicalEnvironmentInSameContext(from, node, typescript) { const startingNodeContext = getStatementContext(from.startingNode, typescript); const nodeContext = getStatementContext(node, typescript); if (startingNodeContext?.pos === nodeContext?.pos) { return from; } if (from.parentEnv == null) { return void 0; } return findLexicalEnvironmentInSameContext(from.parentEnv, node, typescript); } function getFromLexicalEnvironment(node, env, path4) { const [firstBinding] = path4.split("."); if (firstBinding == null) return void 0; if (objectPath.has(env.env, firstBinding)) { const literal = objectPath.get(env.env, path4); switch (path4) { // If we're in a Node environment, the "__dirname" and "__filename" meta-properties should report the current directory or file of the SourceFile and not the parent process case "__dirname": case "__filename": { const preset = getPresetForLexicalEnvironment(env); return (preset === "NODE" || preset === "NODE_CJS") && typeof literal === "function" && node != null ? { literal: literal(node.getSourceFile().fileName) } : { literal }; } case "import.meta": { const preset = getPresetForLexicalEnvironment(env); return (preset === "NODE_ESM" || preset === "BROWSER" || preset === "ECMA") && typeof literal === "object" && literal != null && typeof literal.url === "function" && node != null ? { literal: { url: literal.url(node.getSourceFile().fileName) } } : { literal }; } default: return { literal }; } } if (env.parentEnv != null) return getFromLexicalEnvironment(node, env.parentEnv, path4); return void 0; } function pathInLexicalEnvironmentEquals(node, env, equals, ...matchPaths) { return matchPaths.some((path4) => { const match = getFromLexicalEnvironment(node, env, path4); return match == null ? false : match.literal === equals; }); } function isInternalSymbol(value) { switch (value) { case RETURN_SYMBOL: case BREAK_SYMBOL: case CONTINUE_SYMBOL: case THIS_SYMBOL: case SUPER_SYMBOL: return true; default: return false; } } function setInLexicalEnvironment({ environment, path: path4, value, reporting, node, newBinding = false }) { const [firstBinding] = path4.split("."); if (firstBinding == null) return void 0; if (objectPath.has(environment.env, firstBinding) || newBinding || environment.parentEnv == null) { if (objectPath.has(environment.env, path4) && objectPath.get(environment.env, path4) === value) return; objectPath.set(environment.env, path4, value); if (reporting.reportBindings != null && !isInternalSymbol(path4)) { reporting.reportBindings({ path: path4, value, node }); } } else { let currentParentEnv = environment.parentEnv; while (currentParentEnv != null) { if (objectPath.has(currentParentEnv.env, firstBinding)) { if (objectPath.has(currentParentEnv.env, path4) && objectPath.get(currentParentEnv.env, path4) === value) return; objectPath.set(currentParentEnv.env, path4, value); if (reporting.reportBindings != null && !isInternalSymbol(path4)) { reporting.reportBindings({ path: path4, value, node }); } return; } else { if (currentParentEnv.parentEnv == null) { objectPath.set(currentParentEnv.env, path4, value); if (reporting.reportBindings != null && !isInternalSymbol(path4)) { reporting.reportBindings({ path: path4, value, node }); } } else { currentParentEnv = currentParentEnv.parentEnv; } } } } } function createLexicalEnvironment({ inputEnvironment: { extra, preset }, startingNode, policy }) { let env; switch (preset) { case "NONE": env = mergeDescriptors(extra); break; case "ECMA": env = mergeDescriptors(ECMA_GLOBALS(), extra); break; case "NODE": case "NODE_CJS": env = mergeDescriptors(NODE_CJS_GLOBALS(), extra); break; case "NODE_ESM": env = mergeDescriptors(NODE_ESM_GLOBALS(), extra); break; case "BROWSER": env = mergeDescriptors(BROWSER_GLOBALS(), extra); break; default: env = {}; break; } return { parentEnv: void 0, preset, startingNode, env: createSanitizedEnvironment({ policy, env }) }; } // src/interpreter/util/node/is-boolean-literal.ts function isBooleanLiteral(node, typescript) { return node.kind === typescript.SyntaxKind.TrueKeyword || node.kind === typescript.SyntaxKind.FalseKeyword; } // src/interpreter/util/node/is-null-literal.ts function isNullLiteral(node, typescript) { return node.kind === typescript.SyntaxKind.NullKeyword; } // src/interpreter/evaluator/simple/evaluate-simple-literal.ts function evaluateSimpleLiteral(node, typescript) { if (typescript.isStringLiteralLike(node)) return { success: true, value: node.text }; else if (isBooleanLiteral(node, typescript)) return { success: true, value: node.kind === typescript.SyntaxKind.TrueKeyword }; else if (typescript.isRegularExpressionLiteral(node)) return { success: true, value: new Function(`return ${node.text}`)() }; else if (typescript.isNumericLiteral(node)) return { success: true, value: Number(node.text) }; else if (typescript.isBigIntLiteral?.(node)) return { success: true, value: BigInt(node.text) }; else if (typescript.isIdentifier(node) && node.text === "Infinity") return { success: true, value: Infinity }; else if (typescript.isIdentifier(node) && node.text === "NaN") return { success: true, value: NaN }; else if (typescript.isIdentifier(node) && node.text === "null") return { success: true, value: null }; else if (typescript.isIdentifier(node) && node.text === "undefined") return { success: true, value: void 0 }; else if (isNullLiteral(node, typescript)) return { success: true, value: null }; else return { success: false }; } // src/interpreter/error/policy-error/max-ops-exceeded-error/max-ops-exceeded-error.ts var MaxOpsExceededError = class extends PolicyError { /** * The amount of operations performed before creating this error instance */ ops; constructor({ ops, node, environment, message = `Maximum ops exceeded: ${ops}` }) { super({ violation: "maxOps", message, node, environment }); this.ops = ops; } }; // src/interpreter/evaluator/evaluate-variable-declaration.ts function evaluateVariableDeclaration(options, initializer) { const { node, environment, evaluate: evaluate2, stack, typescript, throwError, getCurrentError } = options; const initializerResult = initializer ?? (node.initializer == null ? ( // A VariableDeclaration with no initializer is implicitly bound to 'undefined' void 0 ) : evaluate2.expression(node.initializer, options)); if (getCurrentError() != null) { return; } if (initializerResult == null && !typescript.isIdentifier(node.name)) { return throwError(new EvaluationError({ node, environment })); } evaluate2.nodeWithArgument(node.name, initializerResult, options); if (getCurrentError() != null) { return; } stack.push(initializerResult); return; } // src/interpreter/util/node/is-this-expression.ts function isThisExpression(node, typescript) { return node.kind === typescript.SyntaxKind.ThisKeyword; } // src/interpreter/util/node/is-super-expression.ts function isSuperExpression(node, typescript) { return node.kind === typescript.SyntaxKind.SuperKeyword; } // src/interpreter/lexical-environment/get-dot-path-from-node.ts function getDotPathFromNode(options) { const { node, evaluate: evaluate2, typescript } = options; if (typescript.isIdentifier(node)) { return node.text; } else if (typescript.isPrivateIdentifier?.(node)) { return node.text; } else if (isThisExpression(node, typescript)) { return THIS_SYMBOL; } else if (isSuperExpression(node, typescript)) { return SUPER_SYMBOL; } else if (typescript.isParenthesizedExpression(node)) { return getDotPathFromNode({ ...options, node: node.expression }); } else if (typescript.isTypeAssertionExpression?.(node) || !("isTypeAssertionExpression" in typescript) && typescript.isTypeAssertion(node)) { return getDotPathFromNode({ ...options, node: node.expression }); } else if (typescript.isPropertyAccessExpression(node)) { let leftHand = getDotPathFromNode({ ...options, node: node.expression }); if (leftHand == null) leftHand = evaluate2.expression(node.expression, options); let rightHand = getDotPathFromNode({ ...options, node: node.name }); if (rightHand == null) rightHand = evaluate2.expression(node.name, options); if (leftHand == null || rightHand == null) return void 0; return `${leftHand}.${rightHand}`; } else if (typescript.isElementAccessExpression(node)) { let leftHand = getDotPathFromNode({ ...options, node: node.expression }); if (leftHand == null) leftHand = evaluate2.expression(node.expression, options); const rightHand = evaluate2.expression(node.argumentExpression, options); if (leftHand == null || rightHand == null) return void 0; return `${leftHand}.${rightHand}`; } else if (typescript.isFunctionDeclaration(node)) { if (node.name == null) return void 0; return node.name.text; } return void 0; } // src/interpreter/error/undefined-left-value-error/undefined-left-value-error.ts var UndefinedLeftValueError = class extends EvaluationError { constructor({ node, environment, message = `'No leftValue could be determined'` }) { super({ message, environment, node }); } }; // src/interpreter/util/node/get-inner-node.ts function getInnerNode(node, typescript) { if (typescript.isParenthesizedExpression(node)) return getInnerNode(node.expression, typescript); else if (typescript.isAsExpression(node)) return getInnerNode(node.expression, typescript); else if (typescript.isTypeAssertionExpression?.(node) || !("isTypeAssertionExpression" in typescript) && typescript.isTypeAssertion(node)) { return getInnerNode(node.expression, typescript); } else { return node; } } // src/interpreter/util/node/is-node.ts function isTypescriptNode(node) { return node != null && typeof node === "object" && "kind" in node && "flags" in node && "pos" in node && "end" in node; } // src/interpreter/evaluator/evaluate-binary-expression.ts function evaluateBinaryExpression(options) { const { node, environment, evaluate: evaluate2, logger, throwError, typeChecker, typescript, getCurrentError } = options; const leftValue = evaluate2.expression(node.left, options); if (getCurrentError() != null) { return; } const rightValue = evaluate2.expression(node.right, options); if (getCurrentError() != null) { return; } const leftIdentifier = getDotPathFromNode({ ...options, node: node.left }); const operator = node.operatorToken.kind; switch (operator) { case typescript.SyntaxKind.AmpersandToken: { return leftValue & rightValue; } case typescript.SyntaxKind.AmpersandAmpersandToken: { return leftValue && rightValue; } case typescript.SyntaxKind.AmpersandEqualsToken: case typescript.SyntaxKind.CaretEqualsToken: case typescript.SyntaxKind.BarEqualsToken: case typescript.SyntaxKind.MinusEqualsToken: case typescript.SyntaxKind.PlusEqualsToken: case typescript.SyntaxKind.PercentEqualsToken: case typescript.SyntaxKind.SlashEqualsTok