[FIX] compiler: fix t-call at root of template causing crashes

Because the default content of a t-call is compiled before the block of
the t-call is created, the default content can sometimes incorrectly be
treated as though it is at the root of the template. This causes various
issues, in the case of a t-if, the default content will just replace the
t-call entirely when truthy, and when falsy the template will not return
anything, leading to a crash. Similar things occur with t-foreach and
t-out with a default content.

This commit fixes that by moving the block creation for the t-call ahead
of the compilation of the default content. In doing so, it makes the
context attribute "preventRoot" obsolete: the purpose of this attribute
is to somehow make the code aware that the root of the template will be
defined later, but because it wasn't propagated everywhere properly it
caused this issue. Now that the root already exists before we compile
the default content, we don't need it anymore.
This commit is contained in:
Samuel Degueldre
2023-08-04 08:52:53 +02:00
committed by Lucas Perais - lpe@odoo
parent 28672096a2
commit ba34811f71
7 changed files with 263 additions and 97 deletions
+10 -14
View File
@@ -160,7 +160,6 @@ interface Context {
block: BlockDescription | null;
index: number | string;
forceNewBlock: boolean;
preventRoot?: boolean;
isLast?: boolean;
translate: boolean;
tKeyExpr: string | null;
@@ -377,7 +376,7 @@ export class CodeGenerator {
): BlockDescription {
const hasRoot = this.target.hasRoot;
const block = new BlockDescription(this.target, type);
if (!hasRoot && !ctx.preventRoot) {
if (!hasRoot) {
this.target.hasRoot = true;
block.isRoot = true;
}
@@ -403,7 +402,7 @@ export class CodeGenerator {
blockExpr = `toggler(${ctx.tKeyExpr}, ${blockExpr})`;
}
if (block.isRoot && !ctx.preventRoot) {
if (block.isRoot) {
if (this.target.on) {
blockExpr = this.wrapWithEventCatcher(blockExpr, this.target.on);
}
@@ -989,7 +988,6 @@ export class CodeGenerator {
block,
index,
forceNewBlock: !isTSet,
preventRoot: ctx.preventRoot,
isLast: ctx.isLast && i === l - 1,
});
this.compileAST(child, subCtx);
@@ -1025,24 +1023,24 @@ export class CodeGenerator {
ctxVar = generateId("ctx");
this.addLine(`let ${ctxVar} = ${compileExpr(ast.context)};`);
}
const isDynamic = INTERP_REGEXP.test(ast.name);
const subTemplate = isDynamic ? interpolate(ast.name) : "`" + ast.name + "`";
if (block && !forceNewBlock) {
this.insertAnchor(block);
}
block = this.createBlock(block, "multi", ctx);
if (ast.body) {
this.addLine(`${ctxVar} = Object.create(${ctxVar});`);
this.addLine(`${ctxVar}[isBoundary] = 1;`);
this.helpers.add("isBoundary");
const subCtx = createContext(ctx, { preventRoot: true, ctxVar });
const subCtx = createContext(ctx, { ctxVar });
const bl = this.compileMulti({ type: ASTType.Multi, content: ast.body }, subCtx);
if (bl) {
this.helpers.add("zero");
this.addLine(`${ctxVar}[zero] = ${bl};`);
}
}
const isDynamic = INTERP_REGEXP.test(ast.name);
const subTemplate = isDynamic ? interpolate(ast.name) : "`" + ast.name + "`";
if (block) {
if (!forceNewBlock) {
this.insertAnchor(block);
}
}
const key = `key + \`${this.generateComponentKey()}\``;
if (isDynamic) {
const templateVar = generateId("template");
@@ -1050,7 +1048,6 @@ export class CodeGenerator {
this.staticDefs.push({ id: "call", expr: `app.callTemplate.bind(app)` });
}
this.define(templateVar, subTemplate);
block = this.createBlock(block, "multi", ctx);
this.insertBlock(`call(this, ${templateVar}, ${ctxVar}, node, ${key})`, block!, {
...ctx,
forceNewBlock: !block,
@@ -1058,7 +1055,6 @@ export class CodeGenerator {
} else {
const id = generateId(`callTemplate_`);
this.staticDefs.push({ id, expr: `app.getTemplate(${subTemplate})` });
block = this.createBlock(block, "multi", ctx);
this.insertBlock(`${id}.call(this, ${ctxVar}, node, ${key})`, block!, {
...ctx,
forceNewBlock: !block,
@@ -112,16 +112,16 @@ exports[`misc global 1`] = `
ctx = Object.create(ctx);
ctx[isBoundary] = 1;
setContextValue(ctx, \\"foo\\", 'aaa');
const b6 = callTemplate_1.call(this, ctx, node, key + \`__1__\${key1}\`);
const b7 = callTemplate_1.call(this, ctx, node, key + \`__1__\${key1}\`);
ctx = ctx.__proto__;
const b7 = callTemplate_2.call(this, ctx, node, key + \`__2__\${key1}\`);
const b8 = callTemplate_2.call(this, ctx, node, key + \`__2__\${key1}\`);
setContextValue(ctx, \\"foo\\", 'bbb');
const b8 = callTemplate_3.call(this, ctx, node, key + \`__3__\${key1}\`);
const b5 = multi([b6, b7, b8]);
ctx[zero] = b5;
const b9 = callTemplate_4.call(this, ctx, node, key + \`__4__\${key1}\`);
const b9 = callTemplate_3.call(this, ctx, node, key + \`__3__\${key1}\`);
const b6 = multi([b7, b8, b9]);
ctx[zero] = b6;
const b5 = callTemplate_4.call(this, ctx, node, key + \`__4__\${key1}\`);
ctx = ctx.__proto__;
c_block2[i1] = withKey(multi([b4, b9]), key1);
c_block2[i1] = withKey(multi([b4, b5]), key1);
}
ctx = ctx.__proto__;
const b2 = list(c_block2);
+187 -59
View File
@@ -61,19 +61,19 @@ exports[`t-call (template calling) call with several sub nodes on same line 1`]
const callTemplate_1 = app.getTemplate(\`sub\`);
let block1 = createBlock(\`<div><block-child-0/></div>\`);
let block3 = createBlock(\`<span>hey</span>\`);
let block5 = createBlock(\`<span>yay</span>\`);
let block4 = createBlock(\`<span>hey</span>\`);
let block6 = createBlock(\`<span>yay</span>\`);
return function template(ctx, node, key = \\"\\") {
ctx = Object.create(ctx);
ctx[isBoundary] = 1;
const b3 = block3();
const b4 = text(\` \`);
const b5 = block5();
const b2 = multi([b3, b4, b5]);
ctx[zero] = b2;
const b6 = callTemplate_1.call(this, ctx, node, key + \`__1\`);
return block1([], [b6]);
const b4 = block4();
const b5 = text(\` \`);
const b6 = block6();
const b3 = multi([b4, b5, b6]);
ctx[zero] = b3;
const b2 = callTemplate_1.call(this, ctx, node, key + \`__1\`);
return block1([], [b2]);
}
}"
`;
@@ -101,19 +101,19 @@ exports[`t-call (template calling) cascading t-call t-out='0' 1`] = `
const callTemplate_1 = app.getTemplate(\`subTemplate\`);
let block1 = createBlock(\`<div><block-child-0/></div>\`);
let block3 = createBlock(\`<span>hey</span>\`);
let block5 = createBlock(\`<span>yay</span>\`);
let block4 = createBlock(\`<span>hey</span>\`);
let block6 = createBlock(\`<span>yay</span>\`);
return function template(ctx, node, key = \\"\\") {
ctx = Object.create(ctx);
ctx[isBoundary] = 1;
const b3 = block3();
const b4 = text(\` \`);
const b5 = block5();
const b2 = multi([b3, b4, b5]);
ctx[zero] = b2;
const b6 = callTemplate_1.call(this, ctx, node, key + \`__1\`);
return block1([], [b6]);
const b4 = block4();
const b5 = text(\` \`);
const b6 = block6();
const b3 = multi([b4, b5, b6]);
ctx[zero] = b3;
const b2 = callTemplate_1.call(this, ctx, node, key + \`__1\`);
return block1([], [b2]);
}
}"
`;
@@ -126,17 +126,17 @@ exports[`t-call (template calling) cascading t-call t-out='0' 2`] = `
const callTemplate_1 = app.getTemplate(\`subSubTemplate\`);
let block1 = createBlock(\`<div><block-child-0/></div>\`);
let block3 = createBlock(\`<span>cascade 0</span>\`);
let block4 = createBlock(\`<span>cascade 0</span>\`);
return function template(ctx, node, key = \\"\\") {
ctx = Object.create(ctx);
ctx[isBoundary] = 1;
const b3 = block3();
const b4 = ctx[zero];
const b2 = multi([b3, b4]);
ctx[zero] = b2;
const b5 = callTemplate_1.call(this, ctx, node, key + \`__1\`);
return block1([], [b5]);
const b4 = block4();
const b5 = ctx[zero];
const b3 = multi([b4, b5]);
ctx[zero] = b3;
const b2 = callTemplate_1.call(this, ctx, node, key + \`__1\`);
return block1([], [b2]);
}
}"
`;
@@ -149,17 +149,17 @@ exports[`t-call (template calling) cascading t-call t-out='0' 3`] = `
const callTemplate_1 = app.getTemplate(\`finalTemplate\`);
let block1 = createBlock(\`<div><block-child-0/></div>\`);
let block3 = createBlock(\`<span>cascade 1</span>\`);
let block4 = createBlock(\`<span>cascade 1</span>\`);
return function template(ctx, node, key = \\"\\") {
ctx = Object.create(ctx);
ctx[isBoundary] = 1;
const b3 = block3();
const b4 = ctx[zero];
const b2 = multi([b3, b4]);
ctx[zero] = b2;
const b5 = callTemplate_1.call(this, ctx, node, key + \`__1\`);
return block1([], [b5]);
const b4 = block4();
const b5 = ctx[zero];
const b3 = multi([b4, b5]);
ctx[zero] = b3;
const b2 = callTemplate_1.call(this, ctx, node, key + \`__1\`);
return block1([], [b2]);
}
}"
`;
@@ -186,17 +186,17 @@ exports[`t-call (template calling) cascading t-call t-out='0', without external
let { isBoundary, zero } = helpers;
const callTemplate_1 = app.getTemplate(\`subTemplate\`);
let block2 = createBlock(\`<span>hey</span>\`);
let block4 = createBlock(\`<span>yay</span>\`);
let block3 = createBlock(\`<span>hey</span>\`);
let block5 = createBlock(\`<span>yay</span>\`);
return function template(ctx, node, key = \\"\\") {
ctx = Object.create(ctx);
ctx[isBoundary] = 1;
const b2 = block2();
const b3 = text(\` \`);
const b4 = block4();
const b1 = multi([b2, b3, b4]);
ctx[zero] = b1;
const b3 = block3();
const b4 = text(\` \`);
const b5 = block5();
const b2 = multi([b3, b4, b5]);
ctx[zero] = b2;
return callTemplate_1.call(this, ctx, node, key + \`__1\`);
}
}"
@@ -209,15 +209,15 @@ exports[`t-call (template calling) cascading t-call t-out='0', without external
let { isBoundary, zero } = helpers;
const callTemplate_1 = app.getTemplate(\`subSubTemplate\`);
let block2 = createBlock(\`<span>cascade 0</span>\`);
let block3 = createBlock(\`<span>cascade 0</span>\`);
return function template(ctx, node, key = \\"\\") {
ctx = Object.create(ctx);
ctx[isBoundary] = 1;
const b2 = block2();
const b3 = ctx[zero];
const b1 = multi([b2, b3]);
ctx[zero] = b1;
const b3 = block3();
const b4 = ctx[zero];
const b2 = multi([b3, b4]);
ctx[zero] = b2;
return callTemplate_1.call(this, ctx, node, key + \`__1\`);
}
}"
@@ -230,15 +230,15 @@ exports[`t-call (template calling) cascading t-call t-out='0', without external
let { isBoundary, zero } = helpers;
const callTemplate_1 = app.getTemplate(\`finalTemplate\`);
let block2 = createBlock(\`<span>cascade 1</span>\`);
let block3 = createBlock(\`<span>cascade 1</span>\`);
return function template(ctx, node, key = \\"\\") {
ctx = Object.create(ctx);
ctx[isBoundary] = 1;
const b2 = block2();
const b3 = ctx[zero];
const b1 = multi([b2, b3]);
ctx[zero] = b1;
const b3 = block3();
const b4 = ctx[zero];
const b2 = multi([b3, b4]);
ctx[zero] = b2;
return callTemplate_1.call(this, ctx, node, key + \`__1\`);
}
}"
@@ -342,15 +342,15 @@ exports[`t-call (template calling) nested t-calls with magic variable 0 1`] = `
const callTemplate_1 = app.getTemplate(\`grandchild\`);
const callTemplate_2 = app.getTemplate(\`child\`);
let block1 = createBlock(\`<p>Some content...</p>\`);
let block3 = createBlock(\`<p>Some content...</p>\`);
return function template(ctx, node, key = \\"\\") {
ctx = Object.create(ctx);
ctx[isBoundary] = 1;
ctx = Object.create(ctx);
ctx[isBoundary] = 1;
const b1 = block1();
ctx[zero] = b1;
const b3 = block3();
ctx[zero] = b3;
const b2 = callTemplate_1.call(this, ctx, node, key + \`__1\`);
ctx = ctx.__proto__;
ctx[zero] = b2;
@@ -571,6 +571,134 @@ exports[`t-call (template calling) recursive template, part 4: with t-set recurs
}"
`;
exports[`t-call (template calling) root t-call with body: t-foreach 1`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let { isBoundary, prepareList, withKey, zero } = helpers;
const callTemplate_1 = app.getTemplate(\`subTemplate\`);
return function template(ctx, node, key = \\"\\") {
ctx = Object.create(ctx);
ctx[isBoundary] = 1;
ctx = Object.create(ctx);
const [k_block2, v_block2, l_block2, c_block2] = prepareList([1]);;
for (let i1 = 0; i1 < l_block2; i1++) {
ctx[\`i\`] = k_block2[i1];
const key1 = ctx['i'];
c_block2[i1] = withKey(text(\`1\`), key1);
}
ctx = ctx.__proto__;
const b2 = list(c_block2);
ctx[zero] = b2;
return callTemplate_1.call(this, ctx, node, key + \`__1\`);
}
}"
`;
exports[`t-call (template calling) root t-call with body: t-foreach 2`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
return function template(ctx, node, key = \\"\\") {
return text(\`sub\`);
}
}"
`;
exports[`t-call (template calling) root t-call with body: t-if false 1`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let { isBoundary, zero } = helpers;
const callTemplate_1 = app.getTemplate(\`subTemplate\`);
return function template(ctx, node, key = \\"\\") {
ctx = Object.create(ctx);
ctx[isBoundary] = 1;
let b3;
if (false) {
b3 = text(\`zero\`);
}
const b2 = multi([b3]);
ctx[zero] = b2;
return callTemplate_1.call(this, ctx, node, key + \`__1\`);
}
}"
`;
exports[`t-call (template calling) root t-call with body: t-if false 2`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
return function template(ctx, node, key = \\"\\") {
return text(\`sub\`);
}
}"
`;
exports[`t-call (template calling) root t-call with body: t-if true 1`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let { isBoundary, zero } = helpers;
const callTemplate_1 = app.getTemplate(\`subTemplate\`);
return function template(ctx, node, key = \\"\\") {
ctx = Object.create(ctx);
ctx[isBoundary] = 1;
let b3;
if (true) {
b3 = text(\`zero\`);
}
const b2 = multi([b3]);
ctx[zero] = b2;
return callTemplate_1.call(this, ctx, node, key + \`__1\`);
}
}"
`;
exports[`t-call (template calling) root t-call with body: t-if true 2`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
return function template(ctx, node, key = \\"\\") {
return text(\`sub\`);
}
}"
`;
exports[`t-call (template calling) root t-call with body: t-out with default 1`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let { isBoundary, safeOutput, zero } = helpers;
const callTemplate_1 = app.getTemplate(\`subTemplate\`);
return function template(ctx, node, key = \\"\\") {
ctx = Object.create(ctx);
ctx[isBoundary] = 1;
const b2 = safeOutput(ctx['nothing']);
ctx[zero] = b2;
return callTemplate_1.call(this, ctx, node, key + \`__1\`);
}
}"
`;
exports[`t-call (template calling) root t-call with body: t-out with default 2`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
return function template(ctx, node, key = \\"\\") {
return text(\`sub\`);
}
}"
`;
exports[`t-call (template calling) scoped parameters 1`] = `
"function anonymous(app, bdom, helpers
) {
@@ -705,13 +833,13 @@ exports[`t-call (template calling) t-call with body content as root of a templat
let { isBoundary, zero } = helpers;
const callTemplate_1 = app.getTemplate(\`antony\`);
let block1 = createBlock(\`<p>antony</p>\`);
let block2 = createBlock(\`<p>antony</p>\`);
return function template(ctx, node, key = \\"\\") {
ctx = Object.create(ctx);
ctx[isBoundary] = 1;
const b1 = block1();
ctx[zero] = b1;
const b2 = block2();
ctx[zero] = b2;
return callTemplate_1.call(this, ctx, node, key + \`__1\`);
}
}"
@@ -1075,8 +1203,8 @@ exports[`t-call (template calling) with unused body 1`] = `
return function template(ctx, node, key = \\"\\") {
ctx = Object.create(ctx);
ctx[isBoundary] = 1;
const b1 = text(\`WHEEE\`);
ctx[zero] = b1;
const b2 = text(\`WHEEE\`);
ctx[zero] = b2;
return callTemplate_1.call(this, ctx, node, key + \`__1\`);
}
}"
@@ -1136,8 +1264,8 @@ exports[`t-call (template calling) with used body 1`] = `
return function template(ctx, node, key = \\"\\") {
ctx = Object.create(ctx);
ctx[isBoundary] = 1;
const b1 = text(\`ok\`);
ctx[zero] = b1;
const b2 = text(\`ok\`);
ctx[zero] = b2;
return callTemplate_1.call(this, ctx, node, key + \`__1\`);
}
}"
@@ -180,15 +180,15 @@ exports[`t-esc t-esc=0 is escaped 1`] = `
const callTemplate_1 = app.getTemplate(\`sub\`);
let block1 = createBlock(\`<div><block-child-0/></div>\`);
let block2 = createBlock(\`<p>escaped</p>\`);
let block3 = createBlock(\`<p>escaped</p>\`);
return function template(ctx, node, key = \\"\\") {
ctx = Object.create(ctx);
ctx[isBoundary] = 1;
const b2 = block2();
ctx[zero] = b2;
const b3 = callTemplate_1.call(this, ctx, node, key + \`__1\`);
return block1([], [b3]);
const b3 = block3();
ctx[zero] = b3;
const b2 = callTemplate_1.call(this, ctx, node, key + \`__1\`);
return block1([], [b2]);
}
}"
`;
+10 -10
View File
@@ -35,15 +35,15 @@ exports[`t-out multiple calls to t-out 1`] = `
const callTemplate_1 = app.getTemplate(\`sub\`);
let block1 = createBlock(\`<div><block-child-0/></div>\`);
let block2 = createBlock(\`<span>coucou</span>\`);
let block3 = createBlock(\`<span>coucou</span>\`);
return function template(ctx, node, key = \\"\\") {
ctx = Object.create(ctx);
ctx[isBoundary] = 1;
const b2 = block2();
ctx[zero] = b2;
const b3 = callTemplate_1.call(this, ctx, node, key + \`__1\`);
return block1([], [b3]);
const b3 = block3();
ctx[zero] = b3;
const b2 = callTemplate_1.call(this, ctx, node, key + \`__1\`);
return block1([], [b2]);
}
}"
`;
@@ -102,15 +102,15 @@ exports[`t-out t-out 0 1`] = `
const callTemplate_1 = app.getTemplate(\`_basic-callee\`);
let block1 = createBlock(\`<div><block-child-0/></div>\`);
let block2 = createBlock(\`<div>zero</div>\`);
let block3 = createBlock(\`<div>zero</div>\`);
return function template(ctx, node, key = \\"\\") {
ctx = Object.create(ctx);
ctx[isBoundary] = 1;
const b2 = block2();
ctx[zero] = b2;
const b3 = callTemplate_1.call(this, ctx, node, key + \`__1\`);
return block1([], [b3]);
const b3 = block3();
ctx[zero] = b3;
const b2 = callTemplate_1.call(this, ctx, node, key + \`__1\`);
return block1([], [b2]);
}
}"
`;
+42
View File
@@ -430,6 +430,48 @@ describe("t-call (template calling)", () => {
expect(context.renderToString("main")).toBe(expected);
});
test("root t-call with body: t-if true", () => {
const context = new TestContext();
const subTemplate = `sub`;
const main = `<t t-call="subTemplate"><t t-if="true">zero</t></t>`;
context.addTemplate("subTemplate", subTemplate);
context.addTemplate("main", main);
const expected = "sub";
expect(context.renderToString("main")).toBe(expected);
});
test("root t-call with body: t-if false", () => {
const context = new TestContext();
const subTemplate = `sub`;
const main = `<t t-call="subTemplate"><t t-if="false">zero</t></t>`;
context.addTemplate("subTemplate", subTemplate);
context.addTemplate("main", main);
const expected = "sub";
expect(context.renderToString("main")).toBe(expected);
});
test("root t-call with body: t-out with default", () => {
const context = new TestContext();
const subTemplate = `sub`;
const main = `<t t-call="subTemplate"><t t-out="nothing">default</t></t>`;
context.addTemplate("subTemplate", subTemplate);
context.addTemplate("main", main);
const expected = "sub";
expect(context.renderToString("main")).toBe(expected);
});
test("root t-call with body: t-foreach", () => {
const context = new TestContext();
const subTemplate = `sub`;
const main = `<t t-call="subTemplate">
<t t-foreach="[1]" t-as="i" t-key="i">1</t>
</t>`;
context.addTemplate("subTemplate", subTemplate);
context.addTemplate("main", main);
const expected = "sub";
expect(context.renderToString("main")).toBe(expected);
});
test("dynamic t-call", () => {
const context = new TestContext();
const foo = `<foo><t t-esc="val"/></foo>`;
@@ -10,8 +10,8 @@ exports[`t-call dynamic t-call 1`] = `
return function template(ctx, node, key = \\"\\") {
ctx = Object.create(ctx);
ctx[isBoundary] = 1;
const b1 = text(\` owl \`);
ctx[zero] = b1;
const b2 = text(\` owl \`);
ctx[zero] = b2;
const template1 = (ctx['current'].template);
return call(this, template1, ctx, node, key + \`__1\`);
}