Skip to content

Commit

Permalink
Merge pull request #89 from noelforte/error-subclasses
Browse files Browse the repository at this point in the history
Implement `TransformError`/`TemplateError` subclasses
  • Loading branch information
oscarotero authored Dec 7, 2024
2 parents 624a477 + 6e30920 commit 89ca698
Show file tree
Hide file tree
Showing 4 changed files with 80 additions and 72 deletions.
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
"deno.enable": true,
"deno.unstable": true,
"deno.importMap": "./docs/deno.json",
"prettier.enable": false
"prettier.enable": false,
"editor.defaultFormatter": "denoland.vscode-deno"
}
61 changes: 10 additions & 51 deletions src/environment.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import tokenize, { Token } from "./tokenizer.ts";

import type { Loader } from "./loader.ts";
import { TransformError, transformTemplateCode } from "./transformer.ts";
import { transformTemplateCode } from "./transformer.ts";
import { TemplateError, TransformError } from "./errors.ts";

export interface TemplateResult {
content: string;
Expand Down Expand Up @@ -138,19 +139,20 @@ export class Environment {
if (autoDataVarname) {
try {
code = transformTemplateCode(code, dataVarname);
} catch (error) {
if (error instanceof TransformError) {
throw this.createError(path, source, error.pos, error);
} catch (cause) {
if (cause instanceof TransformError) {
throw new TemplateError(path, source, cause.position, cause);
}

throw error;
throw new Error(`Unknown error while transforming ${path}`, { cause });
}
}

const constructor = new Function(
"__file",
"__env",
"__defaults",
"__err",
`return${sync ? "" : " async"} function (${dataVarname}) {
let __pos = 0;
try {
Expand All @@ -160,13 +162,13 @@ export class Environment {
return __exports;
} catch (cause) {
const template = __env.cache.get(__file);
throw __env.createError(__file, template?.source || "", __pos, cause);
throw new __err(__file, template?.source, __pos, cause);
}
}
`,
);

const template: Template = constructor(path, this, defaults);
const template: Template = constructor(path, this, defaults, TemplateError);
template.file = path;
template.code = code;
template.source = source;
Expand All @@ -179,7 +181,7 @@ export class Environment {
const { position, error } = result;

if (error) {
throw this.createError(path, source, position, error);
throw new TemplateError(path, source, position, error);
}

for (const tokenPreprocessor of this.tokenPreprocessors) {
Expand Down Expand Up @@ -310,22 +312,6 @@ export class Environment {

return output;
}

createError(
path: string = "unknown",
source: string = "<empty file>",
position: number = 0,
cause: Error,
): Error {
const [line, column, code] = errorLine(source, position);

return new Error(
`Error in the template ${path}:${line}:${column}\n\n${code.trim()}\n\n${
cause.message.replaceAll(/^/gm, "> ")
}\n`,
{ cause },
);
}
}

function isGlobal(name: string) {
Expand All @@ -341,33 +327,6 @@ function isGlobal(name: string) {
}
}

/** Returns the number and code of the errored line */
export function errorLine(
source: string,
pos: number,
): [number, number, string] {
let line = 1;
let column = 1;

for (let index = 0; index < pos; index++) {
if (
source[index] === "\n" ||
(source[index] === "\r" && source[index + 1] === "\n")
) {
line++;
column = 1;

if (source[index] === "\r") {
index++;
}
} else {
column++;
}
}

return [line, column, source.split("\n")[line - 1]];
}

function checkAsync(fn: () => unknown): boolean {
return fn.constructor?.name === "AsyncFunction";
}
59 changes: 59 additions & 0 deletions src/errors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
class VentoBaseError extends Error {
override name = this.constructor.name;
}

export class TemplateError extends VentoBaseError {
constructor(
public path: string = "<unknown>",
public source: string = "<empty file>",
public position: number = 0,
cause?: Error,
) {
const { line, column, code } = errorLine(source, position);
super(
`Error in template ${path}:${line}:${column}\n\n${code.trim()}\n\n`,
{ cause },
);

if (cause) {
this.message += `(via ${cause.name})\n`;
}
}
}

export class TransformError extends VentoBaseError {
constructor(
message: string,
public position: number = 0,
cause?: Error,
) {
super(message, { cause });
}
}

/** Returns the number and code of the errored line */
export function errorLine(
source: string,
position: number,
): { line: number; column: number; code: string } {
let line = 1;
let column = 1;

for (let index = 0; index < position; index++) {
if (
source[index] === "\n" ||
(source[index] === "\r" && source[index + 1] === "\n")
) {
line++;
column = 1;

if (source[index] === "\r") {
index++;
}
} else {
column++;
}
}

return { line, column, code: source.split("\n")[line - 1] };
}
29 changes: 9 additions & 20 deletions src/transformer.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { astring, ESTree, meriyah, walker } from "../deps.ts";
import { TransformError } from "./errors.ts";

// Declare types
// Declare meriyah's ParseError since it isn't exported
interface ParseError extends Error {
start: number;
end: number;
Expand All @@ -9,19 +10,6 @@ interface ParseError extends Error {
description: string;
}

interface TransformErrorOptions extends ErrorOptions {
pos?: number;
}

export class TransformError extends Error {
pos?: number;
constructor(message: string, options?: TransformErrorOptions) {
super(message);
this.name = "TransformError";
this.pos = options?.pos;
}
}

// List of identifiers that are in globalThis
// but should be accessed as templateState.identifier
const INCLUDE_GLOBAL = [
Expand Down Expand Up @@ -149,18 +137,19 @@ export function transformTemplateCode(

// Use information from `meriyah` to annotate the part of
// the compiled template function that triggered the ParseError
const annotation = code.split("\n")[loc.start.line - 1] + "\n" +
" ".repeat(loc.start.column) + "\x1b[31m^\x1b[0m";
const annotation = `\u001B[2m${loc.start.line}\u001B[0m ` +
code.split("\n")[loc.start.line - 1] +
`\n${" ".repeat(loc.start.column)}\u001B[31m^\u001B[39m`;

// Grab the last instance of Vento's `__pos` variable before the
// error was thrown. Pass this back to Vento's createError to
// error was thrown. Pass this back to Vento to
// tie this error with problmatic template code
const matches = [...code.slice(0, start).matchAll(/__pos = (\d+);/g)];
const pos = Number(matches.at(-1)?.[1]);
const position = Number(matches.at(-1)?.[1]);

throw new TransformError(
`[meriyah] ${message}\nthrown while parsing compiled template function:\n\n${annotation}`,
{ pos },
`[meriyah] ${message} while parsing compiled template function:\n\n${annotation}`,
position,
);
}

Expand Down

0 comments on commit 89ca698

Please sign in to comment.