forked from sashin/sashinexists
105 lines
3.0 KiB
JavaScript
105 lines
3.0 KiB
JavaScript
// Text we look for in an error stack to guess whether a given line represents a
|
|
|
|
import { scope as scopeFn, trailingSlash } from "@weborigami/async-tree";
|
|
import codeFragment from "./codeFragment.js";
|
|
import { typos } from "./typos.js";
|
|
|
|
// function in the Origami source code.
|
|
const origamiSourceSignals = [
|
|
"async-tree/src/",
|
|
"language/src/",
|
|
"origami/src/",
|
|
"at Scope.evaluate",
|
|
];
|
|
|
|
export async function builtinReferenceError(tree, builtins, key) {
|
|
const messages = [
|
|
`"${key}" is being called as if it were a builtin function, but it's not.`,
|
|
];
|
|
// See if the key is in scope (but not as a builtin)
|
|
const scope = scopeFn(tree);
|
|
const value = await scope.get(key);
|
|
if (value === undefined) {
|
|
const typos = await formatScopeTypos(builtins, key);
|
|
messages.push(typos);
|
|
} else {
|
|
messages.push(`Use "${key}/" instead.`);
|
|
}
|
|
const message = messages.join(" ");
|
|
return new ReferenceError(message);
|
|
}
|
|
|
|
/**
|
|
* Format an error for display in the console.
|
|
*
|
|
* @param {Error} error
|
|
*/
|
|
export function formatError(error) {
|
|
let message;
|
|
if (error.stack) {
|
|
// Display the stack only until we reach the Origami source code.
|
|
message = "";
|
|
let lines = error.stack.split("\n");
|
|
for (let i = 0; i < lines.length; i++) {
|
|
const line = lines[i];
|
|
if (maybeOrigamiSourceCode(line)) {
|
|
break;
|
|
}
|
|
if (message) {
|
|
message += "\n";
|
|
}
|
|
message += lines[i];
|
|
}
|
|
} else {
|
|
message = error.toString();
|
|
}
|
|
|
|
// Add location
|
|
let location = /** @type {any} */ (error).location;
|
|
if (location) {
|
|
const fragment = codeFragment(location);
|
|
let { source, start } = location;
|
|
|
|
message += `\nevaluating: ${fragment}`;
|
|
if (typeof source === "object" && source.url) {
|
|
message += `\n at ${source.url.href}:${start.line}:${start.column}`;
|
|
} else if (source.text.includes("\n")) {
|
|
message += `\n at line ${start.line}, column ${start.column}`;
|
|
}
|
|
}
|
|
return message;
|
|
}
|
|
|
|
export async function formatScopeTypos(scope, key) {
|
|
const keys = await scopeTypos(scope, key);
|
|
// Don't match deprecated keys
|
|
const filtered = keys.filter((key) => !key.startsWith("@"));
|
|
if (filtered.length === 0) {
|
|
return "";
|
|
}
|
|
const quoted = filtered.map((key) => `"${key}"`);
|
|
const list = quoted.join(", ");
|
|
return `Maybe you meant ${list}?`;
|
|
}
|
|
|
|
export function maybeOrigamiSourceCode(text) {
|
|
return origamiSourceSignals.some((signal) => text.includes(signal));
|
|
}
|
|
|
|
export async function scopeReferenceError(scope, key) {
|
|
const messages = [
|
|
`"${key}" is not in scope.`,
|
|
await formatScopeTypos(scope, key),
|
|
];
|
|
const message = messages.join(" ");
|
|
return new ReferenceError(message);
|
|
}
|
|
|
|
// Return all possible typos for `key` in scope
|
|
async function scopeTypos(scope, key) {
|
|
const scopeKeys = [...(await scope.keys())];
|
|
const normalizedScopeKeys = scopeKeys.map((key) => trailingSlash.remove(key));
|
|
const normalizedKey = trailingSlash.remove(key);
|
|
return typos(normalizedKey, normalizedScopeKeys);
|
|
}
|