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

feat: expose error type #276

Merged
merged 7 commits into from
Mar 18, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
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}'`);
}
});
});
Loading