[REF] parser: make AST definition more consistent

This commit is contained in:
Géry Debongnie
2022-03-07 14:31:17 +01:00
committed by Samuel Degueldre
parent 56086242bb
commit b3062d29f1
10 changed files with 356 additions and 293 deletions
+1 -1
View File
@@ -27,7 +27,7 @@ function callSlot(
const { __render, __ctx, __scope } = slots[name] || {};
const slotScope = Object.create(__ctx || {});
if (__scope) {
slotScope[__scope] = extra || {};
slotScope[__scope] = extra;
}
const slotBDom = __render ? __render.call(__ctx.__owl__.component, slotScope, parent, key) : null;
if (defaultContent) {
+12 -17
View File
@@ -20,6 +20,8 @@ import {
ASTTranslation,
ASTType,
ASTTPortal,
EventHandlers,
Attrs,
} from "./parser";
type BlockType = "block" | "text" | "multi" | "list" | "html" | "comment";
@@ -157,11 +159,11 @@ class CodeTarget {
// maps ref name to [id, expr]
refInfo: { [name: string]: [string, string] } = {};
shouldProtectScope: boolean = false;
on?: { [key: string]: string };
on: EventHandlers | null;
constructor(name: string, on?: { [key: string]: string }) {
constructor(name: string, on?: EventHandlers | null) {
this.name = name;
this.on = on;
this.on = on || null;
}
addLine(line: string, idx?: number) {
@@ -305,12 +307,7 @@ export class CodeGenerator {
return code;
}
compileInNewTarget(
prefix: string,
ast: AST,
ctx: Context,
on?: { [key: string]: string }
): string {
compileInNewTarget(prefix: string, ast: AST, ctx: Context, on?: EventHandlers | null): string {
const name = this.generateId(prefix);
const initialTarget = this.target;
const target = new CodeTarget(name, on);
@@ -562,7 +559,7 @@ export class CodeGenerator {
}
}
// attributes
const attrs: { [key: string]: string } = {};
const attrs: Attrs = {};
const nameSpace = ast.ns || ctx.nameSpace;
if (nameSpace && isNewBlock) {
// specific namespace uri
@@ -1098,19 +1095,17 @@ export class CodeGenerator {
compileComponent(ast: ASTComponent, ctx: Context) {
let { block } = ctx;
// props
const hasSlotsProp = "slots" in ast.props;
const hasSlotsProp = "slots" in (ast.props || {});
const props: string[] = [];
const propExpr = this.formatPropObject(ast.props);
const propExpr = this.formatPropObject(ast.props || {});
if (propExpr) {
props.push(propExpr);
}
// slots
const hasSlot = !!Object.keys(ast.slots).length;
let slotDef: string = "";
if (hasSlot) {
if (ast.slots) {
let ctxStr = "ctx";
if (this.target.loopLevel || !this.hasSafeContext) {
ctxStr = this.generateId("ctx");
@@ -1120,7 +1115,7 @@ export class CodeGenerator {
let slotStr: string[] = [];
for (let slotName in ast.slots) {
const slotAst = ast.slots[slotName];
const name = this.compileInNewTarget("slot", slotAst.content, ctx, slotAst.on || undefined);
const name = this.compileInNewTarget("slot", slotAst.content, ctx, slotAst.on);
const params = [`__render: ${name}, __ctx: ${ctxStr}`];
const scope = ast.slots[slotName].scope;
if (scope) {
@@ -1199,7 +1194,7 @@ export class CodeGenerator {
this.insertBlock(blockExpr, block, ctx);
}
wrapWithEventCatcher(expr: string, on: { [key: string]: string }): string {
wrapWithEventCatcher(expr: string, on: EventHandlers): string {
this.helpers.add("createCatcher");
let name = this.generateId("catcher");
let spec: any = {};
+37 -35
View File
@@ -2,6 +2,9 @@
// AST Type definition
// -----------------------------------------------------------------------------
export type EventHandlers = { [eventName: string]: string };
export type Attrs = { [attrs: string]: string };
export const enum ASTType {
Text,
Comment,
@@ -34,25 +37,25 @@ export interface ASTComment {
}
interface TModelInfo {
hasDynamicChildren?: boolean;
baseExpr: string;
expr: string;
targetAttr: string;
specialInitTargetAttr: string | null;
eventType: "change" | "click" | "input";
shouldTrim: boolean;
shouldNumberize: boolean;
hasDynamicChildren: boolean;
specialInitTargetAttr: string | null;
}
export interface ASTDomNode {
type: ASTType.DomNode;
tag: string;
dynamicTag: string | null;
attrs: { [key: string]: string };
content: AST[];
attrs: Attrs | null;
ref: string | null;
on: { [key: string]: string };
model?: TModelInfo | null;
on: EventHandlers | null;
model: TModelInfo | null;
dynamicTag: string | null;
ns: string | null;
}
@@ -93,13 +96,13 @@ export interface ASTTForEach {
type: ASTType.TForEach;
collection: string;
elem: string;
key: string | null;
body: AST;
memo: string;
hasNoFirst: boolean;
hasNoLast: boolean;
hasNoIndex: boolean;
hasNoValue: boolean;
key: string | null;
}
export interface ASTTKey {
@@ -116,9 +119,9 @@ export interface ASTTCall {
interface SlotDefinition {
content: AST;
attrs?: { [key: string]: string };
scope?: string;
on?: null | { [key: string]: string };
scope: string | null;
on: EventHandlers | null;
attrs: Attrs | null;
}
export interface ASTComponent {
@@ -126,16 +129,16 @@ export interface ASTComponent {
name: string;
isDynamic: boolean;
dynamicProps: string | null;
on: null | { [key: string]: string };
props: { [name: string]: string };
slots: { [name: string]: SlotDefinition };
on: EventHandlers | null;
props: { [name: string]: string } | null;
slots: { [name: string]: SlotDefinition } | null;
}
export interface ASTSlot {
type: ASTType.TSlot;
name: string;
attrs: { [key: string]: string };
on: null | { [key: string]: string };
attrs: Attrs | null;
on: EventHandlers | null;
defaultContent: AST | null;
}
@@ -328,8 +331,8 @@ function parseDOMNode(node: Element, ctx: ParsingContext): AST | null {
node.removeAttribute("t-ref");
const nodeAttrsNames = node.getAttributeNames();
const attrs: ASTDomNode["attrs"] = {};
const on: ASTDomNode["on"] = {};
let attrs: ASTDomNode["attrs"] = null;
let on: EventHandlers | null = null;
let model: TModelInfo | null = null;
for (let attr of nodeAttrsNames) {
@@ -338,6 +341,7 @@ function parseDOMNode(node: Element, ctx: ParsingContext): AST | null {
if (attr === "t-on") {
throw new Error("Missing event name with t-on directive");
}
on = on || {};
on[attr.slice(5)] = value;
} else if (attr.startsWith("t-model")) {
if (!["input", "select", "textarea"].includes(tagName)) {
@@ -375,6 +379,7 @@ function parseDOMNode(node: Element, ctx: ParsingContext): AST | null {
targetAttr: isCheckboxInput ? "checked" : "value",
specialInitTargetAttr: isRadioInput ? "checked" : null,
eventType,
hasDynamicChildren: false,
shouldTrim: hasTrimMod && (isOtherInput || isTextarea),
shouldNumberize: hasNumberMod && (isOtherInput || isTextarea),
};
@@ -393,6 +398,7 @@ function parseDOMNode(node: Element, ctx: ParsingContext): AST | null {
if (tModel && ["t-att-value", "t-attf-value"].includes(attr)) {
tModel.hasDynamicChildren = true;
}
attrs = attrs || {};
attrs[attr] = value;
}
}
@@ -563,7 +569,7 @@ function parseTCall(node: Element, ctx: ParsingContext): AST | null {
if (ast && ast.type === ASTType.TComponent) {
return {
...ast,
slots: { default: { content: tcall } },
slots: { default: { content: tcall, scope: null, on: null, attrs: null } },
};
}
}
@@ -694,7 +700,7 @@ function parseComponent(node: Element, ctx: ParsingContext): AST | null {
node.removeAttribute("t-slot-scope");
let on: ASTComponent["on"] = null;
const props: ASTComponent["props"] = {};
let props: ASTComponent["props"] = null;
for (let name of node.getAttributeNames()) {
const value = node.getAttribute(name)!;
if (name.startsWith("t-")) {
@@ -706,11 +712,12 @@ function parseComponent(node: Element, ctx: ParsingContext): AST | null {
throw new Error(message || `unsupported directive on Component: ${name}`);
}
} else {
props = props || {};
props[name] = value;
}
}
const slots: ASTComponent["slots"] = {};
let slots: ASTComponent["slots"] | null = null;
if (node.hasChildNodes()) {
const clone = <Element>node.cloneNode(true);
@@ -743,38 +750,32 @@ function parseComponent(node: Element, ctx: ParsingContext): AST | null {
slotNode.remove();
const slotAst = parseNode(slotNode, ctx);
if (slotAst) {
const slotInfo: any = { content: slotAst };
let on: SlotDefinition["on"] = null;
const attrs: { [key: string]: string } = {};
let attrs: Attrs | null = null;
let scope: string | null = null;
for (let attributeName of slotNode.getAttributeNames()) {
const value = slotNode.getAttribute(attributeName)!;
if (attributeName === "t-slot-scope") {
slotInfo.scope = value;
scope = value;
continue;
} else if (attributeName.startsWith("t-on-")) {
on = on || {};
on[attributeName.slice(5)] = value;
} else {
attrs = attrs || {};
attrs[attributeName] = value;
}
}
if (Object.keys(attrs).length) {
slotInfo.attrs = attrs;
}
if (on) {
slotInfo.on = on;
}
slots[name] = slotInfo;
slots = slots || {};
slots[name] = { content: slotAst, on, attrs, scope };
}
}
// default slot
const defaultContent = parseChildNodes(clone, ctx);
if (defaultContent) {
slots.default = { content: defaultContent };
if (defaultSlotScope) {
slots.default.scope = defaultSlotScope;
}
slots = slots || {};
slots.default = { content: defaultContent, on, attrs: null, scope: defaultSlotScope };
}
}
return { type: ASTType.TComponent, name, isDynamic, dynamicProps, props, slots, on };
@@ -790,7 +791,7 @@ function parseTSlot(node: Element, ctx: ParsingContext): AST | null {
}
const name = node.getAttribute("t-slot")!;
node.removeAttribute("t-slot");
const attrs: { [key: string]: string } = {};
let attrs: Attrs | null = null;
let on: ASTComponent["on"] = null;
for (let attributeName of node.getAttributeNames()) {
const value = node.getAttribute(attributeName)!;
@@ -798,6 +799,7 @@ function parseTSlot(node: Element, ctx: ParsingContext): AST | null {
on = on || {};
on[attributeName.slice(5)] = value;
} else {
attrs = attrs || {};
attrs[attributeName] = value;
}
}