[FIX] slots: prevent crash when using same slot in different locations

Before this commit, a crash could occur when a component with no props
is defined in a slot, and that slot is conditionally displayed in
multiple locations.

The reason for that is that the key provided to the callSlot function
was identical, so from the perspective of the component function, it was
not possible to make the difference between a component located in
either places.  With this commit, we make sure that a unique key is used
when a slot is reused in a template (or if it is dynamic, because in
that case, we have no idea at compile time if it will be unique or not)

closes #1246
This commit is contained in:
Géry Debongnie
2022-09-02 09:50:31 +02:00
committed by Sam Degueldre
parent 9cb74d619b
commit 3883cec079
3 changed files with 286 additions and 8 deletions
+13 -3
View File
@@ -232,6 +232,7 @@ export class CodeGenerator {
translatableAttributes: string[] = TRANSLATABLE_ATTRS;
ast: AST;
staticDefs: { id: string; expr: string }[] = [];
slotNames: Set<String> = new Set();
helpers: Set<string> = new Set();
constructor(ast: AST, options: CodeGenOptions) {
@@ -1245,28 +1246,37 @@ export class CodeGenerator {
let blockString: string;
let slotName;
let dynamic = false;
let isMultiple = false;
if (ast.name.match(INTERP_REGEXP)) {
dynamic = true;
isMultiple = true;
slotName = interpolate(ast.name);
} else {
slotName = "'" + ast.name + "'";
isMultiple = isMultiple || this.slotNames.has(ast.name);
this.slotNames.add(ast.name);
}
const dynProps = ast.attrs ? ast.attrs["t-props"] : null;
if (ast.attrs) {
delete ast.attrs["t-props"];
}
let key = this.target.loopLevel ? `key${this.target.loopLevel}` : "key";
if (isMultiple) {
key = `${key} + \`${this.generateComponentKey()}\``;
}
const props = ast.attrs ? this.formatPropObject(ast.attrs) : [];
const scope = this.getPropString(props, dynProps);
if (ast.defaultContent) {
const name = this.compileInNewTarget("defaultContent", ast.defaultContent, ctx);
blockString = `callSlot(ctx, node, key, ${slotName}, ${dynamic}, ${scope}, ${name})`;
blockString = `callSlot(ctx, node, ${key}, ${slotName}, ${dynamic}, ${scope}, ${name})`;
} else {
if (dynamic) {
let name = generateId("slot");
this.define(name, slotName);
blockString = `toggler(${name}, callSlot(ctx, node, key, ${name}, ${dynamic}, ${scope}))`;
blockString = `toggler(${name}, callSlot(ctx, node, ${key}, ${name}, ${dynamic}, ${scope}))`;
} else {
blockString = `callSlot(ctx, node, key, ${slotName}, ${dynamic}, ${scope})`;
blockString = `callSlot(ctx, node, ${key}, ${slotName}, ${dynamic}, ${scope})`;
}
}
// event handling