Skip to content

Commit 8f7cfdb

Browse files
committed
feat: change error handling to a continuation function approach
1 parent b67b873 commit 8f7cfdb

112 files changed

Lines changed: 1355 additions & 694 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,6 @@
5353
"@wessberg/prettier-config": "^1.0.0",
5454
"rollup-plugin-ts": "3.0.2",
5555
"ava": "^3.15.0",
56-
"crosspath": "^2.0.0",
5756
"eslint": "^8.20.0",
5857
"eslint-config-prettier": "^8.5.0",
5958
"eslint-plugin-import": "^2.26.0",
@@ -90,7 +89,8 @@
9089
},
9190
"dependencies": {
9291
"ansi-colors": "^4.1.3",
93-
"object-path": "^0.11.8"
92+
"object-path": "^0.11.8",
93+
"crosspath": "^2.0.0"
9494
},
9595
"peerDependencies": {
9696
"typescript": ">=3.2.x || >= 4.x",

src/interpreter/environment/create-sanitized-environment.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,39 +14,40 @@ import {ProcessError} from "../error/policy-error/process-error/process-error.js
1414
import {isProcessSpawnChildOperation} from "../policy/process/is-process-spawn-child-operation.js";
1515
import {ICreateSanitizedEnvironmentOptions} from "./i-create-sanitized-environment-options.js";
1616
import {isConsoleOperation} from "../policy/console/is-console-operation.js";
17+
import { EvaluationErrorIntent } from "../error/evaluation-error/evaluation-error-intent.js";
1718

1819
/**
1920
* Creates an environment that provide hooks into policy checks
2021
*/
21-
export function createSanitizedEnvironment({policy, env, getCurrentNode}: ICreateSanitizedEnvironmentOptions): IndexLiteral {
22+
export function createSanitizedEnvironment({policy, env}: ICreateSanitizedEnvironmentOptions): IndexLiteral {
2223
// eslint-disable-next-line @typescript-eslint/no-explicit-any
2324
const hook = (item: PolicyProxyHookOptions<any>) => {
2425
if (!policy.console && isConsoleOperation(item)) {
2526
return false;
2627
}
2728

2829
if (!policy.io.read && isIoRead(item)) {
29-
throw new IoError({kind: "read", node: getCurrentNode()});
30+
return new EvaluationErrorIntent((node, options) => new IoError({...options, node, kind: "read"}));
3031
}
3132

3233
if (!policy.io.write && isIoWrite(item)) {
33-
throw new IoError({kind: "write", node: getCurrentNode()});
34+
return new EvaluationErrorIntent((node, options) => new IoError({...options, node, kind: "write"}));
3435
}
3536

3637
if (!policy.process.exit && isProcessExitOperation(item)) {
37-
throw new ProcessError({kind: "exit", node: getCurrentNode()});
38+
return new EvaluationErrorIntent((node, options) => new ProcessError({...options, node, kind: "exit"}));
3839
}
3940

4041
if (!policy.process.exit && isProcessSpawnChildOperation(item)) {
41-
throw new ProcessError({kind: "spawnChild", node: getCurrentNode()});
42+
return new EvaluationErrorIntent((node, options) => new ProcessError({...options, node, kind: "spawnChild"}));
4243
}
4344

4445
if (!policy.network && isNetworkOperation(item)) {
45-
throw new NetworkError({operation: stringifyPolicyTrapKindOnPath(item.kind, item.path), node: getCurrentNode()});
46+
return new EvaluationErrorIntent((node, options) => new NetworkError({...options, node, operation: stringifyPolicyTrapKindOnPath(item.kind, item.path)}));
4647
}
4748

4849
if (policy.deterministic && isNonDeterministic(item)) {
49-
throw new NonDeterministicError({operation: stringifyPolicyTrapKindOnPath(item.kind, item.path), node: getCurrentNode()});
50+
return new EvaluationErrorIntent((node, options) => new NonDeterministicError({...options, node, operation: stringifyPolicyTrapKindOnPath(item.kind, item.path)}));
5051
}
5152

5253
return true;
Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
import {EvaluatePolicySanitized} from "../policy/evaluate-policy.js";
22
import {IndexLiteral} from "../literal/literal.js";
3-
import {TS} from "../../type/ts.js";
43

54
export interface ICreateSanitizedEnvironmentOptions {
65
policy: EvaluatePolicySanitized;
76
env: IndexLiteral;
8-
getCurrentNode(): TS.Node;
97
}

src/interpreter/error/async-iterator-not-supported-error/async-iterator-not-supported-error.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {IAsyncIteratorNotSupportedErrorOptions} from "./i-async-iterator-not-sup
55
* An Error that can be thrown when an async iteration operation is attempted
66
*/
77
export class AsyncIteratorNotSupportedError extends EvaluationError {
8-
constructor({message = `It is not possible to evaluate an async iterator'`, typescript}: IAsyncIteratorNotSupportedErrorOptions) {
9-
super({message, node: typescript.createEmptyStatement()});
8+
constructor({message = `It is not possible to evaluate an async iterator'`, typescript, environment}: IAsyncIteratorNotSupportedErrorOptions) {
9+
super({message, environment, node: typescript.factory?.createEmptyStatement() ?? typescript.createEmptyStatement()});
1010
}
1111
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import {TS} from "../../../type/ts.js";
2+
import {NextEvaluatorOptions} from "../../evaluator/evaluator-options.js";
3+
import {EvaluationError} from "./evaluation-error.js";
4+
5+
type EvaluationErrorIntentCallback<T extends EvaluationError> = (node: TS.Node, options: NextEvaluatorOptions) => T;
6+
7+
export class EvaluationErrorIntent<T extends EvaluationError = EvaluationError> {
8+
constructor(private readonly intent: EvaluationErrorIntentCallback<T>) {}
9+
construct(node: TS.Node, options: NextEvaluatorOptions): T {
10+
return this.intent(node, options);
11+
}
12+
}
13+
14+
export function isEvaluationErrorIntent<T extends EvaluationError = EvaluationError>(item: unknown): item is EvaluationErrorIntent<T> {
15+
return typeof item === "object" && item != null && item instanceof EvaluationErrorIntent;
16+
}
17+
18+
export function maybeThrow<Value>(node: TS.Node, options: NextEvaluatorOptions, value: Value | EvaluationErrorIntent): Value | EvaluationError {
19+
return isEvaluationErrorIntent(value) ? options.throwError(value.construct(node, options)) : value;
20+
}

src/interpreter/error/evaluation-error/evaluation-error.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import {IEvaluationErrorOptions} from "./i-evaluation-error-options.js";
22
import {TS} from "../../../type/ts.js";
3+
import { LexicalEnvironment } from "../../lexical-environment/lexical-environment.js";
4+
5+
export type ThrowError = (error: EvaluationError) => EvaluationError;
36

47
/**
58
* A Base class for EvaluationErrors
@@ -9,10 +12,16 @@ export class EvaluationError extends Error {
912
* The node that caused or thew the error
1013
*/
1114
readonly node: TS.Node;
15+
readonly environment: LexicalEnvironment;
1216

13-
constructor({node, message}: IEvaluationErrorOptions) {
17+
constructor({node, environment, message}: IEvaluationErrorOptions) {
1418
super(message);
1519
Error.captureStackTrace(this, this.constructor);
1620
this.node = node;
21+
this.environment = environment;
1722
}
1823
}
24+
25+
export function isEvaluationError (item: unknown): item is EvaluationError {
26+
return typeof item === "object" && item != null && item instanceof EvaluationError;
27+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import {TS} from "../../../type/ts.js";
2+
import { LexicalEnvironment } from "../../lexical-environment/lexical-environment.js";
23

34
export interface IEvaluationErrorOptions {
45
node: TS.Node;
6+
environment: LexicalEnvironment;
57
message?: string;
68
}

src/interpreter/error/missing-catch-or-finally-after-try-error/missing-catch-or-finally-after-try-error.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export class MissingCatchOrFinallyAfterTryError extends EvaluationError {
1111
*/
1212
readonly node!: TS.TryStatement;
1313

14-
constructor({node, message = `Missing catch or finally after try`}: IMissingCatchOrFinallyAfterTryErrorOptions) {
15-
super({node, message});
14+
constructor({node, environment, message = `Missing catch or finally after try`}: IMissingCatchOrFinallyAfterTryErrorOptions) {
15+
super({node, environment, message});
1616
}
1717
}

src/interpreter/error/module-not-found-error/module-not-found-error.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ export class ModuleNotFoundError extends EvaluationError {
1010
*/
1111
readonly path: string;
1212

13-
constructor({path, node, message = `Module '${path}' could not be resolved'`}: IModuleNotFoundErrorOptions) {
14-
super({message, node});
13+
constructor({path, node, environment, message = `Module '${path}' could not be resolved'`}: IModuleNotFoundErrorOptions) {
14+
super({message, environment, node});
1515
this.path = path;
1616
}
1717
}

src/interpreter/error/not-callable-error/not-callable-error.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ export class NotCallableError extends EvaluationError {
1111
*/
1212
readonly value: Literal;
1313

14-
constructor({value, node, message = `${stringifyLiteral(value)} is not a function'`}: INotCallableErrorOptions) {
15-
super({message, node});
14+
constructor({value, node, environment, message = `${stringifyLiteral(value)} is not a function'`}: INotCallableErrorOptions) {
15+
super({message, environment, node});
1616
this.value = value;
1717
}
1818
}

0 commit comments

Comments
 (0)