Skip to content

Commit

Permalink
Merge pull request #88 from noelforte/main
Browse files Browse the repository at this point in the history
Improve transform error handling with compiled function annotations
  • Loading branch information
oscarotero authored Dec 4, 2024
2 parents ecbcf8a + 0fe3383 commit 2ab138a
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 36 deletions.
2 changes: 1 addition & 1 deletion deps.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export * as path from "jsr:@std/[email protected]";
export * as path from "https://deno.land/[email protected]/path/mod.ts";
export * as html from "jsr:@std/[email protected]";

export * as astring from "jsr:@davidbonnet/[email protected]";
Expand Down
41 changes: 10 additions & 31 deletions src/environment.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import tokenize, { Token } from "./tokenizer.ts";

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

export interface TemplateResult {
content: string;
Expand Down Expand Up @@ -139,21 +139,8 @@ export class Environment {
try {
code = transformTemplateCode(code, dataVarname);
} catch (error) {
const end = (error as ParseError).start;
const lastPos = code.slice(0, end).lastIndexOf("__pos = ");
const lastPosEnd = code.slice(lastPos).indexOf(";");

if (lastPos > -1 && lastPosEnd > lastPos) {
const pos = parseInt(
code.slice(lastPos + 8, lastPos + lastPosEnd),
10,
);
throw this.createError(
path || "",
source,
pos,
new Error((error as ParseError).message),
);
if (error instanceof TransformError) {
throw this.createError(path, source, error.pos, error);
}

throw error;
Expand Down Expand Up @@ -192,7 +179,7 @@ export class Environment {
const { position, error } = result;

if (error) {
throw this.createError(path || "unknown", source, position, error);
throw this.createError(path, source, position, error);
}

for (const tokenPreprocessor of this.tokenPreprocessors) {
Expand Down Expand Up @@ -325,19 +312,17 @@ export class Environment {
}

createError(
path: string,
source: string,
position: number,
path: string = "unknown",
source: string = "<empty file>",
position: number = 0,
cause: Error,
): Error {
if (!source) {
return cause;
}

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}\n`,
`Error in the template ${path}:${line}:${column}\n\n${code.trim()}\n\n${
cause.message.replaceAll(/^/gm, "> ")
}\n`,
{ cause },
);
}
Expand Down Expand Up @@ -386,9 +371,3 @@ export function errorLine(
function checkAsync(fn: () => unknown): boolean {
return fn.constructor?.name === "AsyncFunction";
}

interface ParseError extends Error {
start: number;
end: number;
range: [number, number];
}
52 changes: 48 additions & 4 deletions src/transformer.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,27 @@
import { astring, ESTree, meriyah, walker } from "../deps.ts";

// Declare types
interface ParseError extends Error {
start: number;
end: number;
range: [number, number];
loc: Record<"start" | "end", Record<"line" | "column", number>>;
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 @@ -119,7 +141,29 @@ export function transformTemplateCode(
return code;
}

const parsed = meriyah.parseScript(code, { module: true }) as ESTree.Program;
let parsed;
try {
parsed = meriyah.parseScript(code, { module: true }) as ESTree.Program;
} catch (error) {
const { message, start, loc } = error as ParseError;

// 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";

// Grab the last instance of Vento's `__pos` variable before the
// error was thrown. Pass this back to Vento's createError 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]);

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

const tracker = new ScopeTracker();

const exclude = [
Expand All @@ -128,11 +172,11 @@ export function transformTemplateCode(
];

if (parsed.type !== "Program") {
throw new Error("Expected a program");
throw new TransformError("[meriyah] Expected a program");
}

if (parsed.body.length === 0) {
throw new Error("Empty program");
throw new TransformError("[meriyah] Empty program");
}

// Transforms an identifier to a MemberExpression
Expand Down Expand Up @@ -166,7 +210,7 @@ export function transformTemplateCode(
}

walker.walk(parsed, {
enter(node) {
enter(node: ESTree.Node) {
switch (node.type) {
// Track variable declarations
case "VariableDeclaration":
Expand Down

0 comments on commit 2ab138a

Please sign in to comment.