aran
Version:
JavaScript instrumenter
186 lines (165 loc) • 4.61 kB
JavaScript
/* eslint-disable local/no-impure */
/* eslint-disable local/strict-console */
/* eslint-disable local/no-function */
/* eslint-disable local/no-class */
import { join, map, print } from "./util/index.mjs";
const {
console,
console: { dir, log },
Error,
JSON: { stringify },
TypeError,
SyntaxError,
Reflect: { apply },
String: {
prototype: { trim, startsWith, slice, split },
},
} = globalThis;
const ADDITIONAL_CALLSTACK_DEPTH = 3;
const getLocation = () => {
const { stack } = new Error("dummy-message");
if (stack == null) {
return "???";
} else {
const lines = apply(split, stack, ["\n"]);
if (lines.length < ADDITIONAL_CALLSTACK_DEPTH + 1) {
return "???";
} else {
const line = apply(trim, lines[ADDITIONAL_CALLSTACK_DEPTH], []);
if (apply(startsWith, line, ["at "])) {
return apply(slice, line, [3]);
} else {
return line;
}
}
}
};
///////////////////
// AranTypeError //
///////////////////
export const AranTypeError = class extends TypeError {
constructor(/** @type {never} */ cause) {
const message = `unsound type: ${print(cause)} at ${getLocation()}`;
apply(log, console, [message]);
apply(dir, console, [cause, { depth: 5 }]);
super(message);
this.name = "AranTypeError";
this.cause = /** @type {unknown} */ (cause);
}
};
///////////////////
// AranExecError //
///////////////////
export const AranExecError = class extends Error {
constructor(/** @type {string} */ message, /** @type {unknown} */ cause) {
apply(log, console, [`${message} at ${getLocation()}`]);
apply(dir, console, [cause, { depth: 5 }]);
super(message);
this.name = "AranExecError";
this.cause = cause;
}
};
/////////////////////
// AranSyntaxError //
/////////////////////
export const AranSyntaxError = class extends SyntaxError {
constructor(
/** @type {string} */ message,
/** @type {import("./error.d.ts").SyntaxErrorCause} */ cause,
) {
super(message);
this.name = "AranSyntaxError";
this.cause = cause;
}
};
////////////////////
// AranInputError //
////////////////////
/**
* @type {(
* condition: {
* target: string,
* actual: unknown,
* }
* ) => string}
*/
const formatInputConditionMessage = ({ target, actual }) =>
`${target} is ${print(actual)}`;
/**
* @type {(
* cause: import("./error.d.ts").InputErrorCause,
* ) => string}
*/
const formatInputErrorMessage = ({ conditions, target, expect, actual }) => {
const base = `${target} should be ${expect}, but got: ${print(actual)}`;
if (conditions.length === 0) {
return base;
} else {
return `if ${join(map(conditions, formatInputConditionMessage), " and ")} then ${base}`;
}
};
export const AranInputError = class extends Error {
constructor(/** @type {import("./error.d.ts").InputErrorCause} */ cause) {
super(formatInputErrorMessage(cause));
this.name = "AranInputError";
this.cause = cause;
}
};
///////////////////////
// AranPointcutError //
///////////////////////
/**
* @type {(
* cause: import("./error.d.ts").PointcutErrorCause,
* ) => string}
*/
export const formatPointcutErrorMessage = (cause) => {
switch (cause.type) {
case "DuplicateCut": {
return `${cause.point} can only be triggered once but was triggered at #${stringify(cause.tag)} by: ${join(
cause.conflict,
", ",
)}`;
}
case "MissingCut": {
return `${cause.point} must be triggered once at #${stringify(cause.tag)}`;
}
default: {
throw new AranTypeError(cause);
}
}
};
export const AranPointcutError = class extends Error {
constructor(/** @type {import("./error.d.ts").PointcutErrorCause} */ cause) {
super(formatPointcutErrorMessage(cause));
this.name = "AranPointcutError";
this.cause = cause;
}
};
////////////////////
// AranClashError //
////////////////////
/**
* @type {(
* clash: import("./error.d.ts").ClashErrorCause,
* ) => string}
*/
export const formatClashErrorMessage = (cause) => {
switch (cause.type) {
case "exact": {
return `External variable '${cause.variable}' clashes with config.${cause.conf}`;
}
case "prefix": {
return `External variable '${cause.variable}' starts with config.${cause.conf} (${cause.prefix})`;
}
default: {
throw new AranTypeError(cause);
}
}
};
export const AranClashError = class extends Error {
constructor(/** @type {import("./error.d.ts").ClashErrorCause} */ cause) {
super(formatClashErrorMessage(cause));
this.cause = cause;
}
};