Skip to content

Commit

Permalink
feat: add pretty runtime errors and functionHeader option
Browse files Browse the repository at this point in the history
  • Loading branch information
nebrelbug committed May 30, 2023
1 parent a3d4fbf commit df82f2c
Show file tree
Hide file tree
Showing 7 changed files with 71 additions and 37 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@
"size-limit": [
{
"path": "dist/browser.umd.js",
"limit": "3 KB"
"limit": "3.5 KB"
}
],
"lint-staged": {
Expand Down
25 changes: 16 additions & 9 deletions src/compile-string.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,29 +17,34 @@ export function compileToString(this: Eta, str: string, options?: Partial<Option
const buffer: Array<AstObject> = this.parse.call(this, str);

// note: when the include function passes through options, the only parameter that matters is the filepath parameter
let res = `
let res = `${config.functionHeader}
let include = (template, data) => this.render(template, data, options);
let includeAsync = (template, data) => this.renderAsync(template, data, options);
let __eta = {res: "", e: this.config.escapeFunction, f: this.config.filterFunction};
let __eta = {res: "", e: this.config.escapeFunction, f: this.config.filterFunction${
config.debug
? ', line: 1, templateStr: "' +
str.replace(/\\|'/g, "\\$&").replace(/\r\n|\n|\r/g, "\\n") +
'"'
: ""
}};
function layout(path, data) {
__eta.layout = path;
__eta.layoutData = data;
}
${config.useWith ? "with(" + config.varName + "||{}){" : ""}
}${config.debug ? "try {" : ""}${config.useWith ? "with(" + config.varName + "||{}){" : ""}
${compileBody.call(this, buffer)}
if (__eta.layout) {
__eta.res = ${isAsync ? "await includeAsync" : "include"} (__eta.layout, {...${
config.varName
}, body: __eta.res, ...__eta.layoutData});
}
${config.useWith ? "}" : ""}
${config.useWith ? "}" : ""}${
config.debug
? "} catch (e) { this.RuntimeErr(e, __eta.templateStr, __eta.line, options.filepath) }"
: ""
}
return __eta.res;
`;

Expand Down Expand Up @@ -85,6 +90,8 @@ function compileBody(this: Eta, buff: Array<AstObject>) {
const type = currentBlock.t; // "r", "e", or "i"
let content = currentBlock.val || "";

if (config.debug) returnStr += "__eta.line=" + currentBlock.lineNo + "\n";

if (type === "r") {
// raw

Expand Down
14 changes: 12 additions & 2 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,20 @@ export interface EtaConfig {
/** Whether or not to cache templates if `name` or `filename` is passed */
cache: boolean;

/** Holds cache of resolved filepaths. Set to `false` to disable. */
cacheFilepaths: boolean;

/** Whether to pretty-format error messages (introduces runtime penalties) */
debug: boolean;

/** Function to XML-sanitize interpolations */
escapeFunction: (str: unknown) => string;

/** Function applied to all interpolations when autoFilter is true */
filterFunction: (val: unknown) => string;

/** Holds cache of resolved filepaths. Set to `false` to disable. */
cacheFilepaths: boolean;
/** Raw JS code inserted in the template function. Useful for declaring global variables for user templates */
functionHeader: string;

/** Parsing options */
parse: {
Expand Down Expand Up @@ -72,9 +80,11 @@ const defaultConfig: EtaConfig = {
autoTrim: [false, "nl"],
cache: false,
cacheFilepaths: true,
debug: false,
escapeFunction: XMLEscape,
// default filter function (not used unless enables) just stringifies the input
filterFunction: (val) => String(val),
functionHeader: "",
parse: {
exec: "",
interpolate: "=",
Expand Down
12 changes: 9 additions & 3 deletions src/core.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { defaultConfig } from "./config.js";
import { Cacher } from "./storage.js";
import { parse } from "./parse.js";
import { compile } from "./compile.js";
import { compileToString } from "./compile-string.js";
import { defaultConfig } from "./config.js";
import { parse } from "./parse.js";
import { render, renderAsync, renderString, renderStringAsync } from "./render.js";
import { RuntimeErr, EtaError } from "./err.js";
import { TemplateFunction } from "./compile.js";

/* TYPES */
Expand All @@ -21,9 +22,11 @@ export class Eta {

config: EtaConfig;

parse = parse;
RuntimeErr = RuntimeErr;

compile = compile;
compileToString = compileToString;
parse = parse;
render = render;
renderAsync = renderAsync;
renderString = renderString;
Expand Down Expand Up @@ -67,3 +70,6 @@ export class Eta {
}
}
}

// for instance checking against thrown errors
export { EtaError };
27 changes: 25 additions & 2 deletions src/err.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ export class EtaError extends Error {
* Throws an EtaError with a nicely formatted error and message showing where in the template the error occurred.
*/

// TODO: make more advanced

export function ParseErr(message: string, str: string, indx: number): void {
const whitespace = str.slice(0, indx).split(/\n/);

Expand All @@ -30,3 +28,28 @@ export function ParseErr(message: string, str: string, indx: number): void {
"^";
throw new EtaError(message);
}

export function RuntimeErr(originalError: Error, str: string, lineNo: number, path: string): void {
// code gratefully taken from https://github.com/mde/ejs and adapted

const lines = str.split("\n");
const start = Math.max(lineNo - 3, 0);
const end = Math.min(lines.length, lineNo + 3);
const filename = path;
// Error context
const context = lines
.slice(start, end)
.map(function (line, i) {
const curr = i + start + 1;
return (curr == lineNo ? " >> " : " ") + curr + "| " + line;
})
.join("\n");

const header = filename ? filename + ":" + lineNo + "\n" : "line " + lineNo + "\n";

const err = new EtaError(header + context + "\n\n" + originalError.message);

err.name = originalError.name; // the original name (e.g. ReferenceError) may be useful

throw err;
}
8 changes: 8 additions & 0 deletions src/parse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export type TagType = "r" | "e" | "i" | "";
export interface TemplateObject {
t: TagType;
val: string;
lineNo?: number;
}

export type AstObject = string | TemplateObject;
Expand All @@ -29,6 +30,10 @@ function escapeRegExp(string: string) {
return string.replace(/[.*+\-?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string
}

function getLineNo(str: string, index: number) {
return str.slice(0, index).split("\n").length;
}

export function parse(this: Eta, str: string): Array<AstObject> {
const config = this.config;

Expand Down Expand Up @@ -183,6 +188,9 @@ export function parse(this: Eta, str: string): Array<AstObject> {
}
}
if (currentObj) {
if (config.debug) {
currentObj.lineNo = getLineNo(str, m.index);
}
buffer.push(currentObj);
} else {
ParseErr("unclosed tag", str, m.index + precedingString.length);
Expand Down
20 changes: 0 additions & 20 deletions test/compile-string.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +24,13 @@ function layout(path, data) {
__eta.layoutData = data;
}
__eta.res+='hi '
__eta.res+=__eta.e(it.name)
if (__eta.layout) {
__eta.res = include (__eta.layout, {...it, body: __eta.res, ...__eta.layoutData});
}
return __eta.res;
`);
});
Expand All @@ -53,18 +48,13 @@ function layout(path, data) {
__eta.layoutData = data;
}
__eta.res+='hi '
__eta.res+=it.name
if (__eta.layout) {
__eta.res = include (__eta.layout, {...it, body: __eta.res, ...__eta.layoutData});
}
return __eta.res;
`);
});
Expand All @@ -82,19 +72,14 @@ function layout(path, data) {
__eta.layoutData = data;
}
__eta.res+='hi'
__eta.res+=__eta.e(it.firstname)
__eta.res+=__eta.e(it.lastname)
if (__eta.layout) {
__eta.res = include (__eta.layout, {...it, body: __eta.res, ...__eta.layoutData});
}
return __eta.res;
`);
});
Expand All @@ -112,8 +97,6 @@ function layout(path, data) {
__eta.layoutData = data;
}
__eta.res+='Hi\\n'
console.log("Hope you like Eta!")
__eta.res+=__eta.e(it.htmlstuff)
Expand All @@ -138,13 +121,10 @@ __eta.res+=' \\n '
__eta.res+='\\nThis is a partial: '
__eta.res+=include("mypartial")
if (__eta.layout) {
__eta.res = include (__eta.layout, {...it, body: __eta.res, ...__eta.layoutData});
}
return __eta.res;
`);
});
Expand Down

0 comments on commit df82f2c

Please sign in to comment.