Skip to content

Commit

Permalink
feat: expose error type (#276)
Browse files Browse the repository at this point in the history
  • Loading branch information
multivoltage authored Mar 18, 2024
1 parent f42ff51 commit 70bd7e3
Show file tree
Hide file tree
Showing 6 changed files with 147 additions and 18 deletions.
4 changes: 2 additions & 2 deletions src/compile.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { EtaError } from "./err.ts";
import { EtaParseError } from "./err.ts";

/* TYPES */
import type { Eta } from "./core.ts";
Expand Down Expand Up @@ -33,7 +33,7 @@ export function compile(this: Eta, str: string, options?: Partial<Options>): 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" +
Expand Down
36 changes: 32 additions & 4 deletions src/err.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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");
Expand All @@ -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

Expand Down
8 changes: 4 additions & 4 deletions src/file-handling.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { EtaError } from "./err.ts";
import { EtaFileResolutionError } from "./err.ts";

import * as path from "node:path";

Expand All @@ -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;
}
Expand All @@ -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;
Expand Down Expand Up @@ -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`);
}
}

Expand Down
7 changes: 7 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
4 changes: 2 additions & 2 deletions src/render.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { EtaError } from "./err.ts";
import { EtaNameResolutionError } from "./err.ts";

/* TYPES */
import type { Options } from "./config.ts";
Expand Down Expand Up @@ -31,7 +31,7 @@ function handleCache(this: Eta, template: string, options: Partial<Options>): Te
if (cachedTemplate) {
return cachedTemplate;
} else {
throw new EtaError("Failed to get template '" + template + "'");
throw new EtaNameResolutionError("Failed to get template '" + template + "'");
}
}
}
Expand Down
106 changes: 100 additions & 6 deletions test/err.spec.ts
Original file line number Diff line number Diff line change
@@ -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 <%
^`);
Expand All @@ -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
Expand All @@ -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}'`);
}
});
});

0 comments on commit 70bd7e3

Please sign in to comment.