Skip to content

Commit

Permalink
ParseTagHookTypes
Browse files Browse the repository at this point in the history
  • Loading branch information
kawanet committed Aug 27, 2023
1 parent cb82144 commit e5e1315
Show file tree
Hide file tree
Showing 5 changed files with 113 additions and 8 deletions.
2 changes: 1 addition & 1 deletion src/parser/attr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const isSafeKey = (key: string) => /^[A-Za-z_]\w+$/.test(key);
/**
* Parser for HTML tag attributes <tagName attr="value"/>
*/
export class Attr implements NSP.Transpiler {
export class Attr implements NSP.AttrParser<any> {
protected src: string;
private index: { [key: string]: string };

Expand Down
15 changes: 9 additions & 6 deletions src/parser/tag.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,14 +117,17 @@ export class Tag implements NSP.Transpiler {

const attr = new Attr(app, this.src);

const bodyJS = this.getBodyJS(option);
const body = this.getBodyJS(option);

const spaces = +indent ? " ".repeat(+indent) : (indent ?? "");
const currentLF = option?.LF ?? "\n";
const nextLF = currentLF + spaces;
const tagOption = {LF: (bodyJS ? nextLF : currentLF)};
const tagOption = {LF: (body ? nextLF : currentLF)};

const tagJS = this.getTagJS(attr, bodyJS, tagOption);
const type = `parse.tag.${tagName}`;
const def: NSP.TagParserDef<any> = {app, name: tagName, attr, body};

const tagJS = app.process<string>(type, def) ?? this.getTagJS(def, tagOption);

return commentJS ? commentJS + tagJS : tagJS;
}
Expand All @@ -137,19 +140,19 @@ export class Tag implements NSP.Transpiler {
return `// ${src?.replace(/\s*[\r\n]\s*/g, " ") ?? ""}${currentLF}`;
}

private getTagJS(attr: Attr, bodyJS: string, option: NSP.ToJSOption): string {
private getTagJS(def: NSP.TagParserDef<any>, option: NSP.ToJSOption): string {
const {app, tagName} = this;
const {nspName, vName} = app.options;

const attrRaw = attr.toJS(option);
const attrRaw = def.attr.toJS(option);

// transpile attributes to array function if they include variables
const hasVars = /\(.+?\)|\$\{.+?}/s.test(attrRaw);
const attrJS = hasVars ? `${vName} => (${attrRaw})` : attrRaw;

const nameJS = JSON.stringify(tagName);
const hasAttr = /:/.test(attrRaw);
const restJS = bodyJS ? (`, ${attrJS}, ${bodyJS}`) : (hasAttr ? `, ${attrJS}` : "");
const restJS = def.body ? (`, ${attrJS}, ${def.body}`) : (hasAttr ? `, ${attrJS}` : "");

return `${nspName}.tag(${nameJS}${restJS})`;
}
Expand Down
74 changes: 74 additions & 0 deletions test/410.hook-parse-tag.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import {strict as assert} from "assert";
import {createNSP} from "../index.js";

const TITLE = "410.hook-parse-tag.ts";

interface Context {
foo?: string;
bar?: string;
buz?: string;
}

/**
* <c:set>
*/
interface SetTagAttr {
var?: string;
value?: string;
}

/**
* <c:out>
*/
interface OutTagAttr {
value: string;
default?: string;
}

describe(TITLE, () => {
const nsp = createNSP({nullish: true});

nsp.hook<SetTagAttr>("parse.tag.c:set", tag => {
const {vName} = tag.app.options;
const varJS = tag.attr.get("var");
const isSimple = /^"[A-Za-z]\w*"$/.test(varJS);
const accessor = isSimple ? "." + JSON.parse(varJS) : `[${varJS}]`;
const value = tag.attr.get("value");

return `${vName} => { ${vName}${accessor} = ${value} }`;
});

nsp.hook<OutTagAttr>("parse.tag.c:out", tag => {
const {vName} = tag.app.options;
const value = tag.attr.get("value");
const defaultJS = tag.attr.get("default");

if (defaultJS) {
return `${vName} => (${value} || ${defaultJS})`;
} else {
return `${vName} => (${value})`;
}
});

it("<c:out/>", async () => {
const parsed = nsp.parse('[<c:out value="${bar}"/>]');
// console.warn(parsed.toJS());
const render = parsed.toFn<Context>();

assert.equal(render({bar: "Bar"}), "[Bar]");
});

it("<c:set/><c:out/>", async () => {
const parsed = nsp.parse('[<c:set var="bar" value="${foo}"/>][<c:out value="${bar}" default="${buz}"/>]');
// console.warn(parsed.toJS());
const render = parsed.toFn<Context>();

assert.equal(render({}), "[][]");

assert.equal(render({foo: "Foo"}), "[][Foo]");

assert.equal(render({bar: "Bar"}), "[][]");

assert.equal(render({buz: "Buz"}), "[][Buz]");
});
});
17 changes: 16 additions & 1 deletion types/hooks.d.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import type {NSP} from "./index";

type RuntimeErrorHookType = "error";

type RuntimeScriptletHookTypes =
Expand Down Expand Up @@ -36,12 +38,15 @@ type AfterParseHookTypes =
| "after.parse.jsp"
| "after.parse.text";

type ParseTagHookTypes = `parse.tag.${string}`;

type KnownHookTypes =
RuntimeErrorHookType
| RuntimeScriptletHookTypes
| BeforeParseHookTypes
| ParseHookTypes
| AfterParseHookTypes;
| AfterParseHookTypes
| ParseTagHookTypes;

export interface Hooks {
/**
Expand Down Expand Up @@ -81,6 +86,16 @@ export interface Hooks {
*/
hook(type: AfterParseHookTypes, fn: (src: string) => string | void): void;

/**
* hooks called with TagParserDef to transpile tag implementation inline.
*
* @example
* nsp.hook("parse.tag.c:set", tag => {
* return `v => { v[${tag.attr.get("var")}] = ${tag.attr.get("value")} }`;
* });
*/
hook<A>(type: ParseTagHookTypes, fn: (tag: NSP.TagParserDef<A>) => string | void): void;

/**
* ==== OTHER HOOKS ====
*/
Expand Down
13 changes: 13 additions & 0 deletions types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -216,4 +216,17 @@ export declare namespace NSP {
*/
toFn<T>(): NodeFn<T>;
}

interface TagParserDef<A> {
app: App;
name: string;
attr: AttrParser<A>;
body: string;
}

interface AttrParser<A> extends Transpiler {
keys(): (keyof A)[];

get(key: keyof A): string; // JavaScript
}
}

0 comments on commit e5e1315

Please sign in to comment.