From af8732ea6ff90f2dcdddb5024dcea3f2bc88efa8 Mon Sep 17 00:00:00 2001 From: Noel Forte Date: Thu, 5 Dec 2024 19:09:02 -0500 Subject: [PATCH] declare subclasses for `TemplateError` and `TransformError`, migrate all implementations to new classes --- src/environment.ts | 61 +++++++----------------------------------- src/errors.ts | 66 ++++++++++++++++++++++++++++++++++++++++++++++ src/transformer.ts | 29 +++++++------------- 3 files changed, 85 insertions(+), 71 deletions(-) create mode 100644 src/errors.ts diff --git a/src/environment.ts b/src/environment.ts index cd5040f..7f2e4c2 100644 --- a/src/environment.ts +++ b/src/environment.ts @@ -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; @@ -138,12 +139,12 @@ 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 }); } } @@ -151,6 +152,7 @@ export class Environment { "__file", "__env", "__defaults", + "__err", `return${sync ? "" : " async"} function (${dataVarname}) { let __pos = 0; try { @@ -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; @@ -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) { @@ -310,22 +312,6 @@ export class Environment { return output; } - - createError( - path: string = "unknown", - source: string = "", - 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) { @@ -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"; } diff --git a/src/errors.ts b/src/errors.ts new file mode 100644 index 0000000..2cdeeba --- /dev/null +++ b/src/errors.ts @@ -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; + } +} + +export class TemplateError extends VentoError { + constructor( + public path: string = "", + public source: string = "", + 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] }; +} diff --git a/src/transformer.ts b/src/transformer.ts index 73355f6..a166bb7 100644 --- a/src/transformer.ts +++ b/src/transformer.ts @@ -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; @@ -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 = [ @@ -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, ); }