diff --git a/src/compile.ts b/src/compile.ts index 687571b..49535f8 100644 --- a/src/compile.ts +++ b/src/compile.ts @@ -1,4 +1,4 @@ -import { EtaError } from "./err.ts"; +import { EtaParseError } from "./err.ts"; /* TYPES */ import type { Eta } from "./core.ts"; @@ -33,7 +33,7 @@ export function compile(this: Eta, str: string, options?: Partial): Tem ) as TemplateFunction; // eslint-disable-line no-new-func } catch (e) { if (e instanceof SyntaxError) { - throw new EtaError( + throw new EtaParseError( "Bad template syntax\n\n" + e.message + "\n" + diff --git a/src/err.ts b/src/err.ts index ca8aad0..cc158a7 100755 --- a/src/err.ts +++ b/src/err.ts @@ -5,11 +5,39 @@ export class EtaError extends Error { } } +export class EtaParseError extends EtaError { + constructor(message: string) { + super(message); + this.name = "EtaParser Error"; + } +} + +export class EtaRuntimeError extends EtaError { + constructor(message: string) { + super(message); + this.name = "EtaRuntime Error"; + } +} + +export class EtaFileResolutionError extends EtaError { + constructor(message: string) { + super(message); + this.name = "EtaFileResolution Error"; + } +} + +export class EtaNameResolutionError extends EtaError { + constructor(message: string) { + super(message); + this.name = "EtaNameResolution Error"; + } +} + /** * Throws an EtaError with a nicely formatted error and message showing where in the template the error occurred. */ -export function ParseErr(message: string, str: string, indx: number): void { +export function ParseErr(message: string, str: string, indx: number): never { const whitespace = str.slice(0, indx).split(/\n/); const lineNo = whitespace.length; @@ -26,10 +54,10 @@ export function ParseErr(message: string, str: string, indx: number): void { " " + Array(colNo).join(" ") + "^"; - throw new EtaError(message); + throw new EtaParseError(message); } -export function RuntimeErr(originalError: Error, str: string, lineNo: number, path: string): void { +export function RuntimeErr(originalError: Error, str: string, lineNo: number, path: string): never { // code gratefully taken from https://github.com/mde/ejs and adapted const lines = str.split("\n"); @@ -47,7 +75,7 @@ export function RuntimeErr(originalError: Error, str: string, lineNo: number, pa const header = filename ? filename + ":" + lineNo + "\n" : "line " + lineNo + "\n"; - const err = new EtaError(header + context + "\n\n" + originalError.message); + const err = new EtaRuntimeError(header + context + "\n\n" + originalError.message); err.name = originalError.name; // the original name (e.g. ReferenceError) may be useful diff --git a/src/file-handling.ts b/src/file-handling.ts index 38c80fb..c826ae1 100644 --- a/src/file-handling.ts +++ b/src/file-handling.ts @@ -1,4 +1,4 @@ -import { EtaError } from "./err.ts"; +import { EtaFileResolutionError } from "./err.ts"; import * as path from "node:path"; @@ -17,7 +17,7 @@ export function readFile(this: EtaCore, path: string): string { // eslint-disable-line @typescript-eslint/no-explicit-any } catch (err: any) { if (err?.code === "ENOENT") { - throw new EtaError(`Could not find template: ${path}`); + throw new EtaFileResolutionError(`Could not find template: ${path}`); } else { throw err; } @@ -36,7 +36,7 @@ export function resolvePath( const views = this.config.views; if (!views) { - throw new EtaError("Views directory is not defined"); + throw new EtaFileResolutionError("Views directory is not defined"); } const baseFilePath = options && options.filepath; @@ -80,7 +80,7 @@ export function resolvePath( return resolvedFilePath; } else { - throw new EtaError(`Template '${templatePath}' is not in the views directory`); + throw new EtaFileResolutionError(`Template '${templatePath}' is not in the views directory`); } } diff --git a/src/index.ts b/src/index.ts index 6899dc1..f756138 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,12 @@ import { Eta as EtaCore } from "./core.ts"; import { readFile, resolvePath } from "./file-handling.ts"; +export { + EtaError, + EtaParseError, + EtaRuntimeError, + EtaFileResolutionError, + EtaNameResolutionError, +} from "./err.ts"; export class Eta extends EtaCore { readFile = readFile; diff --git a/src/render.ts b/src/render.ts index f9c6d21..9f549bf 100644 --- a/src/render.ts +++ b/src/render.ts @@ -1,4 +1,4 @@ -import { EtaError } from "./err.ts"; +import { EtaNameResolutionError } from "./err.ts"; /* TYPES */ import type { Options } from "./config.ts"; @@ -31,7 +31,7 @@ function handleCache(this: Eta, template: string, options: Partial): Te if (cachedTemplate) { return cachedTemplate; } else { - throw new EtaError("Failed to get template '" + template + "'"); + throw new EtaNameResolutionError("Failed to get template '" + template + "'"); } } } diff --git a/test/err.spec.ts b/test/err.spec.ts index 25114b5..1c48652 100644 --- a/test/err.spec.ts +++ b/test/err.spec.ts @@ -1,17 +1,41 @@ /* global it, expect, describe */ import path from "path"; -import { Eta } from "../src/index"; +import { + Eta, + EtaError, + EtaParseError, + EtaRuntimeError, + EtaFileResolutionError, + EtaNameResolutionError, +} from "../src/index"; describe("ParseErr", () => { const eta = new Eta(); - it("error while parsing", () => { + it("error while parsing - renderString", () => { try { eta.renderString("template <%", {}); } catch (ex) { - expect((ex as Error).name).toBe("Eta Error"); - expect((ex as Error).message).toBe(`unclosed tag at line 1 col 10: + expect(ex).toBeInstanceOf(EtaError); + expect(ex).toBeInstanceOf(EtaParseError); + expect((ex as EtaParseError).name).toBe("EtaParser Error"); + expect((ex as EtaParseError).message).toBe(`unclosed tag at line 1 col 10: + + template <% + ^`); + expect(ex instanceof Error).toBe(true); + } + }); + + it("error while parsing - compile", () => { + try { + eta.compile("template <%"); + } catch (ex) { + expect(ex).toBeInstanceOf(EtaError); + expect(ex).toBeInstanceOf(EtaParseError); + expect((ex as EtaParseError).name).toBe("EtaParser Error"); + expect((ex as EtaParseError).message).toBe(`unclosed tag at line 1 col 10: template <% ^`); @@ -29,8 +53,10 @@ describe("RuntimeErr", () => { try { eta.render("./runtime-error", {}); } catch (ex) { - expect((ex as Error).name).toBe("ReferenceError"); - expect((ex as Error).message).toBe(`${errorFilepath}:2 + expect(ex).toBeInstanceOf(EtaError); + expect(ex).toBeInstanceOf(EtaRuntimeError); + expect((ex as EtaRuntimeError).name).toBe("ReferenceError"); + expect((ex as EtaRuntimeError).message).toBe(`${errorFilepath}:2 1| >> 2| <%= undefinedVariable %> 3| Lorem Ipsum @@ -39,3 +65,71 @@ undefinedVariable is not defined`); } }); }); + +describe("EtaFileResolutionError", () => { + it("error throws correctly when template does not exist", () => { + const eta = new Eta({ debug: true, views: path.join(__dirname, "templates") }); + const errorFilepath = path.join(__dirname, "templates/not-existing-template.eta"); + + try { + eta.render("./not-existing-template", {}); + } catch (ex) { + expect(ex).toBeInstanceOf(EtaError); + expect(ex).toBeInstanceOf(EtaFileResolutionError); + expect((ex as EtaFileResolutionError).name).toBe("EtaFileResolution Error"); + expect((ex as EtaFileResolutionError).message).toBe( + `Could not find template: ${errorFilepath}` + ); + } + }); + + it("error throws correctly when views options is missing", async () => { + const eta = new Eta({ debug: true }); + try { + eta.render("Hi", {}); + } catch (ex) { + expect(ex).toBeInstanceOf(EtaFileResolutionError); + expect((ex as EtaFileResolutionError).name).toBe("EtaFileResolution Error"); + expect((ex as EtaFileResolutionError).message).toBe("Views directory is not defined"); + } + + try { + await eta.renderAsync("Hi", {}); + } catch (ex) { + expect(ex).toBeInstanceOf(EtaFileResolutionError); + expect((ex as EtaFileResolutionError).name).toBe("EtaFileResolution Error"); + expect((ex as EtaFileResolutionError).message).toBe("Views directory is not defined"); + } + }); + + it("error throws correctly when template in not in th view directory", () => { + const eta = new Eta({ debug: true, views: path.join(__dirname, "templates") }); + + const filePath = "../../../simple.eta"; + try { + eta.render(filePath, {}); + } catch (ex) { + expect(ex).toBeInstanceOf(EtaFileResolutionError); + expect((ex as EtaFileResolutionError).name).toBe("EtaFileResolution Error"); + expect((ex as EtaFileResolutionError).message).toBe( + `Template '${filePath}' is not in the views directory` + ); + } + }); +}); + +describe("EtaNameResolutionError", () => { + const eta = new Eta({ debug: true, views: path.join(__dirname, "templates") }); + + it("error throws correctly", () => { + const template = "@not-existing-tp"; + + try { + eta.render(template, {}); + } catch (ex) { + expect(ex).toBeInstanceOf(EtaNameResolutionError); + expect((ex as EtaNameResolutionError).name).toBe("EtaNameResolution Error"); + expect((ex as EtaNameResolutionError).message).toBe(`Failed to get template '${template}'`); + } + }); +});