diff --git a/package.json b/package.json index 91fdbe8..c54d29c 100644 --- a/package.json +++ b/package.json @@ -53,7 +53,6 @@ "@wessberg/prettier-config": "^1.0.0", "rollup-plugin-ts": "3.0.2", "ava": "^3.15.0", - "crosspath": "^2.0.0", "eslint": "^8.20.0", "eslint-config-prettier": "^8.5.0", "eslint-plugin-import": "^2.26.0", @@ -90,7 +89,8 @@ }, "dependencies": { "ansi-colors": "^4.1.3", - "object-path": "^0.11.8" + "object-path": "^0.11.8", + "crosspath": "^2.0.0" }, "peerDependencies": { "typescript": ">=3.2.x || >= 4.x", diff --git a/src/interpreter/environment/create-sanitized-environment.ts b/src/interpreter/environment/create-sanitized-environment.ts index e2a5a9a..f751dab 100644 --- a/src/interpreter/environment/create-sanitized-environment.ts +++ b/src/interpreter/environment/create-sanitized-environment.ts @@ -14,11 +14,12 @@ import {ProcessError} from "../error/policy-error/process-error/process-error.js import {isProcessSpawnChildOperation} from "../policy/process/is-process-spawn-child-operation.js"; import {ICreateSanitizedEnvironmentOptions} from "./i-create-sanitized-environment-options.js"; import {isConsoleOperation} from "../policy/console/is-console-operation.js"; +import { EvaluationErrorIntent } from "../error/evaluation-error/evaluation-error-intent.js"; /** * Creates an environment that provide hooks into policy checks */ -export function createSanitizedEnvironment({policy, env, getCurrentNode}: ICreateSanitizedEnvironmentOptions): IndexLiteral { +export function createSanitizedEnvironment({policy, env}: ICreateSanitizedEnvironmentOptions): IndexLiteral { // eslint-disable-next-line @typescript-eslint/no-explicit-any const hook = (item: PolicyProxyHookOptions) => { if (!policy.console && isConsoleOperation(item)) { @@ -26,27 +27,27 @@ export function createSanitizedEnvironment({policy, env, getCurrentNode}: ICreat } if (!policy.io.read && isIoRead(item)) { - throw new IoError({kind: "read", node: getCurrentNode()}); + return new EvaluationErrorIntent((node, options) => new IoError({...options, node, kind: "read"})); } if (!policy.io.write && isIoWrite(item)) { - throw new IoError({kind: "write", node: getCurrentNode()}); + return new EvaluationErrorIntent((node, options) => new IoError({...options, node, kind: "write"})); } if (!policy.process.exit && isProcessExitOperation(item)) { - throw new ProcessError({kind: "exit", node: getCurrentNode()}); + return new EvaluationErrorIntent((node, options) => new ProcessError({...options, node, kind: "exit"})); } if (!policy.process.exit && isProcessSpawnChildOperation(item)) { - throw new ProcessError({kind: "spawnChild", node: getCurrentNode()}); + return new EvaluationErrorIntent((node, options) => new ProcessError({...options, node, kind: "spawnChild"})); } if (!policy.network && isNetworkOperation(item)) { - throw new NetworkError({operation: stringifyPolicyTrapKindOnPath(item.kind, item.path), node: getCurrentNode()}); + return new EvaluationErrorIntent((node, options) => new NetworkError({...options, node, operation: stringifyPolicyTrapKindOnPath(item.kind, item.path)})); } if (policy.deterministic && isNonDeterministic(item)) { - throw new NonDeterministicError({operation: stringifyPolicyTrapKindOnPath(item.kind, item.path), node: getCurrentNode()}); + return new EvaluationErrorIntent((node, options) => new NonDeterministicError({...options, node, operation: stringifyPolicyTrapKindOnPath(item.kind, item.path)})); } return true; diff --git a/src/interpreter/environment/i-create-sanitized-environment-options.ts b/src/interpreter/environment/i-create-sanitized-environment-options.ts index 1ea8262..9349afa 100644 --- a/src/interpreter/environment/i-create-sanitized-environment-options.ts +++ b/src/interpreter/environment/i-create-sanitized-environment-options.ts @@ -1,9 +1,7 @@ import {EvaluatePolicySanitized} from "../policy/evaluate-policy.js"; import {IndexLiteral} from "../literal/literal.js"; -import {TS} from "../../type/ts.js"; export interface ICreateSanitizedEnvironmentOptions { policy: EvaluatePolicySanitized; env: IndexLiteral; - getCurrentNode(): TS.Node; } diff --git a/src/interpreter/error/async-iterator-not-supported-error/async-iterator-not-supported-error.ts b/src/interpreter/error/async-iterator-not-supported-error/async-iterator-not-supported-error.ts index 478a6f5..9640095 100644 --- a/src/interpreter/error/async-iterator-not-supported-error/async-iterator-not-supported-error.ts +++ b/src/interpreter/error/async-iterator-not-supported-error/async-iterator-not-supported-error.ts @@ -5,7 +5,7 @@ import {IAsyncIteratorNotSupportedErrorOptions} from "./i-async-iterator-not-sup * An Error that can be thrown when an async iteration operation is attempted */ export class AsyncIteratorNotSupportedError extends EvaluationError { - constructor({message = `It is not possible to evaluate an async iterator'`, typescript}: IAsyncIteratorNotSupportedErrorOptions) { - super({message, node: typescript.createEmptyStatement()}); + constructor({message = `It is not possible to evaluate an async iterator'`, typescript, environment}: IAsyncIteratorNotSupportedErrorOptions) { + super({message, environment, node: typescript.factory?.createEmptyStatement() ?? typescript.createEmptyStatement()}); } } diff --git a/src/interpreter/error/evaluation-error/evaluation-error-intent.ts b/src/interpreter/error/evaluation-error/evaluation-error-intent.ts new file mode 100644 index 0000000..e572f50 --- /dev/null +++ b/src/interpreter/error/evaluation-error/evaluation-error-intent.ts @@ -0,0 +1,20 @@ +import {TS} from "../../../type/ts.js"; +import {NextEvaluatorOptions} from "../../evaluator/evaluator-options.js"; +import {EvaluationError} from "./evaluation-error.js"; + +type EvaluationErrorIntentCallback = (node: TS.Node, options: NextEvaluatorOptions) => T; + +export class EvaluationErrorIntent { + constructor(private readonly intent: EvaluationErrorIntentCallback) {} + construct(node: TS.Node, options: NextEvaluatorOptions): T { + return this.intent(node, options); + } +} + +export function isEvaluationErrorIntent(item: unknown): item is EvaluationErrorIntent { + return typeof item === "object" && item != null && item instanceof EvaluationErrorIntent; +} + +export function maybeThrow(node: TS.Node, options: NextEvaluatorOptions, value: Value | EvaluationErrorIntent): Value | EvaluationError { + return isEvaluationErrorIntent(value) ? options.throwError(value.construct(node, options)) : value; +} diff --git a/src/interpreter/error/evaluation-error/evaluation-error.ts b/src/interpreter/error/evaluation-error/evaluation-error.ts index c85b479..b709615 100644 --- a/src/interpreter/error/evaluation-error/evaluation-error.ts +++ b/src/interpreter/error/evaluation-error/evaluation-error.ts @@ -1,5 +1,8 @@ import {IEvaluationErrorOptions} from "./i-evaluation-error-options.js"; import {TS} from "../../../type/ts.js"; +import { LexicalEnvironment } from "../../lexical-environment/lexical-environment.js"; + +export type ThrowError = (error: EvaluationError) => EvaluationError; /** * A Base class for EvaluationErrors @@ -9,10 +12,16 @@ export class EvaluationError extends Error { * The node that caused or thew the error */ readonly node: TS.Node; + readonly environment: LexicalEnvironment; - constructor({node, message}: IEvaluationErrorOptions) { + constructor({node, environment, message}: IEvaluationErrorOptions) { super(message); Error.captureStackTrace(this, this.constructor); this.node = node; + this.environment = environment; } } + +export function isEvaluationError (item: unknown): item is EvaluationError { + return typeof item === "object" && item != null && item instanceof EvaluationError; +} \ No newline at end of file diff --git a/src/interpreter/error/evaluation-error/i-evaluation-error-options.ts b/src/interpreter/error/evaluation-error/i-evaluation-error-options.ts index bce4ff3..9f43629 100644 --- a/src/interpreter/error/evaluation-error/i-evaluation-error-options.ts +++ b/src/interpreter/error/evaluation-error/i-evaluation-error-options.ts @@ -1,6 +1,8 @@ import {TS} from "../../../type/ts.js"; +import { LexicalEnvironment } from "../../lexical-environment/lexical-environment.js"; export interface IEvaluationErrorOptions { node: TS.Node; + environment: LexicalEnvironment; message?: string; } diff --git a/src/interpreter/error/missing-catch-or-finally-after-try-error/missing-catch-or-finally-after-try-error.ts b/src/interpreter/error/missing-catch-or-finally-after-try-error/missing-catch-or-finally-after-try-error.ts index 1ae6be1..477b59e 100644 --- a/src/interpreter/error/missing-catch-or-finally-after-try-error/missing-catch-or-finally-after-try-error.ts +++ b/src/interpreter/error/missing-catch-or-finally-after-try-error/missing-catch-or-finally-after-try-error.ts @@ -11,7 +11,7 @@ export class MissingCatchOrFinallyAfterTryError extends EvaluationError { */ readonly node!: TS.TryStatement; - constructor({node, message = `Missing catch or finally after try`}: IMissingCatchOrFinallyAfterTryErrorOptions) { - super({node, message}); + constructor({node, environment, message = `Missing catch or finally after try`}: IMissingCatchOrFinallyAfterTryErrorOptions) { + super({node, environment, message}); } } diff --git a/src/interpreter/error/module-not-found-error/module-not-found-error.ts b/src/interpreter/error/module-not-found-error/module-not-found-error.ts index 547ee04..e83828a 100644 --- a/src/interpreter/error/module-not-found-error/module-not-found-error.ts +++ b/src/interpreter/error/module-not-found-error/module-not-found-error.ts @@ -10,8 +10,8 @@ export class ModuleNotFoundError extends EvaluationError { */ readonly path: string; - constructor({path, node, message = `Module '${path}' could not be resolved'`}: IModuleNotFoundErrorOptions) { - super({message, node}); + constructor({path, node, environment, message = `Module '${path}' could not be resolved'`}: IModuleNotFoundErrorOptions) { + super({message, environment, node}); this.path = path; } } diff --git a/src/interpreter/error/not-callable-error/not-callable-error.ts b/src/interpreter/error/not-callable-error/not-callable-error.ts index 7d5aa46..5c7dbcf 100644 --- a/src/interpreter/error/not-callable-error/not-callable-error.ts +++ b/src/interpreter/error/not-callable-error/not-callable-error.ts @@ -11,8 +11,8 @@ export class NotCallableError extends EvaluationError { */ readonly value: Literal; - constructor({value, node, message = `${stringifyLiteral(value)} is not a function'`}: INotCallableErrorOptions) { - super({message, node}); + constructor({value, node, environment, message = `${stringifyLiteral(value)} is not a function'`}: INotCallableErrorOptions) { + super({message, environment, node}); this.value = value; } } diff --git a/src/interpreter/error/policy-error/io-error/io-error.ts b/src/interpreter/error/policy-error/io-error/io-error.ts index 56d540e..1a64c69 100644 --- a/src/interpreter/error/policy-error/io-error/io-error.ts +++ b/src/interpreter/error/policy-error/io-error/io-error.ts @@ -11,8 +11,8 @@ export class IoError extends PolicyError { */ readonly kind: keyof EvaluateIOPolicy; - constructor({node, kind, message = `${kind} operations are in violation of the policy`}: IIoErrorOptions) { - super({violation: "io", message, node}); + constructor({node, environment, kind, message = `${kind} operations are in violation of the policy`}: IIoErrorOptions) { + super({violation: "io", message, environment, node}); this.kind = kind; } } diff --git a/src/interpreter/error/policy-error/max-op-duration-exceeded-error/max-op-duration-exceeded-error.ts b/src/interpreter/error/policy-error/max-op-duration-exceeded-error/max-op-duration-exceeded-error.ts index 5894020..8c87508 100644 --- a/src/interpreter/error/policy-error/max-op-duration-exceeded-error/max-op-duration-exceeded-error.ts +++ b/src/interpreter/error/policy-error/max-op-duration-exceeded-error/max-op-duration-exceeded-error.ts @@ -10,8 +10,8 @@ export class MaxOpDurationExceededError extends PolicyError { */ readonly duration: number; - constructor({duration, node, message = `Maximum operation duration exceeded: ${duration}`}: IMaxOpDurationExceededErrorOptions) { - super({violation: "maxOpDuration", message, node}); + constructor({duration, environment, node, message = `Maximum operation duration exceeded: ${duration}`}: IMaxOpDurationExceededErrorOptions) { + super({violation: "maxOpDuration", message, node, environment}); this.duration = duration; } } diff --git a/src/interpreter/error/policy-error/max-ops-exceeded-error/max-ops-exceeded-error.ts b/src/interpreter/error/policy-error/max-ops-exceeded-error/max-ops-exceeded-error.ts index e28e9a1..855e597 100644 --- a/src/interpreter/error/policy-error/max-ops-exceeded-error/max-ops-exceeded-error.ts +++ b/src/interpreter/error/policy-error/max-ops-exceeded-error/max-ops-exceeded-error.ts @@ -10,8 +10,8 @@ export class MaxOpsExceededError extends PolicyError { */ readonly ops: number; - constructor({ops, node, message = `Maximum ops exceeded: ${ops}`}: IMaxOpsExceededErrorOptions) { - super({violation: "maxOps", message, node}); + constructor({ops, node, environment, message = `Maximum ops exceeded: ${ops}`}: IMaxOpsExceededErrorOptions) { + super({violation: "maxOps", message, node, environment}); this.ops = ops; } } diff --git a/src/interpreter/error/policy-error/network-error/network-error.ts b/src/interpreter/error/policy-error/network-error/network-error.ts index 2d3124b..a1321ae 100644 --- a/src/interpreter/error/policy-error/network-error/network-error.ts +++ b/src/interpreter/error/policy-error/network-error/network-error.ts @@ -10,8 +10,8 @@ export class NetworkError extends PolicyError { */ readonly operation: string; - constructor({operation, node, message = `The operation: '${operation}' is performing network activity. That is in violation of the policy`}: INetworkErrorOptions) { - super({violation: "deterministic", message, node}); + constructor({operation, node, environment, message = `The operation: '${operation}' is performing network activity. That is in violation of the policy`}: INetworkErrorOptions) { + super({violation: "deterministic", message, node, environment}); this.operation = operation; } diff --git a/src/interpreter/error/policy-error/non-deterministic-error/non-deterministic-error.ts b/src/interpreter/error/policy-error/non-deterministic-error/non-deterministic-error.ts index f0fe6ef..71bd031 100644 --- a/src/interpreter/error/policy-error/non-deterministic-error/non-deterministic-error.ts +++ b/src/interpreter/error/policy-error/non-deterministic-error/non-deterministic-error.ts @@ -10,8 +10,8 @@ export class NonDeterministicError extends PolicyError { */ readonly operation: string; - constructor({operation, node, message = `The operation: '${operation}' is nondeterministic. That is in violation of the policy`}: INonDeterministicErrorOptions) { - super({violation: "deterministic", message, node}); + constructor({operation, node, environment, message = `The operation: '${operation}' is nondeterministic. That is in violation of the policy`}: INonDeterministicErrorOptions) { + super({violation: "deterministic", message, node, environment}); this.operation = operation; } diff --git a/src/interpreter/error/policy-error/policy-error.ts b/src/interpreter/error/policy-error/policy-error.ts index 3783590..bd12b14 100644 --- a/src/interpreter/error/policy-error/policy-error.ts +++ b/src/interpreter/error/policy-error/policy-error.ts @@ -11,8 +11,8 @@ export class PolicyError extends EvaluationError { */ readonly violation: keyof EvaluatePolicySanitized; - constructor({violation, node, message}: IPolicyErrorOptions) { - super({node, message: `[${violation}]: ${message}`}); + constructor({violation, node, environment, message}: IPolicyErrorOptions) { + super({node, environment, message: `[${violation}]: ${message}`}); this.violation = violation; } } diff --git a/src/interpreter/error/policy-error/process-error/process-error.ts b/src/interpreter/error/policy-error/process-error/process-error.ts index 4e13dd5..d7098ab 100644 --- a/src/interpreter/error/policy-error/process-error/process-error.ts +++ b/src/interpreter/error/policy-error/process-error/process-error.ts @@ -11,8 +11,8 @@ export class ProcessError extends PolicyError { */ readonly kind: keyof EvaluateProcessPolicy; - constructor({kind, node, message = `${kind} operations are in violation of the policy`}: IProcessErrorOptions) { - super({violation: "process", message, node}); + constructor({kind, node, environment, message = `${kind} operations are in violation of the policy`}: IProcessErrorOptions) { + super({violation: "process", message, node, environment}); this.kind = kind; } } diff --git a/src/interpreter/error/undefined-identifier-error/undefined-identifier-error.ts b/src/interpreter/error/undefined-identifier-error/undefined-identifier-error.ts index e6d3597..e009a06 100644 --- a/src/interpreter/error/undefined-identifier-error/undefined-identifier-error.ts +++ b/src/interpreter/error/undefined-identifier-error/undefined-identifier-error.ts @@ -11,7 +11,7 @@ export class UndefinedIdentifierError extends EvaluationError { */ readonly node!: TS.Identifier | TS.PrivateIdentifier; - constructor({node, message = `'${node.text}' is not defined'`}: IUndefinedIdentifierErrorOptions) { - super({message, node}); + constructor({node, environment, message = `'${node.text}' is not defined'`}: IUndefinedIdentifierErrorOptions) { + super({message, environment, node}); } } diff --git a/src/interpreter/error/undefined-left-value-error/undefined-left-value-error.ts b/src/interpreter/error/undefined-left-value-error/undefined-left-value-error.ts index 4187ad5..60f59c6 100644 --- a/src/interpreter/error/undefined-left-value-error/undefined-left-value-error.ts +++ b/src/interpreter/error/undefined-left-value-error/undefined-left-value-error.ts @@ -5,7 +5,7 @@ import {IUndefinedLeftValueErrorOptions} from "./i-undefined-left-value-error-op * An Error that can be thrown when an undefined leftValue is encountered */ export class UndefinedLeftValueError extends EvaluationError { - constructor({node, message = `'No leftValue could be determined'`}: IUndefinedLeftValueErrorOptions) { - super({message, node}); + constructor({node, environment, message = `'No leftValue could be determined'`}: IUndefinedLeftValueErrorOptions) { + super({message, environment, node}); } } diff --git a/src/interpreter/error/unexpected-node-error/unexpected-node-error.ts b/src/interpreter/error/unexpected-node-error/unexpected-node-error.ts index 97bf0a1..b2b358a 100644 --- a/src/interpreter/error/unexpected-node-error/unexpected-node-error.ts +++ b/src/interpreter/error/unexpected-node-error/unexpected-node-error.ts @@ -5,7 +5,7 @@ import {IUnexpectedNodeErrorOptions} from "./i-unexpected-node-error-options.js" * An Error that can be thrown when an unexpected node is encountered */ export class UnexpectedNodeError extends EvaluationError { - constructor({node, typescript, message = `Unexpected Node: '${typescript.SyntaxKind[node.kind]}'`}: IUnexpectedNodeErrorOptions) { - super({message, node}); + constructor({node, environment, typescript, message = `Unexpected Node: '${typescript.SyntaxKind[node.kind]}'`}: IUnexpectedNodeErrorOptions) { + super({message, node, environment}); } } diff --git a/src/interpreter/error/unexpected-syntax-error/unexpected-syntax-error.ts b/src/interpreter/error/unexpected-syntax-error/unexpected-syntax-error.ts index 0dd3aab..3fd6357 100644 --- a/src/interpreter/error/unexpected-syntax-error/unexpected-syntax-error.ts +++ b/src/interpreter/error/unexpected-syntax-error/unexpected-syntax-error.ts @@ -5,7 +5,7 @@ import {IUnexpectedSyntaxErrorOptions} from "./i-unexpected-syntax-error-options * An Error that can be thrown when a certain usage is to be considered a SyntaxError */ export class UnexpectedSyntaxError extends EvaluationError { - constructor({node, message = `'SyntaxError'`}: IUnexpectedSyntaxErrorOptions) { - super({message, node}); + constructor({node, environment, message = `'SyntaxError'`}: IUnexpectedSyntaxErrorOptions) { + super({message, environment, node}); } } diff --git a/src/interpreter/evaluate.ts b/src/interpreter/evaluate.ts index e5a5a6e..4887b1e 100644 --- a/src/interpreter/evaluate.ts +++ b/src/interpreter/evaluate.ts @@ -17,9 +17,8 @@ import {EvaluatePolicySanitized} from "./policy/evaluate-policy.js"; import {reportError} from "./util/reporting/report-error.js"; import {createReportedErrorSet} from "./reporting/reported-error-set.js"; import {ReportingOptionsSanitized} from "./reporting/i-reporting-options.js"; -import {TS} from "../type/ts.js"; -import {EvaluationError} from "./error/evaluation-error/evaluation-error.js"; - +import {EvaluationError, ThrowError} from "./error/evaluation-error/evaluation-error.js"; +import { ICreateNodeEvaluatorOptions } from "./evaluator/node-evaluator/i-create-node-evaluator-options.js"; /** * Will get a literal value for the given Expression, ExpressionStatement, or Declaration. */ @@ -75,65 +74,89 @@ export function evaluate({ reportedErrorSet: createReportedErrorSet() }; - // Prepare a reference to the Node that is currently being evaluated - let currentNode: TS.Node = node; + /** + * The error that has been thrown most recently. + * We can' just throw errors internally, as some tools may patch error handling + * and treat them as uncaught exceptions, which breaks the behavior of evaluate, + * which never throws and instead returns a record with a {success: false, reason: Error} value. + */ + let error: EvaluationError | undefined; // Prepare a logger const logger = new Logger(logLevel); + const throwError: ThrowError = ex => { + // Report the Error + reportError(reporting, ex, ex.node); + error = ex; + return error; + }; + // Prepare the initial environment - const initialEnvironment = createLexicalEnvironment({ + const environment = createLexicalEnvironment({ inputEnvironment: { preset, extra }, - policy, - getCurrentNode: () => currentNode + startingNode: node, + policy }); // Prepare a Stack const stack: Stack = createStack(); - // Prepare a NodeEvaluator - const nodeEvaluator = createNodeEvaluator({ + const statementTraversalStack = createStatementTraversalStack(); + + const nodeEvaluatorOptions: ICreateNodeEvaluatorOptions = { policy, typeChecker, typescript, logger, stack, moduleOverrides, - reporting: reporting, - nextNode: nextNode => (currentNode = nextNode) - }); + reporting, + throwError, + environment, + statementTraversalStack, + getCurrentError: () => error + }; + + // Prepare a NodeEvaluator + const nodeEvaluator = createNodeEvaluator(nodeEvaluatorOptions); try { let value: Literal; if (isExpression(node, typescript)) { - value = nodeEvaluator.expression(node, initialEnvironment, createStatementTraversalStack()); + value = nodeEvaluator.expression(node, nodeEvaluatorOptions); } else if (isStatement(node, typescript)) { - nodeEvaluator.statement(node, initialEnvironment); + nodeEvaluator.statement(node, nodeEvaluatorOptions); value = stack.pop(); } else if (isDeclaration(node, typescript)) { - nodeEvaluator.declaration(node, initialEnvironment, createStatementTraversalStack()); + nodeEvaluator.declaration(node, nodeEvaluatorOptions); value = stack.pop(); } - // Otherwise, throw an UnexpectedNodeError + // Otherwise, produce an UnexpectedNodeError else { - // noinspection ExceptionCaughtLocallyJS - throw new UnexpectedNodeError({node, typescript}); + throwError(new UnexpectedNodeError({node, environment, typescript})); } - // Log the value before returning - logger.logResult(value); + if (error != null) { + return { + success: false, + reason: error + }; + } else { + // Log the value before returning + logger.logResult(value); - return { - success: true, - value - }; + return { + success: true, + value + }; + } } catch (reason) { - // Report the Error - reportError(reporting, reason as EvaluationError, node); + throwError(reason as EvaluationError); return { success: false, diff --git a/src/interpreter/evaluator/evaluate-array-binding-pattern.ts b/src/interpreter/evaluator/evaluate-array-binding-pattern.ts index d982d55..f0d39e8 100644 --- a/src/interpreter/evaluator/evaluate-array-binding-pattern.ts +++ b/src/interpreter/evaluator/evaluate-array-binding-pattern.ts @@ -5,10 +5,7 @@ import {TS} from "../../type/ts.js"; /** * Evaluates, or attempts to evaluate, an ArrayBindingPattern, based on an initializer */ -export function evaluateArrayBindingPattern( - {node, evaluate, environment, statementTraversalStack}: EvaluatorOptions, - rightHandValue: Iterable -): void { +export function evaluateArrayBindingPattern({node, evaluate, ...options}: EvaluatorOptions, rightHandValue: Iterable): void { const iterator = rightHandValue[Symbol.iterator](); let elementsCursor = 0; @@ -16,6 +13,10 @@ export function evaluateArrayBindingPattern( const {done, value} = iterator.next(); if (done === true) break; - evaluate.nodeWithArgument(node.elements[elementsCursor++], environment, value, statementTraversalStack); + evaluate.nodeWithArgument(node.elements[elementsCursor++], value, options); + + if (options.getCurrentError() != null) { + return; + } } } diff --git a/src/interpreter/evaluator/evaluate-array-literal-expression.ts b/src/interpreter/evaluator/evaluate-array-literal-expression.ts index f022a61..bc61cca 100644 --- a/src/interpreter/evaluator/evaluate-array-literal-expression.ts +++ b/src/interpreter/evaluator/evaluate-array-literal-expression.ts @@ -7,13 +7,18 @@ import {TS} from "../../type/ts.js"; /** * Evaluates, or attempts to evaluate, a ArrayLiteralExpression */ -export function evaluateArrayLiteralExpression({node, environment, evaluate, typescript, statementTraversalStack}: EvaluatorOptions): Literal { +export function evaluateArrayLiteralExpression(options: EvaluatorOptions): Literal { + const {node, environment, evaluate, typescript, getCurrentError} = options; // Get the Array constructor from the realm - not that of the executing context. Otherwise, instanceof checks would fail const arrayCtor = getFromLexicalEnvironment(node, environment, "Array")!.literal as ArrayConstructor; const value: Literal[] = arrayCtor.of(); for (const element of node.elements) { - const nextValue = evaluate.expression(element, environment, statementTraversalStack); + const nextValue = evaluate.expression(element, options); + if (getCurrentError() != null) { + return; + } + if (typescript.isSpreadElement(element) && isIterable(nextValue)) { value.push(...nextValue); } else { diff --git a/src/interpreter/evaluator/evaluate-arrow-function-expression.ts b/src/interpreter/evaluator/evaluate-arrow-function-expression.ts index 2df4c32..e8be84a 100644 --- a/src/interpreter/evaluator/evaluate-arrow-function-expression.ts +++ b/src/interpreter/evaluator/evaluate-arrow-function-expression.ts @@ -11,19 +11,20 @@ import {TS} from "../../type/ts.js"; * Evaluates, or attempts to evaluate, an ArrowFunction */ export function evaluateArrowFunctionExpression(options: EvaluatorOptions): Literal { - const {node, environment, evaluate, stack, statementTraversalStack, reporting, typescript} = options; + const {node, environment, evaluate, stack, typescript, getCurrentError} = options; const arrowFunctionExpression = hasModifier(node, typescript.SyntaxKind.AsyncKeyword) ? async (...args: Literal[]) => { // Prepare a lexical environment for the function context const localLexicalEnvironment: LexicalEnvironment = cloneLexicalEnvironment(environment, node); + const nextOptions = {...options, environment: localLexicalEnvironment}; // Define a new binding for a return symbol within the environment - setInLexicalEnvironment({env: localLexicalEnvironment, path: RETURN_SYMBOL, value: false, newBinding: true, reporting, node}); + setInLexicalEnvironment({...nextOptions, path: RETURN_SYMBOL, value: false, newBinding: true}); // Define a new binding for the arguments given to the function // eslint-disable-next-line prefer-rest-params - setInLexicalEnvironment({env: localLexicalEnvironment, path: "arguments", value: arguments, newBinding: true, reporting, node}); + setInLexicalEnvironment({...nextOptions, path: "arguments", value: arguments, newBinding: true}); // Evaluate the parameters based on the given arguments evaluateParameterDeclarations( @@ -35,9 +36,17 @@ export function evaluateArrowFunctionExpression(options: EvaluatorOptions { // Prepare a lexical environment for the function context const localLexicalEnvironment: LexicalEnvironment = cloneLexicalEnvironment(environment, node); + const nextOptions = {...options, environment: localLexicalEnvironment}; // Define a new binding for a return symbol within the environment - setInLexicalEnvironment({env: localLexicalEnvironment, path: RETURN_SYMBOL, value: false, newBinding: true, reporting, node}); + setInLexicalEnvironment({...nextOptions, path: RETURN_SYMBOL, value: false, newBinding: true}); // Define a new binding for the arguments given to the function // eslint-disable-next-line prefer-rest-params - setInLexicalEnvironment({env: localLexicalEnvironment, path: "arguments", value: arguments, newBinding: true, reporting, node}); + setInLexicalEnvironment({...nextOptions, path: "arguments", value: arguments, newBinding: true}); // Evaluate the parameters based on the given arguments evaluateParameterDeclarations( @@ -74,9 +84,17 @@ export function evaluateArrowFunctionExpression(options: EvaluatorOptions): Literal { - return evaluate.expression(node.expression, environment, statementTraversalStack); +export function evaluateAsExpression({node, evaluate, ...options}: EvaluatorOptions): Literal { + return evaluate.expression(node.expression, options); } diff --git a/src/interpreter/evaluator/evaluate-await-expression.ts b/src/interpreter/evaluator/evaluate-await-expression.ts index e15fae1..7da6acc 100644 --- a/src/interpreter/evaluator/evaluate-await-expression.ts +++ b/src/interpreter/evaluator/evaluate-await-expression.ts @@ -6,20 +6,25 @@ import {TS} from "../../type/ts.js"; /** * Evaluates, or attempts to evaluate, an AwaitExpression */ -export async function evaluateAwaitExpression({node, environment, evaluate, policy, statementTraversalStack}: EvaluatorOptions): Promise { +export async function evaluateAwaitExpression(options: EvaluatorOptions): Promise { + const {node, environment, evaluate, policy, throwError, getCurrentError} = options; // If a maximum duration for any operation is given, set a timeout that will throw a PolicyError when and if the duration is exceeded. const timeout = policy.maxOpDuration === Infinity ? undefined : setTimeout(() => { - throw new MaxOpDurationExceededError({duration: policy.maxOpDuration, node}); + throwError(new MaxOpDurationExceededError({duration: policy.maxOpDuration, node, environment})); }, policy.maxOpDuration); - const result = evaluate.expression(node.expression, environment, statementTraversalStack) as Promise; + const result = evaluate.expression(node.expression, options) as Promise; // Make sure to clear the timeout if it exists to avoid throwing unnecessarily if (timeout != null) clearTimeout(timeout); + if (getCurrentError() != null) { + return; + } + // Return the evaluated result return result; } diff --git a/src/interpreter/evaluator/evaluate-binary-expression.ts b/src/interpreter/evaluator/evaluate-binary-expression.ts index ff50ce6..230e961 100644 --- a/src/interpreter/evaluator/evaluate-binary-expression.ts +++ b/src/interpreter/evaluator/evaluate-binary-expression.ts @@ -13,10 +13,20 @@ import {isTypescriptNode} from "../util/node/is-node.js"; * Evaluates, or attempts to evaluate, a BinaryExpression */ export function evaluateBinaryExpression(options: EvaluatorOptions): Literal { - const {node, environment, evaluate, logger, typeChecker, statementTraversalStack, reporting, typescript} = options; + const {node, environment, evaluate, logger, throwError, typeChecker, typescript, getCurrentError} = options; + + const leftValue = evaluate.expression(node.left, options) as number; + + if (getCurrentError() != null) { + return; + } + + const rightValue = evaluate.expression(node.right, options) as number; + + if (getCurrentError() != null) { + return; + } - const leftValue = evaluate.expression(node.left, environment, statementTraversalStack) as number; - const rightValue = evaluate.expression(node.right, environment, statementTraversalStack) as number; const leftIdentifier = getDotPathFromNode({...options, node: node.left}); const operator = node.operatorToken.kind; @@ -101,7 +111,7 @@ export function evaluateBinaryExpression(options: EvaluatorOptions, - rightHandValue: Literal -): void { +export function evaluateBindingElement(options: EvaluatorOptions, rightHandValue: Literal): void { + const {node, evaluate, logger, typescript, getCurrentError} = options; + // Compute the initializer value of the BindingElement, if it has any, that is - const bindingElementInitializer = node.initializer == null ? undefined : evaluate.expression(node.initializer, environment, statementTraversalStack); + const bindingElementInitializer = node.initializer == null ? undefined : evaluate.expression(node.initializer, options); + + if (getCurrentError() != null) { + return; + } // If the element is directly references a property, but then aliases, store that alias in the environment. if ((typescript.isIdentifier(node.name) || typescript.isPrivateIdentifier?.(node.name)) && node.propertyName != null) { @@ -19,7 +22,11 @@ export function evaluateBindingElement( const aliasName = node.name.text; // Compute the property name - const propertyNameResult = evaluate.nodeWithValue(node.propertyName, environment, statementTraversalStack) as IndexLiteralKey; + const propertyNameResult = evaluate.nodeWithValue(node.propertyName, options) as IndexLiteralKey; + + if (getCurrentError() != null) { + return; + } // Extract the property value from the initializer. If it is an ArrayBindingPattern, the rightHandValue will be assigned as-is to the identifier const propertyValue = typescript.isArrayBindingPattern(node.parent) ? rightHandValue : (rightHandValue as IndexLiteral)[propertyNameResult]; @@ -28,12 +35,10 @@ export function evaluateBindingElement( const propertyValueWithInitializerFallback = propertyValue != null ? propertyValue : bindingElementInitializer; setInLexicalEnvironment({ - env: environment, + ...options, path: aliasName, value: propertyValueWithInitializerFallback, - newBinding: true, - node, - reporting + newBinding: true }); } @@ -51,19 +56,21 @@ export function evaluateBindingElement( logger.logBinding(node.name.text, propertyValueWithInitializerFallback); setInLexicalEnvironment({ - env: environment, + ...options, path: node.name.text, value: propertyValueWithInitializerFallback, - newBinding: true, - node, - reporting + newBinding: true }); } // Otherwise, the name is itself a BindingPattern, and the property it is destructuring will always be defined else if (!typescript.isIdentifier(node.name) && !typescript.isPrivateIdentifier?.(node.name) && node.propertyName != null) { // Compute the property name - const propertyNameResult = evaluate.nodeWithValue(node.propertyName, environment, statementTraversalStack) as IndexLiteralKey; + const propertyNameResult = evaluate.nodeWithValue(node.propertyName, options) as IndexLiteralKey; + + if (getCurrentError() != null) { + return; + } // Extract the property value from the initializer. If it is an ArrayBindingPattern, the rightHandValue will be assigned as-is to the identifier const propertyValue = typescript.isArrayBindingPattern(node.parent) ? rightHandValue : (rightHandValue as IndexLiteral)[propertyNameResult]; @@ -72,7 +79,11 @@ export function evaluateBindingElement( const propertyValueWithInitializerFallback = propertyValue != null ? propertyValue : bindingElementInitializer; // Evaluate the BindingPattern based on the narrowed property value - evaluate.nodeWithArgument(node.name, environment, propertyValueWithInitializerFallback, statementTraversalStack); + evaluate.nodeWithArgument(node.name, propertyValueWithInitializerFallback, options); + + if (getCurrentError() != null) { + return; + } } // Otherwise, the name itself is a BindingPattern. This will happen for example if an ObjectBindingPattern occurs within an ArrayBindingPattern @@ -80,6 +91,10 @@ export function evaluateBindingElement( // Fall back to using the initializer of the BindingElement if the property value is null-like and if it has one const propertyValueWithInitializerFallback = rightHandValue != null ? rightHandValue : bindingElementInitializer; - evaluate.nodeWithArgument(node.name, environment, propertyValueWithInitializerFallback, statementTraversalStack); + evaluate.nodeWithArgument(node.name, propertyValueWithInitializerFallback, options); + + if (getCurrentError() != null) { + return; + } } } diff --git a/src/interpreter/evaluator/evaluate-binding-name.ts b/src/interpreter/evaluator/evaluate-binding-name.ts index d8b1859..def6b01 100644 --- a/src/interpreter/evaluator/evaluate-binding-name.ts +++ b/src/interpreter/evaluator/evaluate-binding-name.ts @@ -6,15 +6,12 @@ import {TS} from "../../type/ts.js"; /** * Evaluates, or attempts to evaluate, a BindingName, based on an initializer */ -export function evaluateBindingName( - {node, environment, evaluate, statementTraversalStack, reporting, typescript, logger}: EvaluatorOptions, - rightHandValue: Literal -): void { +export function evaluateBindingName({node, evaluate, typescript, logger, ...options}: EvaluatorOptions, rightHandValue: Literal): void { // If the declaration binds a simple identifier, bind that text to the environment if (typescript.isIdentifier(node) || typescript.isPrivateIdentifier?.(node)) { - setInLexicalEnvironment({env: environment, path: node.text, value: rightHandValue, newBinding: true, reporting, node}); + setInLexicalEnvironment({...options, node, path: node.text, value: rightHandValue, newBinding: true}); logger.logBinding(node.text, rightHandValue, "evaluateBindingName"); } else { - evaluate.nodeWithArgument(node, environment, rightHandValue, statementTraversalStack); + evaluate.nodeWithArgument(node, rightHandValue, options); } } diff --git a/src/interpreter/evaluator/evaluate-block.ts b/src/interpreter/evaluator/evaluate-block.ts index 8ffa814..8578370 100644 --- a/src/interpreter/evaluator/evaluate-block.ts +++ b/src/interpreter/evaluator/evaluate-block.ts @@ -10,7 +10,8 @@ import {TS} from "../../type/ts.js"; /** * Evaluates, or attempts to evaluate, a Block */ -export function evaluateBlock({node, environment, typescript, evaluate}: EvaluatorOptions): void { +export function evaluateBlock(options: EvaluatorOptions): void { + const {node, environment, typescript, evaluate, getCurrentError} = options; // Prepare a lexical environment for the Block context const localLexicalEnvironment: LexicalEnvironment = cloneLexicalEnvironment(environment, node); @@ -28,7 +29,8 @@ export function evaluateBlock({node, environment, typescript, evaluate}: Evaluat continue; } - evaluate.statement(statement, localLexicalEnvironment); + evaluate.statement(statement, {...options, environment: localLexicalEnvironment}); + if (getCurrentError() != null) break; // Check if a 'break', 'continue', or 'return' statement has been encountered, break the block if (pathInLexicalEnvironmentEquals(node, localLexicalEnvironment, true, BREAK_SYMBOL, CONTINUE_SYMBOL, RETURN_SYMBOL)) { diff --git a/src/interpreter/evaluator/evaluate-break-statement.ts b/src/interpreter/evaluator/evaluate-break-statement.ts index a6ddd0a..93adf41 100644 --- a/src/interpreter/evaluator/evaluate-break-statement.ts +++ b/src/interpreter/evaluator/evaluate-break-statement.ts @@ -6,6 +6,6 @@ import {TS} from "../../type/ts.js"; /** * Evaluates, or attempts to evaluate, a BreakStatement */ -export function evaluateBreakStatement({environment, reporting, node}: EvaluatorOptions): void { - setInLexicalEnvironment({env: environment, path: BREAK_SYMBOL, value: true, reporting, node}); +export function evaluateBreakStatement(options: EvaluatorOptions): void { + setInLexicalEnvironment({...options, path: BREAK_SYMBOL, value: true}); } diff --git a/src/interpreter/evaluator/evaluate-call-expression.ts b/src/interpreter/evaluator/evaluate-call-expression.ts index 0a6b3b2..86c28f3 100644 --- a/src/interpreter/evaluator/evaluate-call-expression.ts +++ b/src/interpreter/evaluator/evaluate-call-expression.ts @@ -5,24 +5,39 @@ import {getFromLexicalEnvironment} from "../lexical-environment/lexical-environm import {THIS_SYMBOL} from "../util/this/this-symbol.js"; import {expressionContainsSuperKeyword} from "../util/expression/expression-contains-super-keyword.js"; import {TS} from "../../type/ts.js"; +import {maybeThrow} from "../error/evaluation-error/evaluation-error-intent.js"; /** * Evaluates, or attempts to evaluate, a CallExpression */ -export function evaluateCallExpression({node, environment, evaluate, statementTraversalStack, typescript, logger}: EvaluatorOptions): Literal { +export function evaluateCallExpression(options: EvaluatorOptions): Literal { + const {node, environment, evaluate, throwError, typescript, logger, getCurrentError} = options; const evaluatedArgs: Literal[] = []; for (let i = 0; i < node.arguments.length; i++) { - evaluatedArgs[i] = evaluate.expression(node.arguments[i], environment, statementTraversalStack); + evaluatedArgs[i] = evaluate.expression(node.arguments[i], options); + if (getCurrentError() != null) { + return; + } } // Evaluate the expression - const expressionResult = evaluate.expression(node.expression, environment, statementTraversalStack) as CallableFunction | undefined; + const expressionResult = evaluate.expression(node.expression, options) as CallableFunction | undefined; + + if (getCurrentError() != null) { + return; + } if (isLazyCall(expressionResult)) { const currentThisBinding = expressionContainsSuperKeyword(node.expression, typescript) ? getFromLexicalEnvironment(node, environment, THIS_SYMBOL) : undefined; const value = expressionResult.invoke(currentThisBinding != null ? currentThisBinding.literal : undefined, ...evaluatedArgs); + + if (getCurrentError() != null) { + return; + } + logger.logResult(value, "CallExpression"); + return value; } @@ -30,10 +45,15 @@ export function evaluateCallExpression({node, environment, evaluate, statementTr else { // Unless optional chaining is being used, throw a NotCallableError if (node.questionDotToken == null && typeof expressionResult !== "function") { - throw new NotCallableError({value: expressionResult, node: node.expression}); + return throwError(new NotCallableError({value: expressionResult, node: node.expression, environment})); + } + + const value = typeof expressionResult !== "function" ? undefined : maybeThrow(node, options, expressionResult(...evaluatedArgs)); + + if (getCurrentError() != null) { + return; } - const value = typeof expressionResult !== "function" ? undefined : expressionResult(...evaluatedArgs); logger.logResult(value, "CallExpression"); return value; } diff --git a/src/interpreter/evaluator/evaluate-case-block.ts b/src/interpreter/evaluator/evaluate-case-block.ts index f9b41cd..0cdd844 100644 --- a/src/interpreter/evaluator/evaluate-case-block.ts +++ b/src/interpreter/evaluator/evaluate-case-block.ts @@ -10,14 +10,20 @@ import {TS} from "../../type/ts.js"; /** * Evaluates, or attempts to evaluate, a CaseBlock, based on a switch expression */ -export function evaluateCaseBlock({node, evaluate, environment, reporting, statementTraversalStack}: EvaluatorOptions, switchExpression: Literal): void { +export function evaluateCaseBlock(options: EvaluatorOptions, switchExpression: Literal): void { + const {node, evaluate, environment, getCurrentError} = options; // Prepare a lexical environment for the case block const localEnvironment = cloneLexicalEnvironment(environment, node); + const nextOptions = {...options, environment: localEnvironment}; // Define a new binding for a break symbol within the environment - setInLexicalEnvironment({env: localEnvironment, path: BREAK_SYMBOL, value: false, newBinding: true, reporting, node}); + setInLexicalEnvironment({...nextOptions, path: BREAK_SYMBOL, value: false, newBinding: true}); for (const clause of node.clauses) { - evaluate.nodeWithArgument(clause, localEnvironment, switchExpression, statementTraversalStack); + evaluate.nodeWithArgument(clause, switchExpression, nextOptions); + + if (getCurrentError() != null) { + return; + } // Check if a 'break', 'continue', or 'return' statement has been encountered, break the block if (pathInLexicalEnvironmentEquals(node, localEnvironment, true, BREAK_SYMBOL, CONTINUE_SYMBOL, RETURN_SYMBOL)) { diff --git a/src/interpreter/evaluator/evaluate-case-clause.ts b/src/interpreter/evaluator/evaluate-case-clause.ts index 288ec1d..4823198 100644 --- a/src/interpreter/evaluator/evaluate-case-clause.ts +++ b/src/interpreter/evaluator/evaluate-case-clause.ts @@ -9,16 +9,21 @@ import {TS} from "../../type/ts.js"; /** * Evaluates, or attempts to evaluate, a CaseClause, based on a switch expression */ -export function evaluateCaseClause({node, evaluate, environment, statementTraversalStack}: EvaluatorOptions, switchExpression: Literal): void { - const expressionResult = evaluate.expression(node.expression, environment, statementTraversalStack); +export function evaluateCaseClause({node, evaluate, ...options}: EvaluatorOptions, switchExpression: Literal): void { + const {getCurrentError} = options; + const expressionResult = evaluate.expression(node.expression, options); // Stop immediately if the expression doesn't match the switch expression - if (expressionResult !== switchExpression) return; + if (expressionResult !== switchExpression || getCurrentError() != null) return; for (const statement of node.statements) { - evaluate.statement(statement, environment); + evaluate.statement(statement, options); + + if (getCurrentError() != null) { + return; + } // Check if a 'break', 'continue', or 'return' statement has been encountered, break the block - if (pathInLexicalEnvironmentEquals(node, environment, true, BREAK_SYMBOL, CONTINUE_SYMBOL, RETURN_SYMBOL)) { + if (pathInLexicalEnvironmentEquals(node, options.environment, true, BREAK_SYMBOL, CONTINUE_SYMBOL, RETURN_SYMBOL)) { break; } } diff --git a/src/interpreter/evaluator/evaluate-catch-clause.ts b/src/interpreter/evaluator/evaluate-catch-clause.ts index 61ab044..04bff32 100644 --- a/src/interpreter/evaluator/evaluate-catch-clause.ts +++ b/src/interpreter/evaluator/evaluate-catch-clause.ts @@ -5,15 +5,21 @@ import {TS} from "../../type/ts.js"; /** * Evaluates, or attempts to evaluate, a CatchClause, based on a given Error */ -export function evaluateCatchClause({node, evaluate, environment, statementTraversalStack}: EvaluatorOptions, ex: Error): void { +export function evaluateCatchClause(options: EvaluatorOptions, ex: Error): void { + const {node, evaluate, environment, getCurrentError} = options; // If a catch binding is provided, we must provide a local lexical environment for the CatchBlock const catchEnvironment = node.variableDeclaration == null ? environment : cloneLexicalEnvironment(environment, node); + const nextOptions = {...options, environment: catchEnvironment}; // Evaluate the catch binding, if any is provided if (node.variableDeclaration != null) { - evaluate.nodeWithArgument(node.variableDeclaration, catchEnvironment, ex, statementTraversalStack); + evaluate.nodeWithArgument(node.variableDeclaration, ex, nextOptions); + + if (getCurrentError() != null) { + return; + } } // Evaluate the block - evaluate.statement(node.block, catchEnvironment); + evaluate.statement(node.block, nextOptions); } diff --git a/src/interpreter/evaluator/evaluate-class-declaration.ts b/src/interpreter/evaluator/evaluate-class-declaration.ts index b24107c..ee6371f 100644 --- a/src/interpreter/evaluator/evaluate-class-declaration.ts +++ b/src/interpreter/evaluator/evaluate-class-declaration.ts @@ -7,23 +7,20 @@ import {TS} from "../../type/ts.js"; /** * Evaluates, or attempts to evaluate, a ClassDeclaration */ -export function evaluateClassDeclaration({ - node, - environment, - evaluate, - stack, - logger, - reporting, - typescript, - statementTraversalStack -}: EvaluatorOptions): void { +export function evaluateClassDeclaration(options: EvaluatorOptions): void { + const {node, evaluate, stack, logger, typescript, getCurrentError} = options; let extendedType: CallableFunction | undefined; const ctorMember = node.members.find(typescript.isConstructorDeclaration); const otherMembers = node.members.filter(member => !typescript.isConstructorDeclaration(member)); let ctor: CallableFunction | undefined; if (ctorMember != null) { - evaluate.declaration(ctorMember, environment, statementTraversalStack); + evaluate.declaration(ctorMember, options); + + if (getCurrentError() != null) { + return; + } + ctor = stack.pop() as CallableFunction; } @@ -32,7 +29,11 @@ export function evaluateClassDeclaration({ if (extendsClause != null) { const [firstExtendedType] = extendsClause.types; if (firstExtendedType != null) { - extendedType = evaluate.expression(firstExtendedType.expression, environment, statementTraversalStack) as CallableFunction; + extendedType = evaluate.expression(firstExtendedType.expression, options) as CallableFunction; + + if (getCurrentError() != null) { + return; + } } } } @@ -42,7 +43,12 @@ export function evaluateClassDeclaration({ if (node.decorators != null) { for (const decorator of node.decorators) { - evaluate.nodeWithArgument(decorator, environment, [classDeclaration], statementTraversalStack); + evaluate.nodeWithArgument(decorator, [classDeclaration], options); + + if (getCurrentError() != null) { + return; + } + classDeclaration = stack.pop() as CallableFunction; } } @@ -50,17 +56,16 @@ export function evaluateClassDeclaration({ classDeclaration.toString = () => `[Class${name == null ? "" : `: ${name}`}]`; if (name != null) { - setInLexicalEnvironment({env: environment, path: name, value: classDeclaration, newBinding: true, reporting, node}); + setInLexicalEnvironment({...options, path: name, value: classDeclaration, newBinding: true}); } // Walk through all of the class members for (const member of otherMembers) { - evaluate.nodeWithArgument( - member, - environment, - hasModifier(member, typescript.SyntaxKind.StaticKeyword) ? classDeclaration : classDeclaration.prototype, - statementTraversalStack - ); + evaluate.nodeWithArgument(member, hasModifier(member, typescript.SyntaxKind.StaticKeyword) ? classDeclaration : classDeclaration.prototype, options); + + if (getCurrentError() != null) { + return; + } } logger.logHeritage(classDeclaration); diff --git a/src/interpreter/evaluator/evaluate-class-expression.ts b/src/interpreter/evaluator/evaluate-class-expression.ts index 5ec387a..bfc764b 100644 --- a/src/interpreter/evaluator/evaluate-class-expression.ts +++ b/src/interpreter/evaluator/evaluate-class-expression.ts @@ -7,27 +7,22 @@ import {TS} from "../../type/ts.js"; /** * Evaluates, or attempts to evaluate, a ClassExpression - * - * @param options - * @returns */ -export function evaluateClassExpression({ - node, - environment, - evaluate, - stack, - logger, - reporting, - statementTraversalStack, - typescript -}: EvaluatorOptions): Literal { +export function evaluateClassExpression(options: EvaluatorOptions): Literal { + const {node, evaluate, stack, logger, typescript, getCurrentError} = options; + let extendedType: CallableFunction | undefined; const ctorMember = node.members.find(typescript.isConstructorDeclaration); const otherMembers = node.members.filter(member => !typescript.isConstructorDeclaration(member)); let ctor: CallableFunction | undefined; if (ctorMember != null) { - evaluate.declaration(ctorMember, environment, statementTraversalStack); + evaluate.declaration(ctorMember, options); + + if (getCurrentError() != null) { + return; + } + ctor = stack.pop() as CallableFunction; } @@ -36,7 +31,11 @@ export function evaluateClassExpression({ if (extendsClause != null) { const [firstExtendedType] = extendsClause.types; if (firstExtendedType != null) { - extendedType = evaluate.expression(firstExtendedType.expression, environment, statementTraversalStack) as CallableFunction; + extendedType = evaluate.expression(firstExtendedType.expression, options) as CallableFunction; + + if (getCurrentError() != null) { + return; + } } } } @@ -46,7 +45,12 @@ export function evaluateClassExpression({ if (node.decorators != null) { for (const decorator of node.decorators) { - evaluate.nodeWithArgument(decorator, environment, [classExpression], statementTraversalStack); + evaluate.nodeWithArgument(decorator, [classExpression], options); + + if (getCurrentError() != null) { + return; + } + classExpression = stack.pop() as CallableFunction; } } @@ -54,12 +58,16 @@ export function evaluateClassExpression({ classExpression.toString = () => `[Class${name == null ? "" : `: ${name}`}]`; if (name != null) { - setInLexicalEnvironment({env: environment, path: name, value: classExpression, newBinding: true, reporting, node}); + setInLexicalEnvironment({...options, path: name, value: classExpression, newBinding: true}); } // Walk through all of the class members for (const member of otherMembers) { - evaluate.nodeWithArgument(member, environment, hasModifier(member, typescript.SyntaxKind.StaticKeyword) ? classExpression : classExpression.prototype, statementTraversalStack); + evaluate.nodeWithArgument(member, hasModifier(member, typescript.SyntaxKind.StaticKeyword) ? classExpression : classExpression.prototype, options); + + if (getCurrentError() != null) { + return; + } } logger.logHeritage(classExpression); diff --git a/src/interpreter/evaluator/evaluate-computed-property-name.ts b/src/interpreter/evaluator/evaluate-computed-property-name.ts index 03b7cc1..982a79a 100644 --- a/src/interpreter/evaluator/evaluate-computed-property-name.ts +++ b/src/interpreter/evaluator/evaluate-computed-property-name.ts @@ -5,6 +5,6 @@ import {TS} from "../../type/ts.js"; /** * Evaluates, or attempts to evaluate, a ComputedPropertyName */ -export function evaluateComputedPropertyName({node, environment, evaluate, statementTraversalStack}: EvaluatorOptions): Literal { - return evaluate.expression(node.expression, environment, statementTraversalStack); +export function evaluateComputedPropertyName({node, evaluate, ...options}: EvaluatorOptions): Literal { + return evaluate.expression(node.expression, options); } diff --git a/src/interpreter/evaluator/evaluate-conditional-expression.ts b/src/interpreter/evaluator/evaluate-conditional-expression.ts index 679f1d3..910aff7 100644 --- a/src/interpreter/evaluator/evaluate-conditional-expression.ts +++ b/src/interpreter/evaluator/evaluate-conditional-expression.ts @@ -5,16 +5,21 @@ import {TS} from "../../type/ts.js"; /** * Evaluates, or attempts to evaluate, a ConditionalExpression */ -export function evaluateConditionalExpression({node, environment, evaluate, statementTraversalStack}: EvaluatorOptions): Literal { - const conditionValue = evaluate.expression(node.condition, environment, statementTraversalStack); +export function evaluateConditionalExpression({node, evaluate, ...options}: EvaluatorOptions): Literal { + const {getCurrentError} = options; + const conditionValue = evaluate.expression(node.condition, options); + + if (getCurrentError() != null) { + return; + } // We have to perform a loose boolean expression here to conform with actual spec behavior // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions if (conditionValue) { // Proceed with the truthy branch - return evaluate.expression(node.whenTrue, environment, statementTraversalStack); + return evaluate.expression(node.whenTrue, options); } // Proceed with the falsy branch - return evaluate.expression(node.whenFalse, environment, statementTraversalStack); + return evaluate.expression(node.whenFalse, options); } diff --git a/src/interpreter/evaluator/evaluate-constructor-declaration.ts b/src/interpreter/evaluator/evaluate-constructor-declaration.ts index 6d48cbc..3751e7c 100644 --- a/src/interpreter/evaluator/evaluate-constructor-declaration.ts +++ b/src/interpreter/evaluator/evaluate-constructor-declaration.ts @@ -11,7 +11,7 @@ import {TS} from "../../type/ts.js"; * Evaluates, or attempts to evaluate, a ConstructorDeclaration */ export function evaluateConstructorDeclaration(options: EvaluatorOptions): void { - const {node, environment, evaluate, stack, reporting} = options; + const {node, environment, evaluate, stack, getCurrentError} = options; /** * An implementation of a constructor function @@ -21,32 +21,37 @@ export function evaluateConstructorDeclaration(options: EvaluatorOptions): void { - setInLexicalEnvironment({env: environment, path: CONTINUE_SYMBOL, value: true, reporting, node}); +export function evaluateContinueStatement(options: EvaluatorOptions): void { + setInLexicalEnvironment({...options, path: CONTINUE_SYMBOL, value: true}); } diff --git a/src/interpreter/evaluator/evaluate-decorator.ts b/src/interpreter/evaluator/evaluate-decorator.ts index 36333d4..8e840b4 100644 --- a/src/interpreter/evaluator/evaluate-decorator.ts +++ b/src/interpreter/evaluator/evaluate-decorator.ts @@ -3,22 +3,28 @@ import {IndexLiteral, stringifyLiteral} from "../literal/literal.js"; import {NotCallableError} from "../error/not-callable-error/not-callable-error.js"; import {TS} from "../../type/ts.js"; import {__decorate, __param} from "../util/tslib/tslib-util.js"; +import {EvaluationError} from "../error/evaluation-error/evaluation-error.js"; /** * Evaluates, or attempts to evaluate, a Decorator */ -export function evaluateDecorator( - {node, environment, evaluate, stack, statementTraversalStack}: EvaluatorOptions, - [parent, propertyName, index]: [IndexLiteral, string?, number?] -): void { - const decoratorImplementation = evaluate.expression(node.expression, environment, statementTraversalStack); +export function evaluateDecorator(options: EvaluatorOptions, [parent, propertyName, index]: [IndexLiteral, string?, number?]): void | EvaluationError { + const {node, evaluate, environment, throwError, stack, getCurrentError} = options; + const decoratorImplementation = evaluate.expression(node.expression, options); + + if (getCurrentError() != null) { + return; + } if (typeof decoratorImplementation !== "function") { - throw new NotCallableError({ - node, - value: decoratorImplementation, - message: `${stringifyLiteral(decoratorImplementation)} is not a valid decorator implementation'` - }); + return throwError( + new NotCallableError({ + node, + environment, + value: decoratorImplementation, + message: `${stringifyLiteral(decoratorImplementation)} is not a valid decorator implementation'` + }) + ); } stack.push(__decorate([index != null ? __param(index, decoratorImplementation) : decoratorImplementation], parent, propertyName)); diff --git a/src/interpreter/evaluator/evaluate-default-clause.ts b/src/interpreter/evaluator/evaluate-default-clause.ts index b62a61c..c159e39 100644 --- a/src/interpreter/evaluator/evaluate-default-clause.ts +++ b/src/interpreter/evaluator/evaluate-default-clause.ts @@ -8,9 +8,14 @@ import {TS} from "../../type/ts.js"; /** * Evaluates, or attempts to evaluate, a DefaultClause, based on a switch expression */ -export function evaluateDefaultClause({node, evaluate, environment}: EvaluatorOptions): void { +export function evaluateDefaultClause(options: EvaluatorOptions): void { + const {node, evaluate, environment, getCurrentError} = options; for (const statement of node.statements) { - evaluate.statement(statement, environment); + evaluate.statement(statement, options); + + if (getCurrentError() != null) { + return; + } // Check if a 'break', 'continue', or 'return' statement has been encountered, break the block if (pathInLexicalEnvironmentEquals(node, environment, true, BREAK_SYMBOL, CONTINUE_SYMBOL, RETURN_SYMBOL)) { diff --git a/src/interpreter/evaluator/evaluate-element-access-expression.ts b/src/interpreter/evaluator/evaluate-element-access-expression.ts index 062ab04..7db9e1c 100644 --- a/src/interpreter/evaluator/evaluate-element-access-expression.ts +++ b/src/interpreter/evaluator/evaluate-element-access-expression.ts @@ -2,13 +2,24 @@ import {EvaluatorOptions} from "./evaluator-options.js"; import {IndexLiteral, IndexLiteralKey, LAZY_CALL_FLAG, LazyCall, Literal, LiteralFlagKind} from "../literal/literal.js"; import {isBindCallApply} from "../util/function/is-bind-call-apply.js"; import {TS} from "../../type/ts.js"; +import {maybeThrow} from "../error/evaluation-error/evaluation-error-intent.js"; /** * Evaluates, or attempts to evaluate, a ElementAccessExpression */ -export function evaluateElementAccessExpression({node, environment, evaluate, statementTraversalStack, typescript}: EvaluatorOptions): Literal { - const expressionResult = evaluate.expression(node.expression, environment, statementTraversalStack) as IndexLiteral; - const argumentExpressionResult = evaluate.expression(node.argumentExpression, environment, statementTraversalStack) as IndexLiteralKey; +export function evaluateElementAccessExpression(options: EvaluatorOptions): Literal { + const {node, environment, evaluate, statementTraversalStack, typescript, getCurrentError} = options; + const expressionResult = evaluate.expression(node.expression, options) as IndexLiteral; + + if (getCurrentError() != null) { + return; + } + + const argumentExpressionResult = evaluate.expression(node.argumentExpression, options) as IndexLiteralKey; + + if (getCurrentError() != null) { + return; + } const match = node.questionDotToken != null && expressionResult == null @@ -22,10 +33,14 @@ export function evaluateElementAccessExpression({node, environment, evaluate, st return { [LAZY_CALL_FLAG]: LiteralFlagKind.CALL, invoke: (overriddenThis: Record | CallableFunction | undefined, ...args: Literal[]) => - overriddenThis != null && !isBindCallApply(match, environment) - ? // eslint-disable-next-line @typescript-eslint/ban-types - (expressionResult[argumentExpressionResult] as Function).call(overriddenThis, ...args) - : (expressionResult[argumentExpressionResult] as CallableFunction)(...args) + maybeThrow( + node, + options, + overriddenThis != null && !isBindCallApply(match, environment) + ? // eslint-disable-next-line @typescript-eslint/ban-types + (expressionResult[argumentExpressionResult] as Function).call(overriddenThis, ...args) + : (expressionResult[argumentExpressionResult] as CallableFunction)(...args) + ) } as LazyCall; } else return match; } diff --git a/src/interpreter/evaluator/evaluate-enum-declaration.ts b/src/interpreter/evaluator/evaluate-enum-declaration.ts index fbef8f0..4471ef6 100644 --- a/src/interpreter/evaluator/evaluate-enum-declaration.ts +++ b/src/interpreter/evaluator/evaluate-enum-declaration.ts @@ -6,7 +6,9 @@ import {TS} from "../../type/ts.js"; /** * Evaluates, or attempts to evaluate, an EnumDeclaration */ -export function evaluateEnumDeclaration({node, environment, evaluate, statementTraversalStack, reporting, stack}: EvaluatorOptions): void { +export function evaluateEnumDeclaration(options: EvaluatorOptions): void { + const {node, environment, evaluate, stack, getCurrentError} = options; + // Create a new ObjectLiteral based on the Object implementation from the Realm since this must not be the same as in the parent executing context // Otherwise, instanceof checks would fail const objectCtor = getFromLexicalEnvironment(node, environment, "Object")!.literal as ObjectConstructor; @@ -14,10 +16,14 @@ export function evaluateEnumDeclaration({node, environment, evaluate, statementT const name = node.name.text; // Bind the Enum to the lexical environment as a new binding - setInLexicalEnvironment({env: environment, path: name, value: enumDeclaration, newBinding: true, reporting, node}); + setInLexicalEnvironment({...options, path: name, value: enumDeclaration, newBinding: true}); for (const member of node.members) { - evaluate.nodeWithArgument(member, environment, enumDeclaration, statementTraversalStack); + evaluate.nodeWithArgument(member, enumDeclaration, options); + + if (getCurrentError() != null) { + return; + } } enumDeclaration.toString = () => `[Enum: ${name}]`; diff --git a/src/interpreter/evaluator/evaluate-enum-member.ts b/src/interpreter/evaluator/evaluate-enum-member.ts index db8d019..d62adc6 100644 --- a/src/interpreter/evaluator/evaluate-enum-member.ts +++ b/src/interpreter/evaluator/evaluate-enum-member.ts @@ -2,18 +2,22 @@ import {EvaluatorOptions} from "./evaluator-options.js"; import {IndexLiteral, IndexLiteralKey} from "../literal/literal.js"; import {TS} from "../../type/ts.js"; - /** * Evaluates, or attempts to evaluate, an EnumMember */ -export function evaluateEnumMember({node, typeChecker, evaluate, environment, statementTraversalStack}: EvaluatorOptions, parent: IndexLiteral): void { +export function evaluateEnumMember(options: EvaluatorOptions, parent: IndexLiteral): void { + const {node, typeChecker, evaluate, getCurrentError} = options; let constantValue = typeChecker?.getConstantValue(node); // If the constant value is not defined, that must be due to the type checker either not being given or functioning incorrectly. // Calculate it manually instead if (constantValue == null) { if (node.initializer != null) { - constantValue = evaluate.expression(node.initializer, environment, statementTraversalStack) as string | number | undefined; + constantValue = evaluate.expression(node.initializer, options) as string | number | undefined; + + if (getCurrentError() != null) { + return; + } } else { const siblings = node.parent.members; @@ -24,12 +28,16 @@ export function evaluateEnumMember({node, typeChecker, evaluate, environment, st for (const sibling of [...beforeSiblings].reverse()) { traversal++; if (sibling.initializer != null) { - const siblingConstantValue = evaluate.expression(sibling.initializer, environment, statementTraversalStack) as string | number | undefined; + const siblingConstantValue = evaluate.expression(sibling.initializer, options) as string | number | undefined; + + if (getCurrentError() != null) { + return; + } + if (typeof siblingConstantValue === "number") { constantValue = siblingConstantValue + traversal; break; } - } } @@ -39,7 +47,11 @@ export function evaluateEnumMember({node, typeChecker, evaluate, environment, st } } - const propertyName = evaluate.nodeWithValue(node.name, environment, statementTraversalStack) as IndexLiteralKey; + const propertyName = evaluate.nodeWithValue(node.name, options) as IndexLiteralKey; + + if (getCurrentError() != null) { + return; + } // If it is a String enum, all keys will be initialized to strings if (typeof constantValue === "string") { diff --git a/src/interpreter/evaluator/evaluate-expression-statement.ts b/src/interpreter/evaluator/evaluate-expression-statement.ts index 078f43c..0fbaef8 100644 --- a/src/interpreter/evaluator/evaluate-expression-statement.ts +++ b/src/interpreter/evaluator/evaluate-expression-statement.ts @@ -4,6 +4,10 @@ import {TS} from "../../type/ts.js"; /** * Evaluates, or attempts to evaluate, an ExpressionStatement */ -export function evaluateExpressionStatement({node, environment, evaluate, stack, statementTraversalStack}: EvaluatorOptions): void { - stack.push(evaluate.expression(node.expression, environment, statementTraversalStack)); +export function evaluateExpressionStatement({node, evaluate, stack, ...options}: EvaluatorOptions): void { + const result = evaluate.expression(node.expression, options); + if (options.getCurrentError() != null) { + return; + } + stack.push(result); } diff --git a/src/interpreter/evaluator/evaluate-expression.ts b/src/interpreter/evaluator/evaluate-expression.ts index 9506cd5..3aaba1f 100644 --- a/src/interpreter/evaluator/evaluate-expression.ts +++ b/src/interpreter/evaluator/evaluate-expression.ts @@ -7,9 +7,14 @@ import {TS} from "../../type/ts.js"; * Will get a literal value for the given Expression. If it doesn't succeed, the value will be 'undefined' */ export function evaluateExpression(options: EvaluatorOptions): Literal { + const {getCurrentError} = options; options.logger.logNode(options.node, options.typescript); const value = evaluateNode(options) as Promise; + if (getCurrentError() != null) { + return; + } + // Report intermediate results if (options.reporting.reportIntermediateResults != null) { options.reporting.reportIntermediateResults({ diff --git a/src/interpreter/evaluator/evaluate-for-in-statement.ts b/src/interpreter/evaluator/evaluate-for-in-statement.ts index cadc395..8d23f6a 100644 --- a/src/interpreter/evaluator/evaluate-for-in-statement.ts +++ b/src/interpreter/evaluator/evaluate-for-in-statement.ts @@ -7,39 +7,54 @@ import {BREAK_SYMBOL} from "../util/break/break-symbol.js"; import {CONTINUE_SYMBOL} from "../util/continue/continue-symbol.js"; import {RETURN_SYMBOL} from "../util/return/return-symbol.js"; import {TS} from "../../type/ts.js"; +import {EvaluationError} from "../error/evaluation-error/evaluation-error.js"; /** * Evaluates, or attempts to evaluate, a ForInStatement */ -export function evaluateForInStatement({node, environment, evaluate, logger, reporting, typescript, statementTraversalStack}: EvaluatorOptions): void { +export function evaluateForInStatement(options: EvaluatorOptions): void | EvaluationError { + const {node, environment, evaluate, logger, typescript, throwError, getCurrentError} = options; // Compute the 'of' part - const expressionResult = evaluate.expression(node.expression, environment, statementTraversalStack) as IndexLiteral; + const expressionResult = evaluate.expression(node.expression, options) as IndexLiteral; + + if (getCurrentError() != null) { + return; + } // Ensure that the initializer is a proper VariableDeclarationList if (!typescript.isVariableDeclarationList(node.initializer)) { - throw new UnexpectedNodeError({node: node.initializer, typescript}); + return throwError(new UnexpectedNodeError({node: node.initializer, environment, typescript})); } // Only 1 declaration is allowed in a ForOfStatement else if (node.initializer.declarations.length > 1) { - throw new UnexpectedNodeError({node: node.initializer.declarations[1], typescript}); + return throwError(new UnexpectedNodeError({node: node.initializer.declarations[1], environment, typescript})); } for (const literal in expressionResult) { // Prepare a lexical environment for the current iteration const localEnvironment = cloneLexicalEnvironment(environment, node); + const nextOptions = {...options, environment: localEnvironment}; // Define a new binding for a break symbol within the environment - setInLexicalEnvironment({env: localEnvironment, path: BREAK_SYMBOL, value: false, newBinding: true, reporting, node}); + setInLexicalEnvironment({...nextOptions, path: BREAK_SYMBOL, value: false, newBinding: true}); // Define a new binding for a continue symbol within the environment - setInLexicalEnvironment({env: localEnvironment, path: CONTINUE_SYMBOL, value: false, newBinding: true, reporting, node}); + setInLexicalEnvironment({...nextOptions, path: CONTINUE_SYMBOL, value: false, newBinding: true}); // Evaluate the VariableDeclaration and manually pass in the current literal as the initializer for the variable assignment - evaluate.nodeWithArgument(node.initializer.declarations[0], localEnvironment, literal, statementTraversalStack); + evaluate.nodeWithArgument(node.initializer.declarations[0], literal, nextOptions); + + if (getCurrentError() != null) { + return; + } // Evaluate the Statement - evaluate.statement(node.statement, localEnvironment); + evaluate.statement(node.statement, nextOptions); + + if (getCurrentError() != null) { + return; + } // Check if a 'break' statement has been encountered and break if so if (pathInLexicalEnvironmentEquals(node, localEnvironment, true, BREAK_SYMBOL)) { diff --git a/src/interpreter/evaluator/evaluate-for-of-statement.ts b/src/interpreter/evaluator/evaluate-for-of-statement.ts index 1d8f07c..954b7f9 100644 --- a/src/interpreter/evaluator/evaluate-for-of-statement.ts +++ b/src/interpreter/evaluator/evaluate-for-of-statement.ts @@ -8,43 +8,58 @@ import {CONTINUE_SYMBOL} from "../util/continue/continue-symbol.js"; import {RETURN_SYMBOL} from "../util/return/return-symbol.js"; import {TS} from "../../type/ts.js"; import {AsyncIteratorNotSupportedError} from "../error/async-iterator-not-supported-error/async-iterator-not-supported-error.js"; +import {EvaluationError} from "../error/evaluation-error/evaluation-error.js"; /** * Evaluates, or attempts to evaluate, a ForOfStatement */ -export function evaluateForOfStatement({node, environment, evaluate, logger, reporting, typescript, statementTraversalStack}: EvaluatorOptions): void { +export function evaluateForOfStatement(options: EvaluatorOptions): void | EvaluationError { + const {node, environment, evaluate, logger, typescript, throwError, getCurrentError} = options; // Compute the 'of' part - const expressionResult = evaluate.expression(node.expression, environment, statementTraversalStack) as Iterable; + const expressionResult = evaluate.expression(node.expression, options) as Iterable; + + if (getCurrentError() != null) { + return; + } // Ensure that the initializer is a proper VariableDeclarationList if (!typescript.isVariableDeclarationList(node.initializer)) { - throw new UnexpectedNodeError({node: node.initializer, typescript}); + return throwError(new UnexpectedNodeError({node: node.initializer, environment, typescript})); } // Only 1 declaration is allowed in a ForOfStatement else if (node.initializer.declarations.length > 1) { - throw new UnexpectedNodeError({node: node.initializer.declarations[1], typescript}); + return throwError(new UnexpectedNodeError({node: node.initializer.declarations[1], environment, typescript})); } // As long as we only offer a synchronous API, there's no way to evaluate an async iterator in a synchronous fashion if (node.awaitModifier != null) { - throw new AsyncIteratorNotSupportedError({typescript}); + return throwError(new AsyncIteratorNotSupportedError({typescript, environment})); } else { for (const literal of expressionResult) { // Prepare a lexical environment for the current iteration const localEnvironment = cloneLexicalEnvironment(environment, node); + const nextOptions = {...options, environment: localEnvironment}; // Define a new binding for a break symbol within the environment - setInLexicalEnvironment({env: localEnvironment, path: BREAK_SYMBOL, value: false, newBinding: true, reporting, node}); + setInLexicalEnvironment({...nextOptions, path: BREAK_SYMBOL, value: false, newBinding: true}); // Define a new binding for a continue symbol within the environment - setInLexicalEnvironment({env: localEnvironment, path: CONTINUE_SYMBOL, value: false, newBinding: true, reporting, node}); + setInLexicalEnvironment({...nextOptions, path: CONTINUE_SYMBOL, value: false, newBinding: true}); // Evaluate the VariableDeclaration and manually pass in the current literal as the initializer for the variable assignment - evaluate.nodeWithArgument(node.initializer.declarations[0], localEnvironment, literal, statementTraversalStack); + evaluate.nodeWithArgument(node.initializer.declarations[0], literal, nextOptions); + + if (getCurrentError() != null) { + return; + } // Evaluate the Statement - evaluate.statement(node.statement, localEnvironment); + evaluate.statement(node.statement, nextOptions); + + if (getCurrentError() != null) { + return; + } // Check if a 'break' statement has been encountered and break if so if (pathInLexicalEnvironmentEquals(node, localEnvironment, true, BREAK_SYMBOL)) { diff --git a/src/interpreter/evaluator/evaluate-for-statement.ts b/src/interpreter/evaluator/evaluate-for-statement.ts index 31e48f4..16ae1b3 100644 --- a/src/interpreter/evaluator/evaluate-for-statement.ts +++ b/src/interpreter/evaluator/evaluate-for-statement.ts @@ -9,39 +9,54 @@ import {TS} from "../../type/ts.js"; /** * Evaluates, or attempts to evaluate, a ForStatement */ -export function evaluateForStatement({node, environment, evaluate, reporting, statementTraversalStack, typescript}: EvaluatorOptions): void { +export function evaluateForStatement({environment, evaluate, typescript, ...options}: EvaluatorOptions): void { + const {node, getCurrentError} = options; // Prepare a lexical environment for the ForStatement const forEnvironment = cloneLexicalEnvironment(environment, node); + const forOptions = {...options, environment: forEnvironment}; // Evaluate the initializer if it is given if (node.initializer !== undefined) { if (typescript.isVariableDeclarationList(node.initializer)) { for (const declaration of node.initializer.declarations) { - evaluate.declaration(declaration, forEnvironment, statementTraversalStack); + evaluate.declaration(declaration, forOptions); + + if (getCurrentError() != null) { + return; + } } } else { - evaluate.expression(node.initializer, forEnvironment, statementTraversalStack); + evaluate.expression(node.initializer, forOptions); + + if (getCurrentError() != null) { + return; + } } } while (true) { // Prepare a lexical environment for the current iteration const iterationEnvironment = cloneLexicalEnvironment(forEnvironment, node); + const iterationOptions = {...options, environment: iterationEnvironment}; // Define a new binding for a break symbol within the environment - setInLexicalEnvironment({env: iterationEnvironment, path: BREAK_SYMBOL, value: false, newBinding: true, reporting, node}); + setInLexicalEnvironment({...iterationOptions, path: BREAK_SYMBOL, value: false, newBinding: true}); // Define a new binding for a continue symbol within the environment - setInLexicalEnvironment({env: iterationEnvironment, path: CONTINUE_SYMBOL, value: false, newBinding: true, reporting, node}); + setInLexicalEnvironment({...iterationOptions, path: CONTINUE_SYMBOL, value: false, newBinding: true}); // Evaluate the condition. It may be truthy always - const conditionResult = node.condition == null ? true : (evaluate.expression(node.condition, forEnvironment, statementTraversalStack) as boolean); + const conditionResult = node.condition == null ? true : (evaluate.expression(node.condition, forOptions) as boolean); // If the condition doesn't hold, return immediately - if (!conditionResult) return; + if (!conditionResult || getCurrentError() != null) return; // Execute the Statement - evaluate.statement(node.statement, iterationEnvironment); + evaluate.statement(node.statement, iterationOptions); + + if (getCurrentError() != null) { + return; + } // Check if a 'break' statement has been encountered and break if so if (pathInLexicalEnvironmentEquals(node, iterationEnvironment, true, BREAK_SYMBOL)) { @@ -52,7 +67,11 @@ export function evaluateForStatement({node, environment, evaluate, reporting, st // Run the incrementor if (node.incrementor != null) { - evaluate.expression(node.incrementor, forEnvironment, statementTraversalStack); + evaluate.expression(node.incrementor, forOptions); + + if (getCurrentError() != null) { + return; + } } // Always run the incrementor before continuing diff --git a/src/interpreter/evaluator/evaluate-function-declaration.ts b/src/interpreter/evaluator/evaluate-function-declaration.ts index 5f7b7f8..2151589 100644 --- a/src/interpreter/evaluator/evaluate-function-declaration.ts +++ b/src/interpreter/evaluator/evaluate-function-declaration.ts @@ -13,7 +13,7 @@ import {TS} from "../../type/ts.js"; * Evaluates, or attempts to evaluate, a FunctionDeclaration */ export function evaluateFunctionDeclaration(options: EvaluatorOptions): void { - const {node, environment, evaluate, stack, reporting, typescript} = options; + const {node, environment, evaluate, stack, typescript, getCurrentError} = options; const nameResult = node.name == null ? undefined : node.name.text; @@ -21,16 +21,17 @@ export function evaluateFunctionDeclaration(options: EvaluatorOptions `[Function${nameResult == null ? "" : `: ${nameResult}`}]`; diff --git a/src/interpreter/evaluator/evaluate-function-expression.ts b/src/interpreter/evaluator/evaluate-function-expression.ts index 5e833d3..a0396f1 100644 --- a/src/interpreter/evaluator/evaluate-function-expression.ts +++ b/src/interpreter/evaluator/evaluate-function-expression.ts @@ -12,38 +12,42 @@ import {TS} from "../../type/ts.js"; * Evaluates, or attempts to evaluate, a FunctionExpression */ export function evaluateFunctionExpression(options: EvaluatorOptions): Literal { - const {node, environment, evaluate, stack, reporting, typescript} = options; + const {node, environment, evaluate, stack, typescript, getCurrentError} = options; const nameResult = node.name == null ? undefined : node.name.text; const _functionExpression = hasModifier(node, typescript.SyntaxKind.AsyncKeyword) ? async function functionExpression(this: Literal, ...args: Literal[]) { // Prepare a lexical environment for the function context const localLexicalEnvironment: LexicalEnvironment = cloneLexicalEnvironment(environment, node); + const nextOptions = {...options, environment: localLexicalEnvironment}; // Define a new binding for a return symbol within the environment - setInLexicalEnvironment({env: localLexicalEnvironment, path: RETURN_SYMBOL, value: false, newBinding: true, reporting, node}); + setInLexicalEnvironment({...nextOptions, path: RETURN_SYMBOL, value: false, newBinding: true}); // Define a new binding for the arguments given to the function // eslint-disable-next-line prefer-rest-params - setInLexicalEnvironment({env: localLexicalEnvironment, path: "arguments", value: arguments, newBinding: true, reporting, node}); + setInLexicalEnvironment({...nextOptions, path: "arguments", value: arguments, newBinding: true}); if (this != null) { - setInLexicalEnvironment({env: localLexicalEnvironment, path: THIS_SYMBOL, value: this, newBinding: true, reporting, node}); + setInLexicalEnvironment({...nextOptions, path: THIS_SYMBOL, value: this, newBinding: true}); } // Evaluate the parameters based on the given arguments evaluateParameterDeclarations( { - ...options, - node: node.parameters, - environment: localLexicalEnvironment + ...nextOptions, + node: node.parameters }, args ); // If the body is a block, evaluate it as a statement - if (node.body == null) return; - evaluate.statement(node.body, localLexicalEnvironment); + if (node.body == null || getCurrentError() != null) return; + evaluate.statement(node.body, nextOptions); + + if (getCurrentError() != null) { + return; + } // If a 'return' has occurred within the block, pop the Stack and return that value if (pathInLexicalEnvironmentEquals(node, localLexicalEnvironment, true, RETURN_SYMBOL)) { @@ -56,31 +60,35 @@ export function evaluateFunctionExpression(options: EvaluatorOptions `[Function${nameResult == null ? "" : `: ${nameResult}`}]`; diff --git a/src/interpreter/evaluator/evaluate-get-accessor-declaration.ts b/src/interpreter/evaluator/evaluate-get-accessor-declaration.ts index a2c84d8..cae4d70 100644 --- a/src/interpreter/evaluator/evaluate-get-accessor-declaration.ts +++ b/src/interpreter/evaluator/evaluate-get-accessor-declaration.ts @@ -11,26 +11,38 @@ import {TS} from "../../type/ts.js"; /** * Evaluates, or attempts to evaluate, a GetAccessorDeclaration, before setting it on the given parent */ -export function evaluateGetAccessorDeclaration( - {node, environment, evaluate, stack, reporting, typescript, statementTraversalStack}: EvaluatorOptions, - parent?: IndexLiteral -): void { +export function evaluateGetAccessorDeclaration(options: EvaluatorOptions, parent?: IndexLiteral): void { + const {node, environment, evaluate, stack, typescript, getCurrentError} = options; // We might be attempting to evaluate GetAccessorDeclaration that is placed within an ambient // context such as an InterfaceDeclaration, in which case there's nothing to evaluate if (typescript.isTypeLiteralNode(node.parent) || typescript.isInterfaceDeclaration(node.parent)) { return; } - const nameResult = evaluate.nodeWithValue(node.name, environment, statementTraversalStack) as IndexLiteralKey; + const nameResult = evaluate.nodeWithValue(node.name, options) as IndexLiteralKey; + + if (getCurrentError() != null) { + return; + } + const isStatic = inStaticContext(node, typescript); if (parent == null) { let updatedParent: CallableFunction & IndexLiteral; if (typescript.isClassLike(node.parent)) { - evaluate.declaration(node.parent, environment, statementTraversalStack); + evaluate.declaration(node.parent, options); + + if (getCurrentError() != null) { + return; + } + updatedParent = stack.pop() as CallableFunction & IndexLiteral; } else { - updatedParent = evaluate.expression(node.parent, environment, statementTraversalStack) as CallableFunction & IndexLiteral; + updatedParent = evaluate.expression(node.parent, options) as CallableFunction & IndexLiteral; + + if (getCurrentError() != null) { + return; + } } stack.push(isStatic ? updatedParent[nameResult] : updatedParent.prototype[nameResult]); return; @@ -42,31 +54,35 @@ export function evaluateGetAccessorDeclaration( function getAccessorDeclaration(this: Literal) { // Prepare a lexical environment for the function context const localLexicalEnvironment: LexicalEnvironment = cloneLexicalEnvironment(environment, node); + const nextOptions = {...options, environment: localLexicalEnvironment}; // Define a new binding for a return symbol within the environment - setInLexicalEnvironment({env: localLexicalEnvironment, path: RETURN_SYMBOL, value: false, newBinding: true, reporting, node}); + setInLexicalEnvironment({...nextOptions, path: RETURN_SYMBOL, value: false, newBinding: true}); // Define a new binding for the arguments given to the function // eslint-disable-next-line prefer-rest-params - setInLexicalEnvironment({env: localLexicalEnvironment, path: "arguments", value: arguments, newBinding: true, reporting, node}); + setInLexicalEnvironment({...nextOptions, path: "arguments", value: arguments, newBinding: true}); if (this != null) { - setInLexicalEnvironment({env: localLexicalEnvironment, path: THIS_SYMBOL, value: this, newBinding: true, reporting, node}); + setInLexicalEnvironment({...nextOptions, path: THIS_SYMBOL, value: this, newBinding: true}); // Set the 'super' binding, depending on whether or not we're inside a static context setInLexicalEnvironment({ - env: localLexicalEnvironment, + ...nextOptions, path: SUPER_SYMBOL, value: isStatic ? Object.getPrototypeOf(this) : Object.getPrototypeOf((this as CallableFunction).constructor).prototype, - newBinding: true, - reporting, - node + newBinding: true }); } // If the body is a block, evaluate it as a statement if (node.body == null) return; - evaluate.statement(node.body, localLexicalEnvironment); + evaluate.statement(node.body, nextOptions); + + if (getCurrentError() != null) { + return; + } + // If a 'return' has occurred within the block, pop the Stack and return that value if (pathInLexicalEnvironmentEquals(node, localLexicalEnvironment, true, RETURN_SYMBOL)) { return stack.pop(); diff --git a/src/interpreter/evaluator/evaluate-identifier.ts b/src/interpreter/evaluator/evaluate-identifier.ts index 96e7c11..1ad7a8f 100644 --- a/src/interpreter/evaluator/evaluate-identifier.ts +++ b/src/interpreter/evaluator/evaluate-identifier.ts @@ -13,7 +13,7 @@ import {isTypescriptNode} from "../util/node/is-node.js"; */ export function evaluateIdentifier(options: EvaluatorOptions): Literal { - const {node, environment, typeChecker, evaluate, stack, logger, reporting, typescript, statementTraversalStack} = options; + const {node, environment, typeChecker, evaluate, stack, logger, reporting, typescript, throwError, getCurrentError} = options; // Otherwise, try to resolve it. Maybe it exists in the environment already? const environmentMatch = getFromLexicalEnvironment(node, environment, node.text); @@ -39,28 +39,35 @@ export function evaluateIdentifier(options: EvaluatorOptions(node.parent, node.text, options as EvaluatorOptions); + if (getCurrentError() != null) { + return; + } + if (isTypescriptNode(result) && !typescript.isIdentifier(result)) { valueDeclaration = result; } else if (result != null) { // Bind the value placed on the top of the stack to the local environment - setInLexicalEnvironment({env: environment, path: node.text, value: result, reporting, node: node}); + setInLexicalEnvironment({...options, path: node.text, value: result}); logger.logBinding(node.text, result, `Discovered declaration value`); return result; } } - // If it has a value declaration, go forward with that one if (valueDeclaration != null) { if (valueDeclaration.getSourceFile().isDeclarationFile) { const implementation = getImplementationForDeclarationWithinDeclarationFile({...options, node: valueDeclaration}); + + if (getCurrentError() != null) { + return; + } + // Bind the value placed on the top of the stack to the local environment - setInLexicalEnvironment({env: environment, path: node.text, value: implementation, reporting, node: valueDeclaration}); + setInLexicalEnvironment({environment, path: node.text, value: implementation, reporting, node: valueDeclaration}); logger.logBinding( node.text, implementation, @@ -77,22 +84,27 @@ export function evaluateIdentifier(options: EvaluatorOptions): void { - const expressionValue = evaluate.expression(node.expression, environment, statementTraversalStack); +export function evaluateIfStatement({node, evaluate, ...options}: EvaluatorOptions): void | EvaluationError { + const {getCurrentError} = options; + + const expressionValue = evaluate.expression(node.expression, options); + + if (getCurrentError() != null) { + return; + } // We have to perform a loose boolean expression here to conform with actual spec behavior // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions if (expressionValue) { // Proceed with the truthy branch - evaluate.statement(node.thenStatement, environment); + evaluate.statement(node.thenStatement, options); + + if (getCurrentError() != null) { + return; + } } // Proceed with the falsy branch else if (node.elseStatement != null) { - return evaluate.statement(node.elseStatement, environment); + return evaluate.statement(node.elseStatement, options); } } diff --git a/src/interpreter/evaluator/evaluate-import-clause.ts b/src/interpreter/evaluator/evaluate-import-clause.ts index dcceaad..60fc6d9 100644 --- a/src/interpreter/evaluator/evaluate-import-clause.ts +++ b/src/interpreter/evaluator/evaluate-import-clause.ts @@ -6,18 +6,27 @@ import {TS} from "../../type/ts.js"; * It will only initialize the bindings inside the lexical environment, but not resolve them, since we rely on the TypeChecker to resolve symbols across SourceFiles, * rather than manually parsing and resolving imports/exports */ -export function evaluateImportClause({node, evaluate, environment, statementTraversalStack}: EvaluatorOptions): void { +export function evaluateImportClause({node, evaluate, ...options}: EvaluatorOptions): void { + const {getCurrentError} = options; if (node.name != null) { - evaluate.declaration(node.name, environment, statementTraversalStack); + evaluate.declaration(node.name, options); + + if (getCurrentError() != null) { + return; + } } if (node.namedBindings != null) { if ("elements" in node.namedBindings) { for (const importSpecifier of node.namedBindings.elements) { - evaluate.declaration(importSpecifier, environment, statementTraversalStack); + evaluate.declaration(importSpecifier, options); + + if (getCurrentError() != null) { + return; + } } } else { - evaluate.declaration(node.namedBindings.name, environment, statementTraversalStack); + evaluate.declaration(node.namedBindings.name, options); } } } diff --git a/src/interpreter/evaluator/evaluate-import-declaration.ts b/src/interpreter/evaluator/evaluate-import-declaration.ts index e2ea9bf..733381e 100644 --- a/src/interpreter/evaluator/evaluate-import-declaration.ts +++ b/src/interpreter/evaluator/evaluate-import-declaration.ts @@ -4,7 +4,7 @@ import {TS} from "../../type/ts.js"; /** * Evaluates, or attempts to evaluate, an ImportDeclaration (which is actually a Statement). */ -export function evaluateImportDeclaration({node, evaluate, environment, statementTraversalStack}: EvaluatorOptions): void { +export function evaluateImportDeclaration({node, evaluate, ...options}: EvaluatorOptions): void { if (node.importClause == null) return; - evaluate.declaration(node.importClause, environment, statementTraversalStack); + evaluate.declaration(node.importClause, options); } diff --git a/src/interpreter/evaluator/evaluate-import-specifier.ts b/src/interpreter/evaluator/evaluate-import-specifier.ts index a6d0cf6..fa456e8 100644 --- a/src/interpreter/evaluator/evaluate-import-specifier.ts +++ b/src/interpreter/evaluator/evaluate-import-specifier.ts @@ -6,6 +6,6 @@ import {TS} from "../../type/ts.js"; * It will only initialize the bindings inside the lexical environment, but not resolve them, since we rely on the TypeChecker to resolve symbols across SourceFiles, * rather than manually parsing and resolving imports/exports */ -export function evaluateImportSpecifier({node, evaluate, environment, statementTraversalStack}: EvaluatorOptions): void { - evaluate.declaration(node.propertyName ?? node.name, environment, statementTraversalStack); +export function evaluateImportSpecifier({node, evaluate, ...options}: EvaluatorOptions): void { + evaluate.declaration(node.propertyName ?? node.name, options); } diff --git a/src/interpreter/evaluator/evaluate-meta-property.ts b/src/interpreter/evaluator/evaluate-meta-property.ts index 21e469d..d4617b5 100644 --- a/src/interpreter/evaluator/evaluate-meta-property.ts +++ b/src/interpreter/evaluator/evaluate-meta-property.ts @@ -7,14 +7,14 @@ import {UnexpectedSyntaxError} from "../error/unexpected-syntax-error/unexpected /** * Evaluates, or attempts to evaluate, a MetaProperty. */ -export function evaluateMetaProperty({node, typescript, environment}: EvaluatorOptions): Literal | void { +export function evaluateMetaProperty({node, typescript, throwError, environment}: EvaluatorOptions): Literal | void { switch (node.keywordToken) { case typescript.SyntaxKind.NewKeyword: { switch (node.name.text) { case "target": return getFromLexicalEnvironment(node, environment, "[[NewTarget]]")?.literal; default: - throw new UnexpectedSyntaxError({node: node.name}); + return throwError(new UnexpectedSyntaxError({node: node.name, environment})); } } @@ -23,7 +23,7 @@ export function evaluateMetaProperty({node, typescript, environment}: EvaluatorO case "meta": return getFromLexicalEnvironment(node, environment, "import.meta")?.literal; default: - throw new UnexpectedSyntaxError({node: node.name}); + return throwError(new UnexpectedSyntaxError({node: node.name, environment})); } } } diff --git a/src/interpreter/evaluator/evaluate-method-declaration.ts b/src/interpreter/evaluator/evaluate-method-declaration.ts index b7d7113..bf3ce2b 100644 --- a/src/interpreter/evaluator/evaluate-method-declaration.ts +++ b/src/interpreter/evaluator/evaluate-method-declaration.ts @@ -14,17 +14,31 @@ import {TS} from "../../type/ts.js"; * Evaluates, or attempts to evaluate, a MethodDeclaration, before setting it on the given parent */ export function evaluateMethodDeclaration(options: EvaluatorOptions, parent?: IndexLiteral): void { - const {node, environment, evaluate, stack, statementTraversalStack, reporting, typescript} = options; - const nameResult = evaluate.nodeWithValue(node.name, environment, statementTraversalStack) as IndexLiteralKey; + const {node, environment, evaluate, stack, typescript, getCurrentError} = options; + const nameResult = evaluate.nodeWithValue(node.name, options) as IndexLiteralKey; + + if (getCurrentError() != null) { + return; + } + const isStatic = inStaticContext(node, typescript); if (parent == null) { let updatedParent: CallableFunction & IndexLiteral; if (typescript.isClassLike(node.parent)) { - evaluate.declaration(node.parent, environment, statementTraversalStack); + evaluate.declaration(node.parent, options); + + if (getCurrentError() != null) { + return; + } + updatedParent = stack.pop() as CallableFunction & IndexLiteral; } else { - updatedParent = evaluate.expression(node.parent, environment, statementTraversalStack) as CallableFunction & IndexLiteral; + updatedParent = evaluate.expression(node.parent, options) as CallableFunction & IndexLiteral; + + if (getCurrentError() != null) { + return; + } } stack.push(isStatic ? updatedParent[nameResult] : updatedParent.prototype[nameResult]); return; @@ -34,25 +48,24 @@ export function evaluateMethodDeclaration(options: EvaluatorOptions): void { - options.stack.push(getImplementationForDeclarationWithinDeclarationFile(options)); + const {getCurrentError, stack} = options; + const result = getImplementationForDeclarationWithinDeclarationFile(options); + + if (getCurrentError() != null) { + return; + } + stack.push(result); } diff --git a/src/interpreter/evaluator/evaluate-namespace-import.ts b/src/interpreter/evaluator/evaluate-namespace-import.ts index 3389129..89cef89 100644 --- a/src/interpreter/evaluator/evaluate-namespace-import.ts +++ b/src/interpreter/evaluator/evaluate-namespace-import.ts @@ -6,6 +6,6 @@ import {TS} from "../../type/ts.js"; * It will only initialize the bindings inside the lexical environment, but not resolve them, since we rely on the TypeChecker to resolve symbols across SourceFiles, * rather than manually parsing and resolving imports/exports */ -export function evaluateNamespaceImport({node, evaluate, environment, statementTraversalStack}: EvaluatorOptions): void { - evaluate.declaration(node.name, environment, statementTraversalStack); +export function evaluateNamespaceImport({node, evaluate, ...options}: EvaluatorOptions): void { + evaluate.declaration(node.name, options); } diff --git a/src/interpreter/evaluator/evaluate-new-expression.ts b/src/interpreter/evaluator/evaluate-new-expression.ts index 1d38aca..e1b5c1a 100644 --- a/src/interpreter/evaluator/evaluate-new-expression.ts +++ b/src/interpreter/evaluator/evaluate-new-expression.ts @@ -1,27 +1,36 @@ import {EvaluatorOptions} from "./evaluator-options.js"; import {Literal} from "../literal/literal.js"; import {TS} from "../../type/ts.js"; -import { setInLexicalEnvironment } from "../lexical-environment/lexical-environment.js"; +import {setInLexicalEnvironment} from "../lexical-environment/lexical-environment.js"; +import {maybeThrow} from "../error/evaluation-error/evaluation-error-intent.js"; /** * Evaluates, or attempts to evaluate, a NewExpression */ -export function evaluateNewExpression({node, environment, evaluate, statementTraversalStack, reporting}: EvaluatorOptions): Literal { +export function evaluateNewExpression({node, evaluate, ...options}: EvaluatorOptions): Literal { + const {getCurrentError} = options; const evaluatedArgs: Literal[] = []; if (node.arguments != null) { for (let i = 0; i < node.arguments.length; i++) { - evaluatedArgs[i] = evaluate.expression(node.arguments[i], environment, statementTraversalStack); + evaluatedArgs[i] = evaluate.expression(node.arguments[i], options); + if (getCurrentError() != null) { + return; + } } } // Evaluate the expression - const expressionResult = evaluate.expression(node.expression, environment, statementTraversalStack) as new (...args: Literal[]) => Literal; + const expressionResult = evaluate.expression(node.expression, options) as new (...args: Literal[]) => Literal; + + if (getCurrentError() != null) { + return; + } // If the expression evaluated to a function, mark it as the [[NewTarget]], as per https://tc39.es/ecma262/multipage/executable-code-and-execution-contexts.html#sec-getnewtarget if (typeof expressionResult === "function") { - setInLexicalEnvironment({env: environment, path: "[[NewTarget]]", value: expressionResult, newBinding: true, reporting, node}); + setInLexicalEnvironment({...options, node, path: "[[NewTarget]]", value: expressionResult, newBinding: true}); } - return new expressionResult(...evaluatedArgs); + return maybeThrow(node, options, new expressionResult(...evaluatedArgs)); } diff --git a/src/interpreter/evaluator/evaluate-node-with-argument.ts b/src/interpreter/evaluator/evaluate-node-with-argument.ts index 1d1ae29..798ed4e 100644 --- a/src/interpreter/evaluator/evaluate-node-with-argument.ts +++ b/src/interpreter/evaluator/evaluate-node-with-argument.ts @@ -31,46 +31,48 @@ export function evaluateNodeWithArgument(options: EvaluatorOptions, arg const {node, ...rest} = options; if (rest.typescript.isGetAccessorDeclaration(node)) { - return evaluateGetAccessorDeclaration({node, ...rest}, arg as IndexLiteral); + evaluateGetAccessorDeclaration({node, ...rest}, arg as IndexLiteral); } else if (rest.typescript.isSetAccessorDeclaration(node)) { - return evaluateSetAccessorDeclaration({node, ...rest}, arg as IndexLiteral); + evaluateSetAccessorDeclaration({node, ...rest}, arg as IndexLiteral); } else if (rest.typescript.isPropertyAssignment(node)) { - return evaluatePropertyAssignment({node, ...rest}, arg as IndexLiteral); + evaluatePropertyAssignment({node, ...rest}, arg as IndexLiteral); } else if (rest.typescript.isPropertyDeclaration(node)) { - return evaluatePropertyDeclaration({node, ...rest}, arg as IndexLiteral); + evaluatePropertyDeclaration({node, ...rest}, arg as IndexLiteral); } else if (rest.typescript.isParameter(node)) { - return evaluateParameterDeclaration({node, ...rest}, arg); + evaluateParameterDeclaration({node, ...rest}, arg); } else if (rest.typescript.isEnumMember(node)) { - return evaluateEnumMember({node, ...rest}, arg as IndexLiteral); + evaluateEnumMember({node, ...rest}, arg as IndexLiteral); } else if (rest.typescript.isShorthandPropertyAssignment(node)) { - return evaluateShorthandPropertyAssignment({node, ...rest}, arg as IndexLiteral); + evaluateShorthandPropertyAssignment({node, ...rest}, arg as IndexLiteral); } else if (rest.typescript.isDecorator(node)) { - return evaluateDecorator({node, ...rest}, arg as [IndexLiteral, string?]); + evaluateDecorator({node, ...rest}, arg as [IndexLiteral, string?]); } else if (rest.typescript.isSpreadAssignment(node)) { - return evaluateSpreadAssignment({node, ...rest}, arg as IndexLiteral); + evaluateSpreadAssignment({node, ...rest}, arg as IndexLiteral); } else if (rest.typescript.isMethodDeclaration(node)) { - return evaluateMethodDeclaration({node, ...rest}, arg as IndexLiteral); + evaluateMethodDeclaration({node, ...rest}, arg as IndexLiteral); } else if (rest.typescript.isArrayBindingPattern(node)) { - return evaluateArrayBindingPattern({node, ...rest}, arg as Iterable); + evaluateArrayBindingPattern({node, ...rest}, arg as Iterable); } else if (rest.typescript.isBindingElement(node)) { - return evaluateBindingElement({node, ...rest}, arg); + evaluateBindingElement({node, ...rest}, arg); } else if (rest.typescript.isObjectBindingPattern(node)) { - return evaluateObjectBindingPattern({node, ...rest}, arg); + evaluateObjectBindingPattern({node, ...rest}, arg); } else if (rest.typescript.isVariableDeclaration(node)) { - return evaluateVariableDeclaration({node, ...rest}, arg); + evaluateVariableDeclaration({node, ...rest}, arg); } else if (rest.typescript.isCaseBlock(node)) { - return evaluateCaseBlock({node, ...rest}, arg); + evaluateCaseBlock({node, ...rest}, arg); } else if (rest.typescript.isCaseClause(node)) { - return evaluateCaseClause({node, ...rest}, arg); + evaluateCaseClause({node, ...rest}, arg); } else if (rest.typescript.isDefaultClause(node)) { - return evaluateDefaultClause({node, ...rest}); + evaluateDefaultClause({node, ...rest}); } else if (rest.typescript.isCatchClause(node)) { - return evaluateCatchClause({node, ...rest}, arg as Error); + evaluateCatchClause({node, ...rest}, arg as Error); } else if (rest.typescript.isBindingName(node)) { - return evaluateBindingName({node, ...rest}, arg); + evaluateBindingName({node, ...rest}, arg); } else if (rest.typescript.isOmittedExpression(node)) { - return evaluateOmittedExpression({node, ...rest}); + evaluateOmittedExpression({node, ...rest}); + } else if (options.getCurrentError() != null) { + return; + } else { + rest.throwError(new UnexpectedNodeError({node, environment: rest.environment, typescript: rest.typescript})); } - - throw new UnexpectedNodeError({node, typescript: rest.typescript}); } diff --git a/src/interpreter/evaluator/evaluate-node-with-value.ts b/src/interpreter/evaluator/evaluate-node-with-value.ts index cfc3b2e..093e94a 100644 --- a/src/interpreter/evaluator/evaluate-node-with-value.ts +++ b/src/interpreter/evaluator/evaluate-node-with-value.ts @@ -16,5 +16,5 @@ export function evaluateNodeWithValue(options: EvaluatorOptions): return evaluatePropertyName({node, ...rest}); } - throw new UnexpectedNodeError({node, typescript: options.typescript}); + return options.throwError(new UnexpectedNodeError({node, environment: options.environment, typescript: options.typescript})); } diff --git a/src/interpreter/evaluator/evaluate-node.ts b/src/interpreter/evaluator/evaluate-node.ts index eddd6ca..c527979 100644 --- a/src/interpreter/evaluator/evaluate-node.ts +++ b/src/interpreter/evaluator/evaluate-node.ts @@ -69,7 +69,7 @@ import {evaluateInterfaceDeclaration} from "./evaluate-interface-declaration.js" import {evaluateImportClause} from "./evaluate-import-clause.js"; import {evaluateImportSpecifier} from "./evaluate-import-specifier.js"; import {evaluateNamespaceImport} from "./evaluate-namespace-import.js"; -import { evaluateMetaProperty } from "./evaluate-meta-property.js"; +import {evaluateMetaProperty} from "./evaluate-meta-property.js"; /** * Will get a literal value for the given Node. If it doesn't succeed, the value will be 'undefined' @@ -207,7 +207,9 @@ export function evaluateNode({node, ...rest}: EvaluatorOptions): unknow return evaluateTypeAliasDeclaration({node, ...rest}); } else if (rest.typescript.isInterfaceDeclaration(node)) { return evaluateInterfaceDeclaration({node, ...rest}); + } else if (rest.getCurrentError() != null) { + return; + } else { + return rest.throwError(new UnexpectedNodeError({node, environment: rest.environment, typescript: rest.typescript})); } - - throw new UnexpectedNodeError({node, typescript: rest.typescript}); } diff --git a/src/interpreter/evaluator/evaluate-non-null-expression.ts b/src/interpreter/evaluator/evaluate-non-null-expression.ts index bd70ce2..df112a7 100644 --- a/src/interpreter/evaluator/evaluate-non-null-expression.ts +++ b/src/interpreter/evaluator/evaluate-non-null-expression.ts @@ -5,6 +5,6 @@ import {TS} from "../../type/ts.js"; /** * Evaluates, or attempts to evaluate, a NonNullExpression */ -export function evaluateNonNullExpression({node, environment, evaluate, statementTraversalStack}: EvaluatorOptions): Literal { - return evaluate.expression(node.expression, environment, statementTraversalStack); +export function evaluateNonNullExpression({node, evaluate, ...options}: EvaluatorOptions): Literal { + return evaluate.expression(node.expression, options); } diff --git a/src/interpreter/evaluator/evaluate-object-binding-pattern.ts b/src/interpreter/evaluator/evaluate-object-binding-pattern.ts index 6b3973f..b27db5f 100644 --- a/src/interpreter/evaluator/evaluate-object-binding-pattern.ts +++ b/src/interpreter/evaluator/evaluate-object-binding-pattern.ts @@ -5,8 +5,12 @@ import {TS} from "../../type/ts.js"; /** * Evaluates, or attempts to evaluate, an ObjectBindingPattern, based on an initializer */ -export function evaluateObjectBindingPattern({node, environment, evaluate, statementTraversalStack}: EvaluatorOptions, rightHandValue: Literal): void { +export function evaluateObjectBindingPattern({node, evaluate, ...options}: EvaluatorOptions, rightHandValue: Literal): void { for (const element of node.elements) { - evaluate.nodeWithArgument(element, environment, rightHandValue, statementTraversalStack); + evaluate.nodeWithArgument(element, rightHandValue, options); + + if (options.getCurrentError() != null) { + return; + } } } diff --git a/src/interpreter/evaluator/evaluate-object-literal-expression.ts b/src/interpreter/evaluator/evaluate-object-literal-expression.ts index 914a335..ec9464c 100644 --- a/src/interpreter/evaluator/evaluate-object-literal-expression.ts +++ b/src/interpreter/evaluator/evaluate-object-literal-expression.ts @@ -7,17 +7,22 @@ import {TS} from "../../type/ts.js"; /** * Evaluates, or attempts to evaluate, a ObjectLiteralExpression */ -export function evaluateObjectLiteralExpression({node, evaluate, environment, reporting, statementTraversalStack}: EvaluatorOptions): Literal { +export function evaluateObjectLiteralExpression(options: EvaluatorOptions): Literal { + const {node, evaluate, environment, getCurrentError} = options; // Create a new ObjectLiteral based on the Object implementation from the Realm since this must not be the same as in the parent executing context // Otherwise, instanceof checks would fail const objectCtor = getFromLexicalEnvironment(node, environment, "Object")!.literal as ObjectConstructor; const value: IndexLiteral = objectCtor.create(objectCtor.prototype); // Mark the object as the 'this' value of the scope - setInLexicalEnvironment({env: environment, path: THIS_SYMBOL, value, newBinding: true, reporting, node}); + setInLexicalEnvironment({...options, path: THIS_SYMBOL, value, newBinding: true}); for (const property of node.properties) { - evaluate.nodeWithArgument(property, environment, value, statementTraversalStack); + evaluate.nodeWithArgument(property, value, options); + + if (getCurrentError() != null) { + return; + } } return value; diff --git a/src/interpreter/evaluator/evaluate-parameter-declaration.ts b/src/interpreter/evaluator/evaluate-parameter-declaration.ts index 94a8b80..76d0b34 100644 --- a/src/interpreter/evaluator/evaluate-parameter-declaration.ts +++ b/src/interpreter/evaluator/evaluate-parameter-declaration.ts @@ -5,13 +5,14 @@ import {TS} from "../../type/ts.js"; /** * Evaluates, or attempts to evaluate, a ParameterDeclaration */ -export function evaluateParameterDeclaration( - {node, environment, evaluate, statementTraversalStack, logger}: EvaluatorOptions, - boundArgument: Literal -): void { +export function evaluateParameterDeclaration({node, evaluate, logger, ...options}: EvaluatorOptions, boundArgument: Literal): void { // Use the bound argument if it is given unless it is nullable and the node itself has an initializer - const boundValue = boundArgument != null || node.initializer === undefined ? boundArgument : evaluate.expression(node.initializer, environment, statementTraversalStack); + const boundValue = boundArgument != null || node.initializer === undefined ? boundArgument : evaluate.expression(node.initializer, options); + + if (options.getCurrentError() != null) { + return; + } logger.logBinding(node.name.getText(), boundValue, "evaluateParameterDeclaration"); - evaluate.nodeWithArgument(node.name, environment, boundValue, statementTraversalStack); + evaluate.nodeWithArgument(node.name, boundValue, options); } diff --git a/src/interpreter/evaluator/evaluate-parameter-declarations.ts b/src/interpreter/evaluator/evaluate-parameter-declarations.ts index 442f5e1..4a86348 100644 --- a/src/interpreter/evaluator/evaluate-parameter-declarations.ts +++ b/src/interpreter/evaluator/evaluate-parameter-declarations.ts @@ -7,11 +7,8 @@ import {TS} from "../../type/ts.js"; /** * Evaluates, or attempts to evaluate, a NodeArray of ParameterDeclarations */ -export function evaluateParameterDeclarations( - {node, evaluate, environment, statementTraversalStack, typescript}: EvaluatorOptions>, - boundArguments: Literal[], - context?: IndexLiteral -): void { +export function evaluateParameterDeclarations(options: EvaluatorOptions>, boundArguments: Literal[], context?: IndexLiteral): void { + const {node, evaluate, environment, typescript, getCurrentError} = options; // 'this' is a special parameter which is removed from the emitted results const parameters = node.filter(param => !(typescript.isIdentifier(param.name) && param.name.text === "this")); @@ -20,11 +17,20 @@ export function evaluateParameterDeclarations( // It it is a spread element, it should receive all arguments from the current index. if (parameter.dotDotDotToken != null) { - evaluate.nodeWithArgument(parameter, environment, boundArguments.slice(i), statementTraversalStack); + evaluate.nodeWithArgument(parameter, boundArguments.slice(i), options); + + if (getCurrentError() != null) { + return; + } + // Spread elements must always be the last parameter break; } else { - evaluate.nodeWithArgument(parameter, environment, boundArguments[i], statementTraversalStack); + evaluate.nodeWithArgument(parameter, boundArguments[i], options); + + if (getCurrentError() != null) { + return; + } // If a context is given, and if a [public|protected|private] keyword is in front of the parameter, the initialized value should be // set on the context as an instance property diff --git a/src/interpreter/evaluator/evaluate-parenthesized-expression.ts b/src/interpreter/evaluator/evaluate-parenthesized-expression.ts index 1d4793c..bd3a6c4 100644 --- a/src/interpreter/evaluator/evaluate-parenthesized-expression.ts +++ b/src/interpreter/evaluator/evaluate-parenthesized-expression.ts @@ -5,6 +5,6 @@ import {TS} from "../../type/ts.js"; /** * Evaluates, or attempts to evaluate, a ParenthesizedExpression */ -export function evaluateParenthesizedExpression({node, environment, evaluate, statementTraversalStack}: EvaluatorOptions): Literal { - return evaluate.expression(node.expression, environment, statementTraversalStack); +export function evaluateParenthesizedExpression({node, evaluate, ...options}: EvaluatorOptions): Literal { + return evaluate.expression(node.expression, options); } diff --git a/src/interpreter/evaluator/evaluate-postfix-unary-expression.ts b/src/interpreter/evaluator/evaluate-postfix-unary-expression.ts index 5ed05cb..6851413 100644 --- a/src/interpreter/evaluator/evaluate-postfix-unary-expression.ts +++ b/src/interpreter/evaluator/evaluate-postfix-unary-expression.ts @@ -7,22 +7,17 @@ import {TS} from "../../type/ts.js"; /** * Evaluates, or attempts to evaluate, a PostfixUnaryExpression */ -export function evaluatePostfixUnaryExpression({ - node, - evaluate, - environment, - reporting, - typescript, - statementTraversalStack -}: EvaluatorOptions): Literal { +export function evaluatePostfixUnaryExpression(options: EvaluatorOptions): Literal { + const {evaluate, node, environment, typescript, throwError, reporting} = options; + // Make sure to evaluate the operand to ensure that it is found in the lexical environment - evaluate.expression(node.operand, environment, statementTraversalStack); + evaluate.expression(node.operand, options); switch (node.operator) { case typescript.SyntaxKind.PlusPlusToken: { // If the Operand isn't an identifier, this will be a SyntaxError if (!typescript.isIdentifier(node.operand) && !typescript.isPrivateIdentifier?.(node.operand)) { - throw new UnexpectedNodeError({node: node.operand, typescript}); + return throwError(new UnexpectedNodeError({node: node.operand, environment, typescript})); } // Find the value associated with the identifier within the environment. @@ -38,7 +33,7 @@ export function evaluatePostfixUnaryExpression({ case typescript.SyntaxKind.MinusMinusToken: { // If the Operand isn't an identifier, this will be a SyntaxError if (!typescript.isIdentifier(node.operand) && !typescript.isPrivateIdentifier?.(node.operand)) { - throw new UnexpectedNodeError({node: node.operand, typescript}); + return throwError(new UnexpectedNodeError({node: node.operand, environment, typescript})); } // Find the value associated with the identifier within the environment. diff --git a/src/interpreter/evaluator/evaluate-prefix-unary-expression.ts b/src/interpreter/evaluator/evaluate-prefix-unary-expression.ts index 289a13d..4a9da9e 100644 --- a/src/interpreter/evaluator/evaluate-prefix-unary-expression.ts +++ b/src/interpreter/evaluator/evaluate-prefix-unary-expression.ts @@ -7,8 +7,13 @@ import {TS} from "../../type/ts.js"; /** * Evaluates, or attempts to evaluate, a PrefixUnaryExpression */ -export function evaluatePrefixUnaryExpression({node, environment, evaluate, reporting, typescript, statementTraversalStack}: EvaluatorOptions): Literal { - const operandValue = evaluate.expression(node.operand, environment, statementTraversalStack) as number; +export function evaluatePrefixUnaryExpression(options: EvaluatorOptions): Literal { + const {node, environment, evaluate, reporting, typescript, throwError, getCurrentError} = options; + const operandValue = evaluate.expression(node.operand, options) as number; + + if (getCurrentError() != null) { + return; + } switch (node.operator) { case typescript.SyntaxKind.PlusToken: { @@ -31,7 +36,7 @@ export function evaluatePrefixUnaryExpression({node, environment, evaluate, repo case typescript.SyntaxKind.PlusPlusToken: { // If the Operand isn't an identifier, this will be a SyntaxError if (!typescript.isIdentifier(node.operand) && !typescript.isPrivateIdentifier?.(node.operand)) { - throw new UnexpectedNodeError({node: node.operand, typescript}); + return throwError(new UnexpectedNodeError({node: node.operand, environment, typescript})); } // Find the value associated with the identifier within the environment. @@ -48,7 +53,7 @@ export function evaluatePrefixUnaryExpression({node, environment, evaluate, repo case typescript.SyntaxKind.MinusMinusToken: { // If the Operand isn't an identifier, this will be a SyntaxError if (!typescript.isIdentifier(node.operand) && !typescript.isPrivateIdentifier?.(node.operand)) { - throw new UnexpectedNodeError({node: node.operand, typescript}); + return throwError(new UnexpectedNodeError({node: node.operand, environment, typescript})); } // Find the value associated with the identifier within the environment. diff --git a/src/interpreter/evaluator/evaluate-property-access-expression.ts b/src/interpreter/evaluator/evaluate-property-access-expression.ts index 975e1e5..b90a0b1 100644 --- a/src/interpreter/evaluator/evaluate-property-access-expression.ts +++ b/src/interpreter/evaluator/evaluate-property-access-expression.ts @@ -2,12 +2,18 @@ import {EvaluatorOptions} from "./evaluator-options.js"; import {IndexLiteral, LAZY_CALL_FLAG, LazyCall, Literal, LiteralFlagKind} from "../literal/literal.js"; import {isBindCallApply} from "../util/function/is-bind-call-apply.js"; import {TS} from "../../type/ts.js"; +import {maybeThrow} from "../error/evaluation-error/evaluation-error-intent.js"; /** * Evaluates, or attempts to evaluate, a PropertyAccessExpression */ -export function evaluatePropertyAccessExpression({node, environment, evaluate, typescript, statementTraversalStack}: EvaluatorOptions): Literal { - const expressionResult = evaluate.expression(node.expression, environment, statementTraversalStack) as IndexLiteral; +export function evaluatePropertyAccessExpression(options: EvaluatorOptions): Literal { + const {evaluate, node, statementTraversalStack, environment, typescript, getCurrentError} = options; + const expressionResult = evaluate.expression(node.expression, options) as IndexLiteral; + + if (getCurrentError() != null) { + return; + } const match = node.questionDotToken != null && expressionResult == null @@ -21,10 +27,14 @@ export function evaluatePropertyAccessExpression({node, environment, evaluate, t return { [LAZY_CALL_FLAG]: LiteralFlagKind.CALL, invoke: (overriddenThis: Record | CallableFunction | undefined, ...args: Literal[]) => - overriddenThis != null && !isBindCallApply(match, environment) - ? // eslint-disable-next-line @typescript-eslint/ban-types - (expressionResult[node.name.text] as Function).call(overriddenThis, ...args) - : (expressionResult[node.name.text] as CallableFunction)(...args) + maybeThrow( + node, + options, + overriddenThis != null && !isBindCallApply(match, environment) + ? // eslint-disable-next-line @typescript-eslint/ban-types + (expressionResult[node.name.text] as Function).call(overriddenThis, ...args) + : (expressionResult[node.name.text] as CallableFunction)(...args) + ) } as LazyCall; } else return match; } diff --git a/src/interpreter/evaluator/evaluate-property-assignment.ts b/src/interpreter/evaluator/evaluate-property-assignment.ts index 3b9374e..42bdf1c 100644 --- a/src/interpreter/evaluator/evaluate-property-assignment.ts +++ b/src/interpreter/evaluator/evaluate-property-assignment.ts @@ -5,10 +5,18 @@ import {TS} from "../../type/ts.js"; /** * Evaluates, or attempts to evaluate, a PropertyAssignment, before applying it on the given parent */ -export function evaluatePropertyAssignment({environment, node, evaluate, statementTraversalStack}: EvaluatorOptions, parent: IndexLiteral): void { - const initializer = evaluate.expression(node.initializer, environment, statementTraversalStack); +export function evaluatePropertyAssignment({node, evaluate, ...options}: EvaluatorOptions, parent: IndexLiteral): void { + const initializer = evaluate.expression(node.initializer, options); + + if (options.getCurrentError() != null) { + return; + } // Compute the property name - const propertyNameResult = evaluate.nodeWithValue(node.name, environment, statementTraversalStack) as IndexLiteralKey; + const propertyNameResult = evaluate.nodeWithValue(node.name, options) as IndexLiteralKey; + + if (options.getCurrentError() != null) { + return; + } parent[propertyNameResult] = initializer; } diff --git a/src/interpreter/evaluator/evaluate-property-declaration.ts b/src/interpreter/evaluator/evaluate-property-declaration.ts index 0e0ec7f..a42ab79 100644 --- a/src/interpreter/evaluator/evaluate-property-declaration.ts +++ b/src/interpreter/evaluator/evaluate-property-declaration.ts @@ -6,26 +6,43 @@ import {TS} from "../../type/ts.js"; /** * Evaluates, or attempts to evaluate, a PropertyDeclaration, before applying it on the given parent */ -export function evaluatePropertyDeclaration( - {environment, node, evaluate, statementTraversalStack, typescript, stack}: EvaluatorOptions, - parent?: IndexLiteral -): void { +export function evaluatePropertyDeclaration({node, evaluate, typescript, stack, ...options}: EvaluatorOptions, parent?: IndexLiteral): void { + const {getCurrentError} = options; + // Compute the property name - const propertyNameResult = evaluate.nodeWithValue(node.name, environment, statementTraversalStack) as IndexLiteralKey; + const propertyNameResult = evaluate.nodeWithValue(node.name, options) as IndexLiteralKey; + + if (getCurrentError() != null) { + return; + } if (parent == null) { - evaluate.declaration(node.parent, environment, statementTraversalStack); + evaluate.declaration(node.parent, options); + + if (getCurrentError() != null) { + return; + } + const updatedParent = stack.pop() as CallableFunction & IndexLiteral; const isStatic = inStaticContext(node, typescript); stack.push(isStatic ? updatedParent[propertyNameResult] : updatedParent.prototype[propertyNameResult]); return; } - parent[propertyNameResult] = node.initializer == null ? undefined : evaluate.expression(node.initializer, environment, statementTraversalStack); + parent[propertyNameResult] = node.initializer == null ? undefined : evaluate.expression(node.initializer, options); + + if (getCurrentError() != null) { + return; + } if (node.decorators != null) { for (const decorator of node.decorators) { - evaluate.nodeWithArgument(decorator, environment, [parent, propertyNameResult], statementTraversalStack); + evaluate.nodeWithArgument(decorator, [parent, propertyNameResult], options); + + if (getCurrentError() != null) { + return; + } + // Pop the stack. We don't need the value it has left on the Stack stack.pop(); } diff --git a/src/interpreter/evaluator/evaluate-property-name.ts b/src/interpreter/evaluator/evaluate-property-name.ts index 5c6f335..8ee616c 100644 --- a/src/interpreter/evaluator/evaluate-property-name.ts +++ b/src/interpreter/evaluator/evaluate-property-name.ts @@ -5,12 +5,12 @@ import {TS} from "../../type/ts.js"; /** * Evaluates, or attempts to evaluate, a PropertyName */ -export function evaluatePropertyName({environment, node, evaluate, typescript, statementTraversalStack}: EvaluatorOptions): Literal { +export function evaluatePropertyName({node, evaluate, typescript, ...options}: EvaluatorOptions): Literal { return ( typescript.isComputedPropertyName(node) - ? evaluate.expression(node.expression, environment, statementTraversalStack) + ? evaluate.expression(node.expression, options) : typescript.isIdentifier(node) || typescript.isPrivateIdentifier?.(node) ? node.text - : evaluate.expression(node as TS.StringLiteral | TS.NumericLiteral, environment, statementTraversalStack) + : evaluate.expression(node as TS.StringLiteral | TS.NumericLiteral, options) ) as IndexLiteralKey; } diff --git a/src/interpreter/evaluator/evaluate-return-statement.ts b/src/interpreter/evaluator/evaluate-return-statement.ts index 64dbcc9..ac18fae 100644 --- a/src/interpreter/evaluator/evaluate-return-statement.ts +++ b/src/interpreter/evaluator/evaluate-return-statement.ts @@ -6,13 +6,19 @@ import {TS} from "../../type/ts.js"; /** * Evaluates, or attempts to evaluate, a ReturnStatement */ -export function evaluateReturnStatement({node, environment, evaluate, stack, reporting, statementTraversalStack}: EvaluatorOptions): void { - setInLexicalEnvironment({env: environment, path: RETURN_SYMBOL, value: true, reporting, node}); +export function evaluateReturnStatement({node, evaluate, stack, ...options}: EvaluatorOptions): void { + const {getCurrentError} = options; + setInLexicalEnvironment({...options, environment: options.environment, path: RETURN_SYMBOL, value: true, node}); // If it is a simple 'return', return undefined if (node.expression == null) { stack.push(undefined); } else { - stack.push(evaluate.expression(node.expression, environment, statementTraversalStack)); + const result = evaluate.expression(node.expression, options); + + if (getCurrentError() != null) { + return; + } + stack.push(result); } } diff --git a/src/interpreter/evaluator/evaluate-set-accessor-declaration.ts b/src/interpreter/evaluator/evaluate-set-accessor-declaration.ts index 9058b2c..00195f8 100644 --- a/src/interpreter/evaluator/evaluate-set-accessor-declaration.ts +++ b/src/interpreter/evaluator/evaluate-set-accessor-declaration.ts @@ -13,9 +13,14 @@ import {TS} from "../../type/ts.js"; * Evaluates, or attempts to evaluate, a SetAccessorDeclaration, before setting it on the given parent */ export function evaluateSetAccessorDeclaration(options: EvaluatorOptions, parent: IndexLiteral): void { - const {node, environment, evaluate, statementTraversalStack, reporting, typescript} = options; + const {node, environment, evaluate, typescript, getCurrentError} = options; + + const nameResult = evaluate.nodeWithValue(node.name, options) as IndexLiteralKey; + + if (getCurrentError() != null) { + return; + } - const nameResult = evaluate.nodeWithValue(node.name, environment, statementTraversalStack) as IndexLiteralKey; const isStatic = inStaticContext(node, typescript); /** @@ -24,41 +29,43 @@ export function evaluateSetAccessorDeclaration(options: EvaluatorOptions `[Set: ${nameResult}]`; diff --git a/src/interpreter/evaluator/evaluate-shorthand-property-assignment.ts b/src/interpreter/evaluator/evaluate-shorthand-property-assignment.ts index 6388776..5f37a80 100644 --- a/src/interpreter/evaluator/evaluate-shorthand-property-assignment.ts +++ b/src/interpreter/evaluator/evaluate-shorthand-property-assignment.ts @@ -5,12 +5,14 @@ import {TS} from "../../type/ts.js"; /** * Evaluates, or attempts to evaluate, a ShorthandPropertyAssignment, before applying it on the given parent */ -export function evaluateShorthandPropertyAssignment( - {evaluate, statementTraversalStack, environment, node}: EvaluatorOptions, - parent: IndexLiteral -): void { +export function evaluateShorthandPropertyAssignment({node, evaluate, ...options}: EvaluatorOptions, parent: IndexLiteral): void { + const {getCurrentError} = options; const identifier = node.name.text; - const initializer = evaluate.expression(node.name, environment, statementTraversalStack); + const initializer = evaluate.expression(node.name, options); + + if (getCurrentError() != null) { + return; + } parent[identifier] = initializer; } diff --git a/src/interpreter/evaluator/evaluate-source-file-as-namespace-object.ts b/src/interpreter/evaluator/evaluate-source-file-as-namespace-object.ts index 8a08a81..463b78a 100644 --- a/src/interpreter/evaluator/evaluate-source-file-as-namespace-object.ts +++ b/src/interpreter/evaluator/evaluate-source-file-as-namespace-object.ts @@ -6,7 +6,8 @@ import {TS} from "../../type/ts.js"; /** * Evaluates, or attempts to evaluate, a SourceFile as a namespace object */ -export function evaluateSourceFileAsNamespaceObject({node, environment, evaluate, typeChecker, stack, statementTraversalStack}: EvaluatorOptions): void { +export function evaluateSourceFileAsNamespaceObject(options: EvaluatorOptions): void { + const {node, evaluate, environment, typeChecker, stack, getCurrentError} = options; // Create a new ObjectLiteral based on the Object implementation from the Realm since this must not be the same as in the parent executing context // Otherwise, instanceof checks would fail const objectCtor = getFromLexicalEnvironment(node, environment, "Object")!.literal as ObjectConstructor; @@ -20,7 +21,12 @@ export function evaluateSourceFileAsNamespaceObject({node, environment, evaluate const valueDeclaration = symbol.valueDeclaration; if (valueDeclaration == null) return; - evaluate.declaration(valueDeclaration, environment, statementTraversalStack); + evaluate.declaration(valueDeclaration, options); + + if (getCurrentError() != null) { + return; + } + namespaceObject[identifier] = stack.pop(); } } diff --git a/src/interpreter/evaluator/evaluate-spread-assignment.ts b/src/interpreter/evaluator/evaluate-spread-assignment.ts index 142307f..117dca2 100644 --- a/src/interpreter/evaluator/evaluate-spread-assignment.ts +++ b/src/interpreter/evaluator/evaluate-spread-assignment.ts @@ -5,7 +5,12 @@ import {TS} from "../../type/ts.js"; /** * Evaluates, or attempts to evaluate, a SpreadAssignment, before applying it on the given parent */ -export function evaluateSpreadAssignment({environment, node, evaluate, statementTraversalStack}: EvaluatorOptions, parent: IndexLiteral): void { - const entries = evaluate.expression(node.expression, environment, statementTraversalStack) as IndexLiteral; +export function evaluateSpreadAssignment({node, evaluate, ...options}: EvaluatorOptions, parent: IndexLiteral): void { + const entries = evaluate.expression(node.expression, options) as IndexLiteral; + + if (options.getCurrentError() != null) { + return; + } + Object.assign(parent, entries); } diff --git a/src/interpreter/evaluator/evaluate-spread-element.ts b/src/interpreter/evaluator/evaluate-spread-element.ts index cad2fc3..79c1370 100644 --- a/src/interpreter/evaluator/evaluate-spread-element.ts +++ b/src/interpreter/evaluator/evaluate-spread-element.ts @@ -5,6 +5,6 @@ import {TS} from "../../type/ts.js"; /** * Evaluates, or attempts to evaluate, a SpreadElement, before applying it on the given parent */ -export function evaluateSpreadElement({environment, node, evaluate, statementTraversalStack}: EvaluatorOptions): Literal[] { - return evaluate.expression(node.expression, environment, statementTraversalStack) as Literal[]; +export function evaluateSpreadElement({node, evaluate, ...options}: EvaluatorOptions): Literal[] { + return evaluate.expression(node.expression, options) as Literal[]; } diff --git a/src/interpreter/evaluator/evaluate-switch-statement.ts b/src/interpreter/evaluator/evaluate-switch-statement.ts index e9ef5b2..884a8f4 100644 --- a/src/interpreter/evaluator/evaluate-switch-statement.ts +++ b/src/interpreter/evaluator/evaluate-switch-statement.ts @@ -4,7 +4,11 @@ import {TS} from "../../type/ts.js"; /** * Evaluates, or attempts to evaluate, a SwitchStatement */ -export function evaluateSwitchStatement({node, evaluate, environment, statementTraversalStack}: EvaluatorOptions): void { - const expressionResult = evaluate.expression(node.expression, environment, statementTraversalStack); - evaluate.nodeWithArgument(node.caseBlock, environment, expressionResult, statementTraversalStack); +export function evaluateSwitchStatement({node, evaluate, ...options}: EvaluatorOptions): void { + const expressionResult = evaluate.expression(node.expression, options); + + if (options.getCurrentError() != null) { + return; + } + evaluate.nodeWithArgument(node.caseBlock, expressionResult, options); } diff --git a/src/interpreter/evaluator/evaluate-template-expression.ts b/src/interpreter/evaluator/evaluate-template-expression.ts index 47ebf1b..ace6264 100644 --- a/src/interpreter/evaluator/evaluate-template-expression.ts +++ b/src/interpreter/evaluator/evaluate-template-expression.ts @@ -5,11 +5,16 @@ import {TS} from "../../type/ts.js"; /** * Evaluates, or attempts to evaluate, a TemplateExpression */ -export function evaluateTemplateExpression({node, environment, evaluate, statementTraversalStack}: EvaluatorOptions): Literal { +export function evaluateTemplateExpression({node, evaluate, ...options}: EvaluatorOptions): Literal { let str = ""; str += node.head.text; for (const span of node.templateSpans) { - const expression = evaluate.expression(span.expression, environment, statementTraversalStack) as string; + const expression = evaluate.expression(span.expression, options) as string; + + if (options.getCurrentError() != null) { + return; + } + str += expression; str += span.literal.text; } diff --git a/src/interpreter/evaluator/evaluate-throw-statement.ts b/src/interpreter/evaluator/evaluate-throw-statement.ts index d4e27c5..e52484d 100644 --- a/src/interpreter/evaluator/evaluate-throw-statement.ts +++ b/src/interpreter/evaluator/evaluate-throw-statement.ts @@ -1,9 +1,17 @@ import {EvaluatorOptions} from "./evaluator-options.js"; import {TS} from "../../type/ts.js"; +import { EvaluationError } from "../error/evaluation-error/evaluation-error.js"; /** * Evaluates, or attempts to evaluate, a ThrowStatement */ -export function evaluateThrowStatement({node, environment, evaluate, statementTraversalStack}: EvaluatorOptions): void { - throw evaluate.expression(node.expression, environment, statementTraversalStack); +export function evaluateThrowStatement({node, evaluate, ...options}: EvaluatorOptions): void|EvaluationError { + const {getCurrentError, throwError} = options; + const result = evaluate.expression(node.expression, options) as EvaluationError; + + if (getCurrentError() != null) { + return; + } + + return throwError(result); } diff --git a/src/interpreter/evaluator/evaluate-try-statement.ts b/src/interpreter/evaluator/evaluate-try-statement.ts index 8360955..5705183 100644 --- a/src/interpreter/evaluator/evaluate-try-statement.ts +++ b/src/interpreter/evaluator/evaluate-try-statement.ts @@ -1,65 +1,82 @@ import {EvaluatorOptions} from "./evaluator-options.js"; import {MissingCatchOrFinallyAfterTryError} from "../error/missing-catch-or-finally-after-try-error/missing-catch-or-finally-after-try-error.js"; -import {clearBindingFromLexicalEnvironment, setInLexicalEnvironment} from "../lexical-environment/lexical-environment.js"; -import {TRY_SYMBOL} from "../util/try/try-symbol.js"; import {TS} from "../../type/ts.js"; +import {EvaluationError} from "../error/evaluation-error/evaluation-error.js"; /** * Evaluates, or attempts to evaluate, a TryStatement - * - * @param options - * @returns */ -export function evaluateTryStatement({node, evaluate, environment, reporting, statementTraversalStack}: EvaluatorOptions): void { +export function evaluateTryStatement(options: EvaluatorOptions): void | EvaluationError { + const {node, evaluate, environment, throwError} = options; + + let error: EvaluationError | undefined; + const executeTry = () => { - setInLexicalEnvironment({env: environment, reporting, newBinding: true, node, path: TRY_SYMBOL, value: true}); - // The Block will declare an environment of its own - evaluate.statement(node.tryBlock, environment); + try { + return evaluate.statement(node.tryBlock, { + ...options, + throwError: ex => { + error = ex; + return ex; + }, + getCurrentError: () => error + }); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } catch (ex: any) { + error = ex; + } }; const executeCatch = (ex: Error) => { - clearBindingFromLexicalEnvironment(environment, TRY_SYMBOL); // The CatchClause will declare an environment of its own - evaluate.nodeWithArgument(node.catchClause!, environment, ex, statementTraversalStack); + evaluate.nodeWithArgument(node.catchClause!, ex, options); }; const executeFinally = () => { - clearBindingFromLexicalEnvironment(environment, TRY_SYMBOL); + let finallyError: EvaluationError | undefined; + // The Block will declare an environment of its own - evaluate.statement(node.finallyBlock!, environment); + evaluate.statement(node.finallyBlock!, { + ...options, + throwError: ex => { + finallyError = ex; + // Also set it on the upper context + options.throwError(ex); + return ex; + }, + getCurrentError: () => finallyError + }); }; // A TryStatement must have either a catch or a finally block if (node.catchClause == null && node.finallyBlock == null) { - throw new MissingCatchOrFinallyAfterTryError({node}); + return throwError(new MissingCatchOrFinallyAfterTryError({node, environment})); } // Follows the form: try {...} catch {...} else if (node.catchClause != null && node.finallyBlock == null) { - try { - executeTry(); - } catch (ex) { - executeCatch(ex as Error); + executeTry(); + if (error != null) { + executeCatch(error); } } // Follows the form: try {...} catch {...} finally {...} else if (node.catchClause != null && node.finallyBlock != null) { - try { - executeTry(); - } catch (ex) { - executeCatch(ex as Error); - } finally { - executeFinally(); + executeTry(); + if (error != null) { + executeCatch(error); } + executeFinally(); } // Follows the form: try {...} finally {...} else if (node.catchClause == null && node.finallyBlock != null) { - try { - executeTry(); - } finally { - executeFinally(); + executeTry(); + if (error != null) { + throwError(error); } + + executeFinally(); } } diff --git a/src/interpreter/evaluator/evaluate-type-assertion-expression.ts b/src/interpreter/evaluator/evaluate-type-assertion-expression.ts index 32ebfa6..f9fe137 100644 --- a/src/interpreter/evaluator/evaluate-type-assertion-expression.ts +++ b/src/interpreter/evaluator/evaluate-type-assertion-expression.ts @@ -5,6 +5,6 @@ import {TS} from "../../type/ts.js"; /** * Evaluates, or attempts to evaluate, a TypeAssertion */ -export function evaluateTypeAssertion({node, environment, evaluate, statementTraversalStack}: EvaluatorOptions): Literal { - return evaluate.expression(node.expression, environment, statementTraversalStack); +export function evaluateTypeAssertion({node, evaluate, ...options}: EvaluatorOptions): Literal { + return evaluate.expression(node.expression, options); } diff --git a/src/interpreter/evaluator/evaluate-type-of-expression.ts b/src/interpreter/evaluator/evaluate-type-of-expression.ts index c2fd02b..8d3d26f 100644 --- a/src/interpreter/evaluator/evaluate-type-of-expression.ts +++ b/src/interpreter/evaluator/evaluate-type-of-expression.ts @@ -5,6 +5,11 @@ import {TS} from "../../type/ts.js"; /** * Evaluates, or attempts to evaluate, a TypeOfExpression */ -export function evaluateTypeOfExpression({node, environment, evaluate, statementTraversalStack}: EvaluatorOptions): Literal { - return typeof evaluate.expression(node.expression, environment, statementTraversalStack); +export function evaluateTypeOfExpression({evaluate, node, ...options}: EvaluatorOptions): Literal { + const result = evaluate.expression(node.expression, options); + + if (options.getCurrentError() != null) { + return; + } + return typeof result; } diff --git a/src/interpreter/evaluator/evaluate-variable-declaration-list.ts b/src/interpreter/evaluator/evaluate-variable-declaration-list.ts index faed599..c18f6ee 100644 --- a/src/interpreter/evaluator/evaluate-variable-declaration-list.ts +++ b/src/interpreter/evaluator/evaluate-variable-declaration-list.ts @@ -4,8 +4,12 @@ import {TS} from "../../type/ts.js"; /** * Evaluates, or attempts to evaluate, a VariableDeclarationList */ -export function evaluateVariableDeclarationList({node, evaluate, environment, statementTraversalStack}: EvaluatorOptions): void { +export function evaluateVariableDeclarationList({node, evaluate, ...options}: EvaluatorOptions): void { for (const declaration of node.declarations) { - evaluate.declaration(declaration, environment, statementTraversalStack); + evaluate.declaration(declaration, options); + + if (options.getCurrentError() != null) { + return; + } } } diff --git a/src/interpreter/evaluator/evaluate-variable-declaration.ts b/src/interpreter/evaluator/evaluate-variable-declaration.ts index 9a00492..984262f 100644 --- a/src/interpreter/evaluator/evaluate-variable-declaration.ts +++ b/src/interpreter/evaluator/evaluate-variable-declaration.ts @@ -6,24 +6,31 @@ import {TS} from "../../type/ts.js"; /** * Evaluates, or attempts to evaluate, a VariableDeclaration */ -export function evaluateVariableDeclaration( - {node, environment, evaluate, stack, typescript, statementTraversalStack}: EvaluatorOptions, - initializer?: Literal -): void { +export function evaluateVariableDeclaration(options: EvaluatorOptions, initializer?: Literal): void | EvaluationError { + const {node, environment, evaluate, stack, typescript, throwError, getCurrentError} = options; const initializerResult = initializer != null ? initializer : node.initializer == null ? // A VariableDeclaration with no initializer is implicitly bound to 'undefined' undefined - : evaluate.expression(node.initializer, environment, statementTraversalStack); + : evaluate.expression(node.initializer, options); + + if (getCurrentError() != null) { + return; + } // There's no way of destructuring a nullish value if (initializerResult == null && !typescript.isIdentifier(node.name)) { - throw new EvaluationError({node}); + return throwError(new EvaluationError({node, environment})); } // Evaluate the binding name - evaluate.nodeWithArgument(node.name, environment, initializerResult, statementTraversalStack); + evaluate.nodeWithArgument(node.name, initializerResult, options); + + if (getCurrentError() != null) { + return; + } + stack.push(initializerResult); } diff --git a/src/interpreter/evaluator/evaluate-void-expression.ts b/src/interpreter/evaluator/evaluate-void-expression.ts index 5a32b85..211fb83 100644 --- a/src/interpreter/evaluator/evaluate-void-expression.ts +++ b/src/interpreter/evaluator/evaluate-void-expression.ts @@ -4,12 +4,9 @@ import {TS} from "../../type/ts.js"; /** * Evaluates, or attempts to evaluate, a VoidExpression - * - * @param options - * @returns */ -export function evaluateVoidExpression({node, environment, evaluate, statementTraversalStack}: EvaluatorOptions): Literal { - evaluate.expression(node.expression, environment, statementTraversalStack); +export function evaluateVoidExpression({node, evaluate, ...options}: EvaluatorOptions): Literal { + evaluate.expression(node.expression, options); // The void operator evaluates the expression and then returns undefined return undefined; } diff --git a/src/interpreter/evaluator/evaluate-while-statement.ts b/src/interpreter/evaluator/evaluate-while-statement.ts index f1a275a..d3a7246 100644 --- a/src/interpreter/evaluator/evaluate-while-statement.ts +++ b/src/interpreter/evaluator/evaluate-while-statement.ts @@ -9,21 +9,31 @@ import {TS} from "../../type/ts.js"; /** * Evaluates, or attempts to evaluate, a WhileStatement */ -export function evaluateWhileStatement({node, environment, evaluate, logger, reporting, typescript, statementTraversalStack}: EvaluatorOptions): void { - let condition = evaluate.expression(node.expression, environment, statementTraversalStack) as boolean; +export function evaluateWhileStatement(options: EvaluatorOptions): void { + const {node, environment, evaluate, logger, typescript, getCurrentError} = options; + let condition = evaluate.expression(node.expression, options) as boolean; + + if (getCurrentError() != null) { + return; + } while (condition) { // Prepare a lexical environment for the current iteration const iterationEnvironment = cloneLexicalEnvironment(environment, node); + const iterationOptions = {...options, environment: iterationEnvironment}; // Define a new binding for a break symbol within the environment - setInLexicalEnvironment({env: iterationEnvironment, path: BREAK_SYMBOL, value: false, newBinding: true, reporting, node}); + setInLexicalEnvironment({...iterationOptions, path: BREAK_SYMBOL, value: false, newBinding: true}); // Define a new binding for a continue symbol within the environment - setInLexicalEnvironment({env: iterationEnvironment, path: CONTINUE_SYMBOL, value: false, newBinding: true, reporting, node}); + setInLexicalEnvironment({...iterationOptions, path: CONTINUE_SYMBOL, value: false, newBinding: true}); // Execute the Statement - evaluate.statement(node.statement, iterationEnvironment); + evaluate.statement(node.statement, iterationOptions); + + if (getCurrentError() != null) { + return; + } // Check if a 'break' statement has been encountered and break if so if (pathInLexicalEnvironmentEquals(node, iterationEnvironment, true, BREAK_SYMBOL)) { @@ -34,7 +44,11 @@ export function evaluateWhileStatement({node, environment, evaluate, logger, rep return; } - condition = evaluate.expression(node.expression, environment, statementTraversalStack) as boolean; + condition = evaluate.expression(node.expression, options) as boolean; + + if (getCurrentError() != null) { + return; + } // Always re-evaluate the condition before continuing if (pathInLexicalEnvironmentEquals(node, iterationEnvironment, true, CONTINUE_SYMBOL)) { diff --git a/src/interpreter/evaluator/evaluator-options.ts b/src/interpreter/evaluator/evaluator-options.ts index 14eb5e0..d85753f 100644 --- a/src/interpreter/evaluator/evaluator-options.ts +++ b/src/interpreter/evaluator/evaluator-options.ts @@ -6,17 +6,24 @@ import {Stack} from "../stack/stack.js"; import {EvaluatePolicySanitized} from "../policy/evaluate-policy.js"; import {ReportingOptionsSanitized} from "../reporting/i-reporting-options.js"; import {TS} from "../../type/ts.js"; +import {EvaluationError, ThrowError} from "../error/evaluation-error/evaluation-error.js"; -export interface EvaluatorOptions> { - node: T; - typeChecker?: TS.TypeChecker; +export interface NextEvaluatorOptions { + environment: LexicalEnvironment; + moduleOverrides?: Record; + throwError: ThrowError; + getCurrentError (): EvaluationError|undefined; + statementTraversalStack: StatementTraversalStack; + +} + +export interface EvaluatorOptions> extends NextEvaluatorOptions { typescript: typeof TS; + node: T; evaluate: NodeEvaluator; - environment: LexicalEnvironment; - policy: EvaluatePolicySanitized; - reporting: ReportingOptionsSanitized; - moduleOverrides?: Record; + typeChecker?: TS.TypeChecker; stack: Stack; - statementTraversalStack: StatementTraversalStack; logger: Logger; + policy: EvaluatePolicySanitized; + reporting: ReportingOptionsSanitized; } diff --git a/src/interpreter/evaluator/node-evaluator/create-node-evaluator.ts b/src/interpreter/evaluator/node-evaluator/create-node-evaluator.ts index 9719da1..c5cad14 100644 --- a/src/interpreter/evaluator/node-evaluator/create-node-evaluator.ts +++ b/src/interpreter/evaluator/node-evaluator/create-node-evaluator.ts @@ -1,34 +1,43 @@ import {ICreateNodeEvaluatorOptions} from "./i-create-node-evaluator-options.js"; -import {NodeEvaluator, NodeWithValue} from "./node-evaluator.js"; +import {NodeEvaluator} from "./node-evaluator.js"; import {MaxOpsExceededError} from "../../error/policy-error/max-ops-exceeded-error/max-ops-exceeded-error.js"; -import {LexicalEnvironment, pathInLexicalEnvironmentEquals} from "../../lexical-environment/lexical-environment.js"; import {evaluateStatement} from "../evaluate-statement.js"; -import {Literal} from "../../literal/literal.js"; import {evaluateExpression} from "../evaluate-expression.js"; -import {EvaluatorOptions} from "../evaluator-options.js"; +import {EvaluatorOptions, NextEvaluatorOptions} from "../evaluator-options.js"; import {evaluateDeclaration} from "../evaluate-declaration.js"; import {evaluateNodeWithArgument} from "../evaluate-node-with-argument.js"; import {evaluateNodeWithValue} from "../evaluate-node-with-value.js"; -import {createStatementTraversalStack, StatementTraversalStack} from "../../stack/traversal-stack/statement-traversal-stack.js"; -import {reportError} from "../../util/reporting/report-error.js"; -import {TRY_SYMBOL} from "../../util/try/try-symbol.js"; +import {createStatementTraversalStack} from "../../stack/traversal-stack/statement-traversal-stack.js"; import {TS} from "../../../type/ts.js"; +import {EvaluationError, isEvaluationError} from "../../error/evaluation-error/evaluation-error.js"; +import { Literal } from "../../literal/literal.js"; /** * Creates a Node Evaluator */ -export function createNodeEvaluator({typeChecker, typescript, policy, logger, stack, reporting, nextNode, moduleOverrides}: ICreateNodeEvaluatorOptions): NodeEvaluator { +export function createNodeEvaluator(options: ICreateNodeEvaluatorOptions): NodeEvaluator { let ops = 0; - const handleNewNode = (node: TS.Node, statementTraversalStack: StatementTraversalStack) => { - nextNode(node); + const {policy, reporting} = options; + + const prequalifyNextNode = (node: TS.Node, nextOptions: NextEvaluatorOptions): EvaluationError | void => { + const { + environment = options.environment, + statementTraversalStack = options.statementTraversalStack, + getCurrentError = options.getCurrentError, + throwError = options.throwError + } = nextOptions ?? {}; + const currentError = getCurrentError(); + if (currentError != null) { + return currentError; + } // Increment the amount of encountered ops ops++; // Throw an error if the maximum amount of operations has been exceeded if (ops >= policy.maxOps) { - throw new MaxOpsExceededError({ops, node}); + return throwError(new MaxOpsExceededError({ops, environment, node})); } // Update the statementTraversalStack with the node's kind @@ -36,75 +45,60 @@ export function createNodeEvaluator({typeChecker, typescript, policy, logger, st if (reporting.reportTraversal != null) { reporting.reportTraversal({node}); } + return undefined; }; - /** - * Wraps an evaluation action with error reporting - */ - const wrapWithErrorReporting = (environment: LexicalEnvironment, node: TS.Node, action: CallableFunction) => { - // If we're already inside of a try-block, simply execute the action and do nothing else - if (pathInLexicalEnvironmentEquals(node, environment, true, TRY_SYMBOL)) { - return action(); + const evaluate: NodeEvaluator = { + + statement: (node, nextOptions): void => { + const combinedNextOptions = {...nextOptions, statementTraversalStack: createStatementTraversalStack()}; + const prequalifyResult = prequalifyNextNode(node, combinedNextOptions); + if (isEvaluationError(prequalifyResult)) { + return; + } + return evaluateStatement(getEvaluatorOptions(node, combinedNextOptions)); + }, + declaration: (node, nextOptions): void => { + const prequalifyResult = prequalifyNextNode(node, nextOptions); + if (isEvaluationError(prequalifyResult)) { + return; + } + return evaluateDeclaration(getEvaluatorOptions(node, nextOptions)); + }, + nodeWithArgument: (node, arg, nextOptions): void => { + const prequalifyResult = prequalifyNextNode(node, nextOptions); + if (isEvaluationError(prequalifyResult)) { + return; + } + return evaluateNodeWithArgument(getEvaluatorOptions(node, nextOptions), arg); + }, + expression: (node, nextOptions): Literal|EvaluationError => { + const prequalifyResult = prequalifyNextNode(node, nextOptions); + if (isEvaluationError(prequalifyResult)) { + return prequalifyResult; + } + return evaluateExpression(getEvaluatorOptions(node, nextOptions)); + }, + nodeWithValue: (node, nextOptions): Literal|EvaluationError => { + const prequalifyResult = prequalifyNextNode(node, nextOptions); + if (isEvaluationError(prequalifyResult)) { + return prequalifyResult; + } + return evaluateNodeWithValue(getEvaluatorOptions(node, nextOptions)); } - - try { - return action(); - } catch (ex) { - // Report the Error - reportError(reporting, ex as Error, node); - - // Re-throw the error - throw ex; - } - }; - - const nodeEvaluator: NodeEvaluator = { - expression: (node: TS.Expression | TS.PrivateIdentifier, environment: LexicalEnvironment, statementTraversalStack: StatementTraversalStack): Literal => - wrapWithErrorReporting(environment, node, () => { - handleNewNode(node, statementTraversalStack); - return evaluateExpression(getEvaluatorOptions(node, environment, statementTraversalStack)); - }), - statement: (node: TS.Statement, environment: LexicalEnvironment): void => - wrapWithErrorReporting(environment, node, () => { - const statementTraversalStack = createStatementTraversalStack(); - handleNewNode(node, statementTraversalStack); - return evaluateStatement(getEvaluatorOptions(node, environment, statementTraversalStack)); - }), - declaration: (node: TS.Declaration, environment: LexicalEnvironment, statementTraversalStack: StatementTraversalStack): void => - wrapWithErrorReporting(environment, node, () => { - handleNewNode(node, statementTraversalStack); - return evaluateDeclaration(getEvaluatorOptions(node, environment, statementTraversalStack)); - }), - nodeWithArgument: (node: TS.Node, environment: LexicalEnvironment, arg: Literal, statementTraversalStack: StatementTraversalStack): void => - wrapWithErrorReporting(environment, node, () => { - handleNewNode(node, statementTraversalStack); - return evaluateNodeWithArgument(getEvaluatorOptions(node, environment, statementTraversalStack), arg); - }), - nodeWithValue: (node: NodeWithValue, environment: LexicalEnvironment, statementTraversalStack: StatementTraversalStack): Literal => - wrapWithErrorReporting(environment, node, () => { - handleNewNode(node, statementTraversalStack); - return evaluateNodeWithValue(getEvaluatorOptions(node, environment, statementTraversalStack)); - }) }; /** * Gets an IEvaluatorOptions object ready for passing to one of the evaluation functions */ - function getEvaluatorOptions(node: T, environment: LexicalEnvironment, statementTraversalStack: StatementTraversalStack): EvaluatorOptions { + function getEvaluatorOptions(node: T, nextOptions: NextEvaluatorOptions): EvaluatorOptions { return { - typeChecker, - typescript, - policy, - reporting, - node, - evaluate: nodeEvaluator, - environment, - stack, - logger, - statementTraversalStack, - moduleOverrides + ...options, + ...nextOptions, + evaluate, + node }; } - return nodeEvaluator; + return evaluate; } diff --git a/src/interpreter/evaluator/node-evaluator/i-create-node-evaluator-options.ts b/src/interpreter/evaluator/node-evaluator/i-create-node-evaluator-options.ts index beb7dc7..39d0872 100644 --- a/src/interpreter/evaluator/node-evaluator/i-create-node-evaluator-options.ts +++ b/src/interpreter/evaluator/node-evaluator/i-create-node-evaluator-options.ts @@ -3,6 +3,9 @@ import {Stack} from "../../stack/stack.js"; import {EvaluatePolicySanitized} from "../../policy/evaluate-policy.js"; import {ReportingOptionsSanitized} from "../../reporting/i-reporting-options.js"; import {TS} from "../../../type/ts.js"; +import { EvaluationError, ThrowError } from "../../error/evaluation-error/evaluation-error.js"; +import { LexicalEnvironment } from "../../lexical-environment/lexical-environment.js"; +import { StatementTraversalStack } from "../../stack/traversal-stack/statement-traversal-stack.js"; export interface ICreateNodeEvaluatorOptions { typeChecker?: TS.TypeChecker; @@ -12,5 +15,8 @@ export interface ICreateNodeEvaluatorOptions { moduleOverrides?: Record; logger: Logger; stack: Stack; - nextNode(node: TS.Node): void; + statementTraversalStack: StatementTraversalStack; + environment: LexicalEnvironment; + throwError: ThrowError; + getCurrentError (): EvaluationError|undefined; } diff --git a/src/interpreter/evaluator/node-evaluator/node-evaluator.ts b/src/interpreter/evaluator/node-evaluator/node-evaluator.ts index 3ccb232..efc6d6b 100644 --- a/src/interpreter/evaluator/node-evaluator/node-evaluator.ts +++ b/src/interpreter/evaluator/node-evaluator/node-evaluator.ts @@ -1,15 +1,15 @@ -import {LexicalEnvironment} from "../../lexical-environment/lexical-environment.js"; import {Literal} from "../../literal/literal.js"; -import {StatementTraversalStack} from "../../stack/traversal-stack/statement-traversal-stack.js"; import {TS} from "../../../type/ts.js"; +import { NextEvaluatorOptions } from "../evaluator-options.js"; +import { EvaluationError } from "../../error/evaluation-error/evaluation-error.js"; export type NodeWithValue = TS.PropertyName; -export type StatementEvaluator = (node: TS.Statement, environment: LexicalEnvironment) => void; -export type DeclarationEvaluator = (node: TS.Declaration, environment: LexicalEnvironment, statementTraversalStack: StatementTraversalStack) => void; -export type NodeEvaluatorWithArgument = (node: TS.Node, environment: LexicalEnvironment, arg: Literal, statementTraversalStack: StatementTraversalStack) => void; -export type ExpressionEvaluator = (node: TS.Expression | TS.PrivateIdentifier, environment: LexicalEnvironment, statementTraversalStack: StatementTraversalStack) => Literal; -export type NodeWithValueEvaluator = (node: NodeWithValue, environment: LexicalEnvironment, statementTraversalStack: StatementTraversalStack) => Literal; +export type StatementEvaluator = (node: TS.Statement, nextOptions: NextEvaluatorOptions) => void; +export type DeclarationEvaluator = (node: TS.Declaration, nextOptions: NextEvaluatorOptions) => void; +export type NodeEvaluatorWithArgument = (node: TS.Node, arg: Literal, nextOptions: NextEvaluatorOptions) => void; +export type ExpressionEvaluator = (node: TS.Expression | TS.PrivateIdentifier, nextOptions: NextEvaluatorOptions) => Literal|EvaluationError; +export type NodeWithValueEvaluator = (node: NodeWithValue, nextOptions: NextEvaluatorOptions) => Literal|EvaluationError; export interface NodeEvaluator { statement: StatementEvaluator; diff --git a/src/interpreter/lexical-environment/get-dot-path-from-node.ts b/src/interpreter/lexical-environment/get-dot-path-from-node.ts index 7f6e880..7bb8abc 100644 --- a/src/interpreter/lexical-environment/get-dot-path-from-node.ts +++ b/src/interpreter/lexical-environment/get-dot-path-from-node.ts @@ -10,7 +10,7 @@ import {TS} from "../../type/ts.js"; * And, if it is a PropertyAccessExpression, that path may be "console.log" for example */ export function getDotPathFromNode(options: EvaluatorOptions): string | undefined { - const {node, evaluate, typescript, environment, statementTraversalStack} = options; + const {node, evaluate, typescript} = options; if (typescript.isIdentifier(node)) { return node.text; } else if (typescript.isPrivateIdentifier?.(node)) { @@ -25,16 +25,16 @@ export function getDotPathFromNode(options: EvaluatorOptions< return getDotPathFromNode({...options, node: node.expression}); } else if (typescript.isPropertyAccessExpression(node)) { let leftHand = getDotPathFromNode({...options, node: node.expression}); - if (leftHand == null) leftHand = evaluate.expression(node.expression, environment, statementTraversalStack) as string; + if (leftHand == null) leftHand = evaluate.expression(node.expression, options) as string; let rightHand = getDotPathFromNode({...options, node: node.name}); - if (rightHand == null) rightHand = evaluate.expression(node.name, environment, statementTraversalStack) as string; + if (rightHand == null) rightHand = evaluate.expression(node.name, options) as string; if (leftHand == null || rightHand == null) return undefined; return `${leftHand}.${rightHand}`; } else if (typescript.isElementAccessExpression(node)) { let leftHand = getDotPathFromNode({...options, node: node.expression}); - if (leftHand == null) leftHand = evaluate.expression(node.expression, environment, statementTraversalStack) as string; - const rightHand = evaluate.expression(node.argumentExpression, environment, statementTraversalStack) as string; + if (leftHand == null) leftHand = evaluate.expression(node.expression, options) as string; + const rightHand = evaluate.expression(node.argumentExpression, options) as string; if (leftHand == null || rightHand == null) return undefined; return `${leftHand}.${rightHand}`; diff --git a/src/interpreter/lexical-environment/i-create-lexical-environment-options.ts b/src/interpreter/lexical-environment/i-create-lexical-environment-options.ts index d55566e..8af40a0 100644 --- a/src/interpreter/lexical-environment/i-create-lexical-environment-options.ts +++ b/src/interpreter/lexical-environment/i-create-lexical-environment-options.ts @@ -1,9 +1,9 @@ import {IEnvironment} from "../environment/i-environment.js"; import {EvaluatePolicySanitized} from "../policy/evaluate-policy.js"; -import {TS} from "../../type/ts.js"; +import { TS } from "../../type/ts.js"; export interface ICreateLexicalEnvironmentOptions { + startingNode: TS.Node; inputEnvironment: IEnvironment; policy: EvaluatePolicySanitized; - getCurrentNode(): TS.Node; } diff --git a/src/interpreter/lexical-environment/i-set-in-lexical-environment-options.ts b/src/interpreter/lexical-environment/i-set-in-lexical-environment-options.ts index 7c9c1af..19b495d 100644 --- a/src/interpreter/lexical-environment/i-set-in-lexical-environment-options.ts +++ b/src/interpreter/lexical-environment/i-set-in-lexical-environment-options.ts @@ -4,7 +4,7 @@ import {ReportingOptionsSanitized} from "../reporting/i-reporting-options.js"; import {TS} from "../../type/ts.js"; export interface ISetInLexicalEnvironmentOptions { - env: LexicalEnvironment; + environment: LexicalEnvironment; path: string; value: Literal; reporting: ReportingOptionsSanitized; diff --git a/src/interpreter/lexical-environment/lexical-environment.ts b/src/interpreter/lexical-environment/lexical-environment.ts index 7a928ae..aa4870e 100644 --- a/src/interpreter/lexical-environment/lexical-environment.ts +++ b/src/interpreter/lexical-environment/lexical-environment.ts @@ -1,6 +1,5 @@ -import {IndexLiteral, Literal, LiteralMatch} from "../literal/literal.js"; import objectPath from "object-path"; -import {createSanitizedEnvironment} from "../environment/create-sanitized-environment.js"; +import {IndexLiteral, Literal, LiteralMatch} from "../literal/literal.js"; import {ECMA_GLOBALS} from "../environment/ecma/ecma-globals.js"; import {NODE_CJS_GLOBALS} from "../environment/node/node-cjs-globals.js"; import {EnvironmentPresetKind} from "../environment/environment-preset-kind.js"; @@ -16,6 +15,7 @@ import {ICreateLexicalEnvironmentOptions} from "./i-create-lexical-environment-o import {TS} from "../../type/ts.js"; import {NODE_ESM_GLOBALS} from "../environment/node/node-esm-globals.js"; import {getStatementContext} from "../util/node/find-nearest-parent-node-of-kind.js"; +import { createSanitizedEnvironment } from "../environment/create-sanitized-environment.js"; export interface LexicalEnvironment { parentEnv: LexicalEnvironment | undefined; @@ -84,7 +84,13 @@ export function getFromLexicalEnvironment(node: TS.Node | undefined, env: Lexica } 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}; + 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}; @@ -139,22 +145,22 @@ export function isInternalSymbol(value: Literal): boolean { /** * Gets a value from a Lexical Environment */ -export function setInLexicalEnvironment({env, path, value, reporting, node, newBinding = false}: ISetInLexicalEnvironmentOptions): void { +export function setInLexicalEnvironment({environment, path, value, reporting, node, newBinding = false}: ISetInLexicalEnvironmentOptions): void { const [firstBinding] = path.split("."); - if (objectPath.has(env.env, firstBinding) || newBinding || env.parentEnv == null) { + if (objectPath.has(environment.env, firstBinding) || newBinding || environment.parentEnv == null) { // If the value didn't change, do no more - if (objectPath.has(env.env, path) && objectPath.get(env.env, path) === value) return; + if (objectPath.has(environment.env, path) && objectPath.get(environment.env, path) === value) return; // Otherwise, mutate it - objectPath.set(env.env, path, value); + objectPath.set(environment.env, path, value); // Inform reporting hooks if any is given if (reporting.reportBindings != null && !isInternalSymbol(path)) { reporting.reportBindings({path, value, node}); } } else { - let currentParentEnv: LexicalEnvironment | undefined = env.parentEnv; + let currentParentEnv: LexicalEnvironment | undefined = environment.parentEnv; while (currentParentEnv != null) { if (objectPath.has(currentParentEnv.env, firstBinding)) { // If the value didn't change, do no more @@ -222,44 +228,47 @@ export function simplifyEnvironment(environment: LexicalEnvironment, typescript: /** * Creates a Lexical Environment */ -export function createLexicalEnvironment({inputEnvironment: {extra, preset}, policy, getCurrentNode}: ICreateLexicalEnvironmentOptions): LexicalEnvironment { - let envInput: IndexLiteral; +export function createLexicalEnvironment({ + inputEnvironment: {extra, preset}, + startingNode, + policy, +}: ICreateLexicalEnvironmentOptions): LexicalEnvironment { + let env: IndexLiteral; switch (preset) { case "NONE": - envInput = mergeDescriptors(extra); + env = mergeDescriptors(extra); break; case "ECMA": - envInput = mergeDescriptors(ECMA_GLOBALS(), extra); + env = mergeDescriptors(ECMA_GLOBALS(), extra); break; case "NODE": case "NODE_CJS": - envInput = mergeDescriptors(NODE_CJS_GLOBALS(), extra); + env = mergeDescriptors(NODE_CJS_GLOBALS(), extra); break; case "NODE_ESM": - envInput = mergeDescriptors(NODE_ESM_GLOBALS(), extra); + env = mergeDescriptors(NODE_ESM_GLOBALS(), extra); break; case "BROWSER": - envInput = mergeDescriptors(BROWSER_GLOBALS(), extra); + env = mergeDescriptors(BROWSER_GLOBALS(), extra); break; default: - envInput = {}; + env = {}; break; } return { - preset, parentEnv: undefined, - startingNode: getCurrentNode(), + preset, + startingNode, env: createSanitizedEnvironment({ policy, - env: envInput, - getCurrentNode + env }) }; } diff --git a/src/interpreter/literal/literal.ts b/src/interpreter/literal/literal.ts index cd2cd7a..6a318dc 100644 --- a/src/interpreter/literal/literal.ts +++ b/src/interpreter/literal/literal.ts @@ -13,9 +13,6 @@ export interface LazyCall { /** * Returns true if the given literal is a lazy call - * - * @param literal - * @return */ export function isLazyCall(literal: Literal): literal is LazyCall { return literal != null && typeof literal === "object" && LAZY_CALL_FLAG in literal; @@ -33,9 +30,6 @@ export interface IndexLiteral { /** * Stringifies the given literal - * - * @param literal - * @return */ export function stringifyLiteral(literal: Literal): string { if (literal === undefined) return "undefined"; diff --git a/src/interpreter/proxy/create-policy-proxy.ts b/src/interpreter/proxy/create-policy-proxy.ts index 0e34250..94a8df9 100644 --- a/src/interpreter/proxy/create-policy-proxy.ts +++ b/src/interpreter/proxy/create-policy-proxy.ts @@ -3,6 +3,8 @@ import {canBeObserved} from "../util/proxy/can-be-observed.js"; import {ICreatePolicyProxyOptions} from "./i-create-policy-proxy-options.js"; import {isBindCallApply} from "../util/function/is-bind-call-apply.js"; import {PolicyTrapKind} from "../policy/policy-trap-kind.js"; +import {EvaluationErrorIntent, isEvaluationErrorIntent} from "../error/evaluation-error/evaluation-error-intent.js"; +import { isEvaluationError } from "../error/evaluation-error/evaluation-error.js"; /** * Stringifies the given PropertyKey path @@ -19,6 +21,12 @@ export function createPolicyProxy({hook, item, scope, policy}: * Creates a trap that captures function invocation */ function createAccessTrap(inputPath: PropertyKey[], currentItem: U): U { + const handleHookResult = (result: boolean | EvaluationErrorIntent, successCallback: CallableFunction) => { + if (result === false) return; + if (isEvaluationErrorIntent(result) || isEvaluationError(result)) return result; + return successCallback(); + }; + return !canBeObserved(currentItem) || isBindCallApply(currentItem as Function) ? currentItem : new Proxy(currentItem, { @@ -26,42 +34,34 @@ export function createPolicyProxy({hook, item, scope, policy}: * Constructs a new instance of the given target */ construct(target: U, argArray: unknown[], newTarget?: Function): object { - // Don't proceed if the hook says no - if ( - !hook({ + return handleHookResult( + hook({ kind: PolicyTrapKind.CONSTRUCT, policy, newTarget, argArray, target, path: stringifyPath(inputPath) - }) - ) { - return {}; - } - - return Reflect.construct(target as Function, argArray, newTarget); + }), + () => Reflect.construct(target as Function, argArray, newTarget) + ); }, /** * A trap for a function call. Used to create new proxies for methods on the retrieved module objects */ apply(target: U, thisArg: unknown, argArray: unknown[] = []): unknown { - // Don't proceed if the hook says no - if ( - !hook({ + return handleHookResult( + hook({ kind: PolicyTrapKind.APPLY, policy, thisArg, argArray, target, path: stringifyPath(inputPath) - }) - ) { - return; - } - - return Reflect.apply(target as Function, thisArg, argArray); + }), + () => Reflect.apply(target as Function, thisArg, argArray) + ); }, /** @@ -69,26 +69,23 @@ export function createPolicyProxy({hook, item, scope, policy}: */ get(target: U, property: string, receiver: unknown): unknown { const newPath = [...inputPath, property]; - - // Don't proceed if the hook says no - if ( - !hook({ + return handleHookResult( + hook({ kind: PolicyTrapKind.GET, policy, path: stringifyPath(newPath), target - }) - ) { - return; - } - - const match = Reflect.get(target, property, receiver); + }), + () => { + 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 as keyof U]; - } - return createAccessTrap(newPath, match); + const config = Reflect.getOwnPropertyDescriptor(currentItem, property); + if (config != null && config.configurable === false && config.writable === false) { + return currentItem[property as keyof U]; + } + return createAccessTrap(newPath, match); + } + ); } }); } diff --git a/src/interpreter/proxy/policy-proxy-hook.ts b/src/interpreter/proxy/policy-proxy-hook.ts index 18d1b49..335b7ad 100644 --- a/src/interpreter/proxy/policy-proxy-hook.ts +++ b/src/interpreter/proxy/policy-proxy-hook.ts @@ -1,4 +1,5 @@ /* eslint-disable @typescript-eslint/ban-types */ +import { EvaluationErrorIntent } from "../error/evaluation-error/evaluation-error-intent.js"; import {EvaluatePolicySanitized} from "../policy/evaluate-policy.js"; import {PolicyTrapKind} from "../policy/policy-trap-kind.js"; @@ -29,4 +30,4 @@ export interface IPolicyProxyApplyHookOptions extends IPolicyP // eslint-disable-next-line @typescript-eslint/no-explicit-any export type PolicyProxyHookOptions> = IPolicyProxyGetHookOptions | IPolicyProxyApplyHookOptions | IPolicyProxyConstructHookOptions; -export type PolicyProxyHook = (options: PolicyProxyHookOptions) => boolean; +export type PolicyProxyHook = (options: PolicyProxyHookOptions) => boolean|EvaluationErrorIntent; diff --git a/src/interpreter/util/declaration/get-declaration-name.ts b/src/interpreter/util/declaration/get-declaration-name.ts index fa5a19a..67ac967 100644 --- a/src/interpreter/util/declaration/get-declaration-name.ts +++ b/src/interpreter/util/declaration/get-declaration-name.ts @@ -1,11 +1,13 @@ import {EvaluatorOptions} from "../../evaluator/evaluator-options.js"; import {UnexpectedNodeError} from "../../error/unexpected-node-error/unexpected-node-error.js"; import {TS} from "../../../type/ts.js"; +import { EvaluationError } from "../../error/evaluation-error/evaluation-error.js"; /** * Gets the name of the given declaration */ -export function getDeclarationName({node, evaluate, environment, typescript, statementTraversalStack}: EvaluatorOptions): string | number | undefined { +export function getDeclarationName(options: EvaluatorOptions): string | number | EvaluationError | undefined { + const {node, evaluate, environment, typescript, throwError} = options; const name = typescript.getNameOfDeclaration(node); if (name == null) return undefined; @@ -18,8 +20,8 @@ export function getDeclarationName({node, evaluate, environment, typescript, sta } else if (typescript.isNumericLiteral(name)) { return Number(name.text); } else if (typescript.isComputedPropertyName(name)) { - return evaluate.expression(name.expression, environment, statementTraversalStack) as ReturnType; + return evaluate.expression(name.expression, options) as ReturnType; } else { - throw new UnexpectedNodeError({node: name, typescript}); + return throwError(new UnexpectedNodeError({node: name, environment, typescript})); } } diff --git a/src/interpreter/util/module/get-implementation-for-declaration-within-declaration-file.ts b/src/interpreter/util/module/get-implementation-for-declaration-within-declaration-file.ts index b87ba53..f5af4fb 100644 --- a/src/interpreter/util/module/get-implementation-for-declaration-within-declaration-file.ts +++ b/src/interpreter/util/module/get-implementation-for-declaration-within-declaration-file.ts @@ -4,19 +4,24 @@ import {ModuleNotFoundError} from "../../error/module-not-found-error/module-not import {UnexpectedNodeError} from "../../error/unexpected-node-error/unexpected-node-error.js"; import {EvaluatorOptions} from "../../evaluator/evaluator-options.js"; import {getDeclarationName} from "../declaration/get-declaration-name.js"; -import {EvaluationError} from "../../error/evaluation-error/evaluation-error.js"; +import {isEvaluationError} from "../../error/evaluation-error/evaluation-error.js"; import {getFromLexicalEnvironment} from "../../lexical-environment/lexical-environment.js"; import {TS} from "../../../type/ts.js"; +import {getResolvedModuleName} from "./get-resolved-module-name.js"; /** * Gets an implementation for the given declaration that lives within a declaration file */ export function getImplementationForDeclarationWithinDeclarationFile(options: EvaluatorOptions): Literal { - const {node, typescript} = options; + const {node, typescript, throwError, environment} = options; const name = getDeclarationName(options); + if (isEvaluationError(name)) { + return name; + } + if (name == null) { - throw new UnexpectedNodeError({node, typescript}); + return throwError(new UnexpectedNodeError({node, environment, typescript})); } // First see if it lives within the lexical environment @@ -33,30 +38,33 @@ export function getImplementationForDeclarationWithinDeclarationFile(options: Ev ? node : findNearestParentNodeOfKind(node, typescript.SyntaxKind.ModuleDeclaration, typescript); if (moduleDeclaration == null) { - throw new UnexpectedNodeError({node, typescript}); + return throwError(new UnexpectedNodeError({node, environment, typescript})); } - + const moduleSpecifier = moduleDeclaration.name.text; + const resolvedModuleSpecifier = getResolvedModuleName(moduleSpecifier, options); try { + // eslint-disable-next-line @typescript-eslint/no-require-imports,@typescript-eslint/no-var-requires - const module = options.moduleOverrides?.[moduleDeclaration.name.text] ?? require(moduleDeclaration.name.text); + const module = options.moduleOverrides?.[moduleSpecifier] ?? options.moduleOverrides?.[resolvedModuleSpecifier] ?? require(resolvedModuleSpecifier); return typescript.isModuleDeclaration(node) ? module : module[name] ?? module; } catch (ex) { - if (ex instanceof EvaluationError) throw ex; - else throw new ModuleNotFoundError({node: moduleDeclaration, path: moduleDeclaration.name.text}); + if (isEvaluationError(ex)) return ex; + else return throwError(new ModuleNotFoundError({node: moduleDeclaration, environment, path: resolvedModuleSpecifier})); } } export function getImplementationFromExternalFile(name: string, moduleSpecifier: string, options: EvaluatorOptions): Literal { - const {node} = options; + const {node, throwError, environment} = options; const require = getFromLexicalEnvironment(node, options.environment, "require")!.literal as NodeRequire; + const resolvedModuleSpecifier = getResolvedModuleName(moduleSpecifier, options); try { // eslint-disable-next-line @typescript-eslint/no-require-imports,@typescript-eslint/no-var-requires - const module = options.moduleOverrides?.[moduleSpecifier] ?? require(moduleSpecifier); + const module = options.moduleOverrides?.[moduleSpecifier] ?? options.moduleOverrides?.[resolvedModuleSpecifier] ?? require(resolvedModuleSpecifier); return module[name] ?? module.default ?? module; } catch (ex) { - if (ex instanceof EvaluationError) throw ex; - else throw new ModuleNotFoundError({node, path: moduleSpecifier}); + if (isEvaluationError(ex)) return ex; + else return throwError(new ModuleNotFoundError({node, environment, path: resolvedModuleSpecifier})); } } diff --git a/src/interpreter/util/module/get-resolved-module-name.ts b/src/interpreter/util/module/get-resolved-module-name.ts new file mode 100644 index 0000000..ba1c89e --- /dev/null +++ b/src/interpreter/util/module/get-resolved-module-name.ts @@ -0,0 +1,13 @@ +import path from "crosspath"; +import { TS } from "../../../type/ts.js"; +import { EvaluatorOptions } from "../../evaluator/evaluator-options.js"; + +export function getResolvedModuleName (moduleSpecifier: string, options: EvaluatorOptions): string { + const {node, typescript} = options; + if (!typescript.isExternalModuleNameRelative(moduleSpecifier)) { + return moduleSpecifier; + } + + const parentPath = node.getSourceFile().fileName; + return path.join(path.dirname(parentPath), moduleSpecifier); +} \ No newline at end of file diff --git a/test/import-declaration/import-declaration.test.ts b/test/import-declaration/import-declaration.test.ts index 1a78a91..746bee1 100644 --- a/test/import-declaration/import-declaration.test.ts +++ b/test/import-declaration/import-declaration.test.ts @@ -4,6 +4,7 @@ import {executeProgram} from "../setup/execute-program.js"; import {withTypeScript, withTypeScriptVersions} from "../setup/ts-macro.js"; import path from "crosspath"; + test("Can resolve symbols via ImportDeclarations. #1", withTypeScript, (t, {typescript, useTypeChecker}) => { const {result} = executeProgram( [ diff --git a/test/try-catch/try-catch.test.ts b/test/try-catch/try-catch.test.ts index cf0cf50..47801eb 100644 --- a/test/try-catch/try-catch.test.ts +++ b/test/try-catch/try-catch.test.ts @@ -23,3 +23,104 @@ test("Can capture errors that would otherwise throw with try-catch. #1", withTyp if (!result.success) t.fail(result.reason.stack); else t.true(result.value instanceof Error); }); + +test("Will execute the 'finally' branch correctly. #1", withTypeScript, (t, {typescript, useTypeChecker}) => { + let executedFinally = false; + executeProgram( + // language=TypeScript + ` + (() => { + let executedFinally = false; + try { + throw new Error(); + } finally { + executedFinally = true; + } + return myVar; + })(); + `, + "(() =>", + { + typescript, + useTypeChecker, + reporting: { + reportBindings: ({path, value}) => { + if (path === "executedFinally") { + executedFinally = Boolean(value); + } + } + } + } + ); + + t.true(executedFinally); +}); + +test("Will execute the 'finally' branch correctly. #2", withTypeScript, (t, {typescript, useTypeChecker}) => { + let executedFinally = false; + executeProgram( + // language=TypeScript + ` + (() => { + let executedFinally = false; + try { + throw new Error(); + } catch (ex) { + // Noop + } finally { + executedFinally = true; + } + return myVar; + })(); + `, + "(() =>", + { + typescript, + useTypeChecker, + reporting: { + reportBindings: ({path, value}) => { + if (path === "executedFinally") { + executedFinally = Boolean(value); + } + } + } + } + ); + + t.true(executedFinally); +}); + + +test("Will execute the 'finally' branch correctly. #3", withTypeScript, (t, {typescript, useTypeChecker}) => { + let executedFinally = false; + executeProgram( + // language=TypeScript + ` + (() => { + let executedFinally = false; + try { + throw new Error(); + } catch (ex) { + throw new Error(); + } finally { + executedFinally = true; + } + return myVar; + })(); + `, + "(() =>", + { + typescript, + useTypeChecker, + reporting: { + reportBindings: ({path, value}) => { + if (path === "executedFinally") { + executedFinally = Boolean(value); + } + } + } + } + ); + + t.true(executedFinally); +});