Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement TransformError/TemplateError subclasses #89

Merged
merged 3 commits into from
Dec 7, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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";
}
66 changes: 66 additions & 0 deletions src/errors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
class VentoError extends Error {
constructor(
message?: string,
public override cause?: Error,
) {
super(message);
Error?.captureStackTrace(this, this.constructor);
this.name = this.constructor.name;
}
}
noelforte marked this conversation as resolved.
Show resolved Hide resolved

export class TemplateError extends VentoError {
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 VentoError {
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
Loading